django-ledger 0.6.4__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 (93) hide show
  1. django_ledger/__init__.py +1 -4
  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/contrib/django_ledger_graphene/accounts/schema.py +1 -1
  6. django_ledger/forms/account.py +43 -38
  7. django_ledger/forms/bank_account.py +5 -2
  8. django_ledger/forms/bill.py +24 -36
  9. django_ledger/forms/chart_of_accounts.py +82 -0
  10. django_ledger/forms/customer.py +1 -1
  11. django_ledger/forms/data_import.py +3 -3
  12. django_ledger/forms/estimate.py +1 -1
  13. django_ledger/forms/invoice.py +5 -7
  14. django_ledger/forms/item.py +24 -15
  15. django_ledger/forms/transactions.py +3 -3
  16. django_ledger/io/io_core.py +4 -2
  17. django_ledger/io/io_library.py +1 -1
  18. django_ledger/io/io_middleware.py +5 -0
  19. django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
  20. django_ledger/migrations/0018_transactionmodel_cleared_transactionmodel_reconciled_and_more.py +37 -0
  21. django_ledger/models/__init__.py +1 -1
  22. django_ledger/models/accounts.py +229 -265
  23. django_ledger/models/bank_account.py +6 -6
  24. django_ledger/models/bill.py +7 -6
  25. django_ledger/models/{coa.py → chart_of_accounts.py} +187 -72
  26. django_ledger/models/closing_entry.py +5 -10
  27. django_ledger/models/coa_default.py +10 -9
  28. django_ledger/models/customer.py +6 -6
  29. django_ledger/models/data_import.py +12 -8
  30. django_ledger/models/entity.py +96 -39
  31. django_ledger/models/estimate.py +6 -10
  32. django_ledger/models/invoice.py +14 -11
  33. django_ledger/models/items.py +23 -14
  34. django_ledger/models/journal_entry.py +73 -30
  35. django_ledger/models/ledger.py +8 -8
  36. django_ledger/models/mixins.py +0 -3
  37. django_ledger/models/purchase_order.py +9 -9
  38. django_ledger/models/signals.py +0 -3
  39. django_ledger/models/transactions.py +24 -7
  40. django_ledger/models/unit.py +4 -3
  41. django_ledger/models/utils.py +0 -3
  42. django_ledger/models/vendor.py +4 -3
  43. django_ledger/settings.py +28 -3
  44. django_ledger/templates/django_ledger/account/account_create.html +2 -2
  45. django_ledger/templates/django_ledger/account/account_update.html +1 -1
  46. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
  47. django_ledger/templates/django_ledger/account/tags/accounts_table.html +29 -19
  48. django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
  49. django_ledger/templates/django_ledger/chart_of_accounts/coa_create.html +25 -0
  50. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +25 -6
  51. django_ledger/templates/django_ledger/chart_of_accounts/coa_update.html +2 -2
  52. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +10 -4
  53. django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
  54. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +2 -2
  55. django_ledger/templates/django_ledger/includes/footer.html +2 -2
  56. django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
  57. django_ledger/templatetags/django_ledger.py +7 -1
  58. django_ledger/tests/base.py +23 -7
  59. django_ledger/tests/test_accounts.py +145 -9
  60. django_ledger/urls/account.py +17 -24
  61. django_ledger/urls/chart_of_accounts.py +6 -0
  62. django_ledger/utils.py +9 -36
  63. django_ledger/views/__init__.py +2 -2
  64. django_ledger/views/account.py +91 -116
  65. django_ledger/views/auth.py +1 -1
  66. django_ledger/views/bank_account.py +9 -11
  67. django_ledger/views/bill.py +91 -80
  68. django_ledger/views/{coa.py → chart_of_accounts.py} +49 -44
  69. django_ledger/views/closing_entry.py +8 -0
  70. django_ledger/views/customer.py +1 -1
  71. django_ledger/views/data_import.py +1 -1
  72. django_ledger/views/entity.py +1 -1
  73. django_ledger/views/estimate.py +13 -8
  74. django_ledger/views/feedback.py +1 -1
  75. django_ledger/views/financial_statement.py +1 -1
  76. django_ledger/views/home.py +1 -1
  77. django_ledger/views/inventory.py +9 -0
  78. django_ledger/views/invoice.py +5 -2
  79. django_ledger/views/item.py +58 -68
  80. django_ledger/views/journal_entry.py +1 -1
  81. django_ledger/views/ledger.py +3 -1
  82. django_ledger/views/mixins.py +25 -13
  83. django_ledger/views/purchase_order.py +1 -1
  84. django_ledger/views/transactions.py +1 -1
  85. django_ledger/views/unit.py +9 -0
  86. django_ledger/views/vendor.py +1 -1
  87. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +8 -2
  88. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/METADATA +33 -44
  89. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/RECORD +92 -89
  90. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +1 -1
  91. django_ledger/forms/coa.py +0 -47
  92. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
  93. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/top_level.txt +0 -0
