Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions django_ledger/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django_ledger.admin.chart_of_accounts import ChartOfAccountsModelAdmin
from django_ledger.admin.entity import EntityModelAdmin
from django_ledger.admin.enterprise import *
from django_ledger.admin.ledger import LedgerModelAdmin
from django_ledger.models import EntityModel, ChartOfAccountModel, LedgerModel

Expand Down
133 changes: 133 additions & 0 deletions django_ledger/admin/enterprise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
from django.contrib import admin

from django_ledger.models.enterprise import (
AccountingPeriodModel,
AllocationRuleLineModel,
AllocationRuleModel,
ApprovalPolicyModel,
ApprovalRequestModel,
ApprovalStepModel,
AssetCategoryModel,
AssetDisposalModel,
AuditEventModel,
BankReconciliationModel,
BankStatementLineModel,
BankStatementModel,
BudgetLineModel,
BudgetModel,
BudgetVersionModel,
CloseTaskModel,
CreditNoteModel,
CurrencyModel,
DebitNoteModel,
DepreciationMethodModel,
DepreciationScheduleModel,
DimensionAssignmentModel,
DimensionModel,
DimensionValueModel,
DocumentAttachmentModel,
EntityRoleModel,
ExchangeRateModel,
FixedAssetModel,
IntegrationCredentialModel,
InventoryAdjustmentLineModel,
InventoryAdjustmentModel,
InventoryValuationPolicyModel,
PaymentAllocationModel,
PaymentModel,
TaxAuthorityModel,
TaxCodeModel,
TaxLineModel,
TaxRateModel,
WebhookDeliveryModel,
WebhookEndpointModel,
)


class EntityScopedAdmin(admin.ModelAdmin):
list_display = ['uuid', 'entity_model', 'created', 'updated']
search_fields = ['entity_model__name', 'entity_model__slug']
list_filter = ['entity_model']
readonly_fields = ['uuid', 'created', 'updated']

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
if hasattr(qs, 'for_user'):
return qs.for_user(request.user)
return qs


@admin.register(AuditEventModel)
class AuditEventModelAdmin(EntityScopedAdmin):
list_display = ['created', 'entity_model', 'action', 'actor', 'content_type', 'object_id', 'correlation_id']
list_filter = ['entity_model', 'action', 'content_type']
search_fields = ['object_repr', 'object_id', 'actor__username', 'correlation_id']
readonly_fields = EntityScopedAdmin.readonly_fields + [
'actor',
'action',
'content_type',
'object_id',
'object_repr',
'before',
'after',
'request_meta',
'correlation_id',
]

def has_add_permission(self, request):
return False

def has_delete_permission(self, request, obj=None):
return False


for model in [
AccountingPeriodModel,
AllocationRuleLineModel,
AllocationRuleModel,
ApprovalPolicyModel,
ApprovalRequestModel,
ApprovalStepModel,
AssetCategoryModel,
AssetDisposalModel,
BankReconciliationModel,
BankStatementLineModel,
BankStatementModel,
BudgetLineModel,
BudgetModel,
BudgetVersionModel,
CloseTaskModel,
CreditNoteModel,
DebitNoteModel,
DepreciationMethodModel,
DepreciationScheduleModel,
DimensionAssignmentModel,
DimensionModel,
DimensionValueModel,
DocumentAttachmentModel,
EntityRoleModel,
ExchangeRateModel,
FixedAssetModel,
IntegrationCredentialModel,
InventoryAdjustmentLineModel,
InventoryAdjustmentModel,
InventoryValuationPolicyModel,
PaymentAllocationModel,
PaymentModel,
TaxAuthorityModel,
TaxCodeModel,
TaxLineModel,
TaxRateModel,
WebhookDeliveryModel,
WebhookEndpointModel,
]:
admin.site.register(model, EntityScopedAdmin)


