django-ledger 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-ledger might be problematic. Click here for more details.

Files changed (56) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/forms/account.py +45 -46
  3. django_ledger/forms/data_import.py +182 -64
  4. django_ledger/io/io_core.py +507 -374
  5. django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
  6. django_ledger/models/__init__.py +2 -1
  7. django_ledger/models/bill.py +337 -300
  8. django_ledger/models/customer.py +47 -34
  9. django_ledger/models/data_import.py +770 -289
  10. django_ledger/models/entity.py +882 -637
  11. django_ledger/models/mixins.py +421 -282
  12. django_ledger/models/receipt.py +1083 -0
  13. django_ledger/models/transactions.py +105 -41
  14. django_ledger/models/unit.py +42 -30
  15. django_ledger/models/utils.py +12 -2
  16. django_ledger/models/vendor.py +85 -66
  17. django_ledger/settings.py +1 -0
  18. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  19. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +1 -13
  20. django_ledger/templates/django_ledger/bills/bill_update.html +1 -1
  21. django_ledger/templates/django_ledger/components/period_navigator.html +5 -3
  22. django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
  23. django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
  24. django_ledger/templates/django_ledger/customer/tags/customer_table.html +3 -1
  25. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
  26. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
  27. django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
  28. django_ledger/templates/django_ledger/invoice/invoice_update.html +1 -1
  29. django_ledger/templates/django_ledger/layouts/base.html +3 -1
  30. django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
  31. django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
  32. django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
  33. django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
  34. django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
  35. django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
  36. django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +3 -2
  37. django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
  38. django_ledger/templatetags/django_ledger.py +338 -191
  39. django_ledger/urls/__init__.py +1 -0
  40. django_ledger/urls/customer.py +3 -0
  41. django_ledger/urls/data_import.py +3 -0
  42. django_ledger/urls/receipt.py +102 -0
  43. django_ledger/urls/vendor.py +1 -0
  44. django_ledger/views/__init__.py +1 -0
  45. django_ledger/views/customer.py +56 -14
  46. django_ledger/views/data_import.py +119 -66
  47. django_ledger/views/mixins.py +112 -86
  48. django_ledger/views/receipt.py +294 -0
  49. django_ledger/views/vendor.py +53 -14
  50. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/METADATA +1 -1
  51. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/RECORD +55 -45
  52. django_ledger/static/django_ledger/bundle/styles.bundle.js +0 -1
  53. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/WHEEL +0 -0
  54. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/licenses/AUTHORS.md +0 -0
  55. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/licenses/LICENSE +0 -0
  56. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.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(self, account_list: List[Union[AccountModel, str, UUID]]) -> 'TransactionModelQuerySet':
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=_('Account list must be a list of AccountModel, UUID or str objects (codes).')
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=_('Account list must be a list of AccountModel, UUID or str objects (codes).')
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(self, role_list: Union[str, List[str], Set[str]]) -> 'TransactionModelQuerySet':
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(self, unit_slug: Union[str, EntityUnitModel]) -> 'TransactionModelQuerySet':
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(self, activity_list: Union[str, List[str], Set[str]]) -> 'TransactionModelQuerySet':
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(self, to_date: Union[str, date, datetime]) -> 'TransactionModelQuerySet':
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(self, from_date: Union[str, date, datetime]) -> 'TransactionModelQuerySet':
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(self, ledger_model: Union[LedgerModel, UUID, str]) -> 'TransactionModelQuerySet':
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(self, bill_model: Union[BillModel, str, UUID]) -> 'TransactionModelQuerySet':
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(self, invoice_model: Union[InvoiceModel, str, UUID]) -> 'TransactionModelQuerySet':
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(journal_entry__ledger__invoicemodel__uuid__exact=invoice_model)
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') # Annotates the `coa_model_id` from the related `account`.
409
+ _coa_id=F('account__coa_model_id'),
383
410
  ).select_related(
384
- 'journal_entry', # Pre-loads the related Journal Entry.
385
- 'account', # Pre-loads the Account associated with the Transaction.
386
- 'account__coa_model', # Pre-loads the Chart of Accounts related to the Account.
411
+ 'journal_entry',
412
+ 'account',
413
+ 'account__coa_model',
387
414
  )
388
415
 
389
416
  @deprecated_entity_slug_behavior
390
- def for_entity(self, entity_model: EntityModel | str | UUID = None, **kwargs) -> TransactionModelQuerySet:
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(max_length=10, choices=TX_TYPE, verbose_name=_('Transaction Type'))
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=_('Account from Chart of Accounts to be associated with this transaction.'),
486
- on_delete=models.PROTECT
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=_(f'Cannot create or modify transactions on account model {instance.account}.')
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(
@@ -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, digits, ascii_uppercase
26
+ from string import ascii_lowercase, ascii_uppercase, digits
26
27
  from typing import Optional
27
- from uuid import uuid4, UUID
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 Q, F
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('django_ledger.EntityModel',
158
- editable=False,
159
- on_delete=models.CASCADE,
160
- verbose_name=_('Unit Entity'))
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(queryset_class=EntityUnitModelQuerySet)()
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('django_ledger:unit-dashboard',
216
- kwargs={
217
- 'entity_slug': self.slug
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(self,
224
- name: Optional[str] = None,
225
- force: bool = False,
226
- add_suffix: bool = True,
227
- k: int = 5) -> str:
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
 
@@ -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