django-ledger 0.7.10__py3-none-any.whl → 0.8.0__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 -1
- django_ledger/context.py +12 -0
- django_ledger/forms/bill.py +0 -4
- django_ledger/forms/closing_entry.py +13 -1
- django_ledger/forms/data_import.py +1 -1
- django_ledger/forms/estimate.py +3 -6
- django_ledger/forms/invoice.py +3 -7
- django_ledger/forms/item.py +10 -18
- django_ledger/forms/purchase_order.py +2 -4
- django_ledger/io/io_core.py +25 -32
- django_ledger/io/io_generator.py +7 -6
- django_ledger/io/io_library.py +1 -2
- django_ledger/migrations/0024_billmodel_entity_model_invoicemodel_entity_model.py +24 -0
- django_ledger/migrations/0025_alter_billmodel_cash_account_and_more.py +70 -0
- django_ledger/models/accounts.py +109 -69
- django_ledger/models/bank_account.py +40 -23
- django_ledger/models/bill.py +89 -63
- django_ledger/models/chart_of_accounts.py +173 -105
- django_ledger/models/closing_entry.py +99 -48
- django_ledger/models/customer.py +60 -39
- django_ledger/models/data_import.py +55 -41
- django_ledger/models/deprecations.py +61 -0
- django_ledger/models/entity.py +18 -16
- django_ledger/models/estimate.py +57 -28
- django_ledger/models/invoice.py +58 -28
- django_ledger/models/items.py +503 -142
- django_ledger/models/journal_entry.py +61 -47
- django_ledger/models/ledger.py +106 -42
- django_ledger/models/mixins.py +16 -10
- django_ledger/models/purchase_order.py +39 -17
- django_ledger/models/transactions.py +152 -113
- django_ledger/models/unit.py +57 -30
- django_ledger/models/vendor.py +75 -43
- django_ledger/report/core.py +2 -14
- django_ledger/settings.py +56 -71
- django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
- django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +25 -0
- django_ledger/static/django_ledger/bundle/styles.bundle.js +1 -1
- django_ledger/static/django_ledger/css/djl_styles.css +273 -0
- django_ledger/templates/django_ledger/bills/includes/card_bill.html +2 -2
- django_ledger/templates/django_ledger/components/menu.html +41 -26
- django_ledger/templates/django_ledger/customer/tags/customer_table.html +5 -5
- django_ledger/templates/django_ledger/entity/includes/card_entity.html +12 -6
- django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +1 -1
- django_ledger/templates/django_ledger/financial_statements/cash_flow.html +4 -1
- django_ledger/templates/django_ledger/financial_statements/income_statement.html +4 -1
- django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +27 -3
- django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +16 -4
- django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +73 -18
- django_ledger/templates/django_ledger/includes/widget_ratios.html +18 -24
- django_ledger/templates/django_ledger/invoice/includes/card_invoice.html +3 -3
- django_ledger/templates/django_ledger/layouts/base.html +6 -1
- django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +9 -5
- django_ledger/tests/test_accounts.py +1 -2
- django_ledger/tests/test_io.py +17 -0
- django_ledger/tests/test_purchase_order.py +3 -3
- django_ledger/tests/test_transactions.py +1 -2
- django_ledger/urls/__init__.py +0 -4
- django_ledger/views/bill.py +8 -13
- django_ledger/views/chart_of_accounts.py +6 -4
- django_ledger/views/closing_entry.py +11 -7
- django_ledger/views/customer.py +13 -17
- django_ledger/views/data_import.py +7 -6
- django_ledger/views/djl_api.py +3 -5
- django_ledger/views/entity.py +2 -4
- django_ledger/views/estimate.py +3 -7
- django_ledger/views/inventory.py +3 -5
- django_ledger/views/invoice.py +4 -6
- django_ledger/views/item.py +7 -11
- django_ledger/views/journal_entry.py +1 -2
- django_ledger/views/mixins.py +25 -19
- django_ledger/views/purchase_order.py +24 -35
- django_ledger/views/unit.py +1 -2
- django_ledger/views/vendor.py +1 -2
- {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info}/METADATA +43 -75
- {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info}/RECORD +80 -108
- {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info}/WHEEL +1 -1
- django_ledger-0.8.0.dist-info/top_level.txt +1 -0
- django_ledger/contrib/django_ledger_graphene/__init__.py +0 -0
- django_ledger/contrib/django_ledger_graphene/accounts/schema.py +0 -33
- django_ledger/contrib/django_ledger_graphene/api.py +0 -42
- django_ledger/contrib/django_ledger_graphene/apps.py +0 -6
- django_ledger/contrib/django_ledger_graphene/auth/mutations.py +0 -49
- django_ledger/contrib/django_ledger_graphene/auth/schema.py +0 -6
- django_ledger/contrib/django_ledger_graphene/bank_account/mutations.py +0 -61
- django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +0 -34
- django_ledger/contrib/django_ledger_graphene/bill/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/bill/schema.py +0 -34
- django_ledger/contrib/django_ledger_graphene/coa/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/coa/schema.py +0 -30
- django_ledger/contrib/django_ledger_graphene/customers/__init__.py +0 -0
- django_ledger/contrib/django_ledger_graphene/customers/mutations.py +0 -71
- django_ledger/contrib/django_ledger_graphene/customers/schema.py +0 -43
- django_ledger/contrib/django_ledger_graphene/data_import/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/data_import/schema.py +0 -0
- django_ledger/contrib/django_ledger_graphene/entity/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/entity/schema.py +0 -94
- django_ledger/contrib/django_ledger_graphene/item/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/item/schema.py +0 -31
- django_ledger/contrib/django_ledger_graphene/journal_entry/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +0 -35
- django_ledger/contrib/django_ledger_graphene/ledger/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/ledger/schema.py +0 -32
- django_ledger/contrib/django_ledger_graphene/purchase_order/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/purchase_order/schema.py +0 -31
- django_ledger/contrib/django_ledger_graphene/transaction/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/transaction/schema.py +0 -36
- django_ledger/contrib/django_ledger_graphene/unit/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/unit/schema.py +0 -27
- django_ledger/contrib/django_ledger_graphene/vendor/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/vendor/schema.py +0 -37
- django_ledger/contrib/django_ledger_graphene/views.py +0 -12
- django_ledger-0.7.10.dist-info/top_level.txt +0 -4
- {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info/licenses}/AUTHORS.md +0 -0
- {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -16,9 +16,9 @@ the process of generating accurate financial reports.
|
|
|
16
16
|
The TransactionModel, together with the IOMixIn, is essential for ensuring seamless, efficient, and reliable
|
|
17
17
|
financial statement production in the Django Ledger framework.
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
import warnings
|
|
20
20
|
from datetime import datetime, date
|
|
21
|
-
from typing import List, Union,
|
|
21
|
+
from typing import List, Union, Set
|
|
22
22
|
from uuid import uuid4, UUID
|
|
23
23
|
|
|
24
24
|
from django.contrib.auth import get_user_model
|
|
@@ -30,10 +30,18 @@ from django.db.models.signals import pre_save
|
|
|
30
30
|
from django.utils.translation import gettext_lazy as _
|
|
31
31
|
|
|
32
32
|
from django_ledger.io.io_core import validate_io_timestamp
|
|
33
|
-
from django_ledger.models import
|
|
33
|
+
from django_ledger.models import (
|
|
34
|
+
AccountModel,
|
|
35
|
+
BillModel,
|
|
36
|
+
EntityModel,
|
|
37
|
+
InvoiceModel,
|
|
38
|
+
LedgerModel
|
|
39
|
+
)
|
|
40
|
+
from django_ledger.models.deprecations import deprecated_entity_slug_behavior
|
|
34
41
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
35
42
|
from django_ledger.models.unit import EntityUnitModel
|
|
36
43
|
from django_ledger.models.utils import lazy_loader
|
|
44
|
+
from django_ledger.settings import DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
|
|
37
45
|
|
|
38
46
|
UserModel = get_user_model()
|
|
39
47
|
|
|
@@ -49,7 +57,37 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
49
57
|
based on common use cases.
|
|
50
58
|
"""
|
|
51
59
|
|
|
52
|
-
def
|
|
60
|
+
def for_user(self, user_model) -> 'TransactionModelQuerySet':
|
|
61
|
+
"""
|
|
62
|
+
Filters transactions accessible to a specific user based on their permissions.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
user_model : UserModel
|
|
67
|
+
The user object for which the transactions should be filtered.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
TransactionModelQuerySet
|
|
72
|
+
A queryset containing transactions filtered by the user's access level.
|
|
73
|
+
|
|
74
|
+
Description
|
|
75
|
+
-----------
|
|
76
|
+
- Returns all `TransactionModel` objects for superusers.
|
|
77
|
+
- For regular users, it filters transactions where:
|
|
78
|
+
- The user is an admin of the entity associated with the ledger in the transaction.
|
|
79
|
+
- The user is a manager of the entity associated with the ledger in the transaction.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
if user_model.is_superuser:
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
return self.filter(
|
|
86
|
+
Q(journal_entry__ledger__entity__admin=user_model) |
|
|
87
|
+
Q(journal_entry__ledger__entity__managers__in=[user_model])
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def posted(self) -> 'TransactionModelQuerySet':
|
|
53
91
|
"""
|
|
54
92
|
Retrieves transactions that are part of a posted journal entry and ledger.
|
|
55
93
|
|
|
@@ -67,7 +105,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
67
105
|
Q(journal_entry__ledger__posted=True)
|
|
68
106
|
)
|
|
69
107
|
|
|
70
|
-
def for_accounts(self, account_list: List[Union[AccountModel, str, UUID]]):
|
|
108
|
+
def for_accounts(self, account_list: List[Union[AccountModel, str, UUID]]) -> 'TransactionModelQuerySet':
|
|
71
109
|
"""
|
|
72
110
|
Filters transactions based on the accounts they are associated with.
|
|
73
111
|
|
|
@@ -97,7 +135,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
97
135
|
message=_('Account list must be a list of AccountModel, UUID or str objects (codes).')
|
|
98
136
|
)
|
|
99
137
|
|
|
100
|
-
def for_roles(self, role_list: Union[str, List[str], Set[str]]):
|
|
138
|
+
def for_roles(self, role_list: Union[str, List[str], Set[str]]) -> 'TransactionModelQuerySet':
|
|
101
139
|
"""
|
|
102
140
|
Fetches a QuerySet of TransactionModels which AccountModel has a specific role.
|
|
103
141
|
|
|
@@ -115,7 +153,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
115
153
|
return self.filter(account__role__in=[role_list])
|
|
116
154
|
return self.filter(account__role__in=role_list)
|
|
117
155
|
|
|
118
|
-
def for_unit(self, unit_slug: Union[str, EntityUnitModel]):
|
|
156
|
+
def for_unit(self, unit_slug: Union[str, EntityUnitModel]) -> 'TransactionModelQuerySet':
|
|
119
157
|
"""
|
|
120
158
|
Filters transactions based on their associated entity unit.
|
|
121
159
|
|
|
@@ -133,7 +171,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
133
171
|
return self.filter(journal_entry__entity_unit=unit_slug)
|
|
134
172
|
return self.filter(journal_entry__entity_unit__slug__exact=unit_slug)
|
|
135
173
|
|
|
136
|
-
def for_activity(self, activity_list: Union[str, List[str], Set[str]]):
|
|
174
|
+
def for_activity(self, activity_list: Union[str, List[str], Set[str]]) -> 'TransactionModelQuerySet':
|
|
137
175
|
"""
|
|
138
176
|
Filters transactions based on their associated activity or activities.
|
|
139
177
|
|
|
@@ -151,7 +189,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
151
189
|
return self.filter(journal_entry__activity__in=[activity_list])
|
|
152
190
|
return self.filter(journal_entry__activity__in=activity_list)
|
|
153
191
|
|
|
154
|
-
def to_date(self, to_date: Union[str, date, datetime]):
|
|
192
|
+
def to_date(self, to_date: Union[str, date, datetime]) -> 'TransactionModelQuerySet':
|
|
155
193
|
"""
|
|
156
194
|
Filters transactions occurring on or before a specific date or timestamp.
|
|
157
195
|
|
|
@@ -177,7 +215,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
177
215
|
return self.filter(journal_entry__timestamp__date__lte=to_date)
|
|
178
216
|
return self.filter(journal_entry__timestamp__lte=to_date)
|
|
179
217
|
|
|
180
|
-
def from_date(self, from_date: Union[str, date, datetime]):
|
|
218
|
+
def from_date(self, from_date: Union[str, date, datetime]) -> 'TransactionModelQuerySet':
|
|
181
219
|
"""
|
|
182
220
|
Filters transactions occurring on or after a specific date or timestamp.
|
|
183
221
|
|
|
@@ -203,7 +241,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
203
241
|
|
|
204
242
|
return self.filter(journal_entry__timestamp__gte=from_date)
|
|
205
243
|
|
|
206
|
-
def not_closing_entry(self):
|
|
244
|
+
def not_closing_entry(self) -> 'TransactionModelQuerySet':
|
|
207
245
|
"""
|
|
208
246
|
Filters transactions that are *not* part of a closing journal entry.
|
|
209
247
|
|
|
@@ -214,7 +252,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
214
252
|
"""
|
|
215
253
|
return self.filter(journal_entry__is_closing_entry=False)
|
|
216
254
|
|
|
217
|
-
def is_closing_entry(self):
|
|
255
|
+
def is_closing_entry(self) -> 'TransactionModelQuerySet':
|
|
218
256
|
"""
|
|
219
257
|
Filters transactions that are part of a closing journal entry.
|
|
220
258
|
|
|
@@ -225,7 +263,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
225
263
|
"""
|
|
226
264
|
return self.filter(journal_entry__is_closing_entry=True)
|
|
227
265
|
|
|
228
|
-
def for_ledger(self, ledger_model: Union[LedgerModel, UUID, str]):
|
|
266
|
+
def for_ledger(self, ledger_model: Union[LedgerModel, UUID, str]) -> 'TransactionModelQuerySet':
|
|
229
267
|
"""
|
|
230
268
|
Filters transactions for a specific ledger under a given entity.
|
|
231
269
|
|
|
@@ -243,7 +281,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
243
281
|
return self.filter(journal_entry__ledger__uuid__exact=ledger_model)
|
|
244
282
|
return self.filter(journal_entry__ledger=ledger_model)
|
|
245
283
|
|
|
246
|
-
def for_journal_entry(self, je_model):
|
|
284
|
+
def for_journal_entry(self, je_model) -> 'TransactionModelQuerySet':
|
|
247
285
|
"""
|
|
248
286
|
Filters transactions for a specific journal entry under a given ledger and entity.
|
|
249
287
|
|
|
@@ -261,7 +299,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
261
299
|
return self.filter(journal_entry=je_model)
|
|
262
300
|
return self.filter(journal_entry__uuid__exact=je_model)
|
|
263
301
|
|
|
264
|
-
def for_bill(self, bill_model: Union[BillModel, str, UUID]):
|
|
302
|
+
def for_bill(self, bill_model: Union[BillModel, str, UUID]) -> 'TransactionModelQuerySet':
|
|
265
303
|
"""
|
|
266
304
|
Filters transactions for a specific bill under a given entity.
|
|
267
305
|
|
|
@@ -279,7 +317,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
279
317
|
return self.filter(journal_entry__ledger__billmodel=bill_model)
|
|
280
318
|
return self.filter(journal_entry__ledger__billmodel__uuid__exact=bill_model)
|
|
281
319
|
|
|
282
|
-
def for_invoice(self, invoice_model: Union[InvoiceModel, str, UUID]):
|
|
320
|
+
def for_invoice(self, invoice_model: Union[InvoiceModel, str, UUID]) -> 'TransactionModelQuerySet':
|
|
283
321
|
"""
|
|
284
322
|
Filters transactions for a specific invoice under a given entity.
|
|
285
323
|
|
|
@@ -297,7 +335,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
297
335
|
return self.filter(journal_entry__ledger__invoicemodel=invoice_model)
|
|
298
336
|
return self.filter(journal_entry__ledger__invoicemodel__uuid__exact=invoice_model)
|
|
299
337
|
|
|
300
|
-
def with_annotated_details(self):
|
|
338
|
+
def with_annotated_details(self) -> 'TransactionModelQuerySet':
|
|
301
339
|
return self.annotate(
|
|
302
340
|
entity_unit_name=F('journal_entry__entity_unit__name'),
|
|
303
341
|
account_code=F('account__code'),
|
|
@@ -305,16 +343,16 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
305
343
|
timestamp=F('journal_entry__timestamp'),
|
|
306
344
|
)
|
|
307
345
|
|
|
308
|
-
def is_cleared(self):
|
|
346
|
+
def is_cleared(self) -> 'TransactionModelQuerySet':
|
|
309
347
|
return self.filter(cleared=True)
|
|
310
348
|
|
|
311
|
-
def not_cleared(self):
|
|
349
|
+
def not_cleared(self) -> 'TransactionModelQuerySet':
|
|
312
350
|
return self.filter(cleared=False)
|
|
313
351
|
|
|
314
|
-
def is_reconciled(self):
|
|
352
|
+
def is_reconciled(self) -> 'TransactionModelQuerySet':
|
|
315
353
|
return self.filter(reconciled=True)
|
|
316
354
|
|
|
317
|
-
def not_reconciled(self):
|
|
355
|
+
def not_reconciled(self) -> 'TransactionModelQuerySet':
|
|
318
356
|
return self.filter(reconciled=False)
|
|
319
357
|
|
|
320
358
|
|
|
@@ -348,46 +386,15 @@ class TransactionModelManager(Manager):
|
|
|
348
386
|
'account__coa_model', # Pre-loads the Chart of Accounts related to the Account.
|
|
349
387
|
)
|
|
350
388
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
Filters transactions accessible to a specific user based on their permissions.
|
|
354
|
-
|
|
355
|
-
Parameters
|
|
356
|
-
----------
|
|
357
|
-
user_model : UserModel
|
|
358
|
-
The user object for which the transactions should be filtered.
|
|
359
|
-
|
|
360
|
-
Returns
|
|
361
|
-
-------
|
|
362
|
-
TransactionModelQuerySet
|
|
363
|
-
A queryset containing transactions filtered by the user's access level.
|
|
364
|
-
|
|
365
|
-
Description
|
|
366
|
-
-----------
|
|
367
|
-
- Returns all `TransactionModel` objects for superusers.
|
|
368
|
-
- For regular users, it filters transactions where:
|
|
369
|
-
- The user is an admin of the entity associated with the ledger in the transaction.
|
|
370
|
-
- The user is a manager of the entity associated with the ledger in the transaction.
|
|
371
|
-
"""
|
|
372
|
-
qs = self.get_queryset()
|
|
373
|
-
return qs.filter(
|
|
374
|
-
Q(journal_entry__ledger__entity__admin=user_model) |
|
|
375
|
-
Q(journal_entry__ledger__entity__managers__in=[user_model])
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
def for_entity(self,
|
|
379
|
-
entity_slug: Union[EntityModel, str, UUID],
|
|
380
|
-
user_model: Optional[UserModel] = None) -> TransactionModelQuerySet:
|
|
389
|
+
@deprecated_entity_slug_behavior
|
|
390
|
+
def for_entity(self, entity_model: EntityModel | str | UUID = None, **kwargs) -> TransactionModelQuerySet:
|
|
381
391
|
"""
|
|
382
392
|
Filters transactions for a specific entity, optionally scoped to a specific user.
|
|
383
393
|
|
|
384
394
|
Parameters
|
|
385
395
|
----------
|
|
386
|
-
|
|
396
|
+
entity_model : Union[EntityModel, str, UUID]
|
|
387
397
|
Identifier for the entity. This can be an `EntityModel` object, a slug (str), or a UUID.
|
|
388
|
-
user_model : Optional[UserModel], optional
|
|
389
|
-
The user for whom transactions should be filtered. If provided, applies user-specific
|
|
390
|
-
filtering. Defaults to None.
|
|
391
398
|
|
|
392
399
|
Returns
|
|
393
400
|
-------
|
|
@@ -399,46 +406,60 @@ class TransactionModelManager(Manager):
|
|
|
399
406
|
- If `user_model` is provided, only transactions accessible by the user are included.
|
|
400
407
|
- Supports flexible filtering by accepting different forms of `entity_slug`.
|
|
401
408
|
"""
|
|
402
|
-
if user_model:
|
|
403
|
-
qs = self.for_user(user_model=user_model)
|
|
404
|
-
else:
|
|
405
|
-
qs = self.get_queryset()
|
|
406
409
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
qs = self.get_queryset()
|
|
411
|
+
if 'user_model' in kwargs:
|
|
412
|
+
warnings.warn(
|
|
413
|
+
'user_model parameter is deprecated and will be removed in a future release. '
|
|
414
|
+
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
415
|
+
DeprecationWarning,
|
|
416
|
+
stacklevel=2
|
|
417
|
+
)
|
|
418
|
+
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
419
|
+
qs = qs.for_user(kwargs['user_model'])
|
|
420
|
+
|
|
421
|
+
if isinstance(entity_model, EntityModel):
|
|
422
|
+
qs = qs.filter(journal_entry__ledger__entity=entity_model)
|
|
423
|
+
elif isinstance(entity_model, UUID):
|
|
424
|
+
qs = qs.filter(journal_entry__ledger__entity_id=entity_model)
|
|
425
|
+
elif isinstance(entity_model, str):
|
|
426
|
+
qs = qs.filter(journal_entry__ledger__entity__slug__exact=entity_model)
|
|
427
|
+
else:
|
|
428
|
+
raise TransactionModelValidationError(
|
|
429
|
+
message='entity_model parameter must be either an EntityModel, String or a UUID.'
|
|
430
|
+
)
|
|
431
|
+
return qs
|
|
412
432
|
|
|
413
433
|
|
|
414
434
|
class TransactionModelAbstract(CreateUpdateMixIn):
|
|
415
435
|
"""
|
|
416
|
-
Abstract model for representing
|
|
417
|
-
|
|
418
|
-
This
|
|
419
|
-
|
|
420
|
-
and
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
436
|
+
Abstract base model class for representing financial transactions in a ledger system.
|
|
437
|
+
|
|
438
|
+
This class defines the structure and behavior of transactions in a generic ledger system,
|
|
439
|
+
including attributes such as transaction type, associated account, amount, description,
|
|
440
|
+
and status flags for cleared and reconciled state. It also includes methods and properties
|
|
441
|
+
to facilitate queries and determine transaction type.
|
|
442
|
+
|
|
443
|
+
Attributes
|
|
444
|
+
----------
|
|
445
|
+
uuid : UUID
|
|
446
|
+
Unique identifier of the transaction.
|
|
447
|
+
tx_type : str
|
|
448
|
+
Type of the transaction, either 'credit' or 'debit'.
|
|
449
|
+
journal_entry : ForeignKey
|
|
450
|
+
Reference to the associated JournalEntryModel instance for this transaction.
|
|
451
|
+
account : ForeignKey
|
|
452
|
+
Reference to the associated account in the Chart of Accounts.
|
|
453
|
+
amount : Decimal
|
|
454
|
+
Monetary amount for the transaction. Must be a non-negative value.
|
|
455
|
+
description : str, optional
|
|
456
|
+
Description of the transaction.
|
|
457
|
+
cleared : bool
|
|
458
|
+
Indicates if the transaction has been cleared. Defaults to False.
|
|
459
|
+
reconciled : bool
|
|
460
|
+
Indicates if the transaction has been reconciled. Defaults to False.
|
|
461
|
+
objects : TransactionModelManager
|
|
462
|
+
Custom manager for managing transaction models.
|
|
442
463
|
"""
|
|
443
464
|
|
|
444
465
|
CREDIT = 'credit'
|
|
@@ -481,7 +502,7 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
481
502
|
)
|
|
482
503
|
cleared = models.BooleanField(default=False, verbose_name=_('Cleared'))
|
|
483
504
|
reconciled = models.BooleanField(default=False, verbose_name=_('Reconciled'))
|
|
484
|
-
objects = TransactionModelManager()
|
|
505
|
+
objects = TransactionModelManager.from_queryset(TransactionModelQuerySet)()
|
|
485
506
|
|
|
486
507
|
class Meta:
|
|
487
508
|
abstract = True
|
|
@@ -510,8 +531,17 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
510
531
|
@property
|
|
511
532
|
def coa_id(self):
|
|
512
533
|
"""
|
|
513
|
-
|
|
514
|
-
|
|
534
|
+
Returns the Chart of Accounts (COA) ID associated with the current object.
|
|
535
|
+
|
|
536
|
+
The property attempts to retrieve the COA ID value stored internally, if it exists.
|
|
537
|
+
If the internal value is not set and the `account` attribute is not `None`,
|
|
538
|
+
the COA ID is obtained from the `account.coa_model_id` attribute. Otherwise,
|
|
539
|
+
it will return `None`.
|
|
540
|
+
|
|
541
|
+
Returns
|
|
542
|
+
-------
|
|
543
|
+
Any or None
|
|
544
|
+
The COA ID if it exists, otherwise `None`.
|
|
515
545
|
"""
|
|
516
546
|
try:
|
|
517
547
|
return getattr(self, '_coa_id')
|
|
@@ -521,9 +551,31 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
521
551
|
return self.account.coa_model_id
|
|
522
552
|
|
|
523
553
|
def is_debit(self):
|
|
554
|
+
"""
|
|
555
|
+
Determines if the transaction type is a debit.
|
|
556
|
+
|
|
557
|
+
This method checks whether the transaction type of the current instance is
|
|
558
|
+
set to a debit type.
|
|
559
|
+
|
|
560
|
+
Returns
|
|
561
|
+
-------
|
|
562
|
+
bool
|
|
563
|
+
True if the transaction type is debit, otherwise False.
|
|
564
|
+
"""
|
|
524
565
|
return self.tx_type == self.DEBIT
|
|
525
566
|
|
|
526
567
|
def is_credit(self):
|
|
568
|
+
"""
|
|
569
|
+
Check if the transaction is of type 'credit'.
|
|
570
|
+
|
|
571
|
+
This method evaluates whether the transaction type of the current instance
|
|
572
|
+
matches the 'credit' transaction type.
|
|
573
|
+
|
|
574
|
+
Returns
|
|
575
|
+
-------
|
|
576
|
+
bool
|
|
577
|
+
True if the transaction type is 'credit', False otherwise.
|
|
578
|
+
"""
|
|
527
579
|
return self.tx_type == self.CREDIT
|
|
528
580
|
|
|
529
581
|
|
|
@@ -543,15 +595,6 @@ def transactionmodel_presave(instance: TransactionModel, **kwargs):
|
|
|
543
595
|
This function is executed before saving a `TransactionModel` instance,
|
|
544
596
|
ensuring that certain conditions are met to maintain data integrity.
|
|
545
597
|
|
|
546
|
-
Parameters
|
|
547
|
-
----------
|
|
548
|
-
instance : TransactionModel
|
|
549
|
-
The `TransactionModel` instance that is about to be saved.
|
|
550
|
-
kwargs : dict
|
|
551
|
-
Additional keyword arguments, such as the optional `bypass_account_state`.
|
|
552
|
-
|
|
553
|
-
Validations
|
|
554
|
-
-----------
|
|
555
598
|
The function performs the following validations:
|
|
556
599
|
1. **Account Transactionality**:
|
|
557
600
|
If the `bypass_account_state` flag is not provided or set to `False`,
|
|
@@ -565,6 +608,13 @@ def transactionmodel_presave(instance: TransactionModel, **kwargs):
|
|
|
565
608
|
the transaction cannot be modified. The save process is halted if the
|
|
566
609
|
journal entry is marked as locked.
|
|
567
610
|
|
|
611
|
+
Parameters
|
|
612
|
+
----------
|
|
613
|
+
instance : TransactionModel
|
|
614
|
+
The `TransactionModel` instance that is about to be saved.
|
|
615
|
+
kwargs : dict
|
|
616
|
+
Additional keyword arguments, such as the optional `bypass_account_state`.
|
|
617
|
+
|
|
568
618
|
Raises
|
|
569
619
|
------
|
|
570
620
|
TransactionModelValidationError
|
|
@@ -578,17 +628,6 @@ def transactionmodel_presave(instance: TransactionModel, **kwargs):
|
|
|
578
628
|
When the associated journal entry (`instance.journal_entry`) is locked,
|
|
579
629
|
preventing modification of any related transactions. The error message
|
|
580
630
|
describes the locked journal entry constraint.
|
|
581
|
-
|
|
582
|
-
Example
|
|
583
|
-
-------
|
|
584
|
-
```python
|
|
585
|
-
instance = TransactionModel(...)
|
|
586
|
-
try:
|
|
587
|
-
transactionmodel_presave(instance)
|
|
588
|
-
instance.save() # Save proceeds if no validation error occurs
|
|
589
|
-
except TransactionModelValidationError as e:
|
|
590
|
-
handle_error(str(e)) # Handle validation exception
|
|
591
|
-
```
|
|
592
631
|
"""
|
|
593
632
|
bypass_account_state = kwargs.get('bypass_account_state', False)
|
|
594
633
|
|
django_ledger/models/unit.py
CHANGED
|
@@ -20,11 +20,11 @@ Key advantages of EntityUnits:
|
|
|
20
20
|
flexibility to track inventory, expenses, or income associated with distinct
|
|
21
21
|
business units.
|
|
22
22
|
"""
|
|
23
|
-
|
|
23
|
+
import warnings
|
|
24
24
|
from random import choices
|
|
25
25
|
from string import ascii_lowercase, digits, ascii_uppercase
|
|
26
26
|
from typing import Optional
|
|
27
|
-
from uuid import uuid4
|
|
27
|
+
from uuid import uuid4, UUID
|
|
28
28
|
|
|
29
29
|
from django.core.exceptions import ValidationError
|
|
30
30
|
from django.db import models
|
|
@@ -36,7 +36,9 @@ from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
|
|
|
36
36
|
|
|
37
37
|
from django_ledger.io.io_core import IOMixIn
|
|
38
38
|
from django_ledger.models import lazy_loader
|
|
39
|
+
from django_ledger.models.deprecations import deprecated_entity_slug_behavior
|
|
39
40
|
from django_ledger.models.mixins import CreateUpdateMixIn, SlugNameMixIn
|
|
41
|
+
from django_ledger.settings import DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
|
|
40
42
|
|
|
41
43
|
ENTITY_UNIT_RANDOM_SLUG_SUFFIX = ascii_lowercase + digits
|
|
42
44
|
|
|
@@ -46,9 +48,14 @@ class EntityUnitModelValidationError(ValidationError):
|
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
class EntityUnitModelQuerySet(MP_NodeQuerySet):
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
def for_user(self, user_model) -> 'EntityUnitModelQuerySet':
|
|
53
|
+
if user_model.is_superuser:
|
|
54
|
+
return self
|
|
55
|
+
return self.filter(
|
|
56
|
+
Q(entity__admin=user_model) |
|
|
57
|
+
Q(entity__managers__in=[user_model])
|
|
58
|
+
)
|
|
52
59
|
|
|
53
60
|
|
|
54
61
|
class EntityUnitModelManager(MP_NodeManager):
|
|
@@ -60,41 +67,61 @@ class EntityUnitModelManager(MP_NodeManager):
|
|
|
60
67
|
_entity_name=F('entity__name'),
|
|
61
68
|
)
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if user_model.is_superuser:
|
|
66
|
-
return qs
|
|
67
|
-
return qs.filter(
|
|
68
|
-
Q(entity__admin=user_model) |
|
|
69
|
-
Q(entity__managers__in=[user_model])
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
def for_entity(self, entity_slug: str, user_model):
|
|
70
|
+
@deprecated_entity_slug_behavior
|
|
71
|
+
def for_entity(self, entity_model: 'EntityModel | str | UUID' = None, **kwargs):
|
|
73
72
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
Filter the queryset based on the provided entity model, its slug, or its UUID.
|
|
74
|
+
|
|
75
|
+
Provides functionality to filter entities within a queryset by comparing either
|
|
76
|
+
an EntityModel instance, its slug, or its UUID. This method also handles optional
|
|
77
|
+
deprecated behavior for filtering by the 'user_model' for backward compatibility.
|
|
76
78
|
|
|
77
79
|
Parameters
|
|
78
80
|
----------
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
entity_model : EntityModel | str | UUID
|
|
82
|
+
An entity filter criterion that could be a specific EntityModel instance, a
|
|
83
|
+
string representing the entity slug, or a UUID corresponding to the entity.
|
|
84
|
+
|
|
85
|
+
**kwargs : dict, optional
|
|
86
|
+
Additional keyword arguments. If the 'user_model' parameter is provided,
|
|
87
|
+
a deprecation warning is issued, and the functionality temporarily delegates
|
|
88
|
+
to legacy behavior based on the value of 'DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR'.
|
|
83
89
|
|
|
84
90
|
Returns
|
|
85
91
|
-------
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
QuerySet
|
|
93
|
+
A queryset filtered based on the provided entity criteria.
|
|
94
|
+
|
|
95
|
+
Raises
|
|
96
|
+
------
|
|
97
|
+
EntityUnitModelValidationError
|
|
98
|
+
Raised when the `entity_model` parameter does not match any of the accepted types
|
|
99
|
+
(EntityModel, str, or UUID) and fails validation.
|
|
88
100
|
"""
|
|
89
|
-
|
|
90
|
-
if isinstance(entity_slug, lazy_loader.get_entity_model()):
|
|
91
|
-
return qs.filter(
|
|
92
|
-
Q(entity=entity_slug)
|
|
101
|
+
EntityModel = lazy_loader.get_entity_model()
|
|
93
102
|
|
|
103
|
+
qs = self.get_queryset()
|
|
104
|
+
if 'user_model' in kwargs:
|
|
105
|
+
warnings.warn(
|
|
106
|
+
'user_model parameter is deprecated and will be removed in a future release. '
|
|
107
|
+
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
108
|
+
DeprecationWarning,
|
|
109
|
+
stacklevel=2
|
|
94
110
|
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
112
|
+
qs = qs.for_user(kwargs['user_model'])
|
|
113
|
+
|
|
114
|
+
if isinstance(entity_model, EntityModel):
|
|
115
|
+
qs = qs.filter(entity=entity_model)
|
|
116
|
+
elif isinstance(entity_model, str):
|
|
117
|
+
qs = qs.filter(entity__slug__exact=entity_model)
|
|
118
|
+
elif isinstance(entity_model, UUID):
|
|
119
|
+
qs = qs.filter(entity_id=entity_model)
|
|
120
|
+
else:
|
|
121
|
+
raise EntityUnitModelValidationError(
|
|
122
|
+
message='Must pass EntityModel, slug or UUID'
|
|
123
|
+
)
|
|
124
|
+
return qs
|
|
98
125
|
|
|
99
126
|
|
|
100
127
|
class EntityUnitModelAbstract(MP_Node,
|