django-ledger 0.5.6.3__py3-none-any.whl → 0.5.6.4__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 (31) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/admin/coa.py +3 -3
  3. django_ledger/forms/account.py +1 -1
  4. django_ledger/forms/coa.py +1 -6
  5. django_ledger/io/roles.py +25 -9
  6. django_ledger/migrations/0015_remove_chartofaccountmodel_locked_and_more.py +22 -0
  7. django_ledger/models/accounts.py +9 -9
  8. django_ledger/models/coa.py +244 -35
  9. django_ledger/models/entity.py +43 -28
  10. django_ledger/templates/django_ledger/account/account_create.html +17 -11
  11. django_ledger/templates/django_ledger/account/account_list.html +12 -9
  12. django_ledger/templates/django_ledger/account/tags/accounts_table.html +97 -93
  13. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +17 -0
  14. django_ledger/templates/django_ledger/{code_of_accounts → chart_of_accounts}/coa_update.html +1 -4
  15. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +74 -0
  16. django_ledger/templates/django_ledger/invoice/invoice_list.html +91 -94
  17. django_ledger/templatetags/django_ledger.py +1 -1
  18. django_ledger/tests/base.py +23 -1
  19. django_ledger/tests/test_accounts.py +16 -0
  20. django_ledger/tests/test_chart_of_accounts.py +46 -0
  21. django_ledger/urls/account.py +18 -3
  22. django_ledger/urls/chart_of_accounts.py +21 -1
  23. django_ledger/views/account.py +26 -3
  24. django_ledger/views/coa.py +79 -4
  25. django_ledger/views/mixins.py +3 -0
  26. {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/METADATA +1 -1
  27. {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/RECORD +31 -26
  28. {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/AUTHORS.md +0 -0
  29. {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/LICENSE +0 -0
  30. {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/WHEEL +0 -0
  31. {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/top_level.txt +0 -0
django_ledger/__init__.py CHANGED
@@ -9,7 +9,7 @@ Contributions to this module:
9
9
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
10
10
 
11
11
  """Django Ledger"""
12
- __version__ = '0.5.6.3'
12
+ __version__ = '0.5.6.4'
13
13
  __license__ = 'GPLv3 License'
14
14
 
15
15
  __author__ = 'Miguel Sanda'
@@ -84,7 +84,7 @@ class ChartOfAccountsInLine(TabularInline):
84
84
  show_change_link = True
85
85
  fields = [
86
86
  'name',
87
- 'locked',
87
+ 'active',
88
88
  'assign_as_default'
89
89
  ]
90
90
 
@@ -92,13 +92,13 @@ class ChartOfAccountsInLine(TabularInline):
92
92
  class ChartOfAccountsModelAdmin(ModelAdmin):
93
93
  list_filter = [
94
94
  'entity__name',
95
- 'locked'
95
+ 'active'
96
96
  ]
97
97
  list_display = [
98
98
  'entity_name',
99
99
  'name',
100
100
  'slug',
101
- 'locked',
101
+ 'active',
102
102
  'account_model_count'
103
103
  ]
104
104
  search_fields = [
@@ -53,7 +53,7 @@ class AccountModelCreateForm(ModelForm):
53
53
  'role_default',
54
54
  'balance_type',
55
55
  'active',
56
- 'locked'
56
+ 'active'
57
57
  ]
58
58
  widgets = {
59
59
  'code': TextInput(attrs={
@@ -9,19 +9,14 @@ class ChartOfAccountsModelForm(ModelForm):
9
9
  class Meta:
10
10
  model = ChartOfAccountModel
11
11
  fields = [
12
- # 'slug',
13
12
  'name',
14
13
  'description'
15
14
  ]
16
15
  labels = {
17
- 'slug': _('CoA ID'),
18
16
  'name': _('Name'),
19
17
  'description': _('Description'),
20
18
  }
21
19
  widgets = {
22
- # 'slug': TextInput(attrs={
23
- # 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
24
- # }),
25
20
  'name': TextInput(attrs={
26
21
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
27
22
  }),
@@ -36,7 +31,7 @@ class ChartOfAccountsModelUpdateForm(ModelForm):
36
31
  model = ChartOfAccountModel
37
32
  fields = [
38
33
  'name',
39
- 'locked'
34
+ 'active'
40
35
  ]
41
36
  labels = {
42
37
  'name': _('Name'),
django_ledger/io/roles.py CHANGED
@@ -119,48 +119,52 @@ ROOT_GROUP_LEVEL_2 = [
119
119
  ]
120
120
  ROOT_GROUP_META = {
121
121
  ROOT_COA: {
122
- 'code': '00000',
122
+ 'code': '00000000',
123
123
  'title': 'CoA Root Node',
124
124
  'balance_type': DEBIT
125
125
  },
126
126
  ROOT_ASSETS: {
127
- 'code': '01000',
127
+ 'code': '01000000',
128
128
  'title': 'Asset Accounts Root Node',
129
129
  'balance_type': DEBIT
130
130
  },
131
131
  ROOT_LIABILITIES: {
132
- 'code': '02000',
132
+ 'code': '02000000',
133
133
  'title': 'Liability Accounts Root Node',
134
134
  'balance_type': CREDIT
135
135
  },
136
136
  ROOT_CAPITAL: {
137
- 'code': '03000',
137
+ 'code': '03000000',
138
138
  'title': 'Capital Accounts Root Node',
139
139
  'balance_type': CREDIT
140
140
  },
141
141
  ROOT_INCOME: {
142
- 'code': '04000',
142
+ 'code': '04000000',
143
143
  'title': 'Income Accounts Root Node',
144
144
  'balance_type': CREDIT
145
145
  },
146
146
  ROOT_COGS: {
147
- 'code': '05000',
147
+ 'code': '05000000',
148
148
  'title': 'COGS Accounts Root Node',
149
149
  'balance_type': DEBIT
150
150
  },
151
151
  ROOT_EXPENSES: {
152
- 'code': '06000',
152
+ 'code': '06000000',
153
153
  'title': 'Expense Accounts Root Node',
154
154
  'balance_type': DEBIT
155
155
  },
156
156
  }
157
157
  # ------> ROLE GROUPS <-------#
158
158
 
159
+ GROUP_ASSETS = list()
160
+ GROUP_ASSETS.append(ROOT_ASSETS)
161
+
159
162
  # ASSET GROUPS...
160
163
  GROUP_QUICK_ASSETS = [
161
164
  ASSET_CA_CASH,
162
165
  ASSET_CA_MKT_SECURITIES
163
166
  ]
167
+ GROUP_ASSETS += GROUP_QUICK_ASSETS
164
168
 
165
169
  GROUP_CURRENT_ASSETS = [
166
170
  ASSET_CA_CASH,
@@ -171,6 +175,7 @@ GROUP_CURRENT_ASSETS = [
171
175
  ASSET_CA_UNCOLLECTIBLES,
172
176
  ASSET_CA_OTHER
173
177
  ]
178
+ GROUP_ASSETS += GROUP_CURRENT_ASSETS
174
179
 
175
180
  GROUP_NON_CURRENT_ASSETS = [
176
181
  ASSET_LTI_NOTES_RECEIVABLE,
@@ -187,9 +192,13 @@ GROUP_NON_CURRENT_ASSETS = [
187
192
  ASSET_ADJUSTMENTS
188
193
  ]
189
194
 
190
- GROUP_ASSETS = GROUP_CURRENT_ASSETS + GROUP_NON_CURRENT_ASSETS
195
+ GROUP_ASSETS += GROUP_NON_CURRENT_ASSETS
196
+ GROUP_ASSETS = list(set(GROUP_ASSETS))
191
197
 
192
198
  # LIABILITY GROUPS....
199
+ GROUP_LIABILITIES = list()
200
+ GROUP_LIABILITIES.append(ROOT_LIABILITIES)
201
+
193
202
  GROUP_CURRENT_LIABILITIES = [
194
203
  LIABILITY_CL_ACC_PAYABLE,
195
204
  LIABILITY_CL_DEFERRED_REVENUE,
@@ -200,17 +209,21 @@ GROUP_CURRENT_LIABILITIES = [
200
209
  LIABILITY_CL_WAGES_PAYABLE,
201
210
  LIABILITY_CL_TAXES_PAYABLE
202
211
  ]
212
+ GROUP_LIABILITIES += GROUP_CURRENT_LIABILITIES
203
213
 
204
214
  GROUP_LT_LIABILITIES = [
205
215
  LIABILITY_LTL_NOTES_PAYABLE,
206
216
  LIABILITY_LTL_BONDS_PAYABLE,
207
217
  LIABILITY_LTL_MORTGAGE_PAYABLE,
208
218
  ]
219
+ GROUP_LIABILITIES += GROUP_LT_LIABILITIES
209
220
 
210
- GROUP_LIABILITIES = GROUP_CURRENT_LIABILITIES + GROUP_LT_LIABILITIES
221
+ GROUP_LIABILITIES = list(set(GROUP_LIABILITIES))
211
222
 
212
223
  # CAPITAL/EQUITY...
224
+
213
225
  GROUP_CAPITAL = [
226
+ ROOT_CAPITAL,
214
227
  EQUITY_CAPITAL,
215
228
  EQUITY_COMMON_STOCK,
216
229
  EQUITY_PREFERRED_STOCK,
@@ -219,6 +232,7 @@ GROUP_CAPITAL = [
219
232
  ]
220
233
 
221
234
  GROUP_INCOME = [
235
+ ROOT_INCOME,
222
236
  INCOME_OPERATIONAL,
223
237
  INCOME_PASSIVE,
224
238
  INCOME_INTEREST,
@@ -227,10 +241,12 @@ GROUP_INCOME = [
227
241
  ]
228
242
 
229
243
  GROUP_COGS = [
244
+ ROOT_COGS,
230
245
  COGS
231
246
  ]
232
247
 
233
248
  GROUP_EXPENSES = [
249
+ ROOT_EXPENSES,
234
250
  EXPENSE_OPERATIONAL,
235
251
  EXPENSE_INTEREST_ST,
236
252
  EXPENSE_INTEREST_LT,
@@ -0,0 +1,22 @@
1
+ # Generated by Django 5.0.3 on 2024-03-14 02:17
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('django_ledger', '0014_ledgermodel_ledger_xid_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='chartofaccountmodel',
15
+ name='locked',
16
+ ),
17
+ migrations.AddField(
18
+ model_name='chartofaccountmodel',
19
+ name='active',
20
+ field=models.BooleanField(default=True, verbose_name='Is Active'),
21
+ ),
22
+ ]
@@ -242,8 +242,9 @@ class AccountModelManager(MP_NodeManager):
242
242
  entity_slug=entity_slug,
243
243
  coa_slug=coa_slug)
244
244
  return qs.filter(
245
- active=True,
246
- locked=False
245
+ Q(active=True) &
246
+ Q(locked=False) &
247
+ Q(coa_model__active=True)
247
248
  )
248
249
 
249
250
  def with_roles(self, roles: Union[list, str], entity_slug, user_model) -> AccountModelQuerySet:
@@ -581,7 +582,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
581
582
  return self.balance_type == CREDIT
582
583
 
583
584
  def is_coa_root(self):
584
- return self.role in ROOT_GROUP
585
+ return self.role == ROOT_COA
585
586
 
586
587
  def is_asset(self) -> bool:
587
588
  return self.role in GROUP_ASSETS
@@ -604,6 +605,9 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
604
605
  def is_active(self) -> bool:
605
606
  return self.active is True
606
607
 
608
+ def is_locked(self) -> bool:
609
+ return self.locked is True
610
+
607
611
  def can_activate(self):
608
612
  return all([
609
613
  self.active is False
@@ -656,10 +660,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
656
660
  return '5'
657
661
  elif self.is_expense():
658
662
  return '6'
659
- elif self.is_coa_root():
660
- return '0'
661
- else:
662
- raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
663
+ raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
663
664
 
664
665
  def get_root_role(self) -> str:
665
666
  if self.is_asset():
@@ -676,8 +677,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
676
677
  return ROOT_EXPENSES
677
678
  elif self.is_coa_root():
678
679
  return ROOT_COA
679
- else:
680
- raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
680
+ raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
681
681
 
682
682
  def get_account_move_choice_queryset(self):
683
683
  return self.coa_model.accountmodel_set.filter(
@@ -31,7 +31,7 @@ from django.contrib.auth import get_user_model
31
31
  from django.core.exceptions import ValidationError
32
32
  from django.db import models
33
33
  from django.db.models import Q
34
- from django.db.models.signals import pre_save, post_save
34
+ from django.urls import reverse
35
35
  from django.utils.translation import gettext_lazy as _
36
36
 
37
37
  from django_ledger.io import (ROOT_COA, ROOT_GROUP_LEVEL_2, ROOT_GROUP_META, ROOT_ASSETS,
@@ -40,7 +40,6 @@ from django_ledger.io import (ROOT_COA, ROOT_GROUP_LEVEL_2, ROOT_GROUP_META, ROO
40
40
  from django_ledger.models import lazy_loader
41
41
  from django_ledger.models.accounts import AccountModel, AccountModelQuerySet
42
42
  from django_ledger.models.mixins import CreateUpdateMixIn, SlugNameMixIn
43
- from django_ledger.settings import logger
44
43
 
45
44
  UserModel = get_user_model()
46
45
 
@@ -150,10 +149,10 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
150
149
  entity: EntityModel
151
150
  The EntityModel associated with this Chart of Accounts.
152
151
 
153
- locked: bool
152
+ active: bool
154
153
  This determines whether any changes can be done to the Chart of Accounts.
155
- Before making any update to the ChartOf Account, the account needs to be unlocked.
156
- Default value is set to False (unlocked).
154
+ Inactive Chart of Accounts will not be able to be used in new Transactions.
155
+ Default value is set to False (inactive).
157
156
 
158
157
  description: str
159
158
  A user generated description for this Chart of Accounts.
@@ -164,7 +163,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
164
163
  editable=False,
165
164
  verbose_name=_('Entity'),
166
165
  on_delete=models.CASCADE)
167
- locked = models.BooleanField(default=False, verbose_name=_('Locked'))
166
+ active = models.BooleanField(default=True, verbose_name=_('Is Active'))
168
167
  description = models.TextField(verbose_name=_('CoA Description'), null=True, blank=True)
169
168
  objects = ChartOfAccountModelManager.from_queryset(queryset_class=ChartOfAccountModelQuerySet)()
170
169
 
@@ -194,7 +193,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
194
193
  root_account_qs: Optional[AccountModelQuerySet] = None,
195
194
  as_queryset: bool = False) -> Union[AccountModelQuerySet, AccountModel]:
196
195
 
197
- if not account_model.is_coa_root():
196
+ if not account_model.is_root_account():
198
197
 
199
198
  if not root_account_qs:
200
199
  root_account_qs = self.get_coa_root_accounts_qs()
@@ -262,8 +261,9 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
262
261
  locked=True,
263
262
  balance_type=role_meta['balance_type']
264
263
  )
265
- coa_root_account_model = AccountModel.add_root(instance=root_account)
264
+ AccountModel.add_root(instance=root_account)
266
265
 
266
+ # must retrieve root model after added pero django-treebeard documentation...
267
267
  coa_root_account_model = AccountModel.objects.get(uuid__exact=account_pk)
268
268
 
269
269
  for root_role in ROOT_GROUP_LEVEL_2:
@@ -284,10 +284,11 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
284
284
  ))
285
285
 
286
286
  def is_default(self) -> bool:
287
- if self.entity_id is None:
288
- return False
289
287
  return self.entity.default_coa_id == self.uuid
290
288
 
289
+ def is_active(self) -> bool:
290
+ return self.active is True
291
+
291
292
  def validate_account_model_qs(self, account_model_qs: AccountModelQuerySet):
292
293
  if not isinstance(account_model_qs, AccountModelQuerySet):
293
294
  raise ChartOfAccountsModelValidationError(
@@ -299,23 +300,72 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
299
300
  message=f'Invalid root queryset for CoA {self.name}'
300
301
  )
301
302
 
302
- def create_account(self, account_model: AccountModel, root_account_qs: Optional[AccountModelQuerySet] = None):
303
- if not account_model.coa_model_id:
303
+ def allocate_account(self,
304
+ account_model: AccountModel,
305
+ root_account_qs: Optional[AccountModelQuerySet] = None):
306
+ """
307
+ Allocates a given account model to the appropriate root account depending on the Account Model Role.
304
308
 
305
- if not root_account_qs:
306
- root_account_qs = self.get_coa_root_accounts_qs()
307
- else:
308
- self.validate_account_model_qs(root_account_qs)
309
+ Parameters
310
+ ----------
311
+ account_model: AccountModel
312
+ The Account Model to Allocate
313
+ root_account_qs:
314
+ The Root Account QuerySet of the Chart Of Accounts to use.
315
+ Will be validated against current CoA Model.
309
316
 
310
- l2_root_node: AccountModel = self.get_coa_l2_root(account_model, root_account_qs=root_account_qs)
317
+ Returns
318
+ -------
319
+ AccountModel
320
+ The saved and allocated AccountModel.
321
+ """
311
322
 
312
- account_model.coa_model = self
313
- account_model = l2_root_node.add_child(instance=account_model)
323
+ if account_model.coa_model_id:
324
+ if account_model.coa_model_id != self.uuid:
325
+ raise ChartOfAccountsModelValidationError(
326
+ message=f'Invalid Account Model {account_model} for CoA {self}'
327
+ )
328
+
329
+ if not root_account_qs:
330
+ root_account_qs = self.get_coa_root_accounts_qs()
331
+ else:
332
+ self.validate_account_model_qs(root_account_qs)
333
+
334
+ l2_root_node: AccountModel = self.get_coa_l2_root(
335
+ account_model=account_model,
336
+ root_account_qs=root_account_qs
337
+ )
338
+
339
+ account_model.coa_model = self
340
+ l2_root_node.add_child(instance=account_model)
341
+ coa_accounts_qs = self.get_non_root_coa_accounts_qs()
342
+ return coa_accounts_qs.get(uuid__exact=account_model.uuid)
343
+
344
+ def create_account(self,
345
+ code: str,
346
+ role: str,
347
+ name: str,
348
+ balance_type: str,
349
+ active: bool,
350
+ root_account_qs: Optional[AccountModelQuerySet] = None):
351
+
352
+ account_model = AccountModel(
353
+ code=code,
354
+ name=name,
355
+ role=role,
356
+ active=active,
357
+ balance_type=balance_type
358
+ )
359
+ account_model.clean()
360
+
361
+ account_model = self.allocate_account(
362
+ account_model=account_model,
363
+ root_account_qs=root_account_qs
364
+ )
314
365
  return account_model
315
366
 
316
367
  # ACTIONS -----
317
-
318
- # todo: use these methods once multi CoA fetures are enabled...
368
+ # todo: use these methods once multi CoA features are enabled...
319
369
  def lock_all_accounts(self) -> AccountModelQuerySet:
320
370
  non_root_accounts_qs = self.get_non_root_coa_accounts_qs()
321
371
  non_root_accounts_qs.update(locked=True)
@@ -326,25 +376,184 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
326
376
  non_root_accounts_qs.update(locked=False)
327
377
  return non_root_accounts_qs
328
378
 
329
- def clean(self):
330
- self.generate_slug()
379
+ def mark_as_default(self, commit: bool = False, raise_exception: bool = False, **kwargs):
380
+ """
381
+ Marks the current Chart of Accounts instances as default for the EntityModel.
382
+
383
+ Parameters
384
+ ----------
385
+ commit: bool
386
+ Commit the action into the Database. Default is False.
387
+ raise_exception: bool
388
+ Raises exception if Chart of Account model instance is already marked as default.
389
+ """
390
+ if self.is_default():
391
+ if raise_exception:
392
+ raise ChartOfAccountsModelValidationError(
393
+ message=_(f'The Chart of Accounts {self.slug} is already default')
394
+ )
395
+ return
396
+ self.entity.default_coa_id = self.uuid
397
+ self.clean()
398
+ if commit:
399
+ self.entity.save(
400
+ update_fields=[
401
+ 'default_coa_id',
402
+ 'updated'
403
+ ]
404
+ )
331
405
 
406
+ def mark_as_default_url(self) -> str:
407
+ """
408
+ Returns the URL to mark the current Chart of Accounts instances as Default for the EntityModel.
332
409
 
333
- class ChartOfAccountModel(ChartOfAccountModelAbstract):
334
- """
335
- Base ChartOfAccounts Model
336
- """
410
+ Returns
411
+ -------
412
+ str
413
+ The URL as a String.
414
+ """
415
+ return reverse(
416
+ viewname='django_ledger:coa-action-mark-as-default',
417
+ kwargs={
418
+ 'entity_slug': self.entity.slug,
419
+ 'coa_slug': self.slug
420
+ }
421
+ )
422
+
423
+ def can_activate(self) -> bool:
424
+ return self.active is False
425
+
426
+ def can_deactivate(self) -> bool:
427
+ return all([
428
+ self.is_active(),
429
+ not self.is_default()
430
+ ])
431
+
432
+ def mark_as_active(self, commit: bool = False, raise_exception: bool = False, **kwargs):
433
+ """
434
+ Marks the current Chart of Accounts as Active.
435
+
436
+ Parameters
437
+ ----------
438
+ commit: bool
439
+ Commit the action into the Database. Default is False.
440
+ raise_exception: bool
441
+ Raises exception if Chart of Account model instance is already active. Default is False.
442
+ """
443
+ if self.is_active():
444
+ if raise_exception:
445
+ raise ChartOfAccountsModelValidationError(
446
+ message=_('The Chart of Accounts is currently active.')
447
+ )
448
+ return
449
+
450
+ self.active = True
451
+ self.clean()
452
+ if commit:
453
+ self.save(
454
+ update_fields=[
455
+ 'active',
456
+ 'updated'
457
+ ])
458
+
459
+ def mark_as_active_url(self) -> str:
460
+ """
461
+ Returns the URL to mark the current Chart of Accounts instances as active.
462
+
463
+ Returns
464
+ -------
465
+ str
466
+ The URL as a String.
467
+ """
468
+ return reverse(
469
+ viewname='django_ledger:coa-action-mark-as-active',
470
+ kwargs={
471
+ 'entity_slug': self.entity.slug,
472
+ 'coa_slug': self.slug
473
+ }
474
+ )
475
+
476
+ def mark_as_inactive(self, commit: bool = False, raise_exception: bool = False, **kwargs):
477
+ """
478
+ Marks the current Chart of Accounts as Active.
479
+
480
+ Parameters
481
+ ----------
482
+ commit: bool
483
+ Commit the action into the Database. Default is False.
484
+ raise_exception: bool
485
+ Raises exception if Chart of Account model instance is already active. Default is False.
486
+ """
487
+ if not self.is_active():
488
+ if raise_exception:
489
+ raise ChartOfAccountsModelValidationError(
490
+ message=_('The Chart of Accounts is currently not active.')
491
+ )
492
+ return
337
493
 
494
+ self.active = False
495
+ self.clean()
496
+ if commit:
497
+ self.save(
498
+ update_fields=[
499
+ 'active',
500
+ 'updated'
501
+ ])
338
502
 
339
- # def chartofaccountmodel_presave(instance: ChartOfAccountModel, **kwargs):
340
- # if instance._state.adding:
341
- # instance.configure(raise_exception=True)
503
+ def mark_as_inactive_url(self) -> str:
504
+ """
505
+ Returns the URL to mark the current Chart of Accounts instances as inactive.
342
506
 
507
+ Returns
508
+ -------
509
+ str
510
+ The URL as a String.
511
+ """
512
+ return reverse(
513
+ viewname='django_ledger:coa-action-mark-as-inactive',
514
+ kwargs={
515
+ 'entity_slug': self.entity.slug,
516
+ 'coa_slug': self.slug
517
+ }
518
+ )
519
+
520
+ def get_absolute_url(self) -> str:
521
+ return reverse(
522
+ viewname='django_ledger:coa-detail',
523
+ kwargs={
524
+ 'coa_slug': self.slug,
525
+ 'entity_slug': self.entity.slug
526
+ }
527
+ )
528
+
529
+ def get_account_list_url(self):
530
+ return reverse(
531
+ viewname='django_ledger:account-list-coa',
532
+ kwargs={
533
+ 'entity_slug': self.entity.slug,
534
+ 'coa_slug': self.slug
535
+ }
536
+ )
537
+
538
+ def get_create_coa_account_url(self):
539
+ return reverse(
540
+ viewname='django_ledger:account-create-coa',
541
+ kwargs={
542
+ 'coa_slug': self.slug,
543
+ 'entity_slug': self.entity.slug
544
+ }
545
+ )
343
546
 
344
- def chartofaccountmodel_postsave(instance: ChartOfAccountModel, **kwargs):
345
- if instance._state.adding:
346
- instance.configure(raise_exception=True)
547
+ def clean(self):
548
+ self.generate_slug()
549
+
550
+ if self.is_default() and not self.active:
551
+ raise ChartOfAccountsModelValidationError(
552
+ _('Default Chart of Accounts cannot be deactivated.')
553
+ )
347
554
 
348
555
 
349
- # pre_save.connect(receiver=chartofaccountmodel_presave, sender=ChartOfAccountModel)
350
- post_save.connect(receiver=chartofaccountmodel_postsave, sender=ChartOfAccountModel)
556
+ class ChartOfAccountModel(ChartOfAccountModelAbstract):
557
+ """
558
+ Base ChartOfAccounts Model
559
+ """