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.
- django_ledger/__init__.py +1 -4
- django_ledger/admin/__init__.py +1 -1
- django_ledger/admin/{coa.py → chart_of_accounts.py} +1 -1
- django_ledger/admin/entity.py +1 -1
- django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
- django_ledger/forms/account.py +43 -38
- django_ledger/forms/bank_account.py +5 -2
- django_ledger/forms/bill.py +24 -36
- django_ledger/forms/chart_of_accounts.py +82 -0
- django_ledger/forms/customer.py +1 -1
- django_ledger/forms/data_import.py +3 -3
- django_ledger/forms/estimate.py +1 -1
- django_ledger/forms/invoice.py +5 -7
- django_ledger/forms/item.py +24 -15
- django_ledger/forms/transactions.py +3 -3
- django_ledger/io/io_core.py +4 -2
- django_ledger/io/io_library.py +1 -1
- django_ledger/io/io_middleware.py +5 -0
- django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
- django_ledger/migrations/0018_transactionmodel_cleared_transactionmodel_reconciled_and_more.py +37 -0
- django_ledger/models/__init__.py +1 -1
- django_ledger/models/accounts.py +229 -265
- django_ledger/models/bank_account.py +6 -6
- django_ledger/models/bill.py +7 -6
- django_ledger/models/{coa.py → chart_of_accounts.py} +187 -72
- django_ledger/models/closing_entry.py +5 -10
- django_ledger/models/coa_default.py +10 -9
- django_ledger/models/customer.py +6 -6
- django_ledger/models/data_import.py +12 -8
- django_ledger/models/entity.py +96 -39
- django_ledger/models/estimate.py +6 -10
- django_ledger/models/invoice.py +14 -11
- django_ledger/models/items.py +23 -14
- django_ledger/models/journal_entry.py +73 -30
- django_ledger/models/ledger.py +8 -8
- django_ledger/models/mixins.py +0 -3
- django_ledger/models/purchase_order.py +9 -9
- django_ledger/models/signals.py +0 -3
- django_ledger/models/transactions.py +24 -7
- django_ledger/models/unit.py +4 -3
- django_ledger/models/utils.py +0 -3
- django_ledger/models/vendor.py +4 -3
- django_ledger/settings.py +28 -3
- django_ledger/templates/django_ledger/account/account_create.html +2 -2
- django_ledger/templates/django_ledger/account/account_update.html +1 -1
- django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +29 -19
- django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
- django_ledger/templates/django_ledger/chart_of_accounts/coa_create.html +25 -0
- django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +25 -6
- django_ledger/templates/django_ledger/chart_of_accounts/coa_update.html +2 -2
- django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +10 -4
- django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
- django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +2 -2
- django_ledger/templates/django_ledger/includes/footer.html +2 -2
- django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
- django_ledger/templatetags/django_ledger.py +7 -1
- django_ledger/tests/base.py +23 -7
- django_ledger/tests/test_accounts.py +145 -9
- django_ledger/urls/account.py +17 -24
- django_ledger/urls/chart_of_accounts.py +6 -0
- django_ledger/utils.py +9 -36
- django_ledger/views/__init__.py +2 -2
- django_ledger/views/account.py +91 -116
- django_ledger/views/auth.py +1 -1
- django_ledger/views/bank_account.py +9 -11
- django_ledger/views/bill.py +91 -80
- django_ledger/views/{coa.py → chart_of_accounts.py} +49 -44
- django_ledger/views/closing_entry.py +8 -0
- django_ledger/views/customer.py +1 -1
- django_ledger/views/data_import.py +1 -1
- django_ledger/views/entity.py +1 -1
- django_ledger/views/estimate.py +13 -8
- django_ledger/views/feedback.py +1 -1
- django_ledger/views/financial_statement.py +1 -1
- django_ledger/views/home.py +1 -1
- django_ledger/views/inventory.py +9 -0
- django_ledger/views/invoice.py +5 -2
- django_ledger/views/item.py +58 -68
- django_ledger/views/journal_entry.py +1 -1
- django_ledger/views/ledger.py +3 -1
- django_ledger/views/mixins.py +25 -13
- django_ledger/views/purchase_order.py +1 -1
- django_ledger/views/transactions.py +1 -1
- django_ledger/views/unit.py +9 -0
- django_ledger/views/vendor.py +1 -1
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +8 -2
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/METADATA +33 -44
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/RECORD +92 -89
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +1 -1
- django_ledger/forms/coa.py +0 -47
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/top_level.txt +0 -0
django_ledger/models/bill.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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(
|
|
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(
|
|
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
|
-
)
|
|
135
|
+
)
|
|
93
136
|
|
|
94
|
-
def for_entity(self,
|
|
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(
|
|
115
|
-
return qs.filter(entity=
|
|
116
|
-
return qs.filter(entity__slug__iexact=
|
|
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
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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.
|
|
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.
|
|
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
|
-
'
|
|
707
|
-
'
|
|
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
|
|
814
|
+
viewname='django_ledger:account-list',
|
|
714
815
|
kwargs={
|
|
715
|
-
'entity_slug': self.
|
|
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
|
|
823
|
+
viewname='django_ledger:account-create',
|
|
723
824
|
kwargs={
|
|
724
825
|
'coa_slug': self.slug,
|
|
725
|
-
'entity_slug': self.
|
|
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
|
-
|
|
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.
|
|
18
|
-
5.
|
|
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
|
|
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
|
|
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},
|
django_ledger/models/customer.py
CHANGED
|
@@ -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(
|
|
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(
|
|
27
|
+
class ImportJobModelQuerySet(QuerySet):
|
|
32
28
|
pass
|
|
33
29
|
|
|
34
30
|
|
|
35
|
-
class ImportJobModelManager(
|
|
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
|