django-ledger 0.7.0__py3-none-any.whl → 0.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-ledger might be problematic. Click here for more details.

Files changed (49) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/admin/__init__.py +1 -1
  3. django_ledger/admin/{coa.py → chart_of_accounts.py} +1 -1
  4. django_ledger/admin/entity.py +1 -1
  5. django_ledger/forms/account.py +1 -1
  6. django_ledger/forms/bank_account.py +2 -0
  7. django_ledger/forms/chart_of_accounts.py +82 -0
  8. django_ledger/io/io_library.py +1 -1
  9. django_ledger/migrations/0018_transactionmodel_cleared_transactionmodel_reconciled_and_more.py +37 -0
  10. django_ledger/models/__init__.py +1 -1
  11. django_ledger/models/accounts.py +4 -0
  12. django_ledger/models/bank_account.py +6 -2
  13. django_ledger/models/bill.py +7 -3
  14. django_ledger/models/{coa.py → chart_of_accounts.py} +131 -27
  15. django_ledger/models/closing_entry.py +5 -7
  16. django_ledger/models/coa_default.py +1 -1
  17. django_ledger/models/customer.py +6 -2
  18. django_ledger/models/data_import.py +12 -5
  19. django_ledger/models/entity.py +27 -3
  20. django_ledger/models/estimate.py +6 -1
  21. django_ledger/models/invoice.py +14 -8
  22. django_ledger/models/items.py +19 -8
  23. django_ledger/models/journal_entry.py +71 -25
  24. django_ledger/models/ledger.py +8 -5
  25. django_ledger/models/purchase_order.py +9 -5
  26. django_ledger/models/transactions.py +23 -3
  27. django_ledger/models/unit.py +4 -0
  28. django_ledger/models/vendor.py +4 -0
  29. django_ledger/settings.py +28 -3
  30. django_ledger/templates/django_ledger/account/tags/accounts_table.html +3 -2
  31. django_ledger/templates/django_ledger/chart_of_accounts/coa_create.html +25 -0
  32. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +25 -6
  33. django_ledger/templates/django_ledger/chart_of_accounts/coa_update.html +2 -2
  34. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +10 -4
  35. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +2 -2
  36. django_ledger/templates/django_ledger/includes/footer.html +2 -2
  37. django_ledger/urls/chart_of_accounts.py +6 -0
  38. django_ledger/utils.py +1 -36
  39. django_ledger/views/__init__.py +1 -1
  40. django_ledger/views/account.py +16 -3
  41. django_ledger/views/{coa.py → chart_of_accounts.py} +48 -44
  42. django_ledger/views/mixins.py +16 -5
  43. {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/METADATA +1 -3
  44. {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/RECORD +48 -46
  45. django_ledger/forms/coa.py +0 -47
  46. {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +0 -0
  47. {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
  48. {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +0 -0
  49. {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/top_level.txt +0 -0
django_ledger/__init__.py CHANGED
@@ -6,7 +6,7 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
6
6
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
7
7
 
8
8
  """Django Ledger"""
9
- __version__ = '0.7.0'
9
+ __version__ = '0.7.1'
10
10
  __license__ = 'GPLv3 License'
11
11
 
12
12
  __author__ = 'Miguel Sanda'
@@ -1,6 +1,6 @@
1
1
  from django.contrib import admin
2
2
 
3
- from django_ledger.admin.coa import ChartOfAccountsModelAdmin
3
+ from django_ledger.admin.chart_of_accounts import ChartOfAccountsModelAdmin
4
4
  from django_ledger.admin.entity import EntityModelAdmin
5
5
  from django_ledger.admin.ledger import LedgerModelAdmin
6
6
  from django_ledger.models import EntityModel, ChartOfAccountModel, LedgerModel
@@ -3,7 +3,7 @@ from django.db.models import Count
3
3
  from django.forms import ModelForm, BooleanField, BaseInlineFormSet
4
4
 
5
5
  from django_ledger.models.accounts import AccountModel
6
- from django_ledger.models.coa import ChartOfAccountModel
6
+ from django_ledger.models.chart_of_accounts import ChartOfAccountModel
7
7
  from django_ledger.models.entity import EntityModel
8
8
 
9
9
 
@@ -7,7 +7,7 @@ from django.forms import BaseInlineFormSet
7
7
  from django.urls import reverse
8
8
  from django.utils.html import format_html
9
9
 
10
- from django_ledger.admin.coa import ChartOfAccountsInLine
10
+ from django_ledger.admin.chart_of_accounts import ChartOfAccountsInLine
11
11
  from django_ledger.io.io_core import get_localtime
12
12
  from django_ledger.models import EntityUnitModel
13
13
  from django_ledger.models.entity import EntityModel, EntityManagementModel
@@ -38,7 +38,7 @@ class AccountModelCreateForm(ModelForm):
38
38
  self.fields['role'].choices = ACCOUNT_CHOICES_NO_ROOT
39
39
  self.fields['code'].required = False
40
40
  self.fields['coa_model'].disabled = True
41
- self.fields['coa_model'].required = False
41
+ self.fields['coa_model'].required = True
42
42
 
43
43
  self.form_id: str = self.get_form_id()
44
44
 
@@ -13,6 +13,8 @@ class BankAccountCreateForm(ModelForm):
13
13
  super().__init__(*args, **kwargs)
14
14
  self.ENTITY_SLUG = entity_slug
15
15
  self.USER_MODEL = user_model
16
+
17
+ # todo: only the accounts that do not hava an associated bank account should be available to pick from...
16
18
  account_qs = AccountModel.objects.for_entity(
17
19
  user_model=self.USER_MODEL,
18
20
  entity_model=self.ENTITY_SLUG
@@ -0,0 +1,82 @@
1
+ from random import randint
2
+
3
+ from django.forms import ModelForm, TextInput, Textarea, HiddenInput
4
+ from django.utils.translation import gettext_lazy as _
5
+
6
+ from django_ledger.models.chart_of_accounts import ChartOfAccountModel
7
+ from django_ledger.models.entity import EntityModel
8
+ from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
9
+
10
+
11
+ class ChartOfAccountsModelCreateForm(ModelForm):
12
+ FORM_ID_SEP = '___'
13
+
14
+ def __init__(self, entity_model: EntityModel, *args, **kwargs):
15
+ self.ENTITY_MODEL = entity_model
16
+ super().__init__(*args, **kwargs)
17
+ self.fields['entity'].disabled = True
18
+ self.fields['entity'].required = True
19
+ self.form_id: str = self.get_form_id()
20
+
21
+ def clean_entity(self):
22
+ return self.ENTITY_MODEL
23
+
24
+ def get_form_id(self) -> str:
25
+ return f'coa-model-create-form-{self.ENTITY_MODEL.slug}{self.FORM_ID_SEP}{randint(100000, 999999)}'
26
+
27
+ class Meta:
28
+ model = ChartOfAccountModel
29
+ fields = [
30
+ 'entity',
31
+ 'name',
32
+ 'description'
33
+ ]
34
+ labels = {
35
+ 'name': _('Name'),
36
+ 'description': _('Description'),
37
+ }
38
+ widgets = {
39
+ 'entity': HiddenInput(),
40
+ 'name': TextInput(
41
+ attrs={
42
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
43
+ }),
44
+ 'description': Textarea(
45
+ attrs={
46
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
47
+ }
48
+ ),
49
+ }
50
+
51
+
52
+ class ChartOfAccountsModelUpdateForm(ModelForm):
53
+
54
+ FORM_ID_SEP = '___'
55
+
56
+ def __init__(self, *args, **kwargs):
57
+ super().__init__(*args, **kwargs)
58
+ self.form_id: str = self.get_form_id()
59
+
60
+
61
+ def get_form_id(self) -> str:
62
+ instance: ChartOfAccountModel = self.instance
63
+ return f'coa-model-update-form-{instance.slug}{self.FORM_ID_SEP}{randint(100000, 999999)}'
64
+
65
+ class Meta:
66
+ model = ChartOfAccountModel
67
+ fields = [
68
+ 'name',
69
+ 'active'
70
+ ]
71
+ labels = {
72
+ 'name': _('Name'),
73
+ 'description': _('Description'),
74
+ }
75
+ widgets = {
76
+ 'name': TextInput(attrs={
77
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
78
+ }),
79
+ 'description': Textarea(attrs={
80
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
81
+ }),
82
+ }
@@ -22,7 +22,7 @@ from django.utils.translation import gettext_lazy as _
22
22
 
23
23
  from django_ledger.io.io_core import get_localtime
24
24
  from django_ledger.models.accounts import AccountModel, AccountModelQuerySet, CREDIT, DEBIT
25
- from django_ledger.models.coa import ChartOfAccountModel
25
+ from django_ledger.models.chart_of_accounts import ChartOfAccountModel
26
26
  from django_ledger.models.entity import EntityModel
27
27
  from django_ledger.models.ledger import LedgerModel, LedgerModelQuerySet
28
28
 
@@ -0,0 +1,37 @@
1
+ # Generated by Django 5.1.2 on 2024-11-20 22:26
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='transactionmodel',
16
+ name='cleared',
17
+ field=models.BooleanField(default=False, verbose_name='Cleared'),
18
+ ),
19
+ migrations.AddField(
20
+ model_name='transactionmodel',
21
+ name='reconciled',
22
+ field=models.BooleanField(default=False, verbose_name='Reconciled'),
23
+ ),
24
+ migrations.AlterField(
25
+ model_name='chartofaccountmodel',
26
+ name='entity',
27
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_ledger.entitymodel', verbose_name='Entity'),
28
+ ),
29
+ migrations.AddIndex(
30
+ model_name='transactionmodel',
31
+ index=models.Index(fields=['cleared'], name='django_ledg_cleared_335c0b_idx'),
32
+ ),
33
+ migrations.AddIndex(
34
+ model_name='transactionmodel',
35
+ index=models.Index(fields=['reconciled'], name='django_ledg_reconci_75dbc7_idx'),
36
+ ),
37
+ ]
@@ -1,6 +1,6 @@
1
1
  from django_ledger.models.mixins import *
2
2
  from django_ledger.models.bank_account import *
3
- from django_ledger.models.coa import *
3
+ from django_ledger.models.chart_of_accounts import *
4
4
  from django_ledger.models.bill import *
5
5
  from django_ledger.models.invoice import *
6
6
  from django_ledger.models.items import *
@@ -1058,6 +1058,10 @@ class AccountModel(AccountModelAbstract):
1058
1058
  Base Account Model from Account Model Abstract Class
1059
1059
  """
1060
1060
 
1061
+ class Meta(AccountModelAbstract.Meta):
1062
+ swappable = 'DJANGO_LEDGER_ACCOUNT_MODEL'
1063
+ abstract = False
1064
+
1061
1065
 
1062
1066
  def accountmodel_presave(instance: AccountModel, **kwargs):
1063
1067
  if instance.role_default is False:
@@ -91,7 +91,7 @@ class BankAccountModelManager(models.Manager):
91
91
  )
92
92
 
93
93
 
94
- class BackAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
94
+ class BankAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
95
95
  """
96
96
  This is the main abstract class which the BankAccountModel database will inherit from.
97
97
  The BankAccountModel inherits functionality from the following MixIns:
@@ -204,7 +204,11 @@ class BackAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
204
204
  ])
205
205
 
206
206
 
207
- class BankAccountModel(BackAccountModelAbstract):
207
+ class BankAccountModel(BankAccountModelAbstract):
208
208
  """
209
209
  Base Bank Account Model Implementation
210
210
  """
211
+
212
+ class Meta(BankAccountModelAbstract.Meta):
213
+ swappable = 'DJANGO_LEDGER_BANK_ACCOUNT_MODEL'
214
+ abstract = False
@@ -23,7 +23,7 @@ from uuid import uuid4
23
23
  from django.contrib.auth import get_user_model
24
24
  from django.core.exceptions import ValidationError, ObjectDoesNotExist
25
25
  from django.db import models, transaction, IntegrityError
26
- from django.db.models import Q, Sum, F, Count
26
+ from django.db.models import Q, Sum, F, Count, QuerySet, Manager
27
27
  from django.db.models.signals import pre_save
28
28
  from django.shortcuts import get_object_or_404
29
29
  from django.urls import reverse
@@ -53,7 +53,7 @@ class BillModelValidationError(ValidationError):
53
53
  pass
54
54
 
55
55
 
56
- class BillModelQuerySet(models.QuerySet):
56
+ class BillModelQuerySet(QuerySet):
57
57
  """
58
58
  A custom defined QuerySet for the BillModel. This implements multiple methods or queries needed to get a filtered
59
59
  QuerySet based on the BillModel status. For example, we might want to have list of bills which are paid, unpaid,
@@ -170,7 +170,7 @@ class BillModelQuerySet(models.QuerySet):
170
170
  return self.filter(bill_status__exact=BillModel.BILL_STATUS_APPROVED)
171
171
 
172
172
 
173
- class BillModelManager(models.Manager):
173
+ class BillModelManager(Manager):
174
174
  """
175
175
  A custom defined BillModelManager that will act as an interface to handling the initial DB queries
176
176
  to the BillModel. The default "get_queryset" has been overridden to refer the custom defined
@@ -1904,6 +1904,10 @@ class BillModel(BillModelAbstract):
1904
1904
  Base BillModel from Abstract.
1905
1905
  """
1906
1906
 
1907
+ class Meta(BillModelAbstract.Meta):
1908
+ swappable = 'DJANGO_LEDGER_BILL_MODEL'
1909
+ abstract = False
1910
+
1907
1911
 
1908
1912
  def billmodel_presave(instance: BillModel, **kwargs):
1909
1913
  if instance.can_generate_bill_number():
@@ -5,17 +5,41 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
5
5
  Chart Of Accounts
6
6
  -----------------
7
7
 
8
- A Chart of Accounts (CoA) is a crucial collection of logically grouped accounts within a ChartOfAccountModel,
9
- forming the backbone of financial statements. The CoA includes various account roles such as cash, accounts receivable,
10
- expenses, liabilities, and income. For example, the Balance Sheet may have a Fixed Assets heading consisting of
11
- Tangible and Intangible Assets with multiple accounts like Building, Plant & Equipments, and Machinery under
12
- tangible assets. Aggregation of individual account balances based on the Chart of Accounts and AccountModel roles is
13
- essential for preparing Financial Statements.
14
-
15
- All EntityModel must have a default CoA to create any type of transaction. When no explicit CoA is specified, the
16
- default behavior is to use the EntityModel default CoA. Only ONE Chart of Accounts can be used when creating
17
- Journal Entries. No commingling between CoAs is allowed to preserve the integrity of the Journal Entry.
8
+ A Chart of Accounts (CoA) is a fundamental component of financial management in Django Ledger. It serves as the
9
+ backbone of financial statements and is organized within a ChartOfAccountModel.
10
+
11
+ ### Key Features
12
+
13
+ - **Account Roles**: The CoA includes various account types such as:
14
+ - Cash
15
+ - Accounts Receivable
16
+ - Expenses
17
+ - Liabilities
18
+ - Income
19
+
20
+ - **Hierarchical Structure**: Accounts are logically grouped to form financial statements. For example, the Balance
21
+ Sheet may have a structure like this:
22
+ - Fixed Assets
23
+ - Tangible Assets
24
+ - Building
25
+ - Plant & Equipment
26
+ - Machinery
27
+ - Intangible Assets
28
+
29
+ - **Financial Statement Preparation**: Individual account balances are aggregated based on the CoA and AccountModel
30
+ roles to create comprehensive financial statements.
31
+
32
+ ### Usage in EntityModel
33
+
34
+ - Every EntityModel must have a default CoA to create any type of transaction.
35
+ - If no explicit CoA is specified, the EntityModel's default CoA is used.
36
+ - Only ONE Chart of Accounts can be used when creating Journal Entries.
37
+ - Commingling between different CoAs is not allowed to maintain the integrity of Journal Entries.
38
+
39
+ This structure ensures a clear and organized approach to financial management within Django Ledger, facilitating
40
+ accurate record-keeping and reporting.
18
41
  """
42
+
19
43
  from random import choices
20
44
  from string import ascii_lowercase, digits
21
45
  from typing import Optional, Union, Dict
@@ -25,7 +49,9 @@ from django.apps import apps
25
49
  from django.contrib.auth import get_user_model
26
50
  from django.core.exceptions import ValidationError
27
51
  from django.db import models
28
- from django.db.models import Q, F
52
+ from django.db.models import Q, F, Count, Manager, QuerySet
53
+ from django.db.models.signals import pre_save, post_save
54
+ from django.dispatch import receiver
29
55
  from django.urls import reverse
30
56
  from django.utils.translation import gettext_lazy as _
31
57
 
@@ -47,7 +73,7 @@ class ChartOfAccountsModelValidationError(ValidationError):
47
73
  pass
48
74
 
49
75
 
50
- class ChartOfAccountModelQuerySet(models.QuerySet):
76
+ class ChartOfAccountModelQuerySet(QuerySet):
51
77
 
52
78
  def active(self):
53
79
  """
@@ -56,7 +82,7 @@ class ChartOfAccountModelQuerySet(models.QuerySet):
56
82
  return self.filter(active=True)
57
83
 
58
84
 
59
- class ChartOfAccountModelManager(models.Manager):
85
+ class ChartOfAccountModelManager(Manager):
60
86
  """
61
87
  A custom defined ChartOfAccountModelManager that will act as an interface to handling the initial DB queries
62
88
  to the ChartOfAccountModel.
@@ -65,8 +91,23 @@ class ChartOfAccountModelManager(models.Manager):
65
91
  def get_queryset(self):
66
92
  qs = super().get_queryset()
67
93
  return qs.annotate(
68
- _entity_slug=F('entity__slug')
69
- )
94
+ _entity_slug=F('entity__slug'),
95
+ accountmodel_total__count=Count(
96
+ 'accountmodel',
97
+ # excludes coa root accounts...
98
+ filter=Q(accountmodel__depth__gt=2)
99
+ ),
100
+ accountmodel_locked__count=Count(
101
+ 'accountmodel',
102
+ # excludes coa root accounts...
103
+ filter=Q(accountmodel__depth__gt=2) & Q(accountmodel__locked=True)
104
+ ),
105
+ accountmodel_active__count=Count(
106
+ 'accountmodel',
107
+ # excludes coa root accounts...
108
+ filter=Q(accountmodel__depth__gt=2) & Q(accountmodel__active=True)
109
+ ),
110
+ ).select_related('entity')
70
111
 
71
112
  def for_user(self, user_model) -> ChartOfAccountModelQuerySet:
72
113
  """
@@ -91,9 +132,9 @@ class ChartOfAccountModelManager(models.Manager):
91
132
  Q(entity__admin=user_model) |
92
133
  Q(entity__managers__in=[user_model])
93
134
  )
94
- ).select_related('entity')
135
+ )
95
136
 
96
- def for_entity(self, entity_slug, user_model) -> ChartOfAccountModelQuerySet:
137
+ def for_entity(self, entity_model, user_model) -> ChartOfAccountModelQuerySet:
97
138
  """
98
139
  Fetches a QuerySet of ChartOfAccountsModel associated with a specific EntityModel & UserModel.
99
140
  May pass an instance of EntityModel or a String representing the EntityModel slug.
@@ -113,9 +154,9 @@ class ChartOfAccountModelManager(models.Manager):
113
154
  Returns a ChartOfAccountQuerySet with applied filters.
114
155
  """
115
156
  qs = self.for_user(user_model)
116
- if isinstance(entity_slug, lazy_loader.get_entity_model()):
117
- return qs.filter(entity=entity_slug).select_related('entity')
118
- return qs.filter(entity__slug__iexact=entity_slug).select_related('entity')
157
+ if isinstance(entity_model, lazy_loader.get_entity_model()):
158
+ return qs.filter(entity=entity_model)
159
+ return qs.filter(entity__slug__iexact=entity_model)
119
160
 
120
161
 
121
162
  class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
@@ -138,7 +179,6 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
138
179
 
139
180
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
140
181
  entity = models.ForeignKey('django_ledger.EntityModel',
141
- editable=False,
142
182
  verbose_name=_('Entity'),
143
183
  on_delete=models.CASCADE)
144
184
  active = models.BooleanField(default=True, verbose_name=_('Is Active'))
@@ -292,7 +332,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
292
332
  root_account = self.get_coa_root_node()
293
333
  return AccountModel.dump_bulk(parent=root_account)
294
334
 
295
- def generate_slug(self, raise_exception: bool = False) -> str:
335
+ def generate_slug(self, commit: bool = False, raise_exception: bool = False) -> str:
296
336
  """
297
337
  Generates and assigns a slug based on the ChartOfAccounts model instance EntityModel information.
298
338
 
@@ -321,6 +361,14 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
321
361
  return
322
362
  self.slug = f'coa-{self.entity.slug[-5:]}-' + ''.join(choices(SLUG_SUFFIX, k=15))
323
363
 
364
+ if commit:
365
+ self.save(
366
+ update_fields=[
367
+ 'slug',
368
+ 'updated'
369
+ ]
370
+ )
371
+
324
372
  def configure(self, raise_exception: bool = True):
325
373
  """
326
374
  A method that properly configures the ChartOfAccounts model and creates the appropriate hierarchy boilerplate
@@ -333,7 +381,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
333
381
  Whether to raise an exception if root nodes already exist in the Chart of Accounts (default is True).
334
382
  This indicates that the ChartOfAccountModel instance is already configured.
335
383
  """
336
- self.generate_slug()
384
+ self.generate_slug(commit=False)
337
385
 
338
386
  root_accounts_qs = self.get_coa_root_accounts_qs()
339
387
  existing_root_roles = list(set(acc.role for acc in root_accounts_qs))
@@ -546,6 +594,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
546
594
  account_qs.update(locked=False)
547
595
  return account_qs
548
596
 
597
+
549
598
  def mark_as_default(self, commit: bool = False, raise_exception: bool = False, **kwargs):
550
599
  """
551
600
  Marks the current Chart of Accounts instances as default for the EntityModel.
@@ -563,6 +612,12 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
563
612
  message=_(f'The Chart of Accounts {self.slug} is already default')
564
613
  )
565
614
  return
615
+ if not self.can_mark_as_default():
616
+ if raise_exception:
617
+ raise ChartOfAccountsModelValidationError(
618
+ message=_(f'The Chart of Accounts {self.slug} cannot be marked as default')
619
+ )
620
+ return
566
621
  self.entity.default_coa_id = self.uuid
567
622
  self.clean()
568
623
  if commit:
@@ -573,6 +628,12 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
573
628
  ]
574
629
  )
575
630
 
631
+ def can_mark_as_default(self):
632
+ return all([
633
+ self.is_active(),
634
+ not self.is_default()
635
+ ])
636
+
576
637
  def can_activate(self) -> bool:
577
638
  """
578
639
  Check if the ChartOffAccountModel instance can be activated.
@@ -710,6 +771,22 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
710
771
  }
711
772
  )
