django-ledger 0.5.6.2__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 (65) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/admin/coa.py +3 -3
  3. django_ledger/forms/account.py +2 -0
  4. django_ledger/forms/coa.py +1 -6
  5. django_ledger/forms/transactions.py +3 -1
  6. django_ledger/io/io_core.py +95 -79
  7. django_ledger/io/io_digest.py +4 -5
  8. django_ledger/io/io_generator.py +5 -4
  9. django_ledger/io/io_library.py +241 -16
  10. django_ledger/io/roles.py +32 -10
  11. django_ledger/migrations/0015_remove_chartofaccountmodel_locked_and_more.py +22 -0
  12. django_ledger/models/accounts.py +13 -9
  13. django_ledger/models/bill.py +3 -3
  14. django_ledger/models/closing_entry.py +39 -28
  15. django_ledger/models/coa.py +244 -35
  16. django_ledger/models/entity.py +119 -51
  17. django_ledger/models/invoice.py +3 -2
  18. django_ledger/models/journal_entry.py +8 -4
  19. django_ledger/models/ledger.py +63 -11
  20. django_ledger/models/mixins.py +2 -2
  21. django_ledger/models/transactions.py +20 -11
  22. django_ledger/report/balance_sheet.py +1 -1
  23. django_ledger/report/cash_flow_statement.py +5 -5
  24. django_ledger/report/core.py +2 -2
  25. django_ledger/report/income_statement.py +2 -2
  26. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  27. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +10 -11
  28. django_ledger/templates/django_ledger/account/account_create.html +17 -11
  29. django_ledger/templates/django_ledger/account/account_list.html +12 -9
  30. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +6 -1
  31. django_ledger/templates/django_ledger/account/tags/accounts_table.html +97 -93
  32. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +17 -0
  33. django_ledger/templates/django_ledger/{code_of_accounts → chart_of_accounts}/coa_update.html +1 -4
  34. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +74 -0
  35. django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +1 -1
  36. django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +6 -6
  37. django_ledger/templates/django_ledger/includes/widget_ic.html +1 -1
  38. django_ledger/templates/django_ledger/invoice/invoice_list.html +91 -94
  39. django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +16 -7
  40. django_ledger/templates/django_ledger/journal_entry/je_detail.html +1 -1
  41. django_ledger/templates/django_ledger/ledger/tags/ledgers_table.html +10 -0
  42. django_ledger/templatetags/django_ledger.py +9 -8
  43. django_ledger/tests/base.py +134 -8
  44. django_ledger/tests/test_accounts.py +16 -0
  45. django_ledger/tests/test_auth.py +5 -7
  46. django_ledger/tests/test_bill.py +1 -1
  47. django_ledger/tests/test_chart_of_accounts.py +46 -0
  48. django_ledger/tests/test_closing_entry.py +16 -19
  49. django_ledger/tests/test_entity.py +3 -3
  50. django_ledger/tests/test_io.py +192 -2
  51. django_ledger/tests/test_transactions.py +290 -0
  52. django_ledger/urls/account.py +18 -3
  53. django_ledger/urls/chart_of_accounts.py +21 -1
  54. django_ledger/urls/ledger.py +7 -0
  55. django_ledger/views/account.py +29 -4
  56. django_ledger/views/coa.py +79 -4
  57. django_ledger/views/djl_api.py +4 -1
  58. django_ledger/views/journal_entry.py +1 -1
  59. django_ledger/views/mixins.py +3 -0
  60. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/METADATA +1 -1
  61. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/RECORD +65 -59
  62. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/AUTHORS.md +0 -0
  63. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/LICENSE +0 -0
  64. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/WHEEL +0 -0
  65. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/top_level.txt +0 -0
@@ -95,14 +95,24 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
95
95
  return make_aware(datetime.combine(self.closing_date, time.max))
96
96
 
97
97
  def migrate(self):