@@ -2,9 +2,6 @@
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
- Contributions to this module:
6
- * Miguel Sanda <msanda@arrobalytics.com>
7
-
8
5
  This module implements the BillModel, which represents an Invoice received from a Supplier/Vendor, on which
9
6
  the Vendor states the amount owed by the recipient for the purposes of supplying goods and/or services.
10
7
  In addition to tracking the bill amount, it tracks the paid and due amount.
@@ -26,7 +23,7 @@ from uuid import uuid4
26
23
  from django.contrib.auth import get_user_model
27
24
  from django.core.exceptions import ValidationError, ObjectDoesNotExist
28
25
  from django.db import models, transaction, IntegrityError
29
- from django.db.models import Q, Sum, F, Count
26
+ from django.db.models import Q, Sum, F, Count, QuerySet, Manager
30
27
  from django.db.models.signals import pre_save
31
28
  from django.shortcuts import get_object_or_404
32
29
  from django.urls import reverse
@@ -56,7 +53,7 @@ class BillModelValidationError(ValidationError):
56
53
  pass
57
54
 
58
55
 
59
- class BillModelQuerySet(models.QuerySet):
56
+ class BillModelQuerySet(QuerySet):
60
57
  """
61
58
  A custom defined QuerySet for the BillModel. This implements multiple methods or queries needed to get a filtered
62
59
  QuerySet based on the BillModel status. For example, we might want to have list of bills which are paid, unpaid,
@@ -173,7 +170,7 @@ class BillModelQuerySet(models.QuerySet):
173
170
  return self.filter(bill_status__exact=BillModel.BILL_STATUS_APPROVED)
174
171
 
175
172
 
176
- class BillModelManager(models.Manager):
173
+ class BillModelManager(Manager):
177
174
  """
178
175
  A custom defined BillModelManager that will act as an interface to handling the initial DB queries
179
176
  to the BillModel. The default "get_queryset" has been overridden to refer the custom defined
@@ -1907,6 +1904,10 @@ class BillModel(BillModelAbstract):
1907
1904
  Base BillModel from Abstract.
1908
1905
  """
1909
1906
 
1907
+ class Meta(BillModelAbstract.Meta):
1908
+ swappable = 'DJANGO_LEDGER_BILL_MODEL'
1909
+ abstract = False
1910
+
1910
1911
 
1911
1912
  def billmodel_presave(instance: BillModel, **kwargs):
1912
1913
  if instance.can_generate_bill_number():
@@ -2,24 +2,44 @@
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
- Contributions to this module:
6
- * Miguel Sanda <msanda@arrobalytics.com>
7
- * Pranav P Tulshyan <ptulshyan77@gmail.com>
8
-
9
5
  Chart Of Accounts
10
- _________________
11
-
12
- A Chart of Accounts (CoA) is a crucial collection of logically grouped accounts within a ChartOfAccountModel,
13
- forming the backbone of financial statements. The CoA includes various account roles such as cash, accounts receivable,
14
- expenses, liabilities, and income. For example, the Balance Sheet may have a Fixed Assets heading consisting of
15
- Tangible and Intangible Assets with multiple accounts like Building, Plant &amp; Equipments, and Machinery under
16
- tangible assets. Aggregation of individual account balances based on the Chart of Accounts and AccountModel roles is
17
- essential for preparing Financial Statements.
18
-
19
- All EntityModel must have a default CoA to create any type of transaction. When no explicit CoA is specified, the
20
- default behavior is to use the EntityModel default CoA. Only ONE Chart of Accounts can be used when creating
21
- Journal Entries. No commingling between CoAs is allowed to preserve the integrity of the Journal Entry.
6
+ -----------------
7
+
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.
22
41
  """