712
773
 
774
+ def get_coa_list_inactive_url(self):
775
+ return reverse(
776
+ viewname='django_ledger:coa-list-inactive',
777
+ kwargs={
778
+ 'entity_slug': self.entity_slug
779
+ }
780
+ )
781
+
782
+ def get_coa_create_url(self):
783
+ return reverse(
784
+ viewname='django_ledger:coa-create',
785
+ kwargs={
786
+ 'entity_slug': self.entity_slug
787
+ }
788
+ )
789
+
713
790
  def get_absolute_url(self) -> str:
714
791
  return reverse(
715
792
  viewname='django_ledger:coa-detail',
@@ -719,7 +796,20 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
719
796
  }
720
797
  )
721
798
 
799
+ def get_update_url(self) -> str:
800
+ return reverse(
801
+ viewname='django_ledger:coa-update',
802
+ kwargs={
803
+ 'entity_slug': self.entity_slug,
804
+ 'coa_slug': self.slug
805
+ }
806
+ )
807
+
722
808
  def get_account_list_url(self):
809
+
810
+ if not self.slug:
811
+ self.generate_slug(commit=True)
812
+
723
813
  return reverse(
724
814
  viewname='django_ledger:account-list',
725
815
  kwargs={
@@ -739,13 +829,27 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
739
829
 
740
830
  def clean(self):
741
831
  self.generate_slug()
742
- if self.is_default() and not self.active:
743
- raise ChartOfAccountsModelValidationError(
744
- _('Default Chart of Accounts cannot be deactivated.')
745
- )
746
832
 
747
833
 
748
834
  class ChartOfAccountModel(ChartOfAccountModelAbstract):
749
835
  """
750
836
  Base ChartOfAccounts Model
751
837
  """
838
+ class Meta(ChartOfAccountModelAbstract.Meta):
839
+ swappable = 'DJANGO_LEDGER_CHART_OF_ACCOUNTS_MODEL'
840
+ abstract = False
841
+
842
+
843
+ @receiver(pre_save, sender=ChartOfAccountModel)
844
+ def chartofaccountsmodel_presave(instance: ChartOfAccountModelAbstract, **kwargs):
845
+ instance.generate_slug()
846
+ if instance.is_default() and not instance.active:
847
+ raise ChartOfAccountsModelValidationError(
848
+ _('Default Chart of Accounts cannot be deactivated.')
849
+ )
850
+
851
+
852
+ @receiver(post_save, sender=ChartOfAccountModel)
853
+ def chartofaccountsmodel_postsave(instance: ChartOfAccountModelAbstract, **kwargs):
854
+ if instance._state.adding:
855
+ instance.configure()
@@ -9,6 +9,7 @@ from itertools import groupby, chain
9
9
  from typing import Optional
10
10
  from uuid import uuid4, UUID
11
11
 
12
+ from django.conf import settings
12
13
  from django.core.exceptions import ValidationError
13
14
  from django.core.validators import MinValueValidator
14
15
  from django.db import models
@@ -23,6 +24,7 @@ from django_ledger.models.ledger import LedgerModel
23
24
  from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn
24
25
  from django_ledger.models.transactions import TransactionModel
25
26
  from django_ledger.models.utils import lazy_loader
27
+ from django_ledger.settings import DJANGO_LEDGER_LEDGER_MODEL
26
28
 
27
29
 
28
30
  class ClosingEntryValidationError(ValidationError):
@@ -338,7 +340,9 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
338
340
 
339
341
 
340
342
  class ClosingEntryModel(ClosingEntryModelAbstract):
341
- pass
343
+ class Meta(ClosingEntryModelAbstract.Meta):
344
+ swappable = 'DJANGO_LEDGER_CLOSING_ENTRY_MODEL'
345
+ abstract = False
342
346
 
343
347
 
344
348
  # todo: Remove this model!
@@ -349,12 +353,6 @@ class ClosingEntryTransactionModelQuerySet(models.QuerySet):
349
353
 
350
354
  class ClosingEntryTransactionModelManager(models.Manager):
351
355
 
352
- # def get_queryset(self):
353
- # return super().get_queryset().select_related(
354
- # 'closing_entry_model',
355
- # 'closing_entry_model__entity_model'
356
- # )
357
-
358
356
  def for_entity(self, entity_slug):
359
357
  qs = self.get_queryset()
360
358
  if isinstance(entity_slug, lazy_loader.get_entity_model()):
@@ -33,8 +33,8 @@ Default Chart of Accounts Table
33
33
  1910 asset_adjustment debit Securities Unrealized Gains/Losses root_assets
34
34
  1920 asset_adjustment debit PPE Unrealized Gains/Losses root_assets
35
35
  1010 asset_ca_cash debit Cash root_assets
36
- 1200 asset_ca_inv debit Inventory root_assets
37
36
  1050 asset_ca_mkt_sec debit Short Term Investments root_assets
37
+ 1200 asset_ca_inv debit Inventory root_assets
38
38
  1300 asset_ca_prepaid debit Prepaid Expenses root_assets
39
39
  1100 asset_ca_recv debit Accounts Receivable root_assets
40
40
  1110 asset_ca_uncoll credit Uncollectibles root_assets
@@ -10,7 +10,7 @@ from uuid import uuid4
10
10
 
11
11
  from django.core.exceptions import ObjectDoesNotExist
12
12
  from django.db import models, transaction, IntegrityError
13
- from django.db.models import Q, F, QuerySet
13
+ from django.db.models import Q, F, QuerySet, Manager
14
14
  from django.utils.translation import gettext_lazy as _
15
15
 
16
16
  from django_ledger.models.mixins import ContactInfoMixIn, CreateUpdateMixIn, TaxCollectionMixIn
@@ -77,7 +77,7 @@ class CustomerModelQueryset(QuerySet):
77
77
  )
78
78
 
79
79
 
80
- class CustomerModelManager(models.Manager):
80
+ class CustomerModelManager(Manager):
81
81
  """
82
82
  A custom defined CustomerModelManager that will act as an interface to handling the DB queries to the
83
83
  CustomerModel.
@@ -329,3 +329,7 @@ class CustomerModel(CustomerModelAbstract):
329
329
  """
330
330
  Base Customer Model Implementation
331
331
  """
332
+
333
+ class Meta:
334
+ swappable = 'DJANGO_LEDGER_CUSTOMER_MODEL'
335
+ abstract = False