98
- ce_txs = self.closingentrytransactionmodel_set.all().order_by(
98
+ ce_txs = self.closingentrytransactionmodel_set.all().select_related(
99
+ 'account_model',
100
+ 'unit_model'
101
+ ).order_by(
99
102
  'tx_type',
100
103
  'account_model',
101
104
  'unit_model',
102
105
  )
106
+
107
+ # evaluate the QS...
108
+ ce_txs = list(ce_txs)
109
+
110
+ # grouping by DEBIT/CREDIT
103
111
  ce_txs_gb = groupby(ce_txs, key=lambda k: k.tx_type)
104
- ce_txs_gb = {k: list(l) for k, l in ce_txs_gb}
105
- ce_txs_sum = {k: sum(v.balance for v in l) for k, l in ce_txs_gb.items()}
112
+
113
+ # adding DEBITS and CREDITS...
114
+ # ce_txs_gb = {k: list(l) for k, l in ce_txs_gb}
115
+ ce_txs_sum = {k: sum(v.balance for v in l) for k, l in ce_txs_gb}
106
116
 
107
117
  if len(ce_txs_sum) and ce_txs_sum[TransactionModel.DEBIT] != ce_txs_sum[TransactionModel.CREDIT]:
108
118
  raise ClosingEntryValidationError(
@@ -110,20 +120,17 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
110
120
  f'do not equal Debits {ce_txs_sum[TransactionModel.DEBIT]}'
111
121
  )
112
122
 
113
- ce_txs = list(ce_txs)
114
- key_func = lambda i: (
115
- str(i.unit_model_id) if i.unit_model_id else '',
116
- i.activity if i.activity else ''
117
- )
123
+
124
+
125
+ key_func = lambda i: (str(i.unit_model_id) if i.unit_model_id else '', i.activity if i.activity else '')
126
+
118
127
  ce_txs.sort(key=key_func)
119
128
  ce_txs_gb = groupby(ce_txs, key=key_func)
120
- ce_txs_gb = {
121
- unit_model_id: list(je_txs) for unit_model_id, je_txs in ce_txs_gb
122
- }
129
+ ce_txs_gb = {unit_model_id: list(je_txs) for unit_model_id, je_txs in ce_txs_gb}
123
130
 
124
131
  ce_txs_journal_entries = {
125
132
  (unit_model_id, activity): JournalEntryModel(
126
- ledger=self.ledger_model,
133
+ ledger_id=self.ledger_model_id,
127
134
  timestamp=self.get_closing_date_as_timestamp(),
128
135
  activity=activity if activity else None,
129
136
  entity_unit_id=unit_model_id if unit_model_id else None,
@@ -177,7 +184,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
177
184
  def can_post(self) -> bool:
178
185
  return not self.is_posted()
179
186
 
180
- def mark_as_posted(self, commit: bool = False, **kwargs):
187
+ def mark_as_posted(self, commit: bool = False, update_entity_meta: bool = False, **kwargs):
181
188
  if not self.can_post():
182
189
  raise ClosingEntryValidationError(
183
190
  message=_(f'Closing Entry {self.closing_date} is already posted.')
@@ -186,12 +193,14 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
186
193
  self.migrate()
187
194
  self.posted = True
188
195
  if commit:
189
- self.save(update_fields=[
190
- 'posted',
191
- 'ledger_model',
192
- 'updated'
193
- ])
194
- self.entity_model.save_closing_entry_dates_meta(commit=True)
196
+ self.save(
197
+ update_fields=[
198
+ 'posted',
199
+ 'ledger_model',
200
+ 'updated'
201
+ ])
202
+ if update_entity_meta:
203
+ self.entity_model.save_closing_entry_dates_meta(commit=True)
195
204
 
196
205
  def get_mark_as_posted_html_id(self) -> str:
197
206
  return f'closing_entry_post_{self.uuid}'
@@ -212,7 +221,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
212
221
  def can_unpost(self) -> bool:
213
222
  return self.is_posted()
214
223
 
215
- def mark_as_unposted(self, commit: bool = False, **kwargs):
224
+ def mark_as_unposted(self, commit: bool = False, update_entity_meta: bool = False, **kwargs):
216
225
  if not self.can_unpost():
217
226
  raise ClosingEntryValidationError(
218
227
  message=_(f'Closing Entry {self.closing_date} is not posted.')
@@ -224,12 +233,14 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
224
233
  ).delete()
225
234
  self.ledger_model.journal_entries.all().delete()
226
235
  if commit:
227
- self.save(update_fields=[
228
- 'posted',
229
- 'ledger_model',
230
- 'updated'
231
- ])
232
- self.entity_model.save_closing_entry_dates_meta(commit=True)
236
+ self.save(
237
+ update_fields=[
238
+ 'posted',
239
+ 'ledger_model',
240
+ 'updated'
241
+ ])
242
+ if update_entity_meta:
243
+ self.entity_model.save_closing_entry_dates_meta(commit=True)
233
244
 
234
245
  def get_mark_as_unposted_html_id(self) -> str:
235
246
  return f'closing_entry_unpost_{self.uuid}'
@@ -250,7 +261,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
250
261
  def can_update_txs(self) -> bool:
251
262
  return not self.is_posted()
252
263
 
253
- def update_transactions(self, force_update: bool = False, commit: bool = True, **kwargs):
264
+ def update_transactions(self, force_update: bool = False, post_closing_entry: bool = True, **kwargs):
254
265
  if not self.can_update_txs():
255
266
  raise ClosingEntryValidationError(
256
267
  message=_('Cannot update transactions of a posted Closing Entry.')
@@ -259,7 +270,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
259
270
  entity_model.close_entity_books(
260
271
  closing_entry_model=self,
261
272
  force_update=force_update,
262
- commit=commit
273
+ post_closing_entry=post_closing_entry
263
274
  )
264
275
 
265
276
  def get_update_transactions_html_id(self) -> str:
@@ -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
+ """