Skip to content

Commit 15cac9d

Browse files
committed
- **Added is_role_default and force_role_default to account creation logic:**
- Updated `EntityModel.create_account` method to support new flags for default role assignment. - Passed `is_role_default` and `force_role_default` to the `ChartOfAccountsModel.create_account` method. - **Implemented default role handling in `ChartOfAccountsModel.create_account`:** - Added validation to prevent multiple default accounts for the same role unless `force_role_default` is enabled. - Updated existing default account behavior to unset the previous default when `force_role_default` is used. - Included transaction atomicity to ensure account creation consistency. - **Refactored account creation logic for clarity and maintainability:** - Reorganized parameter handling and method logic for `AccountModel` creation. - Enhanced validation error messaging for default role assignment conflicts. ### **Summary** Introduced `is_role_default` and `force_role_default` flags for `AccountModel` creation to manage default role accounts more effectively. Implemented validation and transaction safety measures for default account assignments. ### **Backwards Compatibility** Changes are backwards compatible. Default behavior is preserved unless the new flags are explicitly used.
1 parent e0a4bbb commit 15cac9d

File tree

4 files changed

+206
-529
lines changed

4 files changed

+206
-529
lines changed

django_ledger/models/chart_of_accounts.py

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,28 @@
4343
import warnings
4444
from random import choices
4545
from string import ascii_lowercase, digits
46-
from typing import Optional, Union, Dict
47-
from uuid import uuid4, UUID
46+
from typing import Dict, Optional, Union
47+
from uuid import UUID, uuid4
4848

4949
from django.contrib.auth import get_user_model
5050
from django.core.exceptions import ValidationError
51-
from django.db import models
52-
from django.db.models import Q, F, Count, Manager, QuerySet, BooleanField, Value
53-
from django.db.models.signals import pre_save, post_save
51+
from django.db import models, transaction
52+
from django.db.models import BooleanField, Count, F, Manager, Q, QuerySet, Value
53+
from django.db.models.signals import post_save, pre_save
5454
from django.dispatch import receiver
5555
from django.urls import reverse
5656
from django.utils.translation import gettext_lazy as _
57-
5857
from django_ledger.io import (
59-
ROOT_COA,
60-
ROOT_GROUP_LEVEL_2,
61-
ROOT_GROUP_META,
6258
ROOT_ASSETS,
63-
ROOT_LIABILITIES,
6459
ROOT_CAPITAL,
65-
ROOT_INCOME,
60+
ROOT_COA,
6661
ROOT_COGS,
6762
ROOT_EXPENSES,
6863
ROOT_GROUP,
64+
ROOT_GROUP_LEVEL_2,
65+
ROOT_GROUP_META,
66+
ROOT_INCOME,
67+
ROOT_LIABILITIES,
6968
)
7069
from django_ledger.models import lazy_loader
7170
from django_ledger.models.accounts import AccountModel, AccountModelQuerySet
@@ -605,6 +604,8 @@ def create_account(
605604
balance_type: str,
606605
active: bool,
607606
root_account_qs: Optional[AccountModelQuerySet] = None,
607+
is_role_default: bool = False,
608+
force_role_default: bool = False,
608609
):
609610
"""
610611
Proper method for inserting a new Account Model into a CoA.
@@ -624,23 +625,50 @@ def create_account(
624625
Specifies whether the account is active or not.
625626
root_account_qs : Optional[AccountModelQuerySet], optional
626627
The query set of root accounts to which the created account should be linked. Defaults to None.
628+
is_role_default : bool
629+
Marks the new account as the default for the account role.
630+
force_role_default: bool
631+
Forces the new account model to be set as default for a specified role. Any pre-existing default account
632+
will be removed as default for the specified role.
627633
628634
Returns
629635
-------
630636
AccountModel
631637
The created account model instance.
632638
"""
633-
account_model = AccountModel(
634-
code=code,
635-
name=name,
636-
role=role,
637-
active=active,
638-
balance_type=balance_type,
639-
coa_model=self,
640-
)
641-
account_model.clean()
642639

643-
account_model = self.insert_account(account_model=account_model, root_account_qs=root_account_qs)
640+
with transaction.atomic():
641+
642+
if is_role_default:
643+
account_model_qs: AccountModelQuerySet = self.get_coa_accounts()
644+
645+
default_role_account_qs: AccountModelQuerySet = account_model_qs.filter(role__exact=role, role_default=True)
646+
default_account_exists = default_role_account_qs.exists()
647+
648+
if default_account_exists and not force_role_default:
649+
raise ChartOfAccountsModelValidationError(
650+
f'The role {role} already has a default account {default_role_account_qs.code} for CoA {self}'
651+
)
652+
653+
elif default_account_exists and force_role_default:
654+
default_role_account: AccountModel = default_role_account_qs.get()
655+
default_role_account.role_default = False
656+
default_role_account.save(update_fields=['role_default', 'updated'])
657+
658+
659+
account_model = AccountModel(
660+
code=code,
661+
name=name,
662+
role=role,
663+
active=active,
664+
balance_type=balance_type,
665+
coa_model=self,
666+
role_default=is_role_default,
667+
)
668+
669+
account_model.clean()
670+
account_model = self.insert_account(account_model=account_model, root_account_qs=root_account_qs)
671+
644672
return account_model
645673

646674
# ACTIONS -----

django_ledger/models/data_import.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,8 +686,7 @@ def get_queryset(self) -> StagedTransactionModelQuerySet:
686686
F('matched_transaction_model__journal_entry__entity_unit__name'),
687687
),
688688
import_account_uuid=F('import_job__bank_account_model__account_model_id'),
689-
children_count=Count(F('split_transaction_set'),
690-
distinct=True),
689+
children_count=Count(F('split_transaction_set'), distinct=True),
691690
children_mapped_count=Count('split_transaction_set__account_model__uuid', distinct=True),
692691
imported_count=Count(
693692
'split_transaction_set',
@@ -1689,7 +1688,12 @@ def can_have_account(self) -> bool:
16891688
bool
16901689
True if the entity can have an account, False otherwise.
16911690
"""
1692-
return not self.is_parent()
1691+
return any(
1692+
[
1693+
all([self.is_parent(), not self.has_children()]),
1694+
self.is_children()
1695+
]
1696+
)
16931697

16941698
def can_have_activity(self) -> bool:
16951699
if any([self.is_transfer(), not self.is_cash_transaction()]):

0 commit comments

Comments
 (0)