42
+
23
43
  from random import choices
24
44
  from string import ascii_lowercase, digits
25
45
  from typing import Optional, Union, Dict
@@ -29,7 +49,9 @@ from django.apps import apps
29
49
  from django.contrib.auth import get_user_model
30
50
  from django.core.exceptions import ValidationError
31
51
  from django.db import models
32
- from django.db.models import Q
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
33
55
  from django.urls import reverse
34
56
  from django.utils.translation import gettext_lazy as _
35
57
 
@@ -51,7 +73,7 @@ class ChartOfAccountsModelValidationError(ValidationError):
51
73
  pass
52
74
 
53
75
 
54
- class ChartOfAccountModelQuerySet(models.QuerySet):
76
+ class ChartOfAccountModelQuerySet(QuerySet):
55
77
 
56
78
  def active(self):
57
79
  """
@@ -60,12 +82,33 @@ class ChartOfAccountModelQuerySet(models.QuerySet):
60
82
  return self.filter(active=True)
61
83
 
62
84
 
63
- class ChartOfAccountModelManager(models.Manager):
85
+ class ChartOfAccountModelManager(Manager):
64
86
  """
65
87
  A custom defined ChartOfAccountModelManager that will act as an interface to handling the initial DB queries
66
88
  to the ChartOfAccountModel.
67
89
  """
68
90
 
91
+ def get_queryset(self):
92
+ qs = super().get_queryset()
93
+ return qs.annotate(
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')
111
+
69
112
  def for_user(self, user_model) -> ChartOfAccountModelQuerySet:
70
113
  """
71
114
  Fetches a QuerySet of ChartOfAccountModel that the UserModel as access to. May include ChartOfAccountModel from
@@ -89,9 +132,9 @@ class ChartOfAccountModelManager(models.Manager):
89
132
  Q(entity__admin=user_model) |
90
133
  Q(entity__managers__in=[user_model])
91
134
  )
92
- ).select_related('entity')
135
+ )
93
136
 
94
- def for_entity(self, entity_slug, user_model) -> ChartOfAccountModelQuerySet:
137
+ def for_entity(self, entity_model, user_model) -> ChartOfAccountModelQuerySet:
95
138
  """
96
139
  Fetches a QuerySet of ChartOfAccountsModel associated with a specific EntityModel & UserModel.
97
140
  May pass an instance of EntityModel or a String representing the EntityModel slug.
@@ -111,9 +154,9 @@ class ChartOfAccountModelManager(models.Manager):
111
154
  Returns a ChartOfAccountQuerySet with applied filters.
112
155
  """
113
156
  qs = self.for_user(user_model)
114
- if isinstance(entity_slug, lazy_loader.get_entity_model()):
115
- return qs.filter(entity=entity_slug).select_related('entity')
116
- 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)
117
160
 
118
161
 
119
162
  class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
@@ -136,7 +179,6 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
136
179
 
137
180
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
138
181
  entity = models.ForeignKey('django_ledger.EntityModel',
139
- editable=False,
140
182
  verbose_name=_('Entity'),
141
183
  on_delete=models.CASCADE)
142
184
  active = models.BooleanField(default=True, verbose_name=_('Is Active'))
@@ -157,6 +199,14 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
157
199
  return f'{self.name} ({self.slug})'
158
200
  return self.slug
159
201
 
202
+ @property
203
+ def entity_slug(self) -> str:
204
+ try:
205
+ # from QS annotation...
206
+ return getattr(self, '_entity_slug')
207
+ except AttributeError:
208
+ return self.entity.slug
209
+
160
210
  def get_coa_root_accounts_qs(self) -> AccountModelQuerySet:
161
211
  """
162
212
  Retrieves the root accounts in the chart of accounts.
@@ -282,7 +332,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
282
332
  root_account = self.get_coa_root_node()
283
333
  return AccountModel.dump_bulk(parent=root_account)
284
334
 
285
- def generate_slug(self, raise_exception: bool = False) -> str:
335
+ def generate_slug(self, commit: bool = False, raise_exception: bool = False) -> str:
286
336
  """