@admin.register(CurrencyModel)
class CurrencyModelAdmin(admin.ModelAdmin):
list_display = ['code', 'name', 'symbol', 'decimal_places', 'active']
search_fields = ['code', 'name']
list_filter = ['active']
9 changes: 9 additions & 0 deletions django_ledger/forms/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ def clean_role_default(self):
def clean_coa_model(self):
return self.COA_MODEL

def clean_code(self):
code = self.cleaned_data.get('code')
if code and AccountModel.objects.filter(
coa_model=self.COA_MODEL,
code__exact=code
).exists():
raise ValidationError(_('Account with this Chart of Accounts and Account Code already exists'))
return code

class Meta:
model = AccountModel
fields = [
Expand Down
65 changes: 58 additions & 7 deletions django_ledger/forms/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
- Michael Noel <noel.michael87@gmail.com>
"""

from django.forms import ModelForm, modelformset_factory, BaseModelFormSet, TextInput, Select, ValidationError
from django.core.exceptions import ObjectDoesNotExist
from django.forms import (
ModelChoiceField,
ModelForm,
modelformset_factory,
BaseModelFormSet,
TextInput,
Select,
ValidationError
)
from django.utils.translation import gettext_lazy as _

from django_ledger.io.io_core import check_tx_balance
Expand All @@ -17,16 +26,28 @@
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES


class TransactionModelAccountChoiceField(ModelChoiceField):
def to_python(self, value):
try:
return super().to_python(value)
except ValidationError as exc:
raise ObjectDoesNotExist from exc


class TransactionModelForm(ModelForm):
account = TransactionModelAccountChoiceField(
queryset=TransactionModel._meta.get_field('account').remote_field.model.objects.all(),
widget=Select(
attrs={
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small',
}
)
)

class Meta:
model = TransactionModel
fields = ['account', 'tx_type', 'amount', 'description']
widgets = {
'account': Select(
attrs={
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small',
}
),
'tx_type': Select(
attrs={
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small',
Expand All @@ -46,8 +67,22 @@ class Meta:


class TransactionModelFormSet(BaseModelFormSet):
def __init__(self, *args, entity_model: EntityModel, je_model: JournalEntryModel, **kwargs):
def __init__(self, *args, entity_model: EntityModel = None, je_model: JournalEntryModel, **kwargs):
entity_slug = kwargs.pop('entity_slug', None)
user_model = kwargs.pop('user_model', None)
kwargs.pop('ledger_pk', None)
super().__init__(*args, **kwargs)

if entity_model is None:
if getattr(je_model, 'entity_model_id', None):
entity_model = je_model.entity_model
elif entity_slug and user_model:
entity_model = EntityModel.objects.for_user(
user_model=user_model
).get(slug__exact=entity_slug)
else:
raise ValidationError(message=_('Must provide entity_model or entity_slug and user_model.'))

je_model.validate_for_entity(entity_model)
self.JE_MODEL: JournalEntryModel = je_model
self.ENTITY_MODEL = entity_model
Expand All @@ -64,7 +99,23 @@ def __init__(self, *args, entity_model: EntityModel, je_model: JournalEntryModel
def get_queryset(self):
return self.JE_MODEL.transactionmodel_set.all()

def is_valid(self):
if not self.is_bound:
txs_balances = [
{'tx_type': tx.tx_type, 'amount': tx.amount}
for tx in self.get_queryset()
]
return check_tx_balance(txs_balances, perform_correction=False)
return super().is_valid()

def save(self, commit=True):
if not self.is_bound:
return []
return super().save(commit=commit)

def clean(self):
if not self.is_bound:
return
if any(self.errors):
return
for form in self.forms:
Expand Down
9 changes: 9 additions & 0 deletions django_ledger/io/ofx.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ def get_account_data(self):
][0]
return self.ACCOUNT_DATA

def get_accounts(self):
"""
Returns parsed OFX account statement metadata.

Older callers expect a list even though django-ledger currently supports
importing a single account per OFX file.
"""
return [self.get_account_data()]

def get_account_number(self):
return self.get_account_data()['account'].acctid

Expand Down
Loading