django-ledger 0.8.0__py3-none-any.whl → 0.8.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 -1
- django_ledger/forms/account.py +45 -46
- django_ledger/forms/data_import.py +182 -63
- django_ledger/io/io_core.py +507 -374
- django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
- django_ledger/models/__init__.py +2 -1
- django_ledger/models/bill.py +337 -300
- django_ledger/models/customer.py +47 -34
- django_ledger/models/data_import.py +770 -289
- django_ledger/models/entity.py +882 -637
- django_ledger/models/mixins.py +421 -280
- django_ledger/models/receipt.py +1083 -0
- django_ledger/models/transactions.py +105 -41
- django_ledger/models/unit.py +42 -30
- django_ledger/models/utils.py +12 -2
- django_ledger/models/vendor.py +85 -66
- django_ledger/settings.py +1 -0
- django_ledger/templates/django_ledger/components/period_navigator.html +5 -3
- django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
- django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
- django_ledger/templates/django_ledger/customer/tags/customer_table.html +3 -1
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
- django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
- django_ledger/templates/django_ledger/layouts/base.html +1 -1
- django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
- django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
- django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
- django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
- django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
- django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
- django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +3 -2
- django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
- django_ledger/templatetags/django_ledger.py +338 -191
- django_ledger/urls/__init__.py +1 -0
- django_ledger/urls/customer.py +3 -0
- django_ledger/urls/data_import.py +3 -0
- django_ledger/urls/receipt.py +102 -0
- django_ledger/urls/vendor.py +1 -0
- django_ledger/views/__init__.py +1 -0
- django_ledger/views/customer.py +56 -14
- django_ledger/views/data_import.py +119 -66
- django_ledger/views/mixins.py +112 -86
- django_ledger/views/receipt.py +294 -0
- django_ledger/views/vendor.py +53 -14
- {django_ledger-0.8.0.dist-info → django_ledger-0.8.1.dist-info}/METADATA +1 -1
- {django_ledger-0.8.0.dist-info → django_ledger-0.8.1.dist-info}/RECORD +51 -40
- {django_ledger-0.8.0.dist-info → django_ledger-0.8.1.dist-info}/WHEEL +0 -0
- {django_ledger-0.8.0.dist-info → django_ledger-0.8.1.dist-info}/licenses/AUTHORS.md +0 -0
- {django_ledger-0.8.0.dist-info → django_ledger-0.8.1.dist-info}/licenses/LICENSE +0 -0
- {django_ledger-0.8.0.dist-info → django_ledger-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -16,6 +16,7 @@ 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
20
|
import warnings
|
|
20
21
|
from datetime import datetime, date
|
|
21
22
|
from typing import List, Union, Set
|
|
@@ -27,6 +28,7 @@ from django.core.validators import MinValueValidator
|
|
|
27
28
|
from django.db import models
|
|
28
29
|
from django.db.models import Q, QuerySet, Manager, F
|
|
29
30
|
from django.db.models.signals import pre_save
|
|
31
|
+
from django.urls import reverse
|
|
30
32
|
from django.utils.translation import gettext_lazy as _
|
|
31
33
|
|
|
32
34
|
from django_ledger.io.io_core import validate_io_timestamp
|
|
@@ -35,7 +37,7 @@ from django_ledger.models import (
|
|
|
35
37
|
BillModel,
|
|
36
38
|
EntityModel,
|
|
37
39
|
InvoiceModel,
|
|
38
|
-
LedgerModel
|
|
40
|
+
LedgerModel,
|
|
39
41
|
)
|
|
40
42
|
from django_ledger.models.deprecations import deprecated_entity_slug_behavior
|
|
41
43
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
@@ -83,8 +85,8 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
83
85
|
return self
|
|
84
86
|
|
|
85
87
|
return self.filter(
|
|
86
|
-
Q(journal_entry__ledger__entity__admin=user_model)
|
|
87
|
-
Q(journal_entry__ledger__entity__managers__in=[user_model])
|
|
88
|
+
Q(journal_entry__ledger__entity__admin=user_model)
|
|
89
|
+
| Q(journal_entry__ledger__entity__managers__in=[user_model])
|
|
88
90
|
)
|
|
89
91
|
|
|
90
92
|
def posted(self) -> 'TransactionModelQuerySet':
|
|
@@ -101,11 +103,12 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
101
103
|
A QuerySet containing only transactions that meet the "posted" criteria.
|
|
102
104
|
"""
|
|
103
105
|
return self.filter(
|
|
104
|
-
Q(journal_entry__posted=True) &
|
|
105
|
-
Q(journal_entry__ledger__posted=True)
|
|
106
|
+
Q(journal_entry__posted=True) & Q(journal_entry__ledger__posted=True)
|
|
106
107
|
)
|
|
107
108
|
|
|
108
|
-
def for_accounts(
|
|
109
|
+
def for_accounts(
|
|
110
|
+
self, account_list: List[Union[AccountModel, str, UUID]]
|
|
111
|
+
) -> 'TransactionModelQuerySet':
|
|
109
112
|
"""
|
|
110
113
|
Filters transactions based on the accounts they are associated with.
|
|
111
114
|
|
|
@@ -123,7 +126,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
123
126
|
|
|
124
127
|
if not isinstance(account_list, list) or not len(account_list) > 0:
|
|
125
128
|
raise TransactionModelValidationError(
|
|
126
|
-
message=_(
|
|
129
|
+
message=_(
|
|
130
|
+
'Account list must be a list of AccountModel, UUID or str objects (codes).'
|
|
131
|
+
)
|
|
127
132
|
)
|
|
128
133
|
if isinstance(account_list[0], str):
|
|
129
134
|
return self.filter(account__code__in=account_list)
|
|
@@ -132,10 +137,14 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
132
137
|
elif isinstance(account_list[0], AccountModel):
|
|
133
138
|
return self.filter(account__in=account_list)
|
|
134
139
|
raise TransactionModelValidationError(
|
|
135
|
-
message=_(
|
|
140
|
+
message=_(
|
|
141
|
+
'Account list must be a list of AccountModel, UUID or str objects (codes).'
|
|
142
|
+
)
|
|
136
143
|
)
|
|
137
144
|
|
|
138
|
-
def for_roles(
|
|
145
|
+
def for_roles(
|
|
146
|
+
self, role_list: Union[str, List[str], Set[str]]
|
|
147
|
+
) -> 'TransactionModelQuerySet':
|
|
139
148
|
"""
|
|
140
149
|
Fetches a QuerySet of TransactionModels which AccountModel has a specific role.
|
|
141
150
|
|
|
@@ -153,7 +162,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
153
162
|
return self.filter(account__role__in=[role_list])
|
|
154
163
|
return self.filter(account__role__in=role_list)
|
|
155
164
|
|
|
156
|
-
def for_unit(
|
|
165
|
+
def for_unit(
|
|
166
|
+
self, unit_slug: Union[str, EntityUnitModel]
|
|
167
|
+
) -> 'TransactionModelQuerySet':
|
|
157
168
|
"""
|
|
158
169
|
Filters transactions based on their associated entity unit.
|
|
159
170
|
|
|
@@ -171,7 +182,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
171
182
|
return self.filter(journal_entry__entity_unit=unit_slug)
|
|
172
183
|
return self.filter(journal_entry__entity_unit__slug__exact=unit_slug)
|
|
173
184
|
|
|
174
|
-
def for_activity(
|
|
185
|
+
def for_activity(
|
|
186
|
+
self, activity_list: Union[str, List[str], Set[str]]
|
|
187
|
+
) -> 'TransactionModelQuerySet':
|
|
175
188
|
"""
|
|
176
189
|
Filters transactions based on their associated activity or activities.
|
|
177
190
|
|
|
@@ -189,7 +202,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
189
202
|
return self.filter(journal_entry__activity__in=[activity_list])
|
|
190
203
|
return self.filter(journal_entry__activity__in=activity_list)
|
|
191
204
|
|
|
192
|
-
def to_date(
|
|
205
|
+
def to_date(
|
|
206
|
+
self, to_date: Union[str, date, datetime]
|
|
207
|
+
) -> 'TransactionModelQuerySet':
|
|
193
208
|
"""
|
|
194
209
|
Filters transactions occurring on or before a specific date or timestamp.
|
|
195
210
|
|
|
@@ -215,7 +230,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
215
230
|
return self.filter(journal_entry__timestamp__date__lte=to_date)
|
|
216
231
|
return self.filter(journal_entry__timestamp__lte=to_date)
|
|
217
232
|
|
|
218
|
-
def from_date(
|
|
233
|
+
def from_date(
|
|
234
|
+
self, from_date: Union[str, date, datetime]
|
|
235
|
+
) -> 'TransactionModelQuerySet':
|
|
219
236
|
"""
|
|
220
237
|
Filters transactions occurring on or after a specific date or timestamp.
|
|
221
238
|
|
|
@@ -263,7 +280,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
263
280
|
"""
|
|
264
281
|
return self.filter(journal_entry__is_closing_entry=True)
|
|
265
282
|
|
|
266
|
-
def for_ledger(
|
|
283
|
+
def for_ledger(
|
|
284
|
+
self, ledger_model: Union[LedgerModel, UUID, str]
|
|
285
|
+
) -> 'TransactionModelQuerySet':
|
|
267
286
|
"""
|
|
268
287
|
Filters transactions for a specific ledger under a given entity.
|
|
269
288
|
|
|
@@ -299,7 +318,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
299
318
|
return self.filter(journal_entry=je_model)
|
|
300
319
|
return self.filter(journal_entry__uuid__exact=je_model)
|
|
301
320
|
|
|
302
|
-
def for_bill(
|
|
321
|
+
def for_bill(
|
|
322
|
+
self, bill_model: Union[BillModel, str, UUID]
|
|
323
|
+
) -> 'TransactionModelQuerySet':
|
|
303
324
|
"""
|
|
304
325
|
Filters transactions for a specific bill under a given entity.
|
|
305
326
|
|
|
@@ -317,7 +338,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
317
338
|
return self.filter(journal_entry__ledger__billmodel=bill_model)
|
|
318
339
|
return self.filter(journal_entry__ledger__billmodel__uuid__exact=bill_model)
|
|
319
340
|
|
|
320
|
-
def for_invoice(
|
|
341
|
+
def for_invoice(
|
|
342
|
+
self, invoice_model: Union[InvoiceModel, str, UUID]
|
|
343
|
+
) -> 'TransactionModelQuerySet':
|
|
321
344
|
"""
|
|
322
345
|
Filters transactions for a specific invoice under a given entity.
|
|
323
346
|
|
|
@@ -333,7 +356,9 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
333
356
|
"""
|
|
334
357
|
if isinstance(invoice_model, InvoiceModel):
|
|
335
358
|
return self.filter(journal_entry__ledger__invoicemodel=invoice_model)
|
|
336
|
-
return self.filter(
|
|
359
|
+
return self.filter(
|
|
360
|
+
journal_entry__ledger__invoicemodel__uuid__exact=invoice_model
|
|
361
|
+
)
|
|
337
362
|
|
|
338
363
|
def with_annotated_details(self) -> 'TransactionModelQuerySet':
|
|
339
364
|
return self.annotate(
|
|
@@ -378,16 +403,20 @@ class TransactionModelManager(Manager):
|
|
|
378
403
|
"""
|
|
379
404
|
qs = TransactionModelQuerySet(self.model, using=self._db)
|
|
380
405
|
return qs.annotate(
|
|
406
|
+
_entity_slug=F('journal_entry__ledger__entity__slug'),
|
|
407
|
+
_ledger_uuid=F('journal_entry__ledger_id'),
|
|
381
408
|
timestamp=F('journal_entry__timestamp'),
|
|
382
|
-
_coa_id=F('account__coa_model_id')
|
|
409
|
+
_coa_id=F('account__coa_model_id'),
|
|
383
410
|
).select_related(
|
|
384
|
-
'journal_entry',
|
|
385
|
-
'account',
|
|
386
|
-
'account__coa_model',
|
|
411
|
+
'journal_entry',
|
|
412
|
+
'account',
|
|
413
|
+
'account__coa_model',
|
|
387
414
|
)
|
|
388
415
|
|
|
389
416
|
@deprecated_entity_slug_behavior
|
|
390
|
-
def for_entity(
|
|
417
|
+
def for_entity(
|
|
418
|
+
self, entity_model: EntityModel | str | UUID = None, **kwargs
|
|
419
|
+
) -> TransactionModelQuerySet:
|
|
391
420
|
"""
|
|
392
421
|
Filters transactions for a specific entity, optionally scoped to a specific user.
|
|
393
422
|
|
|
@@ -413,7 +442,7 @@ class TransactionModelManager(Manager):
|
|
|
413
442
|
'user_model parameter is deprecated and will be removed in a future release. '
|
|
414
443
|
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
415
444
|
DeprecationWarning,
|
|
416
|
-
stacklevel=2
|
|
445
|
+
stacklevel=2,
|
|
417
446
|
)
|
|
418
447
|
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
419
448
|
qs = qs.for_user(kwargs['user_model'])
|
|
@@ -464,26 +493,27 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
464
493
|
|
|
465
494
|
CREDIT = 'credit'
|
|
466
495
|
DEBIT = 'debit'
|
|
467
|
-
TX_TYPE = [
|
|
468
|
-
(CREDIT, _('Credit')),
|
|
469
|
-
(DEBIT, _('Debit'))
|
|
470
|
-
]
|
|
496
|
+
TX_TYPE = [(CREDIT, _('Credit')), (DEBIT, _('Debit'))]
|
|
471
497
|
|
|
472
498
|
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
|
473
|
-
tx_type = models.CharField(
|
|
499
|
+
tx_type = models.CharField(
|
|
500
|
+
max_length=10, choices=TX_TYPE, verbose_name=_('Transaction Type')
|
|
501
|
+
)
|
|
474
502
|
|
|
475
503
|
journal_entry = models.ForeignKey(
|
|
476
504
|
'django_ledger.JournalEntryModel',
|
|
477
505
|
editable=False,
|
|
478
506
|
verbose_name=_('Journal Entry'),
|
|
479
507
|
help_text=_('Journal Entry to be associated with this transaction.'),
|
|
480
|
-
on_delete=models.CASCADE
|
|
508
|
+
on_delete=models.CASCADE,
|
|
481
509
|
)
|
|
482
510
|
account = models.ForeignKey(
|
|
483
511
|
'django_ledger.AccountModel',
|
|
484
512
|
verbose_name=_('Account'),
|
|
485
|
-
help_text=_(
|
|
486
|
-
|
|
513
|
+
help_text=_(
|
|
514
|
+
'Account from Chart of Accounts to be associated with this transaction.'
|
|
515
|
+
),
|
|
516
|
+
on_delete=models.PROTECT,
|
|
487
517
|
)
|
|
488
518
|
amount = models.DecimalField(
|
|
489
519
|
decimal_places=2,
|
|
@@ -491,14 +521,14 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
491
521
|
default=0.00,
|
|
492
522
|
verbose_name=_('Amount'),
|
|
493
523
|
help_text=_('Amount of the transaction.'),
|
|
494
|
-
validators=[MinValueValidator(0)]
|
|
524
|
+
validators=[MinValueValidator(0)],
|
|
495
525
|
)
|
|
496
526
|
description = models.CharField(
|
|
497
527
|
max_length=100,
|
|
498
528
|
null=True,
|
|
499
529
|
blank=True,
|
|
500
530
|
verbose_name=_('Transaction Description'),
|
|
501
|
-
help_text=_('A description to be included with this individual transaction.')
|
|
531
|
+
help_text=_('A description to be included with this individual transaction.'),
|
|
502
532
|
)
|
|
503
533
|
cleared = models.BooleanField(default=False, verbose_name=_('Cleared'))
|
|
504
534
|
reconciled = models.BooleanField(default=False, verbose_name=_('Reconciled'))
|
|
@@ -525,9 +555,25 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
525
555
|
name=self.account.name,
|
|
526
556
|
balance_type=self.account.balance_type,
|
|
527
557
|
amount=self.amount,
|
|
528
|
-
tx_type=self.tx_type
|
|
558
|
+
tx_type=self.tx_type,
|
|
529
559
|
)
|
|
530
560
|
|
|
561
|
+
@property
|
|
562
|
+
def entity_slug(self) -> str:
|
|
563
|
+
try:
|
|
564
|
+
return getattr(self, '_entity_slug')
|
|
565
|
+
except AttributeError:
|
|
566
|
+
pass
|
|
567
|
+
return self.journal_entry.ledger.entity.slug
|
|
568
|
+
|
|
569
|
+
@property
|
|
570
|
+
def ledger_uuid(self) -> UUID:
|
|
571
|
+
try:
|
|
572
|
+
return getattr(self, '_ledger_uuid')
|
|
573
|
+
except AttributeError:
|
|
574
|
+
pass
|
|
575
|
+
return self.journal_entry.ledger.uuid
|
|
576
|
+
|
|
531
577
|
@property
|
|
532
578
|
def coa_id(self):
|
|
533
579
|
"""
|
|
@@ -550,7 +596,7 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
550
596
|
return None
|
|
551
597
|
return self.account.coa_model_id
|
|
552
598
|
|
|
553
|
-
def is_debit(self):
|
|
599
|
+
def is_debit(self) -> bool:
|
|
554
600
|
"""
|
|
555
601
|
Determines if the transaction type is a debit.
|
|
556
602
|
|
|
@@ -564,7 +610,7 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
564
610
|
"""
|
|
565
611
|
return self.tx_type == self.DEBIT
|
|
566
612
|
|
|
567
|
-
def is_credit(self):
|
|
613
|
+
def is_credit(self) -> bool:
|
|
568
614
|
"""
|
|
569
615
|
Check if the transaction is of type 'credit'.
|
|
570
616
|
|
|
@@ -578,6 +624,25 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
578
624
|
"""
|
|
579
625
|
return self.tx_type == self.CREDIT
|
|
580
626
|
|
|
627
|
+
def get_ledger_detailr_url(self) -> str:
|
|
628
|
+
return reverse(
|
|
629
|
+
viewname='django_ledger:ledger-detail',
|
|
630
|
+
kwargs={
|
|
631
|
+
'ledger_pk': self.ledger_uuid,
|
|
632
|
+
'entity_slug': self.entity_slug,
|
|
633
|
+
},
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
def get_journal_entry_detail_url(self) -> str:
|
|
637
|
+
return reverse(
|
|
638
|
+
viewname='django_ledger:je-detail',
|
|
639
|
+
kwargs={
|
|
640
|
+
'je_pk': self.journal_entry_id,
|
|
641
|
+
'entity_slug': self.entity_slug,
|
|
642
|
+
'ledger_pk': self.ledger_uuid,
|
|
643
|
+
},
|
|
644
|
+
)
|
|
645
|
+
|
|
581
646
|
|
|
582
647
|
class TransactionModel(TransactionModelAbstract):
|
|
583
648
|
"""
|
|
@@ -636,12 +701,11 @@ def transactionmodel_presave(instance: TransactionModel, **kwargs):
|
|
|
636
701
|
message=_('Transactions cannot be linked to root accounts.')
|
|
637
702
|
)
|
|
638
703
|
|
|
639
|
-
if all([
|
|
640
|
-
not bypass_account_state,
|
|
641
|
-
not instance.account.can_transact()
|
|
642
|
-
]):
|
|
704
|
+
if all([not bypass_account_state, not instance.account.can_transact()]):
|
|
643
705
|
raise TransactionModelValidationError(
|
|
644
|
-
message=_(
|
|
706
|
+
message=_(
|
|
707
|
+
f'Cannot create or modify transactions on account model {instance.account}.'
|
|
708
|
+
)
|
|
645
709
|
)
|
|
646
710
|
if instance.journal_entry.is_locked():
|
|
647
711
|
raise TransactionModelValidationError(
|
django_ledger/models/unit.py
CHANGED
|
@@ -20,15 +20,16 @@ Key advantages of EntityUnits:
|
|
|
20
20
|
flexibility to track inventory, expenses, or income associated with distinct
|
|
21
21
|
business units.
|
|
22
22
|
"""
|
|
23
|
+
|
|
23
24
|
import warnings
|
|
24
25
|
from random import choices
|
|
25
|
-
from string import ascii_lowercase,
|
|
26
|
+
from string import ascii_lowercase, ascii_uppercase, digits
|
|
26
27
|
from typing import Optional
|
|
27
|
-
from uuid import
|
|
28
|
+
from uuid import UUID, uuid4
|
|
28
29
|
|
|
29
30
|
from django.core.exceptions import ValidationError
|
|
30
31
|
from django.db import models
|
|
31
|
-
from django.db.models import
|
|
32
|
+
from django.db.models import F, Q
|
|
32
33
|
from django.urls import reverse
|
|
33
34
|
from django.utils.text import slugify
|
|
34
35
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -48,18 +49,15 @@ class EntityUnitModelValidationError(ValidationError):
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class EntityUnitModelQuerySet(MP_NodeQuerySet):
|
|
51
|
-
|
|
52
52
|
def for_user(self, user_model) -> 'EntityUnitModelQuerySet':
|
|
53
53
|
if user_model.is_superuser:
|
|
54
54
|
return self
|
|
55
55
|
return self.filter(
|
|
56
|
-
Q(entity__admin=user_model) |
|
|
57
|
-
Q(entity__managers__in=[user_model])
|
|
56
|
+
Q(entity__admin=user_model) | Q(entity__managers__in=[user_model])
|
|
58
57
|
)
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
class EntityUnitModelManager(MP_NodeManager):
|
|
62
|
-
|
|
63
61
|
def get_queryset(self):
|
|
64
62
|
qs = EntityUnitModelQuerySet(self.model, using=self._db)
|
|
65
63
|
return qs.annotate(
|
|
@@ -106,7 +104,7 @@ class EntityUnitModelManager(MP_NodeManager):
|
|
|
106
104
|
'user_model parameter is deprecated and will be removed in a future release. '
|
|
107
105
|
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
108
106
|
DeprecationWarning,
|
|
109
|
-
stacklevel=2
|
|
107
|
+
stacklevel=2,
|
|
110
108
|
)
|
|
111
109
|
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
112
110
|
qs = qs.for_user(kwargs['user_model'])
|
|
@@ -124,10 +122,7 @@ class EntityUnitModelManager(MP_NodeManager):
|
|
|
124
122
|
return qs
|
|
125
123
|
|
|
126
124
|
|
|
127
|
-
class EntityUnitModelAbstract(MP_Node,
|
|
128
|
-
IOMixIn,
|
|
129
|
-
SlugNameMixIn,
|
|
130
|
-
CreateUpdateMixIn):
|
|
125
|
+
class EntityUnitModelAbstract(MP_Node, IOMixIn, SlugNameMixIn, CreateUpdateMixIn):
|
|
131
126
|
"""
|
|
132
127
|
Base implementation of the EntityUnitModel.
|
|
133
128
|
|
|
@@ -152,17 +147,22 @@ class EntityUnitModelAbstract(MP_Node,
|
|
|
152
147
|
hidden: bool
|
|
153
148
|
Hidden Units will not show on drop down menus on the UI. Defaults to False.
|
|
154
149
|
"""
|
|
150
|
+
|
|
155
151
|
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
|
156
152
|
slug = models.SlugField(max_length=50)
|
|
157
|
-
entity = models.ForeignKey(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
entity = models.ForeignKey(
|
|
154
|
+
'django_ledger.EntityModel',
|
|
155
|
+
editable=False,
|
|
156
|
+
on_delete=models.CASCADE,
|
|
157
|
+
verbose_name=_('Unit Entity'),
|
|
158
|
+
)
|
|
161
159
|
document_prefix = models.CharField(max_length=3)
|
|
162
160
|
active = models.BooleanField(default=True, verbose_name=_('Is Active'))
|
|
163
161
|
hidden = models.BooleanField(default=False, verbose_name=_('Is Hidden'))
|
|
164
162
|
|
|
165
|
-
objects = EntityUnitModelManager.from_queryset(
|
|
163
|
+
objects = EntityUnitModelManager.from_queryset(
|
|
164
|
+
queryset_class=EntityUnitModelQuerySet
|
|
165
|
+
)()
|
|
166
166
|
|
|
167
167
|
class Meta:
|
|
168
168
|
abstract = True
|
|
@@ -212,19 +212,20 @@ class EntityUnitModelAbstract(MP_Node,
|
|
|
212
212
|
str
|
|
213
213
|
The EntityModelUnit instance dashboard URL.
|
|
214
214
|
"""
|
|
215
|
-
return reverse(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
})
|
|
215
|
+
return reverse(
|
|
216
|
+
'django_ledger:unit-dashboard', kwargs={'entity_slug': self.slug}
|
|
217
|
+
)
|
|
219
218
|
|
|
220
219
|
def get_entity_name(self) -> str:
|
|
221
220
|
return self.entity.name
|
|
222
221
|
|
|
223
|
-
def create_entity_unit_slug(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
222
|
+
def create_entity_unit_slug(
|
|
223
|
+
self,
|
|
224
|
+
name: Optional[str] = None,
|
|
225
|
+
force: bool = False,
|
|
226
|
+
add_suffix: bool = True,
|
|
227
|
+
k: int = 5,
|
|
228
|
+
) -> str:
|
|
228
229
|
"""
|
|
229
230
|
Automatically generates a EntityUnitModel slug. If slug is present, will not be replaced.
|
|
230
231
|
Called during the clean() method.
|
|
@@ -255,13 +256,24 @@ class EntityUnitModelAbstract(MP_Node,
|
|
|
255
256
|
self.slug = unit_slug
|
|
256
257
|
return self.slug
|
|
257
258
|
|
|
259
|
+
def validate_for_entity(self, entity_model: 'EntityModel | str | UUID'):
|
|
260
|
+
EntityModel = lazy_loader.get_entity_model()
|
|
261
|
+
|
|
262
|
+
if isinstance(entity_model, UUID):
|
|
263
|
+
is_valid = self.entity_id == entity_model
|
|
264
|
+
elif isinstance(entity_model, str):
|
|
265
|
+
is_valid = self.entity_slug == entity_model
|
|
266
|
+
elif isinstance(entity_model, EntityModel):
|
|
267
|
+
is_valid = self.entity == entity_model
|
|
268
|
+
if not is_valid:
|
|
269
|
+
raise EntityUnitModelValidationError(
|
|
270
|
+
f'Entity Unit Model {entity_model} is not a valid Entity Model'
|
|
271
|
+
)
|
|
272
|
+
|
|
258
273
|
def get_absolute_url(self):
|
|
259
274
|
return reverse(
|
|
260
275
|
viewname='django_ledger:unit-detail',
|
|
261
|
-
kwargs={
|
|
262
|
-
'entity_slug': self.entity.slug,
|
|
263
|
-
'unit_slug': self.slug
|
|
264
|
-
}
|
|
276
|
+
kwargs={'entity_slug': self.entity.slug, 'unit_slug': self.slug},
|
|
265
277
|
)
|
|
266
278
|
|
|
267
279
|
|
django_ledger/models/utils.py
CHANGED
|
@@ -75,6 +75,7 @@ class LazyLoader:
|
|
|
75
75
|
LEDGER_MODEL = 'ledgermodel'
|
|
76
76
|
JE_MODEL = 'journalentrymodel'
|
|
77
77
|
TRANSACTION_MODEL = 'transactionmodel'
|
|
78
|
+
STAGED_TRANSACTION_MODEL = 'stagedtransactionmodel'
|
|
78
79
|
ACCOUNT_MODEL = 'accountmodel'
|
|
79
80
|
COA_MODEL = 'chartofaccountmodel'
|
|
80
81
|
|
|
@@ -88,6 +89,7 @@ class LazyLoader:
|
|
|
88
89
|
|
|
89
90
|
CUSTOMER_MODEL = 'customermodel'
|
|
90
91
|
INVOICE_MODEL = 'invoicemodel'
|
|
92
|
+
RECEIPT_MODEL = 'receiptmodel'
|
|
91
93
|
BILL_MODEL = 'billmodel'
|
|
92
94
|
UOM_MODEL = 'unitofmeasuremodel'
|
|
93
95
|
VENDOR_MODEL = 'vendormodel'
|
|
@@ -100,8 +102,6 @@ class LazyLoader:
|
|
|
100
102
|
INCOME_STATEMENT_REPORT_CLASS = None
|
|
101
103
|
CASH_FLOW_STATEMENT_REPORT_CLASS = None
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
105
|
def get_entity_model(self):
|
|
106
106
|
return self.app_config.get_model(self.ENTITY_MODEL)
|
|
107
107
|
|
|
@@ -123,6 +123,9 @@ class LazyLoader:
|
|
|
123
123
|
def get_txs_model(self):
|
|
124
124
|
return self.app_config.get_model(self.TRANSACTION_MODEL)
|
|
125
125
|
|
|
126
|
+
def get_staged_txs_model(self):
|
|
127
|
+
return self.app_config.get_model(self.STAGED_TRANSACTION_MODEL)
|
|
128
|
+
|
|
126
129
|
def get_purchase_order_model(self):
|
|
127
130
|
return self.app_config.get_model(self.PURCHASE_ORDER_MODEL)
|
|
128
131
|
|
|
@@ -139,6 +142,9 @@ class LazyLoader:
|
|
|
139
142
|
def get_item_transaction_model(self):
|
|
140
143
|
return self.app_config.get_model(self.ITEM_TRANSACTION_MODEL)
|
|
141
144
|
|
|
145
|
+
def get_receipt_model(self):
|
|
146
|
+
return self.app_config.get_model(self.RECEIPT_MODEL)
|
|
147
|
+
|
|
142
148
|
def get_customer_model(self):
|
|
143
149
|
return self.app_config.get_model(self.CUSTOMER_MODEL)
|
|
144
150
|
|
|
@@ -166,24 +172,28 @@ class LazyLoader:
|
|
|
166
172
|
def get_entity_data_generator(self):
|
|
167
173
|
if not self.ENTITY_DATA_GENERATOR:
|
|
168
174
|
from django_ledger.io.io_generator import EntityDataGenerator
|
|
175
|
+
|
|
169
176
|
self.ENTITY_DATA_GENERATOR = EntityDataGenerator
|
|
170
177
|
return self.ENTITY_DATA_GENERATOR
|
|
171
178
|
|
|
172
179
|
def get_balance_sheet_report_class(self):
|
|
173
180
|
if not self.BALANCE_SHEET_REPORT_CLASS:
|
|
174
181
|
from django_ledger.report.balance_sheet import BalanceSheetReport
|
|
182
|
+
|
|
175
183
|
self.BALANCE_SHEET_REPORT_CLASS = BalanceSheetReport
|
|
176
184
|
return self.BALANCE_SHEET_REPORT_CLASS
|
|
177
185
|
|
|
178
186
|
def get_income_statement_report_class(self):
|
|
179
187
|
if not self.INCOME_STATEMENT_REPORT_CLASS:
|
|
180
188
|
from django_ledger.report.income_statement import IncomeStatementReport
|
|
189
|
+
|
|
181
190
|
self.INCOME_STATEMENT_REPORT_CLASS = IncomeStatementReport
|
|
182
191
|
return self.INCOME_STATEMENT_REPORT_CLASS
|
|
183
192
|
|
|
184
193
|
def get_cash_flow_statement_report_class(self):
|
|
185
194
|
if not self.CASH_FLOW_STATEMENT_REPORT_CLASS:
|
|
186
195
|
from django_ledger.report.cash_flow_statement import CashFlowStatementReport
|
|
196
|
+
|
|
187
197
|
self.CASH_FLOW_STATEMENT_REPORT_CLASS = CashFlowStatementReport
|
|
188
198
|
return self.CASH_FLOW_STATEMENT_REPORT_CLASS
|
|
189
199
|
|