287
337
  Generates and assigns a slug based on the ChartOfAccounts model instance EntityModel information.
288
338
 
@@ -311,6 +361,14 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
311
361
  return
312
362
  self.slug = f'coa-{self.entity.slug[-5:]}-' + ''.join(choices(SLUG_SUFFIX, k=15))
313
363
 
364
+ if commit:
365
+ self.save(
366
+ update_fields=[
367
+ 'slug',
368
+ 'updated'
369
+ ]
370
+ )
371
+
314
372
  def configure(self, raise_exception: bool = True):
315
373
  """
316
374
  A method that properly configures the ChartOfAccounts model and creates the appropriate hierarchy boilerplate
@@ -323,7 +381,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
323
381
  Whether to raise an exception if root nodes already exist in the Chart of Accounts (default is True).
324
382
  This indicates that the ChartOfAccountModel instance is already configured.
325
383
  """
326
- self.generate_slug()
384
+ self.generate_slug(commit=False)
327
385
 
328
386
  root_accounts_qs = self.get_coa_root_accounts_qs()
329
387
  existing_root_roles = list(set(acc.role for acc in root_accounts_qs))
@@ -536,6 +594,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
536
594
  account_qs.update(locked=False)
537
595
  return account_qs
538
596
 
597
+
539
598
  def mark_as_default(self, commit: bool = False, raise_exception: bool = False, **kwargs):
540
599
  """
541
600
  Marks the current Chart of Accounts instances as default for the EntityModel.
@@ -553,6 +612,12 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
553
612
  message=_(f'The Chart of Accounts {self.slug} is already default')
554
613
  )
555
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
556
621
  self.entity.default_coa_id = self.uuid
557
622
  self.clean()
558
623
  if commit:
@@ -563,22 +628,11 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
563
628
  ]
564
629
  )
565
630
 
566
- def mark_as_default_url(self) -> str:
567
- """
568
- Returns the URL to mark the current Chart of Accounts instances as Default for the EntityModel.
569
-
570
- Returns
571
- -------
572
- str
573
- The URL as a String.
574
- """
575
- return reverse(
576
- viewname='django_ledger:coa-action-mark-as-default',
577
- kwargs={
578
- 'entity_slug': self.entity.slug,
579
- 'coa_slug': self.slug
580
- }
581
- )
631
+ def can_mark_as_default(self):
632
+ return all([
633
+ self.is_active(),
634
+ not self.is_default()
635
+ ])
582
636
 
583
637
  def can_activate(self) -> bool:
584
638
  """
@@ -630,23 +684,6 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
630
684
  'updated'
631
685
  ])
632
686
 
633
- def mark_as_active_url(self) -> str:
634
- """
635
- Returns the URL to mark the current Chart of Accounts instances as active.
636
-
637
- Returns
638
- -------
639
- str
640
- The URL as a String.
641
- """
642
- return reverse(
643
- viewname='django_ledger:coa-action-mark-as-active',
644
- kwargs={
645
- 'entity_slug': self.entity.slug,
646
- 'coa_slug': self.slug
647
- }
648
- )
649
-
650
687
  def mark_as_inactive(self, commit: bool = False, raise_exception: bool = False, **kwargs):
651
688
  """
652
689
  Marks the current Chart of Accounts as Active.
@@ -674,6 +711,41 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
674
711
  'updated'
675
712
  ])
676
713
 
714
+ # URLS....
715
+ def mark_as_default_url(self) -> str:
716
+ """
717
+ Returns the URL to mark the current Chart of Accounts instances as Default for the EntityModel.
718
+
719
+ Returns
720
+ -------
721
+ str
722
+ The URL as a String.
723
+ """
724
+ return reverse(
725
+ viewname='django_ledger:coa-action-mark-as-default',
726
+ kwargs={
727
+ 'entity_slug': self.entity_slug,
728
+ 'coa_slug': self.slug
729
+ }
730
+ )
731
+
732
+ def mark_as_active_url(self) -> str:
733
+ """
734
+ Returns the URL to mark the current Chart of Accounts instances as active.
735
+
736
+ Returns
737
+ -------
738
+ str
739
+ The URL as a String.
740
+ """
741
+ return reverse(
742
+ viewname='django_ledger:coa-action-mark-as-active',
743
+ kwargs={
744
+ 'entity_slug': self.entity_slug,
745
+ 'coa_slug': self.slug
746
+ }
747
+ )
748
+
677
749
  def mark_as_inactive_url(self) -> str:
678
750
  """
679
751
  Returns the URL to mark the current Chart of Accounts instances as inactive.
@@ -686,7 +758,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
686
758
  return reverse(
687
759
  viewname='django_ledger:coa-action-mark-as-inactive',
688
760
  kwargs={
689
- 'entity_slug': self.entity.slug,
761
+ 'entity_slug': self.entity_slug,
690
762
  'coa_slug': self.slug
691
763
  }
692
764
  )
@@ -695,7 +767,23 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
695
767
  return reverse(
696
768
  viewname='django_ledger:coa-list',
697
769
  kwargs={
698
- 'entity_slug': self.entity.slug
770
+ 'entity_slug': self.entity_slug
771
+ }
772
+ )
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
699
787
  }
700
788
  )
701
789
 
@@ -703,38 +791,65 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
703
791
  return reverse(
704
792
  viewname='django_ledger:coa-detail',
705
793
  kwargs={
706
- 'coa_slug': self.slug,
707
- 'entity_slug': self.entity.slug
794
+ 'entity_slug': self.entity_slug,
795
+ 'coa_slug': self.slug
796
+ }
797
+ )
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
708
805
  }
709
806
  )
710
807
 
711
808
  def get_account_list_url(self):
809
+
810
+ if not self.slug:
811
+ self.generate_slug(commit=True)
812
+
712
813
  return reverse(
713
- viewname='django_ledger:account-list-coa',
814
+ viewname='django_ledger:account-list',
714
815
  kwargs={
715
- 'entity_slug': self.entity.slug,
816
+ 'entity_slug': self.entity_slug,
716
817
  'coa_slug': self.slug
717
818
  }
718
819
  )
719
820
 
720
821
  def get_create_coa_account_url(self):
721
822
  return reverse(
722
- viewname='django_ledger:account-create-coa',
823
+ viewname='django_ledger:account-create',
723
824
  kwargs={
724
825
  'coa_slug': self.slug,
725
- 'entity_slug': self.entity.slug
826
+ 'entity_slug': self.entity_slug
726
827
  }
727
828
  )
728
829
 
729
830
  def clean(self):
730
831
  self.generate_slug()
731
- if self.is_default() and not self.active:
732
- raise ChartOfAccountsModelValidationError(
733
- _('Default Chart of Accounts cannot be deactivated.')
734
- )
735
832
 
736
833
 
737
834
  class ChartOfAccountModel(ChartOfAccountModelAbstract):
738
835
  """
739
836
  Base ChartOfAccounts Model
740
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()
@@ -1,9 +1,6 @@
1
1
  """
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
-
5
- Contributions to this module:
6
- * Miguel Sanda <msanda@arrobalytics.com>
7
4
  """
8
5
 
9
6
  from datetime import datetime, time
@@ -12,6 +9,7 @@ from itertools import groupby, chain
12
9
  from typing import Optional
13
10
  from uuid import uuid4, UUID
14
11
 
12
+ from django.conf import settings
15
13
  from django.core.exceptions import ValidationError
16
14
  from django.core.validators import MinValueValidator
17
15
  from django.db import models
@@ -26,6 +24,7 @@ from django_ledger.models.ledger import LedgerModel
26
24
  from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn
27
25
  from django_ledger.models.transactions import TransactionModel
28
26
  from django_ledger.models.utils import lazy_loader
27
+ from django_ledger.settings import DJANGO_LEDGER_LEDGER_MODEL
29
28
 
30
29
 
31
30
  class ClosingEntryValidationError(ValidationError):
@@ -341,7 +340,9 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
341
340
 
342
341
 
343
342
  class ClosingEntryModel(ClosingEntryModelAbstract):
344
- pass
343
+ class Meta(ClosingEntryModelAbstract.Meta):
344
+ swappable = 'DJANGO_LEDGER_CLOSING_ENTRY_MODEL'
345
+ abstract = False
345
346
 
346
347
 
347
348
  # todo: Remove this model!
@@ -352,12 +353,6 @@ class ClosingEntryTransactionModelQuerySet(models.QuerySet):
352
353
 
353
354
  class ClosingEntryTransactionModelManager(models.Manager):
354
355
 
355
- # def get_queryset(self):
356
- # return super().get_queryset().select_related(
357
- # 'closing_entry_model',
358
- # 'closing_entry_model__entity_model'
359
- # )
360
-
361
356
  def for_entity(self, entity_slug):
362
357
  qs = self.get_queryset()
363
358
  if isinstance(entity_slug, lazy_loader.get_entity_model()):
@@ -2,10 +2,6 @@
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
- Contributions to this module:
6
- * Miguel Sanda <msanda@arrobalytics.com>
7
- * Pranav P Tulshyan <ptulshyan77@gmail.com>
8
-
9
5
  This is the base Chart of Accounts that has all the possible accounts that are useful for the preparation of the
10
6
  Financial Statements. A user may choose to use the default CoA at the creation of each EntityModel but it is not
11
7
  required. The default CoA is intended to provide a QuickStart solution for most use cases.
@@ -14,8 +10,10 @@ The Chart of Accounts is broadly bifurcated into 5 different Sections:
14
10
  1. Assets:
15
11
  2. Liabilities
16
12
  3. Shareholder's Equity
17
- 4. Expenses
18
- 5. Revenue
13
+ 4. Income
14
+ 5. COGS
15
+ 6. Expenses
16
+
19
17
 
20
18
  The Django Ledger Default Chart of Accounts must include the following fields:
21
19
  * Code - String
@@ -35,8 +33,8 @@ Default Chart of Accounts Table
35
33
  1910 asset_adjustment debit Securities Unrealized Gains/Losses root_assets
36
34
  1920 asset_adjustment debit PPE Unrealized Gains/Losses root_assets
37
35
  1010 asset_ca_cash debit Cash root_assets
38
- 1200 asset_ca_inv debit Inventory root_assets
39
36
  1050 asset_ca_mkt_sec debit Short Term Investments root_assets
37
+ 1200 asset_ca_inv debit Inventory root_assets
40
38
  1300 asset_ca_prepaid debit Prepaid Expenses root_assets
41
39
  1100 asset_ca_recv debit Accounts Receivable root_assets
42
40
  1110 asset_ca_uncoll credit Uncollectibles root_assets
@@ -75,7 +73,8 @@ Default Chart of Accounts Table
75
73
  6040 ex_regular debit Bad Debt root_expenses
76
74
  6050 ex_regular debit Bank Charges root_expenses
77
75
  6060 ex_regular debit Commission Expense root_expenses
78
- 6080 ex_regular debit Employee Benefits root_expenses
76
+ 6080 ex_regular debit Employee Benefits root_expenses
77
+ 6081 ex_regular debit Employee Wages root_expenses
79
78
  6090 ex_regular debit Freight root_expenses
80
79
  6110 ex_regular debit Gifts root_expenses
81
80
  6120 ex_regular debit Insurance root_expenses
@@ -83,7 +82,7 @@ Default Chart of Accounts Table
83
82
  6150 ex_regular debit License Expense root_expenses
84
83
  6170 ex_regular debit Maintenance Expense root_expenses
85
84
  6180 ex_regular debit Meals & Entertainment root_expenses
86
- 6190 ex_regular debit Office Expense root_expenses
85
+ 6190 ex_regular debit Office Expense root_expenses
87
86
  6220 ex_regular debit Printing root_expenses
88
87
  6230 ex_regular debit Postage root_expenses
89
88
  6240 ex_regular debit Rent root_expenses
@@ -262,6 +261,8 @@ DEFAULT_CHART_OF_ACCOUNTS = [
262
261
  'parent': None},
263
262
  {'code': '6080', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Employee Benefits',
264
263
  'parent': None},
264
+ {'code': '6081', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Employee Wages',
265
+ 'parent': None},
265
266
  {'code': '6090', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Freight', 'parent': None},
266
267
  {'code': '6110', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Gifts', 'parent': None},
267
268
  {'code': '6120', 'role': roles.EXPENSE_OPERATIONAL, 'balance_type': 'debit', 'name': 'Insurance', 'parent': None},
@@ -2,10 +2,6 @@
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
- Contributions to this module:
6
- * Miguel Sanda <msanda@arrobalytics.com>
7
- * Pranav P Tulshyan <ptulshyan77@gmail.com>
8
-
9
5
  A Customer refers to the person or entity that buys product and services. When issuing Invoices, a Customer must be
10
6
  created before it can be assigned to the InvoiceModel. Only customers who are active can be assigned to new Invoices.
11
7
  """
@@ -14,7 +10,7 @@ from uuid import uuid4
14
10
 
15
11
  from django.core.exceptions import ObjectDoesNotExist
16
12
  from django.db import models, transaction, IntegrityError
17
- from django.db.models import Q, F, QuerySet
13
+ from django.db.models import Q, F, QuerySet, Manager
18
14
  from django.utils.translation import gettext_lazy as _
19
15
 
20
16
  from django_ledger.models.mixins import ContactInfoMixIn, CreateUpdateMixIn, TaxCollectionMixIn
@@ -81,7 +77,7 @@ class CustomerModelQueryset(QuerySet):
81
77
  )
82
78
 
83
79
 
84
- class CustomerModelManager(models.Manager):
80
+ class CustomerModelManager(Manager):
85
81
  """
86
82
  A custom defined CustomerModelManager that will act as an interface to handling the DB queries to the
87
83
  CustomerModel.
@@ -333,3 +329,7 @@ class CustomerModel(CustomerModelAbstract):
333
329
  """
334
330
  Base Customer Model Implementation
335
331
  """
332
+
333
+ class Meta:
334
+ swappable = 'DJANGO_LEDGER_CUSTOMER_MODEL'
335
+ abstract = False
@@ -1,9 +1,6 @@
1
1
  """
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
-
5
- Contributions to this module:
6
- Miguel Sanda <msanda@arrobalytics.com>
7
4
  """
8
5
 
9
6
  from decimal import Decimal
@@ -12,27 +9,26 @@ from uuid import uuid4
12
9
 
13
10
  from django.core.exceptions import ValidationError
14
11
  from django.db import models
15
- from django.db.models import Q, Count, Sum, Case, When, F, Value, DecimalField, BooleanField
12
+ from django.db.models import Q, Count, Sum, Case, When, F, Value, DecimalField, BooleanField, Manager, QuerySet
16
13
  from django.db.models.functions import Coalesce
17
14
  from django.db.models.signals import pre_save
18
15
  from django.utils.translation import gettext_lazy as _
19
16
 
20
17
  from django_ledger.io import ASSET_CA_CASH, CREDIT, DEBIT
18
+ from django_ledger.models import JournalEntryModel
21
19
  from django_ledger.models.mixins import CreateUpdateMixIn
22
20
  from django_ledger.models.utils import lazy_loader
23
21
 
24
- from django_ledger.models import JournalEntryModel
25
-
26
22
 
27
23
  class ImportJobModelValidationError(ValidationError):
28
24
  pass
29
25
 
30
26
 
31
- class ImportJobModelQuerySet(models.QuerySet):
27
+ class ImportJobModelQuerySet(QuerySet):
32
28
  pass
33
29
 
34
30
 
35
- class ImportJobModelManager(models.Manager):
31
+ class ImportJobModelManager(Manager):
36
32
 
37
33
  def get_queryset(self):
38
34
  qs = super().get_queryset()
@@ -505,6 +501,10 @@ class ImportJobModel(ImportJobModelAbstract):
505
501
  Transaction Import Job Model Base Class.
506
502
  """
507
503
 
504
+ class Meta(ImportJobModelAbstract.Meta):
505
+ swappable = 'DJANGO_LEDGER_IMPORT_JOB_MODEL'
506
+ abstract = False
507
+
508
508
 
509
509
  def importjobmodel_presave(instance: ImportJobModel, **kwargs):
510
510
  if instance.is_configured():
@@ -521,3 +521,7 @@ class StagedTransactionModel(StagedTransactionModelAbstract):
521
521
  """
522
522
  Staged Transaction Model Base Class.
523
523
  """
524
+
525
+ class Meta(StagedTransactionModelAbstract.Meta):
526
+ swappable = 'DJANGO_LEDGER_STAGED_TRANSACTION_MODEL'
527
+ abstract = False