django-ledger 0.7.11__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.

Files changed (139) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/context.py +12 -0
  3. django_ledger/forms/account.py +45 -46
  4. django_ledger/forms/bill.py +0 -4
  5. django_ledger/forms/closing_entry.py +13 -1
  6. django_ledger/forms/data_import.py +182 -63
  7. django_ledger/forms/estimate.py +3 -6
  8. django_ledger/forms/invoice.py +3 -7
  9. django_ledger/forms/item.py +10 -18
  10. django_ledger/forms/purchase_order.py +2 -4
  11. django_ledger/io/io_core.py +515 -400
  12. django_ledger/io/io_generator.py +7 -6
  13. django_ledger/io/io_library.py +1 -2
  14. django_ledger/migrations/0025_alter_billmodel_cash_account_and_more.py +70 -0
  15. django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
  16. django_ledger/models/__init__.py +2 -1
  17. django_ledger/models/accounts.py +109 -69
  18. django_ledger/models/bank_account.py +40 -23
  19. django_ledger/models/bill.py +386 -333
  20. django_ledger/models/chart_of_accounts.py +173 -105
  21. django_ledger/models/closing_entry.py +99 -48
  22. django_ledger/models/customer.py +100 -66
  23. django_ledger/models/data_import.py +818 -323
  24. django_ledger/models/deprecations.py +61 -0
  25. django_ledger/models/entity.py +891 -644
  26. django_ledger/models/estimate.py +57 -28
  27. django_ledger/models/invoice.py +46 -26
  28. django_ledger/models/items.py +503 -142
  29. django_ledger/models/journal_entry.py +61 -47
  30. django_ledger/models/ledger.py +106 -42
  31. django_ledger/models/mixins.py +424 -281
  32. django_ledger/models/purchase_order.py +39 -17
  33. django_ledger/models/receipt.py +1083 -0
  34. django_ledger/models/transactions.py +242 -139
  35. django_ledger/models/unit.py +93 -54
  36. django_ledger/models/utils.py +12 -2
  37. django_ledger/models/vendor.py +121 -70
  38. django_ledger/report/core.py +2 -14
  39. django_ledger/settings.py +57 -71
  40. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  41. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +25 -0
  42. django_ledger/static/django_ledger/bundle/styles.bundle.js +1 -1
  43. django_ledger/static/django_ledger/css/djl_styles.css +273 -0
  44. django_ledger/templates/django_ledger/bills/includes/card_bill.html +2 -2
  45. django_ledger/templates/django_ledger/components/menu.html +41 -26
  46. django_ledger/templates/django_ledger/components/period_navigator.html +5 -3
  47. django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
  48. django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
  49. django_ledger/templates/django_ledger/customer/tags/customer_table.html +8 -6
  50. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
  51. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
  52. django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
  53. django_ledger/templates/django_ledger/entity/includes/card_entity.html +12 -6
  54. django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +1 -1
  55. django_ledger/templates/django_ledger/financial_statements/cash_flow.html +4 -1
  56. django_ledger/templates/django_ledger/financial_statements/income_statement.html +4 -1
  57. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +27 -3
  58. django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +16 -4
  59. django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +73 -18
  60. django_ledger/templates/django_ledger/includes/widget_ratios.html +18 -24
  61. django_ledger/templates/django_ledger/invoice/includes/card_invoice.html +3 -3
  62. django_ledger/templates/django_ledger/layouts/base.html +7 -2
  63. django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
  64. django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
  65. django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
  66. django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
  67. django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
  68. django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
  69. django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +12 -7
  70. django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
  71. django_ledger/templatetags/django_ledger.py +338 -191
  72. django_ledger/tests/test_accounts.py +1 -2
  73. django_ledger/tests/test_io.py +17 -0
  74. django_ledger/tests/test_purchase_order.py +3 -3
  75. django_ledger/tests/test_transactions.py +1 -2
  76. django_ledger/urls/__init__.py +1 -4
  77. django_ledger/urls/customer.py +3 -0
  78. django_ledger/urls/data_import.py +3 -0
  79. django_ledger/urls/receipt.py +102 -0
  80. django_ledger/urls/vendor.py +1 -0
  81. django_ledger/views/__init__.py +1 -0
  82. django_ledger/views/bill.py +8 -11
  83. django_ledger/views/chart_of_accounts.py +6 -4
  84. django_ledger/views/closing_entry.py +11 -7
  85. django_ledger/views/customer.py +68 -30
  86. django_ledger/views/data_import.py +120 -66
  87. django_ledger/views/djl_api.py +3 -5
  88. django_ledger/views/entity.py +2 -4
  89. django_ledger/views/estimate.py +3 -7
  90. django_ledger/views/inventory.py +3 -5
  91. django_ledger/views/invoice.py +4 -6
  92. django_ledger/views/item.py +7 -11
  93. django_ledger/views/journal_entry.py +1 -2
  94. django_ledger/views/mixins.py +125 -93
  95. django_ledger/views/purchase_order.py +24 -35
  96. django_ledger/views/receipt.py +294 -0
  97. django_ledger/views/unit.py +1 -2
  98. django_ledger/views/vendor.py +54 -16
  99. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/METADATA +43 -75
  100. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/RECORD +104 -122
  101. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/WHEEL +1 -1
  102. django_ledger-0.8.1.dist-info/top_level.txt +1 -0
  103. django_ledger/contrib/django_ledger_graphene/__init__.py +0 -0
  104. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +0 -33
  105. django_ledger/contrib/django_ledger_graphene/api.py +0 -42
  106. django_ledger/contrib/django_ledger_graphene/apps.py +0 -6
  107. django_ledger/contrib/django_ledger_graphene/auth/mutations.py +0 -49
  108. django_ledger/contrib/django_ledger_graphene/auth/schema.py +0 -6
  109. django_ledger/contrib/django_ledger_graphene/bank_account/mutations.py +0 -61
  110. django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +0 -34
  111. django_ledger/contrib/django_ledger_graphene/bill/mutations.py +0 -0
  112. django_ledger/contrib/django_ledger_graphene/bill/schema.py +0 -34
  113. django_ledger/contrib/django_ledger_graphene/coa/mutations.py +0 -0
  114. django_ledger/contrib/django_ledger_graphene/coa/schema.py +0 -30
  115. django_ledger/contrib/django_ledger_graphene/customers/__init__.py +0 -0
  116. django_ledger/contrib/django_ledger_graphene/customers/mutations.py +0 -71
  117. django_ledger/contrib/django_ledger_graphene/customers/schema.py +0 -43
  118. django_ledger/contrib/django_ledger_graphene/data_import/mutations.py +0 -0
  119. django_ledger/contrib/django_ledger_graphene/data_import/schema.py +0 -0
  120. django_ledger/contrib/django_ledger_graphene/entity/mutations.py +0 -0
  121. django_ledger/contrib/django_ledger_graphene/entity/schema.py +0 -94
  122. django_ledger/contrib/django_ledger_graphene/item/mutations.py +0 -0
  123. django_ledger/contrib/django_ledger_graphene/item/schema.py +0 -31
  124. django_ledger/contrib/django_ledger_graphene/journal_entry/mutations.py +0 -0
  125. django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +0 -35
  126. django_ledger/contrib/django_ledger_graphene/ledger/mutations.py +0 -0
  127. django_ledger/contrib/django_ledger_graphene/ledger/schema.py +0 -32
  128. django_ledger/contrib/django_ledger_graphene/purchase_order/mutations.py +0 -0
  129. django_ledger/contrib/django_ledger_graphene/purchase_order/schema.py +0 -31
  130. django_ledger/contrib/django_ledger_graphene/transaction/mutations.py +0 -0
  131. django_ledger/contrib/django_ledger_graphene/transaction/schema.py +0 -36
  132. django_ledger/contrib/django_ledger_graphene/unit/mutations.py +0 -0
  133. django_ledger/contrib/django_ledger_graphene/unit/schema.py +0 -27
  134. django_ledger/contrib/django_ledger_graphene/vendor/mutations.py +0 -0
  135. django_ledger/contrib/django_ledger_graphene/vendor/schema.py +0 -37
  136. django_ledger/contrib/django_ledger_graphene/views.py +0 -12
  137. django_ledger-0.7.11.dist-info/top_level.txt +0 -4
  138. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info/licenses}/AUTHORS.md +0 -0
  139. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info/licenses}/LICENSE +0 -0
@@ -17,6 +17,7 @@ EntityModels may also have different financial reporting periods, (also known as
17
17
  specified at the time of creation. All key functionality around the Fiscal Year is encapsulated in the
18
18
  EntityReportMixIn.
19
19
  """
20
+
20
21
  from calendar import monthrange
21
22
  from collections import defaultdict
22
23
  from datetime import date, datetime, timedelta
@@ -24,36 +25,56 @@ from decimal import Decimal
24
25
  from itertools import zip_longest
25
26
  from random import choices
26
27
  from string import ascii_lowercase, digits
27
- from typing import Tuple, Union, Optional, List, Dict, Set
28
- from uuid import uuid4, UUID
28
+ from typing import Dict, List, Optional, Set, Tuple, Union
29
+ from uuid import UUID, uuid4
29
30
 
30
31
  from django.contrib.auth import get_user_model
31
32
  from django.core import serializers
32
33
  from django.core.cache import caches
33
- from django.core.exceptions import ValidationError, ObjectDoesNotExist
34
+ from django.core.exceptions import ObjectDoesNotExist, ValidationError
34
35
  from django.core.validators import MinValueValidator
35
36
  from django.db import models
36
- from django.db.models import Q, F, Model
37
+ from django.db.models import F, Model, Q
37
38
  from django.db.models.signals import pre_save
38
39
  from django.urls import reverse
39
40
  from django.utils.text import slugify
40
41
  from django.utils.translation import gettext_lazy as _
41
42
  from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
42
43
 
43
- from django_ledger.io import roles as roles_module, validate_roles, IODigestContextManager
44
- from django_ledger.io.io_core import IOMixIn, get_localtime, get_localdate
45
- from django_ledger.models.accounts import AccountModel, AccountModelQuerySet, DEBIT, CREDIT
46
- from django_ledger.models.bank_account import BankAccountModelQuerySet, BankAccountModel
47
- from django_ledger.models.chart_of_accounts import ChartOfAccountModel, ChartOfAccountModelQuerySet
44
+ from django_ledger.io import IODigestContextManager, validate_roles
45
+ from django_ledger.io import roles as roles_module
46
+ from django_ledger.io.io_core import IOMixIn, get_localdate, get_localtime
47
+
48
+ from django_ledger.models.accounts import (
49
+ CREDIT,
50
+ DEBIT,
51
+ AccountModel,
52
+ AccountModelQuerySet,
53
+ )
54
+ from django_ledger.models.bank_account import BankAccountModel, BankAccountModelQuerySet
55
+ from django_ledger.models.chart_of_accounts import (
56
+ ChartOfAccountModel,
57
+ ChartOfAccountModelQuerySet,
58
+ )
48
59
  from django_ledger.models.coa_default import CHART_OF_ACCOUNTS_ROOT_MAP
49
- from django_ledger.models.customer import CustomerModelQueryset, CustomerModel
50
- from django_ledger.models.items import (ItemModelQuerySet, ItemTransactionModelQuerySet,
51
- UnitOfMeasureModel, UnitOfMeasureModelQuerySet, ItemModel)
60
+ from django_ledger.models.customer import CustomerModel, CustomerModelQueryset
61
+ from django_ledger.models.items import (
62
+ ItemModel,
63
+ ItemModelQuerySet,
64
+ ItemTransactionModelQuerySet,
65
+ UnitOfMeasureModel,
66
+ UnitOfMeasureModelQuerySet,
67
+ )
52
68
  from django_ledger.models.ledger import LedgerModel
53
- from django_ledger.models.mixins import CreateUpdateMixIn, SlugNameMixIn, ContactInfoMixIn, LoggingMixIn
69
+ from django_ledger.models.mixins import (
70
+ ContactInfoMixIn,
71
+ CreateUpdateMixIn,
72
+ LoggingMixIn,
73
+ SlugNameMixIn,
74
+ )
54
75
  from django_ledger.models.unit import EntityUnitModel
55
76
  from django_ledger.models.utils import lazy_loader
56
- from django_ledger.models.vendor import VendorModelQuerySet, VendorModel
77
+ from django_ledger.models.vendor import VendorModel, VendorModelQuerySet
57
78
  from django_ledger.settings import DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT
58
79
 
59
80
  UserModel = get_user_model()
@@ -71,7 +92,7 @@ class EntityModelQuerySet(MP_NodeQuerySet):
71
92
  Inherits from the Materialized Path Node QuerySet Class from Django Treebeard.
72
93
  """
73
94
 
74
- def hidden(self):
95
+ def hidden(self) -> 'EntityModelQuerySet':
75
96
  """
76
97
  A QuerySet of all hidden EntityModel.
77
98
 
@@ -82,7 +103,7 @@ class EntityModelQuerySet(MP_NodeQuerySet):
82
103
  """
83
104
  return self.filter(hidden=True)
84
105
 
85
- def visible(self):
106
+ def visible(self) -> 'EntityModelQuerySet':
86
107
  """
87
108
  A Queryset of all visible EntityModel.
88
109
 
@@ -108,15 +129,15 @@ class EntityModelManager(MP_NodeManager):
108
129
 
109
130
  """
110
131
 
111
- def get_queryset(self):
132
+ def get_queryset(self) -> EntityModelQuerySet:
112
133
  """Sets the custom queryset as the default."""
113
- qs = EntityModelQuerySet(
114
- self.model,
115
- using=self._db).order_by('path')
116
- return qs.order_by('path').select_related(
117
- 'admin',
118
- 'default_coa').annotate(
119
- _default_coa_slug=F('default_coa__slug'),
134
+ qs = EntityModelQuerySet(self.model, using=self._db).order_by('path')
135
+ return (
136
+ qs.order_by('path')
137
+ .select_related('admin', 'default_coa')
138
+ .annotate(
139
+ _default_coa_slug=F('default_coa__slug'),
140
+ )
120
141
  )
121
142
 
122
143
  def for_user(self, user_model, authorized_superuser: bool = False):
@@ -141,10 +162,7 @@ class EntityModelManager(MP_NodeManager):
141
162
  qs = self.get_queryset()
142
163
  if user_model.is_superuser and authorized_superuser:
143
164
  return qs
144
- return qs.filter(
145
- Q(admin=user_model) |
146
- Q(managers__in=[user_model])
147
- )
165
+ return qs.filter(Q(admin=user_model) | Q(managers__in=[user_model]))
148
166
 
149
167
 
150
168
  class EntityModelFiscalPeriodMixIn:
@@ -153,6 +171,7 @@ class EntityModelFiscalPeriodMixIn:
153
171
  EntityModel. At the moment of creation, an EntityModel must be assigned a calendar month which is going to
154
172
  determine the start of the Fiscal Year.
155
173
  """
174
+
156
175
  VALID_QUARTERS = list(range(1, 5))
157
176
  VALID_MONTHS = list(range(1, 13))
158
177
 
@@ -179,7 +198,9 @@ class EntityModelFiscalPeriodMixIn:
179
198
  # current object is not an entity, get current entity and fetch its fy_start_month value
180
199
 
181
200
  # if current object is a detail view with an object...
182
- obj = getattr(self, 'object')
201
+ obj = getattr(self, 'object', None) or getattr(
202
+ self, 'AUTHORIZED_ENTITY_MODEL'
203
+ )
183
204
  if isinstance(obj, EntityModel):
184
205
  entity = obj
185
206
  elif isinstance(obj, LedgerModel):
@@ -251,7 +272,9 @@ class EntityModelFiscalPeriodMixIn:
251
272
  """
252
273
  if fy_start_month:
253
274
  self.validate_month(fy_start_month)
254
- fy_start_month = self.get_fy_start_month() if not fy_start_month else fy_start_month
275
+ fy_start_month = (
276
+ self.get_fy_start_month() if not fy_start_month else fy_start_month
277
+ )
255
278
  return date(year, fy_start_month, 1)
256
279
 
257
280
  def get_fy_end(self, year: int, fy_start_month: int = None) -> date:
@@ -273,12 +296,16 @@ class EntityModelFiscalPeriodMixIn:
273
296
  """
274
297
  if fy_start_month:
275
298
  self.validate_month(fy_start_month)
276
- fy_start_month = self.get_fy_start_month() if not fy_start_month else fy_start_month
299
+ fy_start_month = (
300
+ self.get_fy_start_month() if not fy_start_month else fy_start_month
301
+ )
277
302
  ye = year if fy_start_month == 1 else year + 1
278
303
  me = 12 if fy_start_month == 1 else fy_start_month - 1
279
304
  return date(ye, me, monthrange(ye, me)[1])
280
305
 
281
- def get_quarter_start(self, year: int, quarter: int, fy_start_month: int = None) -> date:
306
+ def get_quarter_start(
307
+ self, year: int, quarter: int, fy_start_month: int = None
308
+ ) -> date:
282
309
  """
283
310
  The fiscal year quarter starting date of the EntityModel, according to its settings.
284
311
 
@@ -300,7 +327,9 @@ class EntityModelFiscalPeriodMixIn:
300
327
  """
301
328
  if fy_start_month:
302
329
  self.validate_month(fy_start_month)
303
- fy_start_month = self.get_fy_start_month() if not fy_start_month else fy_start_month
330
+ fy_start_month = (
331
+ self.get_fy_start_month() if not fy_start_month else fy_start_month
332
+ )
304
333
  self.validate_quarter(quarter)
305
334
  quarter_month_start = (quarter - 1) * 3 + fy_start_month
306
335
  year_start = year
@@ -309,7 +338,9 @@ class EntityModelFiscalPeriodMixIn:
309
338
  year_start = year + 1
310
339
  return date(year_start, quarter_month_start, 1)
311
340
 
312
- def get_quarter_end(self, year: int, quarter: int, fy_start_month: int = None) -> date:
341
+ def get_quarter_end(
342
+ self, year: int, quarter: int, fy_start_month: int = None
343
+ ) -> date:
313
344
  """
314
345
  The fiscal year quarter ending date of the EntityModel, according to its settings.
315
346
 
@@ -331,16 +362,22 @@ class EntityModelFiscalPeriodMixIn:
331
362
  """
332
363
  if fy_start_month:
333
364
  self.validate_month(fy_start_month)
334
- fy_start_month = self.get_fy_start_month() if not fy_start_month else fy_start_month
365
+ fy_start_month = (
366
+ self.get_fy_start_month() if not fy_start_month else fy_start_month
367
+ )
335
368
  self.validate_quarter(quarter)
336
369
  quarter_month_end = quarter * 3 + fy_start_month - 1
337
370
  year_end = year
338
371
  if quarter_month_end > 12:
339
372
  quarter_month_end -= 12
340
373
  year_end += 1
341
- return date(year_end, quarter_month_end, monthrange(year_end, quarter_month_end)[1])
374
+ return date(
375
+ year_end, quarter_month_end, monthrange(year_end, quarter_month_end)[1]
376
+ )
342
377
 
343
- def get_fiscal_year_dates(self, year: int, fy_start_month: int = None) -> Tuple[date, date]:
378
+ def get_fiscal_year_dates(
379
+ self, year: int, fy_start_month: int = None
380
+ ) -> Tuple[date, date]:
344
381
  """
345
382
  Convenience method to get in one shot both, fiscal year start and end dates.
346
383
 
@@ -365,7 +402,9 @@ class EntityModelFiscalPeriodMixIn:
365
402
  ed = self.get_fy_end(year, fy_start_month)
366
403
  return sd, ed
367
404
 
368
- def get_fiscal_quarter_dates(self, year: int, quarter: int, fy_start_month: int = None) -> Tuple[date, date]:
405
+ def get_fiscal_quarter_dates(
406
+ self, year: int, quarter: int, fy_start_month: int = None
407
+ ) -> Tuple[date, date]:
369
408
  """
370
409
  Convenience method to get in one shot both, fiscal year quarter start and end dates.
371
410
 
@@ -394,7 +433,9 @@ class EntityModelFiscalPeriodMixIn:
394
433
  qe = self.get_quarter_end(year, quarter, fy_start_month)
395
434
  return qs, qe
396
435
 
397
- def get_fy_for_date(self, dt: Union[date, datetime], as_str: bool = False) -> Union[str, int]:
436
+ def get_fy_for_date(
437
+ self, dt: Union[date, datetime], as_str: bool = False
438
+ ) -> Union[str, int]:
398
439
  """
399
440
  Given a known date, returns the EntityModel fiscal year associated with the given date.
400
441
 
@@ -428,32 +469,39 @@ class EntityModelClosingEntryMixIn:
428
469
  Closing Entries provide
429
470
  """
430
471
 
431
- def validate_closing_entry_model(self, closing_entry_model, closing_date: Optional[date] = None):
472
+ def validate_closing_entry_model(
473
+ self, closing_entry_model, closing_date: Optional[date] = None
474
+ ):
432
475
  if isinstance(self, EntityModel):
433
476
  if self.uuid != closing_entry_model.entity_model_id:
434
477
  raise EntityModelValidationError(
435
- message=_(f'The Closing Entry Model {closing_entry_model} does not belong to Entity {self.name}')
478
+ message=_(
479
+ f'The Closing Entry Model {closing_entry_model} does not belong to Entity {self.name}'
480
+ )
436
481
  )
437
482
  if closing_date and closing_entry_model.closing_date != closing_date:
438
483
  raise EntityModelValidationError(
439
- message=_(f'The Closing Entry Model date {closing_entry_model.closing_date} '
440
- f'does not match explicitly provided closing_date {closing_date}')
484
+ message=_(
485
+ f'The Closing Entry Model date {closing_entry_model.closing_date} '
486
+ f'does not match explicitly provided closing_date {closing_date}'
487
+ )
441
488
  )
442
489
 
443
490
  # ---> Closing Entry IO Digest <---
444
- def get_closing_entry_digest(self,
445
- to_date: date,
446
- from_date: Optional[date] = None,
447
- user_model: Optional[UserModel] = None,
448
- closing_entry_model=None,
449
- **kwargs: Dict) -> Tuple:
491
+ def get_closing_entry_digest(
492
+ self,
493
+ to_date: date,
494
+ from_date: Optional[date] = None,
495
+ user_model: Optional[UserModel] = None,
496
+ closing_entry_model=None,
497
+ **kwargs: Dict,
498
+ ) -> Tuple:
450
499
  ClosingEntryModel = lazy_loader.get_closing_entry_model()
451
500
  ClosingEntryTransactionModel = lazy_loader.get_closing_entry_transaction_model()
452
501
 
453
502
  if not closing_entry_model:
454
503
  closing_entry_model = ClosingEntryModel(
455
- entity_model=self,
456
- closing_date=to_date
504
+ entity_model=self, closing_date=to_date
457
505
  )
458
506
  closing_entry_model.clean()
459
507
  else:
@@ -466,7 +514,7 @@ class EntityModelClosingEntryMixIn:
466
514
  by_unit=True,
467
515
  by_activity=True,
468
516
  signs=False,
469
- **kwargs
517
+ **kwargs,
470
518
  )
471
519
  ce_data = io_digest.get_closing_entry_data()
472
520
 
@@ -477,8 +525,9 @@ class EntityModelClosingEntryMixIn:
477
525
  unit_model_id=ce['unit_uuid'],
478
526
  tx_type=ce['balance_type'],
479
527
  activity=ce['activity'],
480
- balance=ce['balance']
481
- ) for ce in ce_data
528
+ balance=ce['balance'],
529
+ )
530
+ for ce in ce_data
482
531
  ]
483
532
 
484
533
  for ce in ce_txs_list:
@@ -486,25 +535,25 @@ class EntityModelClosingEntryMixIn:
486
535
 
487
536
  return closing_entry_model, ce_txs_list
488
537
 
489
- def get_closing_entry_digest_for_date(self,
490
- closing_date: date,
491
- closing_entry_model=None,
492
- **kwargs) -> Tuple:
538
+ def get_closing_entry_digest_for_date(
539
+ self, closing_date: date, closing_entry_model=None, **kwargs
540
+ ) -> Tuple:
493
541
  return self.get_closing_entry_digest(
494
- to_date=closing_date,
495
- closing_entry_model=closing_entry_model,
496
- **kwargs
542
+ to_date=closing_date, closing_entry_model=closing_entry_model, **kwargs
497
543
  )
498
544
 
499
- def get_closing_entry_digest_for_month(self,
500
- year: int,
501
- month: int,
502
- **kwargs: Dict) -> Tuple:
545
+ def get_closing_entry_digest_for_month(
546
+ self, year: int, month: int, **kwargs: Dict
547
+ ) -> Tuple:
503
548
  _, day_end = monthrange(year, month)
504
549
  closing_date = date(year=year, month=month, day=day_end)
505
- return self.get_closing_entry_digest_for_date(closing_date=closing_date, **kwargs)
550
+ return self.get_closing_entry_digest_for_date(
551
+ closing_date=closing_date, **kwargs
552
+ )
506
553
 
507
- def get_closing_entry_digest_for_fiscal_year(self, fiscal_year: int, **kwargs: Dict) -> Tuple:
554
+ def get_closing_entry_digest_for_fiscal_year(
555
+ self, fiscal_year: int, **kwargs: Dict
556
+ ) -> Tuple:
508
557
  closing_date = getattr(self, 'get_fy_end')(year=fiscal_year)
509
558
  return self.get_closing_entry_digest_for_date(to_date=closing_date, **kwargs)
510
559
 
@@ -512,8 +561,8 @@ class EntityModelClosingEntryMixIn:
512
561
  def get_closing_entry_queryset_for_date(self, closing_date: date):
513
562
  ClosingEntryTransactionModel = lazy_loader.get_closing_entry_transaction_model()
514
563
  return ClosingEntryTransactionModel.objects.for_entity(
515
- entity_slug=self,
516
- ).filter(closing_entry_model__closing_date__exact=closing_date)
564
+ entity_model=self,
565
+ ).for_closing_date(closing_date)
517
566
 
518
567
  def get_closing_entry_queryset_for_month(self, year: int, month: int):
519
568
  _, end_day = monthrange(year, month)
@@ -525,27 +574,28 @@ class EntityModelClosingEntryMixIn:
525
574
  return self.get_closing_entry_queryset_for_date(closing_date=closing_date)
526
575
 
527
576
  # ----> Create Closing Entries <----
528
- def create_closing_entry_for_date(self,
529
- closing_date: date,
530
- closing_entry_model=None,
531
- closing_entry_exists=True):
532
-
577
+ def create_closing_entry_for_date(
578
+ self, closing_date: date, closing_entry_model=None, closing_entry_exists=True
579
+ ):
533
580
  if closing_entry_model:
534
- self.validate_closing_entry_model(closing_entry_model, closing_date=closing_date)
581
+ self.validate_closing_entry_model(
582
+ closing_entry_model, closing_date=closing_date
583
+ )
535
584
 
536
585
  if closing_date > get_localdate():
537
586
  raise EntityModelValidationError(
538
- message=_(f'Cannot create closing entry with a future date {closing_date}.')
587
+ message=_(
588
+ f'Cannot create closing entry with a future date {closing_date}.'
589
+ )
539
590
  )
540
591
 
541
- if closing_entry_model is None or closing_entry_exists:
592
+ if closing_entry_model is None:
542
593
  self.closingentrymodel_set.filter(closing_date__exact=closing_date).delete()
543
594
  else:
544
595
  closing_entry_model.closingentrytransactionmodel_set.all().delete()
545
596
 
546
597
  closing_entry_model, ce_txs_list = self.get_closing_entry_digest_for_date(
547
- closing_date=closing_date,
548
- closing_entry_model=closing_entry_model
598
+ closing_date=closing_date, closing_entry_model=closing_entry_model
549
599
  )
550
600
 
551
601
  if closing_entry_model is not None:
@@ -553,8 +603,7 @@ class EntityModelClosingEntryMixIn:
553
603
 
554
604
  ClosingEntryTransactionModel = lazy_loader.get_closing_entry_transaction_model()
555
605
  return closing_entry_model, ClosingEntryTransactionModel.objects.bulk_create(
556
- objs=ce_txs_list,
557
- batch_size=100
606
+ objs=ce_txs_list, batch_size=100
558
607
  )
559
608
 
560
609
  def create_closing_entry_for_month(self, year: int, month: int):
@@ -583,21 +632,26 @@ class EntityModelClosingEntryMixIn:
583
632
  return f'closing_entry_{end_dt_str}_{self.uuid}'
584
633
 
585
634
  # ----> Closing Entry Caching Month < -----
586
- def get_closing_entry_cache_for_date(self,
587
- closing_date: date,
588
- cache_name: str = 'default',
589
- force_cache_update: bool = False,
590
- cache_timeout: Optional[int] = None,
591
- **kwargs):
592
-
635
+ def get_closing_entry_cache_for_date(
636
+ self,
637
+ closing_date: date,
638
+ cache_name: str = 'default',
639
+ force_cache_update: bool = False,
640
+ cache_timeout: Optional[int] = None,
641
+ **kwargs,
642
+ ):
593
643
  if not force_cache_update:
594
644
  cache_system = caches[cache_name]
595
- ce_cache_key = self.get_closing_entry_cache_key_for_date(closing_date=closing_date)
645
+ ce_cache_key = self.get_closing_entry_cache_key_for_date(
646
+ closing_date=closing_date
647
+ )
596
648
  ce_ser = cache_system.get(ce_cache_key)
597
649
 
598
650
  # if closing entry is in cache...
599
651
  if ce_ser:
600
- ce_qs_serde_gen = serializers.deserialize(format='json', stream_or_string=ce_ser)
652
+ ce_qs_serde_gen = serializers.deserialize(
653
+ format='json', stream_or_string=ce_ser
654
+ )
601
655
  return list(ce.object for ce in ce_qs_serde_gen)
602
656
  return
603
657
 
@@ -605,16 +659,18 @@ class EntityModelClosingEntryMixIn:
605
659
  closing_date=closing_date,
606
660
  cache_name=cache_name,
607
661
  cache_timeout=cache_timeout,
608
- **kwargs)
609
-
610
- def get_closing_entry_cache_for_month(self,
611
- year: int,
612
- month: int,
613
- cache_name: str = 'default',
614
- force_cache_update: bool = False,
615
- cache_timeout: Optional[int] = None,
616
- **kwargs):
662
+ **kwargs,
663
+ )
617
664
 
665
+ def get_closing_entry_cache_for_month(
666
+ self,
667
+ year: int,
668
+ month: int,
669
+ cache_name: str = 'default',
670
+ force_cache_update: bool = False,
671
+ cache_timeout: Optional[int] = None,
672
+ **kwargs,
673
+ ):
618
674
  _, day = monthrange(year, month)
619
675
  closing_date = date(year, month, day)
620
676
  return self.get_closing_entry_cache_for_date(
@@ -622,33 +678,39 @@ class EntityModelClosingEntryMixIn:
622
678
  cache_name=cache_name,
623
679
  force_cache_update=force_cache_update,
624
680
  cache_timeout=cache_timeout,
625
- **kwargs
681
+ **kwargs,
626
682
  )
627
683
 
628
- def get_closing_entry_cache_for_fiscal_year(self,
629
- fiscal_year: int,
630
- cache_name: str = 'default',
631
- force_cache_update: bool = False,
632
- cache_timeout: Optional[int] = None,
633
- **kwargs):
684
+ def get_closing_entry_cache_for_fiscal_year(
685
+ self,
686
+ fiscal_year: int,
687
+ cache_name: str = 'default',
688
+ force_cache_update: bool = False,
689
+ cache_timeout: Optional[int] = None,
690
+ **kwargs,
691
+ ):
634
692
  closing_date: date = getattr(self, 'get_fy_end')(year=fiscal_year)
635
693
  return self.get_closing_entry_cache_for_date(
636
694
  closing_date=closing_date,
637
695
  cache_name=cache_name,
638
696
  force_cache_update=force_cache_update,
639
697
  cache_timeout=cache_timeout,
640
- **kwargs
698
+ **kwargs,
641
699
  )
642
700
 
643
701
  # ---> SAVE CLOSING ENTRY <---
644
- def save_closing_entry_cache_for_date(self,
645
- closing_date: date,
646
- cache_name: str = 'default',
647
- cache_timeout: Optional[int] = None,
648
- **kwargs):
702
+ def save_closing_entry_cache_for_date(
703
+ self,
704
+ closing_date: date,
705
+ cache_name: str = 'default',
706
+ cache_timeout: Optional[int] = None,
707
+ **kwargs,
708
+ ):
649
709
  cache_system = caches[cache_name]
650
710
  ce_qs = self.get_closing_entry_queryset_for_date(closing_date=closing_date)
651
- ce_cache_key = self.get_closing_entry_cache_key_for_date(closing_date=closing_date)
711
+ ce_cache_key = self.get_closing_entry_cache_key_for_date(
712
+ closing_date=closing_date
713
+ )
652
714
  ce_ser = serializers.serialize(format='json', queryset=ce_qs)
653
715
 
654
716
  if not cache_timeout:
@@ -657,43 +719,49 @@ class EntityModelClosingEntryMixIn:
657
719
  cache_system.set(ce_cache_key, ce_ser, cache_timeout, **kwargs)
658
720
  return list(ce_qs)
659
721
 
660
- def save_closing_entry_cache_for_month(self,
661
- year: int,
662
- month: int,
663
- cache_name: str = 'default',
664
- cache_timeout: Optional[int] = None,
665
- **kwargs):
722
+ def save_closing_entry_cache_for_month(
723
+ self,
724
+ year: int,
725
+ month: int,
726
+ cache_name: str = 'default',
727
+ cache_timeout: Optional[int] = None,
728
+ **kwargs,
729
+ ):
666
730
  _, day = monthrange(year, month)
667
731
  closing_date = date(year, month, day)
668
732
  return self.save_closing_entry_cache_for_date(
669
733
  closing_date=closing_date,
670
734
  cache_name=cache_name,
671
735
  cache_timeout=cache_timeout,
672
- **kwargs
736
+ **kwargs,
673
737
  )
674
738
 
675
- def save_closing_entry_cache_for_fiscal_year(self,
676
- fiscal_year: int,
677
- cache_name: str = 'default',
678
- cache_timeout: Optional[int] = None,
679
- **kwargs):
739
+ def save_closing_entry_cache_for_fiscal_year(
740
+ self,
741
+ fiscal_year: int,
742
+ cache_name: str = 'default',
743
+ cache_timeout: Optional[int] = None,
744
+ **kwargs,
745
+ ):
680
746
  closing_date: date = getattr(self, 'get_fy_end')(year=fiscal_year)
681
747
  return self.save_closing_entry_cache_for_date(
682
748
  closing_date=closing_date,
683
749
  cache_name=cache_name,
684
750
  cache_timeout=cache_timeout,
685
- **kwargs
751
+ **kwargs,
686
752
  )
687
753
 
688
754
 
689
- class EntityModelAbstract(MP_Node,
690
- SlugNameMixIn,
691
- CreateUpdateMixIn,
692
- ContactInfoMixIn,
693
- IOMixIn,
694
- LoggingMixIn,
695
- EntityModelFiscalPeriodMixIn,
696
- EntityModelClosingEntryMixIn):
755
+ class EntityModelAbstract(
756
+ MP_Node,
757
+ SlugNameMixIn,
758
+ CreateUpdateMixIn,
759
+ ContactInfoMixIn,
760
+ IOMixIn,
761
+ LoggingMixIn,
762
+ EntityModelFiscalPeriodMixIn,
763
+ EntityModelClosingEntryMixIn,
764
+ ):
697
765
  """
698
766
  The base implementation of the EntityModel. The EntityModel represents the Company, Corporation, Legal Entity,
699
767
  Enterprise or Person that engage and operate as a business. The base model inherit from the Materialized Path Node
@@ -765,37 +833,46 @@ class EntityModelAbstract(MP_Node,
765
833
 
766
834
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
767
835
  name = models.CharField(max_length=150, verbose_name=_('Entity Name'))
768
- default_coa = models.OneToOneField('django_ledger.ChartOfAccountModel',
769
- verbose_name=_('Default Chart of Accounts'),
770
- blank=True,
771
- null=True,
772
- on_delete=models.PROTECT)
773
- admin = models.ForeignKey(UserModel,
774
- on_delete=models.CASCADE,
775
- related_name='admin_of',
776
- verbose_name=_('Admin'))
777
- managers = models.ManyToManyField(UserModel,
778
- through='EntityManagementModel',
779
- related_name='managed_by',
780
- verbose_name=_('Managers'))
836
+ default_coa = models.OneToOneField(
837
+ 'django_ledger.ChartOfAccountModel',
838
+ verbose_name=_('Default Chart of Accounts'),
839
+ blank=True,
840
+ null=True,
841
+ on_delete=models.PROTECT,
842
+ )
843
+ admin = models.ForeignKey(
844
+ UserModel,
845
+ on_delete=models.CASCADE,
846
+ related_name='admin_of',
847
+ verbose_name=_('Admin'),
848
+ )
849
+ managers = models.ManyToManyField(
850
+ UserModel,
851
+ through='EntityManagementModel',
852
+ related_name='managed_by',
853
+ verbose_name=_('Managers'),
854
+ )
781
855
 
782
856
  hidden = models.BooleanField(default=False)
783
- accrual_method = models.BooleanField(default=False, verbose_name=_('Use Accrual Method'))
784
- fy_start_month = models.IntegerField(choices=FY_MONTHS, default=1, verbose_name=_('Fiscal Year Start'))
785
- last_closing_date = models.DateField(null=True, blank=True, verbose_name=_('Last Closing Entry Date'))
857
+ accrual_method = models.BooleanField(
858
+ default=False, verbose_name=_('Use Accrual Method')
859
+ )
860
+ fy_start_month = models.IntegerField(
861
+ choices=FY_MONTHS, default=1, verbose_name=_('Fiscal Year Start')
862
+ )
863
+ last_closing_date = models.DateField(
864
+ null=True, blank=True, verbose_name=_('Last Closing Entry Date')
865
+ )
786
866
  picture = models.ImageField(blank=True, null=True)
787
867
  meta = models.JSONField(default=dict, null=True, blank=True)
788
868
  objects = EntityModelManager.from_queryset(queryset_class=EntityModelQuerySet)()
789
869
 
790
-
791
870
  class Meta:
792
871
  abstract = True
793
872
  ordering = ['-created']
794
873
  verbose_name = _('Entity')
795
874
  verbose_name_plural = _('Entities')
796
- indexes = [
797
- models.Index(fields=['admin'])
798
- ]
875
+ indexes = [models.Index(fields=['admin'])]
799
876
 
800
877
  def __str__(self):
801
878
  return f'EntityModel {self.slug}: {self.name}'
@@ -817,12 +894,14 @@ class EntityModelAbstract(MP_Node,
817
894
 
818
895
  # ## ENTITY CREATION ###
819
896
  @classmethod
820
- def create_entity(cls,
821
- name: str,
822
- use_accrual_method: bool,
823
- admin: UserModel,
824
- fy_start_month: int,
825
- parent_entity=None):
897
+ def create_entity(
898
+ cls,
899
+ name: str,
900
+ use_accrual_method: bool,
901
+ admin: UserModel,
902
+ fy_start_month: int,
903
+ parent_entity=None,
904
+ ):
826
905
  """
827
906
  Convenience Method to Create a new Entity Model. This is the preferred method to create new Entities in order
828
907
  to properly handle potential parent/child relationships between EntityModels.
@@ -849,7 +928,7 @@ class EntityModelAbstract(MP_Node,
849
928
  name=name,
850
929
  accrual_method=use_accrual_method,
851
930
  fy_start_month=fy_start_month,
852
- admin=admin
931
+ admin=admin,
853
932
  )
854
933
  entity_model.clean()
855
934
  entity_model = cls.add_root(instance=entity_model)
@@ -857,22 +936,28 @@ class EntityModelAbstract(MP_Node,
857
936
  if isinstance(parent_entity, str):
858
937
  # get by slug...
859
938
  try:
860
- parent_entity_model = EntityModel.objects.get(slug__exact=parent_entity, admin=admin)
939
+ parent_entity_model = EntityModel.objects.get(
940
+ slug__exact=parent_entity, admin=admin
941
+ )
861
942
  except ObjectDoesNotExist:
862
943
  raise EntityModelValidationError(
863
944
  message=_(
864
945
  f'Invalid Parent Entity. '
865
- f'Entity with slug {parent_entity} is not administered by {admin.username}')
946
+ f'Entity with slug {parent_entity} is not administered by {admin.username}'
947
+ )
866
948
  )
867
949
  elif isinstance(parent_entity, UUID):
868
950
  # get by uuid...
869
951
  try:
870
- parent_entity_model = EntityModel.objects.get(uuid__exact=parent_entity, admin=admin)
952
+ parent_entity_model = EntityModel.objects.get(
953
+ uuid__exact=parent_entity, admin=admin
954
+ )
871
955
  except ObjectDoesNotExist:
872
956
  raise EntityModelValidationError(
873
957
  message=_(
874
958
  f'Invalid Parent Entity. '
875
- f'Entity with UUID {parent_entity} is not administered by {admin.username}')
959
+ f'Entity with UUID {parent_entity} is not administered by {admin.username}'
960
+ )
876
961
  )
877
962
  elif isinstance(parent_entity, cls):
878
963
  # EntityModel instance provided...
@@ -880,7 +965,8 @@ class EntityModelAbstract(MP_Node,
880
965
  raise EntityModelValidationError(
881
966
  message=_(
882
967
  f'Invalid Parent Entity. '
883
- f'Entity {parent_entity} is not administered by {admin.username}')
968
+ f'Entity {parent_entity} is not administered by {admin.username}'
969
+ )
884
970
  )
885
971
  parent_entity_model = parent_entity
886
972
  else:
@@ -907,15 +993,18 @@ class EntityModelAbstract(MP_Node,
907
993
  return user_model.id == self.admin_id
908
994
 
909
995
  # #### LEDGER MANAGEMENT....
910
- def create_ledger(self, name: str, ledger_xid: Optional[str] = None, posted: bool = False, commit: bool = True):
996
+ def create_ledger(
997
+ self,
998
+ name: str,
999
+ ledger_xid: Optional[str] = None,
1000
+ posted: bool = False,
1001
+ commit: bool = True,
1002
+ ):
911
1003
  if commit:
912
- return self.ledgermodel_set.create(name=name, ledger_xid=ledger_xid, posted=posted)
913
- return LedgerModel(
914
- entity=self,
915
- posted=posted,
916
- name=name,
917
- ledger_xid=ledger_xid
918
- )
1004
+ return self.ledgermodel_set.create(
1005
+ name=name, ledger_xid=ledger_xid, posted=posted
1006
+ )
1007
+ return LedgerModel(entity=self, posted=posted, name=name, ledger_xid=ledger_xid)
919
1008
 
920
1009
  # #### SLUG GENERATION ###
921
1010
  @staticmethod
@@ -937,10 +1026,12 @@ class EntityModelAbstract(MP_Node,
937
1026
  entity_slug = f'{slug}-{suffix}'
938
1027
  return entity_slug
939
1028
 
940
- def generate_slug(self,
941
- commit: bool = False,
942
- raise_exception: bool = True,
943
- force_update: bool = False) -> str:
1029
+ def generate_slug(
1030
+ self,
1031
+ commit: bool = False,
1032
+ raise_exception: bool = True,
1033
+ force_update: bool = False,
1034
+ ) -> str:
944
1035
  """
945
1036
  Convenience method to create the EntityModel slug.
946
1037
 
@@ -958,16 +1049,15 @@ class EntityModelAbstract(MP_Node,
958
1049
  if not force_update and self.slug:
959
1050
  if raise_exception:
960
1051
  raise ValidationError(
961
- message=_(f'Cannot replace existing slug {self.slug}. Use force_update=True if needed.')
1052
+ message=_(
1053
+ f'Cannot replace existing slug {self.slug}. Use force_update=True if needed.'
1054
+ )
962
1055
  )
963
1056
 
964
1057
  self.slug = self.generate_slug_from_name(self.name)
965
1058
 
966
1059
  if commit:
967
- self.save(update_fields=[
968
- 'slug',
969
- 'updated'
970
- ])
1060
+ self.save(update_fields=['slug', 'updated'])
971
1061
  return self.slug
972
1062
 
973
1063
  # #### CHART OF ACCOUNTS ####
@@ -982,7 +1072,9 @@ class EntityModelAbstract(MP_Node,
982
1072
  """
983
1073
  return self.default_coa_id is not None
984
1074
 
985
- def get_default_coa(self, raise_exception: bool = True) -> Optional[ChartOfAccountModel]:
1075
+ def get_default_coa(
1076
+ self, raise_exception: bool = True
1077
+ ) -> Optional[ChartOfAccountModel]:
986
1078
  """
987
1079
  Fetches the EntityModel default Chart of Account.
988
1080
 
@@ -999,11 +1091,14 @@ class EntityModelAbstract(MP_Node,
999
1091
 
1000
1092
  if not self.default_coa_id:
1001
1093
  if raise_exception:
1002
- raise EntityModelValidationError(f'EntityModel {self.slug} does not have a default CoA')
1094
+ raise EntityModelValidationError(
1095
+ f'EntityModel {self.slug} does not have a default CoA'
1096
+ )
1003
1097
  return self.default_coa
1004
1098
 
1005
- def set_default_coa(self, coa_model: Optional[Union[ChartOfAccountModel, str]], commit: bool = False):
1006
-
1099
+ def set_default_coa(
1100
+ self, coa_model: Optional[Union[ChartOfAccountModel, str]], commit: bool = False
1101
+ ):
1007
1102
  # if str, will look up CoA Model by slug...
1008
1103
  if isinstance(coa_model, str):
1009
1104
  coa_model = self.chartofaccountmodel_set.get(slug=coa_model)
@@ -1012,15 +1107,14 @@ class EntityModelAbstract(MP_Node,
1012
1107
 
1013
1108
  self.default_coa = coa_model
1014
1109
  if commit:
1015
- self.save(update_fields=[
1016
- 'default_coa',
1017
- 'updated'
1018
- ])
1110
+ self.save(update_fields=['default_coa', 'updated'])
1019
1111
 
1020
- def create_chart_of_accounts(self,
1021
- assign_as_default: bool = False,
1022
- coa_name: Optional[str] = None,
1023
- commit: bool = False) -> ChartOfAccountModel:
1112
+ def create_chart_of_accounts(
1113
+ self,
1114
+ assign_as_default: bool = False,
1115
+ coa_name: Optional[str] = None,
1116
+ commit: bool = False,
1117
+ ) -> ChartOfAccountModel:
1024
1118
  """
1025
1119
  Creates a Chart of Accounts for the Entity Model and optionally assign it as the default Chart of Accounts.
1026
1120
  EntityModel must have a default Chart of Accounts before being able to transact.
@@ -1045,10 +1139,7 @@ class EntityModelAbstract(MP_Node,
1045
1139
  if not coa_name:
1046
1140
  coa_name = 'Default CoA'
1047
1141
 
1048
- chart_of_accounts = ChartOfAccountModel(
1049
- name=coa_name,
1050
- entity=self
1051
- )
1142
+ chart_of_accounts = ChartOfAccountModel(name=coa_name, entity=self)
1052
1143
 
1053
1144
  chart_of_accounts.clean()
1054
1145
  chart_of_accounts.save()
@@ -1057,18 +1148,17 @@ class EntityModelAbstract(MP_Node,
1057
1148
  if assign_as_default:
1058
1149
  self.default_coa = chart_of_accounts
1059
1150
  if commit:
1060
- self.save(update_fields=[
1061
- 'default_coa',
1062
- 'updated'
1063
- ])
1151
+ self.save(update_fields=['default_coa', 'updated'])
1064
1152
  return chart_of_accounts
1065
1153
 
1066
- def populate_default_coa(self,
1067
- activate_accounts: bool = False,
1068
- force: bool = False,
1069
- ignore_if_default_coa: bool = True,
1070
- coa_model: Optional[ChartOfAccountModel] = None,
1071
- commit: bool = True):
1154
+ def populate_default_coa(
1155
+ self,
1156
+ activate_accounts: bool = False,
1157
+ force: bool = False,
1158
+ ignore_if_default_coa: bool = True,
1159
+ coa_model: Optional[ChartOfAccountModel] = None,
1160
+ commit: bool = True,
1161
+ ):
1072
1162
  """
1073
1163
  Populates the EntityModel default CoA with the default Chart of Account list provided by Django Ledger or user
1074
1164
  defined. See DJANGO_LEDGER_DEFAULT_COA setting.
@@ -1113,13 +1203,18 @@ class EntityModelAbstract(MP_Node,
1113
1203
  balance_type=a['balance_type'],
1114
1204
  active=activate_accounts,
1115
1205
  coa_model=coa_model,
1116
- ) for a in v] for k, v in CHART_OF_ACCOUNTS_ROOT_MAP.items()
1206
+ )
1207
+ for a in v
1208
+ ]
1209
+ for k, v in CHART_OF_ACCOUNTS_ROOT_MAP.items()
1117
1210
  }
1118
1211
 
1119
1212
  for root_acc, acc_model_list in root_maps.items():
1120
1213
  roles_set = set(account_model.role for account_model in acc_model_list)
1121
1214
  for i, account_model in enumerate(acc_model_list):
1122
- account_model.role_default = True if account_model.role in roles_set else False
1215
+ account_model.role_default = (
1216
+ True if account_model.role in roles_set else False
1217
+ )
1123
1218
 
1124
1219
  try:
1125
1220
  roles_set.remove(account_model.role)
@@ -1127,7 +1222,9 @@ class EntityModelAbstract(MP_Node,
1127
1222
  pass
1128
1223
 
1129
1224
  account_model.clean()
1130
- coa_model.insert_account(account_model, root_account_qs=root_account_qs)
1225
+ coa_model.insert_account(
1226
+ account_model, root_account_qs=root_account_qs
1227
+ )
1131
1228
 
1132
1229
  else:
1133
1230
  if not ignore_if_default_coa:
@@ -1156,9 +1253,9 @@ class EntityModelAbstract(MP_Node,
1156
1253
  return coa_model_qs
1157
1254
 
1158
1255
  # Model Validators....
1159
- def validate_chart_of_accounts_for_entity(self,
1160
- coa_model: ChartOfAccountModel,
1161
- raise_exception: bool = True) -> bool:
1256
+ def validate_chart_of_accounts_for_entity(
1257
+ self, coa_model: ChartOfAccountModel, raise_exception: bool = True
1258
+ ) -> bool:
1162
1259
  """
1163
1260
  Validates the CoA Model against the EntityModel instance.
1164
1261
 
@@ -1178,13 +1275,16 @@ class EntityModelAbstract(MP_Node,
1178
1275
  return True
1179
1276
  if raise_exception:
1180
1277
  raise EntityModelValidationError(
1181
- f'Invalid ChartOfAccounts model {coa_model.slug} for EntityModel {self.slug}')
1278
+ f'Invalid ChartOfAccounts model {coa_model.slug} for EntityModel {self.slug}'
1279
+ )
1182
1280
  return False
1183
1281
 
1184
- def validate_account_model_for_coa(self,
1185
- account_model: AccountModel,
1186
- coa_model: ChartOfAccountModel,
1187
- raise_exception: bool = True) -> bool:
1282
+ def validate_account_model_for_coa(
1283
+ self,
1284
+ account_model: AccountModel,
1285
+ coa_model: ChartOfAccountModel,
1286
+ raise_exception: bool = True,
1287
+ ) -> bool:
1188
1288
  """
1189
1289
  Validates that the AccountModel provided belongs to the CoA Model provided.
1190
1290
 
@@ -1202,7 +1302,9 @@ class EntityModelAbstract(MP_Node,
1202
1302
  bool
1203
1303
  True if valid, else False.
1204
1304
  """
1205
- valid = self.validate_chart_of_accounts_for_entity(coa_model, raise_exception=raise_exception)
1305
+ valid = self.validate_chart_of_accounts_for_entity(
1306
+ coa_model, raise_exception=raise_exception
1307
+ )
1206
1308
  if not valid:
1207
1309
  return valid
1208
1310
  if valid and account_model.coa_model_id == coa_model.uuid:
@@ -1216,17 +1318,23 @@ class EntityModelAbstract(MP_Node,
1216
1318
  @staticmethod
1217
1319
  def validate_account_model_for_role(account_model: AccountModel, role: str):
1218
1320
  if account_model.role != role:
1219
- raise EntityModelValidationError(f'Invalid account role: {account_model.role}, expected {role}')
1321
+ raise EntityModelValidationError(
1322
+ f'Invalid account role: {account_model.role}, expected {role}'
1323
+ )
1220
1324
 
1221
- def validate_ledger_model_for_entity(self, ledger_model: Union[LedgerModel, UUID, str]):
1325
+ def validate_ledger_model_for_entity(
1326
+ self, ledger_model: Union[LedgerModel, UUID, str]
1327
+ ):
1222
1328
  if ledger_model.entity_id != self.uuid:
1223
- raise EntityModelValidationError(f'Invalid LedgerModel {ledger_model.uuid} for entity {self.slug}')
1224
-
1225
- def get_all_coa_accounts(self,
1226
- order_by: Optional[Tuple[str]] = ('code',),
1227
- active: bool = True) -> Tuple[
1228
- ChartOfAccountModelQuerySet, Dict[ChartOfAccountModel, AccountModelQuerySet]]:
1329
+ raise EntityModelValidationError(
1330
+ f'Invalid LedgerModel {ledger_model.uuid} for entity {self.slug}'
1331
+ )
1229
1332
 
1333
+ def get_all_coa_accounts(
1334
+ self, order_by: Optional[Tuple[str]] = ('code',), active: bool = True
1335
+ ) -> Tuple[
1336
+ ChartOfAccountModelQuerySet, Dict[ChartOfAccountModel, AccountModelQuerySet]
1337
+ ]:
1230
1338
  """
1231
1339
  Fetches all the AccountModels associated with the EntityModel grouped by ChartOfAccountModel.
1232
1340
 
@@ -1243,17 +1351,23 @@ class EntityModelAbstract(MP_Node,
1243
1351
  The ChartOfAccountModelQuerySet and a grouping of AccountModels by ChartOfAccountModel as keys.
1244
1352
  """
1245
1353
 
1246
- account_model_qs = ChartOfAccountModel.objects.filter(
1247
- entity_id=self.uuid
1248
- ).select_related('entity').prefetch_related('accountmodel_set')
1354
+ account_model_qs = (
1355
+ ChartOfAccountModel.objects.filter(entity_id=self.uuid)
1356
+ .select_related('entity')
1357
+ .prefetch_related('accountmodel_set')
1358
+ )
1249
1359
 
1250
1360
  return account_model_qs, {
1251
- coa_model: coa_model.accountmodel_set.filter(active=active).order_by(*order_by) for coa_model in
1252
- account_model_qs
1361
+ coa_model: coa_model.accountmodel_set.filter(active=active).order_by(
1362
+ *order_by
1363
+ )
1364
+ for coa_model in account_model_qs
1253
1365
  }
1254
1366
 
1255
1367
  # ##### ACCOUNT MANAGEMENT ######
1256
- def get_all_accounts(self, active: bool = True, order_by: Optional[Tuple[str]] = ('code',)) -> AccountModelQuerySet:
1368
+ def get_all_accounts(
1369
+ self, active: bool = True, order_by: Optional[Tuple[str]] = ('code',)
1370
+ ) -> AccountModelQuerySet:
1257
1371
  """
1258
1372
  Fetches all AccountModelQuerySet associated with the EntityModel.
1259
1373
 
@@ -1279,13 +1393,14 @@ class EntityModelAbstract(MP_Node,
1279
1393
  account_model_qs = account_model_qs.order_by(*order_by)
1280
1394
  return account_model_qs
1281
1395
 
1282
- def get_coa_accounts(self,
1283
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1284
- active: bool = True,
1285
- locked: bool = False,
1286
- order_by: Optional[Tuple] = ('code',),
1287
- return_coa_model: bool = False,
1288
- ) -> Union[AccountModelQuerySet, Tuple[ChartOfAccountModel, AccountModelQuerySet]]:
1396
+ def get_coa_accounts(
1397
+ self,
1398
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1399
+ active: bool = True,
1400
+ locked: bool = False,
1401
+ order_by: Optional[Tuple] = ('code',),
1402
+ return_coa_model: bool = False,
1403
+ ) -> Union[AccountModelQuerySet, Tuple[ChartOfAccountModel, AccountModelQuerySet]]:
1289
1404
  """
1290
1405
  Fetches the AccountModelQuerySet for a specific ChartOfAccountModel.
1291
1406
 
@@ -1309,9 +1424,13 @@ class EntityModelAbstract(MP_Node,
1309
1424
  if not coa_model:
1310
1425
  coa_model = self.default_coa
1311
1426
  elif isinstance(coa_model, UUID):
1312
- coa_model = self.chartofaccountmodel_set.select_related('entity').get(uuid__exact=coa_model)
1427
+ coa_model = self.chartofaccountmodel_set.select_related('entity').get(
1428
+ uuid__exact=coa_model
1429
+ )
1313
1430
  elif isinstance(coa_model, str):
1314
- coa_model = self.chartofaccountmodel_set.select_related('entity').get(slug__exact=coa_model)
1431
+ coa_model = self.chartofaccountmodel_set.select_related('entity').get(
1432
+ slug__exact=coa_model
1433
+ )
1315
1434
  elif isinstance(coa_model, ChartOfAccountModel):
1316
1435
  self.validate_chart_of_accounts_for_entity(coa_model=coa_model)
1317
1436
  else:
@@ -1319,7 +1438,9 @@ class EntityModelAbstract(MP_Node,
1319
1438
  f'CoA Model {coa_model} must be an instance of ChartOfAccountModel, UUID, str or None.'
1320
1439
  )
1321
1440
 
1322
- account_model_qs = coa_model.accountmodel_set.select_related('coa_model', 'coa_model__entity').not_coa_root()
1441
+ account_model_qs = coa_model.accountmodel_set.select_related(
1442
+ 'coa_model', 'coa_model__entity'
1443
+ ).not_coa_root()
1323
1444
 
1324
1445
  if active:
1325
1446
  account_model_qs = account_model_qs.active()
@@ -1334,10 +1455,12 @@ class EntityModelAbstract(MP_Node,
1334
1455
  return coa_model, account_model_qs
1335
1456
  return account_model_qs
1336
1457
 
1337
- def get_default_coa_accounts(self,
1338
- active: bool = True,
1339
- order_by: Optional[Tuple[str]] = ('code',),
1340
- raise_exception: bool = True) -> Optional[AccountModelQuerySet]:
1458
+ def get_default_coa_accounts(
1459
+ self,
1460
+ active: bool = True,
1461
+ order_by: Optional[Tuple[str]] = ('code',),
1462
+ raise_exception: bool = True,
1463
+ ) -> Optional[AccountModelQuerySet]:
1341
1464
  """
1342
1465
  Fetches the default AccountModelQuerySet.
1343
1466
 
@@ -1362,10 +1485,11 @@ class EntityModelAbstract(MP_Node,
1362
1485
 
1363
1486
  return self.get_coa_accounts(active=active, order_by=order_by)
1364
1487
 
1365
- def get_accounts_with_codes(self,
1366
- code_list: Union[str, List[str], Set[str]],
1367
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None
1368
- ) -> AccountModelQuerySet:
1488
+ def get_accounts_with_codes(
1489
+ self,
1490
+ code_list: Union[str, List[str], Set[str]],
1491
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1492
+ ) -> AccountModelQuerySet:
1369
1493
  """
1370
1494
  Fetches the AccountModelQuerySet with provided code list.
1371
1495
 
@@ -1392,9 +1516,9 @@ class EntityModelAbstract(MP_Node,
1392
1516
  return account_model_qs.filter(code__exact=code_list)
1393
1517
  return account_model_qs.filter(code__in=code_list)
1394
1518
 
1395
- def get_default_account_for_role(self,
1396
- role: str,
1397
- coa_model: Optional[ChartOfAccountModel] = None) -> AccountModel:
1519
+ def get_default_account_for_role(
1520
+ self, role: str, coa_model: Optional[ChartOfAccountModel] = None
1521
+ ) -> AccountModel:
1398
1522
  """
1399
1523
  Gets the given role default AccountModel from the provided CoA.
1400
1524
  CoA will be validated against the EntityModel instance.
@@ -1420,14 +1544,16 @@ class EntityModelAbstract(MP_Node,
1420
1544
  account_model_qs = coa_model.accountmodel_set.all().is_role_default()
1421
1545
  return account_model_qs.get(role__exact=role)
1422
1546
 
1423
- def create_account(self,
1424
- code: str,
1425
- role: str,
1426
- name: str,
1427
- balance_type: str,
1428
- active: bool = False,
1429
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1430
- raise_exception: bool = True) -> AccountModel:
1547
+ def create_account(
1548
+ self,
1549
+ code: str,
1550
+ role: str,
1551
+ name: str,
1552
+ balance_type: str,
1553
+ active: bool = False,
1554
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1555
+ raise_exception: bool = True,
1556
+ ) -> AccountModel:
1431
1557
  """
1432
1558
  Creates a new AccountModel for the EntityModel.
1433
1559
 
@@ -1461,24 +1587,21 @@ class EntityModelAbstract(MP_Node,
1461
1587
  coa_model = self.chartofaccountsmodel_set.get(slug__exact=coa_model)
1462
1588
  elif isinstance(coa_model, ChartOfAccountModel):
1463
1589
  self.validate_chart_of_accounts_for_entity(
1464
- coa_model=coa_model,
1465
- raise_exception=raise_exception
1590
+ coa_model=coa_model, raise_exception=raise_exception
1466
1591
  )
1467
1592
  else:
1468
1593
  coa_model = self.default_coa
1469
1594
 
1470
1595
  return coa_model.create_account(
1471
- code=code,
1472
- role=role,
1473
- name=name,
1474
- balance_type=balance_type,
1475
- active=active
1596
+ code=code, role=role, name=name, balance_type=balance_type, active=active
1476
1597
  )
1477
1598
 
1478
- def create_account_by_kwargs(self,
1479
- account_model_kwargs: Dict,
1480
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1481
- raise_exception: bool = True) -> Tuple[ChartOfAccountModel, AccountModel]:
1599
+ def create_account_by_kwargs(
1600
+ self,
1601
+ account_model_kwargs: Dict,
1602
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1603
+ raise_exception: bool = True,
1604
+ ) -> Tuple[ChartOfAccountModel, AccountModel]:
1482
1605
  """
1483
1606
  Creates a new AccountModel for the EntityModel by passing AccountModel KWARGS.
1484
1607
  This is a legacy method for creating a new AccountModel for the EntityModel.
@@ -1506,8 +1629,7 @@ class EntityModelAbstract(MP_Node,
1506
1629
  coa_model = self.chartofaccountsmodel_set.get(slug__exact=coa_model)
1507
1630
  elif isinstance(coa_model, ChartOfAccountModel):
1508
1631
  self.validate_chart_of_accounts_for_entity(
1509
- coa_model=coa_model,
1510
- raise_exception=raise_exception
1632
+ coa_model=coa_model, raise_exception=raise_exception
1511
1633
  )
1512
1634
  else:
1513
1635
  coa_model = self.default_coa
@@ -1516,16 +1638,11 @@ class EntityModelAbstract(MP_Node,
1516
1638
  # account_model.clean()
1517
1639
  return coa_model, coa_model.create_account(**account_model_kwargs)
1518
1640
 
1519
- def get_account_balance(self,
1520
- account_codes: List[str],
1521
- to_date: Union[datetime, date, str],
1522
- **kwargs):
1523
-
1641
+ def get_account_balance(
1642
+ self, account_codes: List[str], to_date: Union[datetime, date, str], **kwargs
1643
+ ):
1524
1644
  io_context = self.digest(
1525
- entity_model=self.slug,
1526
- accounts=account_codes,
1527
- to_date=to_date,
1528
- **kwargs
1645
+ entity_model=self.slug, accounts=account_codes, to_date=to_date, **kwargs
1529
1646
  )
1530
1647
 
1531
1648
  return io_context
@@ -1538,7 +1655,6 @@ class EntityModelAbstract(MP_Node,
1538
1655
 
1539
1656
  # ### JOURNAL ENTRY MANAGEMENT ####
1540
1657
  def get_journal_entries(self, ledger_model: LedgerModel, posted: bool = True):
1541
-
1542
1658
  if ledger_model:
1543
1659
  self.validate_ledger_model_for_entity(ledger_model)
1544
1660
  qs = ledger_model.journal_entries.all()
@@ -1547,7 +1663,7 @@ class EntityModelAbstract(MP_Node,
1547
1663
  return qs
1548
1664
 
1549
1665
  JournalEntryModel = lazy_loader.get_journal_entry_model()
1550
- qs = JournalEntryModel.objects.for_entity(entity_slug=self)
1666
+ qs = JournalEntryModel.objects.for_entity(entity_model=self)
1551
1667
  if posted:
1552
1668
  return qs.posted()
1553
1669
  return qs
@@ -1580,7 +1696,9 @@ class EntityModelAbstract(MP_Node,
1580
1696
  vendor_model_qs = self.get_vendors()
1581
1697
  return vendor_model_qs.get(uuid__exact=vendor_uuid)
1582
1698
 
1583
- def create_vendor(self, vendor_model_kwargs: Dict, commit: bool = True) -> VendorModel:
1699
+ def create_vendor(
1700
+ self, vendor_model_kwargs: Dict, commit: bool = True
1701
+ ) -> VendorModel:
1584
1702
  """
1585
1703
  Creates a new VendorModel associated with the EntityModel instance.
1586
1704
 
@@ -1631,9 +1749,13 @@ class EntityModelAbstract(MP_Node,
1631
1749
 
1632
1750
  def validate_customer(self, customer_model: CustomerModel):
1633
1751
  if customer_model.entity_model_id != self.uuid:
1634
- raise EntityModelValidationError(f'Invalid CustomerModel {self.uuid} for EntityModel {self.uuid}...')
1752
+ raise EntityModelValidationError(
1753
+ f'Invalid CustomerModel {self.uuid} for EntityModel {self.uuid}...'
1754
+ )
1635
1755
 
1636
- def create_customer(self, customer_model_kwargs: Dict, commit: bool = True) -> CustomerModel:
1756
+ def create_customer(
1757
+ self, customer_model_kwargs: Dict, commit: bool = True
1758
+ ) -> CustomerModel:
1637
1759
  """
1638
1760
  Creates a new CustomerModel associated with the EntityModel instance.
1639
1761
 
@@ -1654,6 +1776,11 @@ class EntityModelAbstract(MP_Node,
1654
1776
  customer_model.save()
1655
1777
  return customer_model
1656
1778
 
1779
+ # ### RECEIPT MANAGEMENT ####
1780
+ def get_receipts(self):
1781
+ ReceiptModel = lazy_loader.get_receipt_model()
1782
+ return ReceiptModel.objects.for_entity(entity_model=self)
1783
+
1657
1784
  # ### BILL MANAGEMENT ####
1658
1785
  def get_bills(self):
1659
1786
  """
@@ -1668,18 +1795,20 @@ class EntityModelAbstract(MP_Node,
1668
1795
  ledger__entity__uuid__exact=self.uuid
1669
1796
  ).select_related('ledger', 'ledger__entity', 'vendor')
1670
1797
 
1671
- def create_bill(self,
1672
- vendor_model: Union[VendorModel, UUID, str],
1673
- terms: str,
1674
- date_draft: Optional[Union[date, datetime]] = None,
1675
- xref: Optional[str] = None,
1676
- cash_account: Optional[AccountModel] = None,
1677
- prepaid_account: Optional[AccountModel] = None,
1678
- payable_account: Optional[AccountModel] = None,
1679
- additional_info: Optional[Dict] = None,
1680
- ledger_name: Optional[str] = None,
1681
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1682
- commit: bool = True):
1798
+ def create_bill(
1799
+ self,
1800
+ vendor_model: Union[VendorModel, UUID, str],
1801
+ terms: str,
1802
+ date_draft: Optional[Union[date, datetime]] = None,
1803
+ xref: Optional[str] = None,
1804
+ cash_account: Optional[AccountModel] = None,
1805
+ prepaid_account: Optional[AccountModel] = None,
1806
+ payable_account: Optional[AccountModel] = None,
1807
+ additional_info: Optional[Dict] = None,
1808
+ ledger_name: Optional[str] = None,
1809
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1810
+ commit: bool = True,
1811
+ ):
1683
1812
  """
1684
1813
  Creates a new BillModel for the EntityModel instance.
1685
1814
  Bill will have DRAFT status.
@@ -1720,13 +1849,17 @@ class EntityModelAbstract(MP_Node,
1720
1849
 
1721
1850
  if isinstance(vendor_model, VendorModel):
1722
1851
  if not vendor_model.entity_model_id == self.uuid:
1723
- raise EntityModelValidationError(f'VendorModel {vendor_model.uuid} belongs to a different EntityModel.')
1852
+ raise EntityModelValidationError(
1853
+ f'VendorModel {vendor_model.uuid} belongs to a different EntityModel.'
1854
+ )
1724
1855
  elif isinstance(vendor_model, UUID):
1725
1856
  vendor_model = self.get_vendor_by_uuid(vendor_uuid=vendor_model)
1726
1857
  elif isinstance(vendor_model, str):
1727
1858
  vendor_model = self.get_vendor_by_number(vendor_number=vendor_model)
1728
1859
  else:
1729
- raise EntityModelValidationError('VendorModel must be an instance of VendorModel, UUID or str.')
1860
+ raise EntityModelValidationError(
1861
+ 'VendorModel must be an instance of VendorModel, UUID or str.'
1862
+ )
1730
1863
 
1731
1864
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
1732
1865
 
@@ -1734,8 +1867,9 @@ class EntityModelAbstract(MP_Node,
1734
1867
  roles=[
1735
1868
  roles_module.ASSET_CA_CASH,
1736
1869
  roles_module.ASSET_CA_PREPAID,
1737
- roles_module.LIABILITY_CL_ACC_PAYABLE
1738
- ]).is_role_default()
1870
+ roles_module.LIABILITY_CL_ACC_PAYABLE,
1871
+ ]
1872
+ ).is_role_default()
1739
1873
 
1740
1874
  # evaluates the queryset...
1741
1875
  len(account_model_qs)
@@ -1745,20 +1879,26 @@ class EntityModelAbstract(MP_Node,
1745
1879
  vendor=vendor_model,
1746
1880
  terms=terms,
1747
1881
  additional_info=additional_info,
1748
- cash_account=account_model_qs.get(role=roles_module.ASSET_CA_CASH) if not cash_account else cash_account,
1749
- prepaid_account=account_model_qs.get(
1750
- role=roles_module.ASSET_CA_PREPAID
1751
- ) if not prepaid_account else prepaid_account,
1882
+ cash_account=account_model_qs.get(role=roles_module.ASSET_CA_CASH)
1883
+ if not cash_account
1884
+ else cash_account,
1885
+ prepaid_account=account_model_qs.get(role=roles_module.ASSET_CA_PREPAID)
1886
+ if not prepaid_account
1887
+ else prepaid_account,
1752
1888
  unearned_account=account_model_qs.get(
1753
1889
  role=roles_module.LIABILITY_CL_ACC_PAYABLE
1754
- ) if not payable_account else payable_account
1890
+ )
1891
+ if not payable_account
1892
+ else payable_account,
1755
1893
  )
1756
1894
 
1757
- _, bill_model = bill_model.configure(entity_slug=self,
1758
- ledger_name=ledger_name,
1759
- date_draft=date_draft,
1760
- commit=commit,
1761
- commit_ledger=commit)
1895
+ _, bill_model = bill_model.configure(
1896
+ entity_slug=self,
1897
+ ledger_name=ledger_name,
1898
+ date_draft=date_draft,
1899
+ commit=commit,
1900
+ commit_ledger=commit,
1901
+ )
1762
1902
 
1763
1903
  return bill_model
1764
1904
 
@@ -1780,18 +1920,19 @@ class EntityModelAbstract(MP_Node,
1780
1920
  ledger__entity__uuid__exact=self.uuid
1781
1921
  ).select_related('ledger', 'ledger__entity', 'customer')
1782
1922
 
1783
- def create_invoice(self,
1784
- customer_model: Union[VendorModel, UUID, str],
1785
- terms: str,
1786
- cash_account: Optional[AccountModel] = None,
1787
- prepaid_account: Optional[AccountModel] = None,
1788
- payable_account: Optional[AccountModel] = None,
1789
- additional_info: Optional[Dict] = None,
1790
- ledger_name: Optional[str] = None,
1791
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1792
- date_draft: Optional[date] = None,
1793
- commit: bool = True):
1794
-
1923
+ def create_invoice(
1924
+ self,
1925
+ customer_model: Union[VendorModel, UUID, str],
1926
+ terms: str,
1927
+ cash_account: Optional[AccountModel] = None,
1928
+ prepaid_account: Optional[AccountModel] = None,
1929
+ payable_account: Optional[AccountModel] = None,
1930
+ additional_info: Optional[Dict] = None,
1931
+ ledger_name: Optional[str] = None,
1932
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1933
+ date_draft: Optional[date] = None,
1934
+ commit: bool = True,
1935
+ ):
1795
1936
  """
1796
1937
  Creates a new InvoiceModel for the EntityModel instance.
1797
1938
  Invoice will have DRAFT status.
@@ -1831,21 +1972,25 @@ class EntityModelAbstract(MP_Node,
1831
1972
  if isinstance(customer_model, CustomerModel):
1832
1973
  if not customer_model.entity_model_id == self.uuid:
1833
1974
  raise EntityModelValidationError(
1834
- f'CustomerModel {customer_model.uuid} belongs to a different EntityModel.')
1975
+ f'CustomerModel {customer_model.uuid} belongs to a different EntityModel.'
1976
+ )
1835
1977
  elif isinstance(customer_model, UUID):
1836
1978
  customer_model = self.get_customer_by_uuid(customer_uuid=customer_model)
1837
1979
  elif isinstance(customer_model, str):
1838
1980
  customer_model = self.get_customer_by_number(customer_number=customer_model)
1839
1981
  else:
1840
- raise EntityModelValidationError('CustomerModel must be an instance of CustomerModel, UUID or str.')
1982
+ raise EntityModelValidationError(
1983
+ 'CustomerModel must be an instance of CustomerModel, UUID or str.'
1984
+ )
1841
1985
 
1842
1986
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
1843
1987
  account_model_qs = account_model_qs.with_roles(
1844
1988
  roles=[
1845
1989
  roles_module.ASSET_CA_CASH,
1846
1990
  roles_module.ASSET_CA_RECEIVABLES,
1847
- roles_module.LIABILITY_CL_DEFERRED_REVENUE
1848
- ]).is_role_default()
1991
+ roles_module.LIABILITY_CL_DEFERRED_REVENUE,
1992
+ ]
1993
+ ).is_role_default()
1849
1994
 
1850
1995
  # evaluates the queryset...
1851
1996
  len(account_model_qs)
@@ -1854,20 +1999,26 @@ class EntityModelAbstract(MP_Node,
1854
1999
  customer=customer_model,
1855
2000
  additional_info=additional_info,
1856
2001
  terms=terms,
1857
- cash_account=account_model_qs.get(role=roles_module.ASSET_CA_CASH) if not cash_account else cash_account,
1858
- prepaid_account=account_model_qs.get(
1859
- role=roles_module.ASSET_CA_RECEIVABLES
1860
- ) if not prepaid_account else prepaid_account,
2002
+ cash_account=account_model_qs.get(role=roles_module.ASSET_CA_CASH)
2003
+ if not cash_account
2004
+ else cash_account,
2005
+ prepaid_account=account_model_qs.get(role=roles_module.ASSET_CA_RECEIVABLES)
2006
+ if not prepaid_account
2007
+ else prepaid_account,
1861
2008
  unearned_account=account_model_qs.get(
1862
2009
  role=roles_module.LIABILITY_CL_DEFERRED_REVENUE
1863
- ) if not payable_account else payable_account
2010
+ )
2011
+ if not payable_account
2012
+ else payable_account,
1864
2013
  )
1865
2014
 
1866
- _, invoice_model = invoice_model.configure(entity_slug=self,
1867
- ledger_name=ledger_name,
1868
- commit=commit,
1869
- date_draft=date_draft,
1870
- commit_ledger=commit)
2015
+ _, invoice_model = invoice_model.configure(
2016
+ entity_slug=self,
2017
+ ledger_name=ledger_name,
2018
+ commit=commit,
2019
+ date_draft=date_draft,
2020
+ commit_ledger=commit,
2021
+ )
1871
2022
 
1872
2023
  return invoice_model
1873
2024
 
@@ -1882,11 +2033,13 @@ class EntityModelAbstract(MP_Node,
1882
2033
  """
1883
2034
  return self.purchaseordermodel_set.all().select_related('entity')
1884
2035
 
1885
- def create_purchase_order(self,
1886
- po_title: Optional[str] = None,
1887
- estimate_model=None,
1888
- date_draft: Optional[date] = None,
1889
- commit: bool = True):
2036
+ def create_purchase_order(
2037
+ self,
2038
+ po_title: Optional[str] = None,
2039
+ estimate_model=None,
2040
+ date_draft: Optional[date] = None,
2041
+ commit: bool = True,
2042
+ ):
1890
2043
  """
1891
2044
  Creates a new PurchaseOrderModel for the EntityModel instance.
1892
2045
  PO will have DRAFT status.
@@ -1914,7 +2067,7 @@ class EntityModelAbstract(MP_Node,
1914
2067
  draft_date=date_draft,
1915
2068
  estimate_model=estimate_model,
1916
2069
  commit=commit,
1917
- po_title=po_title
2070
+ po_title=po_title,
1918
2071
  )
1919
2072
 
1920
2073
  # ### ESTIMATE/CONTRACT MANAGEMENT ####
@@ -1928,12 +2081,14 @@ class EntityModelAbstract(MP_Node,
1928
2081
  """
1929
2082
  return self.estimatemodel_set.all().select_related('entity')
1930
2083
 
1931
- def create_estimate(self,
1932
- estimate_title: str,
1933
- contract_terms: str,
1934
- customer_model: Union[CustomerModel, UUID, str],
1935
- date_draft: Optional[date] = None,
1936
- commit: bool = True):
2084
+ def create_estimate(
2085
+ self,
2086
+ estimate_title: str,
2087
+ contract_terms: str,
2088
+ customer_model: Union[CustomerModel, UUID, str],
2089
+ date_draft: Optional[date] = None,
2090
+ commit: bool = True,
2091
+ ):
1937
2092
  """
1938
2093
  Creates a new EstimateModel for the EntityModel instance.
1939
2094
  Estimate will have DRAFT status.
@@ -1963,7 +2118,9 @@ class EntityModelAbstract(MP_Node,
1963
2118
  elif isinstance(customer_model, UUID):
1964
2119
  customer_model = self.get_customer_by_uuid(customer_uuid=customer_model)
1965
2120
  else:
1966
- raise EntityModelValidationError('CustomerModel must be an instance of CustomerModel, UUID or str.')
2121
+ raise EntityModelValidationError(
2122
+ 'CustomerModel must be an instance of CustomerModel, UUID or str.'
2123
+ )
1967
2124
 
1968
2125
  EstimateModel = lazy_loader.get_estimate_model()
1969
2126
  estimate_model = EstimateModel(terms=contract_terms)
@@ -1972,7 +2129,7 @@ class EntityModelAbstract(MP_Node,
1972
2129
  date_draft=date_draft,
1973
2130
  customer_model=customer_model,
1974
2131
  estimate_title=estimate_title,
1975
- commit=commit
2132
+ commit=commit,
1976
2133
  )
1977
2134
 
1978
2135
  # ### BANK ACCOUNT MANAGEMENT ####
@@ -1994,15 +2151,16 @@ class EntityModelAbstract(MP_Node,
1994
2151
  bank_account_qs = bank_account_qs.active()
1995
2152
  return bank_account_qs
1996
2153
 
1997
- def create_bank_account(self,
1998
- name: str,
1999
- account_type: str,
2000
- active=False,
2001
- account_model: Optional[AccountModel] = None,
2002
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2003
- bank_account_model_kwargs: Optional[Dict] = None,
2004
- commit: bool = True):
2005
-
2154
+ def create_bank_account(
2155
+ self,
2156
+ name: str,
2157
+ account_type: str,
2158
+ active=False,
2159
+ account_model: Optional[AccountModel] = None,
2160
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2161
+ bank_account_model_kwargs: Optional[Dict] = None,
2162
+ commit: bool = True,
2163
+ ):
2006
2164
  """
2007
2165
  Create a bank account entry for the entity model with specified attributes and validation.
2008
2166
 
@@ -2042,13 +2200,14 @@ class EntityModelAbstract(MP_Node,
2042
2200
 
2043
2201
  if account_type not in BankAccountModel.VALID_ACCOUNT_TYPES:
2044
2202
  raise EntityModelValidationError(
2045
- _(f'Invalid Account Type: choices are {BankAccountModel.VALID_ACCOUNT_TYPES}'))
2203
+ _(
2204
+ f'Invalid Account Type: choices are {BankAccountModel.VALID_ACCOUNT_TYPES}'
2205
+ )
2206
+ )
2046
2207
 
2047
2208
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
2048
2209
  account_model_qs = account_model_qs.with_roles(
2049
- roles=[
2050
- BankAccountModel.ACCOUNT_TYPE_DEFAULT_ROLE_MAPPING[account_type]
2051
- ]
2210
+ roles=[BankAccountModel.ACCOUNT_TYPE_DEFAULT_ROLE_MAPPING[account_type]]
2052
2211
  ).is_role_default()
2053
2212
 
2054
2213
  bank_account_model = BankAccountModel(
@@ -2056,8 +2215,10 @@ class EntityModelAbstract(MP_Node,
2056
2215
  entity_model=self,
2057
2216
  account_type=account_type,
2058
2217
  active=active,
2059
- account_model=account_model_qs.get() if not account_model else account_model,
2060
- **bank_account_model_kwargs
2218
+ account_model=account_model_qs.get()
2219
+ if not account_model
2220
+ else account_model,
2221
+ **bank_account_model_kwargs,
2061
2222
  )
2062
2223
 
2063
2224
  bank_account_model.clean()
@@ -2066,7 +2227,9 @@ class EntityModelAbstract(MP_Node,
2066
2227
  return bank_account_model
2067
2228
 
2068
2229
  # #### ITEM MANAGEMENT ###
2069
- def validate_item_qs(self, item_qs: ItemModelQuerySet, raise_exception: bool = True) -> bool:
2230
+ def validate_item_qs(
2231
+ self, item_qs: ItemModelQuerySet, raise_exception: bool = True
2232
+ ) -> bool:
2070
2233
  """
2071
2234
  Validates the given ItemModelQuerySet against the EntityModel instance.
2072
2235
  Parameters
@@ -2084,7 +2247,9 @@ class EntityModelAbstract(MP_Node,
2084
2247
  for item_model in item_qs:
2085
2248
  if item_model.entity_id != self.uuid:
2086
2249
  if raise_exception:
2087
- raise EntityModelValidationError(f'Invalid item_qs provided for entity {self.slug}...')
2250
+ raise EntityModelValidationError(
2251
+ f'Invalid item_qs provided for entity {self.slug}...'
2252
+ )
2088
2253
  return False
2089
2254
  return True
2090
2255
 
@@ -2098,7 +2263,9 @@ class EntityModelAbstract(MP_Node,
2098
2263
  """
2099
2264
  return self.unitofmeasuremodel_set.all().select_related('entity')
2100
2265
 
2101
- def create_uom(self, name: str, unit_abbr: str, active: bool = True, commit: bool = True) -> UnitOfMeasureModel:
2266
+ def create_uom(
2267
+ self, name: str, unit_abbr: str, active: bool = True, commit: bool = True
2268
+ ) -> UnitOfMeasureModel:
2102
2269
  """
2103
2270
  Creates a new Unit of Measure Model associated with the EntityModel instance
2104
2271
 
@@ -2118,10 +2285,7 @@ class EntityModelAbstract(MP_Node,
2118
2285
  UnitOfMeasureModel
2119
2286
  """
2120
2287
  uom_model = UnitOfMeasureModel(
2121
- name=name,
2122
- unit_abbr=unit_abbr,
2123
- is_active=active,
2124
- entity=self
2288
+ name=name, unit_abbr=unit_abbr, is_active=active, entity=self
2125
2289
  )
2126
2290
  uom_model.clean()
2127
2291
  uom_model.clean_fields()
@@ -2150,7 +2314,7 @@ class EntityModelAbstract(MP_Node,
2150
2314
  'inventory_account',
2151
2315
  'cogs_account',
2152
2316
  'earnings_account',
2153
- 'expense_account'
2317
+ 'expense_account',
2154
2318
  )
2155
2319
  if active:
2156
2320
  return qs.active()
@@ -2174,12 +2338,14 @@ class EntityModelAbstract(MP_Node,
2174
2338
  qs = self.get_items_all(active=active)
2175
2339
  return qs.products()
2176
2340
 
2177
- def create_item_product(self,
2178
- name: str,
2179
- item_type: str,
2180
- uom_model: Union[UUID, UnitOfMeasureModel],
2181
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2182
- commit: bool = True) -> ItemModel:
2341
+ def create_item_product(
2342
+ self,
2343
+ name: str,
2344
+ item_type: str,
2345
+ uom_model: Union[UUID, UnitOfMeasureModel],
2346
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2347
+ commit: bool = True,
2348
+ ) -> ItemModel:
2183
2349
  """
2184
2350
  Creates a new items of type PRODUCT.
2185
2351
 
@@ -2201,18 +2367,23 @@ class EntityModelAbstract(MP_Node,
2201
2367
  The created Product.
2202
2368
  """
2203
2369
  if isinstance(uom_model, UUID):
2204
- uom_model = self.unitofmeasuremodel_set.select_related('entity').get(uuid__exact=uom_model)
2370
+ uom_model = self.unitofmeasuremodel_set.select_related('entity').get(
2371
+ uuid__exact=uom_model
2372
+ )
2205
2373
  elif isinstance(uom_model, UnitOfMeasureModel):
2206
2374
  if uom_model.entity_id != self.uuid:
2207
- raise EntityModelValidationError(f'Invalid UnitOfMeasureModel for entity {self.slug}...')
2375
+ raise EntityModelValidationError(
2376
+ f'Invalid UnitOfMeasureModel for entity {self.slug}...'
2377
+ )
2208
2378
 
2209
2379
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
2210
2380
  account_model_qs = account_model_qs.with_roles(
2211
2381
  roles=[
2212
2382
  roles_module.ASSET_CA_INVENTORY,
2213
2383
  roles_module.COGS,
2214
- roles_module.INCOME_OPERATIONAL
2215
- ]).is_role_default()
2384
+ roles_module.INCOME_OPERATIONAL,
2385
+ ]
2386
+ ).is_role_default()
2216
2387
 
2217
2388
  # evaluates the queryset...
2218
2389
  len(account_model_qs)
@@ -2223,9 +2394,13 @@ class EntityModelAbstract(MP_Node,
2223
2394
  uom=uom_model,
2224
2395
  item_role=ItemModel.ITEM_ROLE_PRODUCT,
2225
2396
  item_type=item_type,
2226
- inventory_account=account_model_qs.filter(role=roles_module.ASSET_CA_INVENTORY).get(),
2227
- earnings_account=account_model_qs.filter(role=roles_module.INCOME_OPERATIONAL).get(),
2228
- cogs_account=account_model_qs.filter(role=roles_module.COGS).get()
2397
+ inventory_account=account_model_qs.filter(
2398
+ role=roles_module.ASSET_CA_INVENTORY
2399
+ ).get(),
2400
+ earnings_account=account_model_qs.filter(
2401
+ role=roles_module.INCOME_OPERATIONAL
2402
+ ).get(),
2403
+ cogs_account=account_model_qs.filter(role=roles_module.COGS).get(),
2229
2404
  )
2230
2405
  product_model.clean()
2231
2406
  product_model.clean_fields()
@@ -2251,11 +2426,13 @@ class EntityModelAbstract(MP_Node,
2251
2426
  qs = self.get_items_all(active=active)
2252
2427
  return qs.services()
2253
2428
 
2254
- def create_item_service(self,
2255
- name: str,
2256
- uom_model: Union[UUID, UnitOfMeasureModel],
2257
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2258
- commit: bool = True) -> ItemModel:
2429
+ def create_item_service(
2430
+ self,
2431
+ name: str,
2432
+ uom_model: Union[UUID, UnitOfMeasureModel],
2433
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2434
+ commit: bool = True,
2435
+ ) -> ItemModel:
2259
2436
  """
2260
2437
  Creates a new items of type SERVICE.
2261
2438
 
@@ -2277,17 +2454,19 @@ class EntityModelAbstract(MP_Node,
2277
2454
  """
2278
2455
 
2279
2456
  if isinstance(uom_model, UUID):
2280
- uom_model = self.unitofmeasuremodel_set.select_related('entity').get(uuid__exact=uom_model)
2457
+ uom_model = self.unitofmeasuremodel_set.select_related('entity').get(
2458
+ uuid__exact=uom_model
2459
+ )
2281
2460
  elif isinstance(uom_model, UnitOfMeasureModel):
2282
2461
  if uom_model.entity_id != self.uuid:
2283
- raise EntityModelValidationError(f'Invalid UnitOfMeasureModel for entity {self.slug}...')
2462
+ raise EntityModelValidationError(
2463
+ f'Invalid UnitOfMeasureModel for entity {self.slug}...'
2464
+ )
2284
2465
 
2285
2466
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
2286
2467
  account_model_qs = account_model_qs.with_roles(
2287
- roles=[
2288
- roles_module.COGS,
2289
- roles_module.INCOME_OPERATIONAL
2290
- ]).is_role_default()
2468
+ roles=[roles_module.COGS, roles_module.INCOME_OPERATIONAL]
2469
+ ).is_role_default()
2291
2470
 
2292
2471
  # evaluates the queryset...
2293
2472
  len(account_model_qs)
@@ -2298,8 +2477,10 @@ class EntityModelAbstract(MP_Node,
2298
2477
  uom=uom_model,
2299
2478
  item_role=ItemModel.ITEM_ROLE_SERVICE,
2300
2479
  item_type=ItemModel.ITEM_TYPE_LABOR,
2301
- earnings_account=account_model_qs.filter(role=roles_module.INCOME_OPERATIONAL).get(),
2302
- cogs_account=account_model_qs.filter(role=roles_module.COGS).get()
2480
+ earnings_account=account_model_qs.filter(
2481
+ role=roles_module.INCOME_OPERATIONAL
2482
+ ).get(),
2483
+ cogs_account=account_model_qs.filter(role=roles_module.COGS).get(),
2303
2484
  )
2304
2485
  service_model.clean()
2305
2486
  service_model.clean_fields()
@@ -2325,14 +2506,15 @@ class EntityModelAbstract(MP_Node,
2325
2506
  qs = self.get_items_all(active=active)
2326
2507
  return qs.expenses()
2327
2508
 
2328
- def create_item_expense(self,
2329
- name: str,
2330
- expense_type: str,
2331
- uom_model: Union[UUID, UnitOfMeasureModel],
2332
- expense_account: Optional[Union[UUID, AccountModel]] = None,
2333
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2334
- commit: bool = True) -> ItemModel:
2335
-
2509
+ def create_item_expense(
2510
+ self,
2511
+ name: str,
2512
+ expense_type: str,
2513
+ uom_model: Union[UUID, UnitOfMeasureModel],
2514
+ expense_account: Optional[Union[UUID, AccountModel]] = None,
2515
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2516
+ commit: bool = True,
2517
+ ) -> ItemModel:
2336
2518
  """
2337
2519
  Creates a new items of type EXPENSE.
2338
2520
 
@@ -2357,10 +2539,14 @@ class EntityModelAbstract(MP_Node,
2357
2539
  ItemModel
2358
2540
  """
2359
2541
  if isinstance(uom_model, UUID):
2360
- uom_model = self.unitofmeasuremodel_set.select_related('entity').get(uuid__exact=uom_model)
2542
+ uom_model = self.unitofmeasuremodel_set.select_related('entity').get(
2543
+ uuid__exact=uom_model
2544
+ )
2361
2545
  elif isinstance(uom_model, UnitOfMeasureModel):
2362
2546
  if uom_model.entity_id != self.uuid:
2363
- raise EntityModelValidationError(f'Invalid UnitOfMeasureModel for entity {self.slug}...')
2547
+ raise EntityModelValidationError(
2548
+ f'Invalid UnitOfMeasureModel for entity {self.slug}...'
2549
+ )
2364
2550
 
2365
2551
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
2366
2552
  account_model_qs = account_model_qs.with_roles(
@@ -2372,7 +2558,9 @@ class EntityModelAbstract(MP_Node,
2372
2558
  expense_account = account_model_qs.get(uuid__exact=expense_account)
2373
2559
  elif isinstance(expense_account, AccountModel):
2374
2560
  if expense_account.coa_model.entity_id != self.uuid:
2375
- raise EntityModelValidationError(f'Invalid account for entity {self.slug}...')
2561
+ raise EntityModelValidationError(
2562
+ f'Invalid account for entity {self.slug}...'
2563
+ )
2376
2564
 
2377
2565
  expense_item_model = ItemModel(
2378
2566
  entity=self,
@@ -2380,7 +2568,7 @@ class EntityModelAbstract(MP_Node,
2380
2568
  uom=uom_model,
2381
2569
  item_role=ItemModel.ITEM_ROLE_EXPENSE,
2382
2570
  item_type=expense_type,
2383
- expense_account=expense_account
2571
+ expense_account=expense_account,
2384
2572
  )
2385
2573
  expense_item_model.clean()
2386
2574
  expense_item_model.clean_fields()
@@ -2426,13 +2614,15 @@ class EntityModelAbstract(MP_Node,
2426
2614
  qs = self.get_items_all(active=active)
2427
2615
  return qs.inventory_wip()
2428
2616
 
2429
- def create_item_inventory(self,
2430
- name: str,
2431
- uom_model: Union[UUID, UnitOfMeasureModel],
2432
- item_type: str,
2433
- inventory_account: Optional[Union[UUID, AccountModel]] = None,
2434
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2435
- commit: bool = True):
2617
+ def create_item_inventory(
2618
+ self,
2619
+ name: str,
2620
+ uom_model: Union[UUID, UnitOfMeasureModel],
2621
+ item_type: str,
2622
+ inventory_account: Optional[Union[UUID, AccountModel]] = None,
2623
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2624
+ commit: bool = True,
2625
+ ):
2436
2626
  """
2437
2627
  Creates a new items of type INVENTORY.
2438
2628
 
@@ -2458,10 +2648,14 @@ class EntityModelAbstract(MP_Node,
2458
2648
  ItemModel
2459
2649
  """
2460
2650
  if isinstance(uom_model, UUID):
2461
- uom_model = self.unitofmeasuremodel_set.select_related('entity').get(uuid__exact=uom_model)
2651
+ uom_model = self.unitofmeasuremodel_set.select_related('entity').get(
2652
+ uuid__exact=uom_model
2653
+ )
2462
2654
  elif isinstance(uom_model, UnitOfMeasureModel):
2463
2655
  if uom_model.entity_id != self.uuid:
2464
- raise EntityModelValidationError(f'Invalid UnitOfMeasureModel for entity {self.slug}...')
2656
+ raise EntityModelValidationError(
2657
+ f'Invalid UnitOfMeasureModel for entity {self.slug}...'
2658
+ )
2465
2659
 
2466
2660
  account_model_qs = self.get_coa_accounts(coa_model=coa_model, active=True)
2467
2661
  account_model_qs = account_model_qs.with_roles(
@@ -2473,9 +2667,13 @@ class EntityModelAbstract(MP_Node,
2473
2667
  inventory_account = account_model_qs.get(uuid__exact=inventory_account)
2474
2668
  elif isinstance(inventory_account, AccountModel):
2475
2669
  if inventory_account.coa_model.entity_id != self.uuid:
2476
- raise EntityModelValidationError(f'Invalid account for entity {self.slug}...')
2670
+ raise EntityModelValidationError(
2671
+ f'Invalid account for entity {self.slug}...'
2672
+ )
2477
2673
  elif inventory_account.coa_model_id != coa_model.uuid:
2478
- raise EntityModelValidationError(f'Invalid account for coa {coa_model.slug}...')
2674
+ raise EntityModelValidationError(
2675
+ f'Invalid account for coa {coa_model.slug}...'
2676
+ )
2479
2677
 
2480
2678
  inventory_item_model = ItemModel(
2481
2679
  name=name,
@@ -2483,7 +2681,7 @@ class EntityModelAbstract(MP_Node,
2483
2681
  entity=self,
2484
2682
  item_type=item_type,
2485
2683
  item_role=ItemModel.ITEM_ROLE_INVENTORY,
2486
- inventory_account=inventory_account
2684
+ inventory_account=inventory_account,
2487
2685
  )
2488
2686
  inventory_item_model.clean()
2489
2687
  inventory_item_model.clean_fields()
@@ -2523,42 +2721,49 @@ class EntityModelAbstract(MP_Node,
2523
2721
  'count': i['quantity_onhand'],
2524
2722
  'value': i['value_onhand'],
2525
2723
  'avg_cost': i['cost_average']
2526
- if i['quantity_onhand'] else Decimal('0.00')
2527
- } for i in counted_qs
2724
+ if i['quantity_onhand']
2725
+ else Decimal('0.00'),
2726
+ }
2727
+ for i in counted_qs
2528
2728
  }
2529
2729
  recorded_map = {
2530
2730
  (i['uuid'], i['name'], i['uom__name']): {
2531
2731
  'count': i['inventory_received'] or Decimal.from_float(0.0),
2532
2732
  'value': i['inventory_received_value'] or Decimal.from_float(0.0),
2533
2733
  'avg_cost': i['inventory_received_value'] / i['inventory_received']
2534
- if i['inventory_received'] else Decimal('0.00')
2535
- } for i in recorded_qs
2734
+ if i['inventory_received']
2735
+ else Decimal('0.00'),
2736
+ }
2737
+ for i in recorded_qs
2536
2738
  }
2537
2739
 
2538
2740
  # todo: change this to use a groupby then sum...
2539
2741
  item_ids = list(set(list(counted_map.keys()) + list(recorded_map)))
2540
- adjustment = defaultdict(lambda: {
2541
- # keeps track of inventory recounts...
2542
- 'counted': Decimal('0.000'),
2543
- 'counted_value': Decimal('0.00'),
2544
- 'counted_avg_cost': Decimal('0.00'),
2545
-
2546
- # keeps track of inventory level...
2547
- 'recorded': Decimal('0.000'),
2548
- 'recorded_value': Decimal('0.00'),
2549
- 'recorded_avg_cost': Decimal('0.00'),
2550
-
2551
- # keeps track of necessary inventory adjustment...
2552
- 'count_diff': Decimal('0.000'),
2553
- 'value_diff': Decimal('0.00'),
2554
- 'avg_cost_diff': Decimal('0.00')
2555
- })
2742
+ adjustment = defaultdict(
2743
+ lambda: {
2744
+ # keeps track of inventory recounts...
2745
+ 'counted': Decimal('0.000'),
2746
+ 'counted_value': Decimal('0.00'),
2747
+ 'counted_avg_cost': Decimal('0.00'),
2748
+ # keeps track of inventory level...
2749
+ 'recorded': Decimal('0.000'),
2750
+ 'recorded_value': Decimal('0.00'),
2751
+ 'recorded_avg_cost': Decimal('0.00'),
2752
+ # keeps track of necessary inventory adjustment...
2753
+ 'count_diff': Decimal('0.000'),
2754
+ 'value_diff': Decimal('0.00'),
2755
+ 'avg_cost_diff': Decimal('0.00'),
2756
+ }
2757
+ )
2556
2758
 
2557
2759
  for uid in item_ids:
2558
-
2559
2760
  count_data = counted_map.get(uid)
2560
2761
  if count_data:
2561
- avg_cost = count_data['value'] / count_data['count'] if count_data['count'] else Decimal('0.000')
2762
+ avg_cost = (
2763
+ count_data['value'] / count_data['count']
2764
+ if count_data['count']
2765
+ else Decimal('0.000')
2766
+ )
2562
2767
 
2563
2768
  adjustment[uid]['counted'] = count_data['count']
2564
2769
  adjustment[uid]['counted_value'] = count_data['value']
@@ -2571,7 +2776,11 @@ class EntityModelAbstract(MP_Node,
2571
2776
  recorded_data = recorded_map.get(uid)
2572
2777
  if recorded_data:
2573
2778
  counted = recorded_data['count']
2574
- avg_cost = recorded_data['value'] / counted if recorded_data['count'] else Decimal('0.000')
2779
+ avg_cost = (
2780
+ recorded_data['value'] / counted
2781
+ if recorded_data['count']
2782
+ else Decimal('0.000')
2783
+ )
2575
2784
 
2576
2785
  adjustment[uid]['recorded'] = counted
2577
2786
  adjustment[uid]['recorded_value'] = recorded_data['value']
@@ -2582,8 +2791,9 @@ class EntityModelAbstract(MP_Node,
2582
2791
  adjustment[uid]['avg_cost_diff'] -= avg_cost
2583
2792
  return adjustment
2584
2793
 
2585
- def update_inventory(self,
2586
- commit: bool = False) -> Tuple[defaultdict, ItemTransactionModelQuerySet, ItemModelQuerySet]:
2794
+ def update_inventory(
2795
+ self, commit: bool = False
2796
+ ) -> Tuple[defaultdict, ItemTransactionModelQuerySet, ItemModelQuerySet]:
2587
2797
  """
2588
2798
  Triggers an inventory recount with optional commitment of transaction.
2589
2799
 
@@ -2603,9 +2813,13 @@ class EntityModelAbstract(MP_Node,
2603
2813
  ItemTransactionModel = lazy_loader.get_item_transaction_model()
2604
2814
  ItemModel = lazy_loader.get_item_model()
2605
2815
 
2606
- counted_qs: ItemTransactionModelQuerySet = ItemTransactionModel.objects.inventory_count(entity_slug=self.slug)
2816
+ counted_qs: ItemTransactionModelQuerySet = (
2817
+ ItemTransactionModel.objects.inventory_count(entity_model=self.slug)
2818
+ )
2607
2819
  recorded_qs: ItemModelQuerySet = self.recorded_inventory(as_values=False)
2608
- recorded_qs_values = self.recorded_inventory(item_qs=recorded_qs, as_values=True)
2820
+ recorded_qs_values = self.recorded_inventory(
2821
+ item_qs=recorded_qs, as_values=True
2822
+ )
2609
2823
 
2610
2824
  adj = self.inventory_adjustment(counted_qs, recorded_qs_values)
2611
2825
 
@@ -2618,18 +2832,16 @@ class EntityModelAbstract(MP_Node,
2618
2832
  updated_items.append(item_model)
2619
2833
 
2620
2834
  if commit:
2621
- ItemModel.objects.bulk_update(updated_items,
2622
- fields=[
2623
- 'inventory_received',
2624
- 'inventory_received_value',
2625
- 'updated'
2626
- ])
2835
+ ItemModel.objects.bulk_update(
2836
+ updated_items,
2837
+ fields=['inventory_received', 'inventory_received_value', 'updated'],
2838
+ )
2627
2839
 
2628
2840
  return adj, counted_qs, recorded_qs
2629
2841
 
2630
- def recorded_inventory(self,
2631
- item_qs: Optional[ItemModelQuerySet] = None,
2632
- as_values: bool = True) -> ItemModelQuerySet:
2842
+ def recorded_inventory(
2843
+ self, item_qs: Optional[ItemModelQuerySet] = None, as_values: bool = True
2844
+ ) -> ItemModelQuerySet:
2633
2845
  """
2634
2846
  Recorded inventory on the books marked as received. PurchaseOrderModel drives the ordering and receiving of
2635
2847
  inventory. Once inventory is marked as "received" recorded inventory of each item is updated by calling
@@ -2658,21 +2870,27 @@ class EntityModelAbstract(MP_Node,
2658
2870
  recorded_qs = item_qs
2659
2871
  if as_values:
2660
2872
  return recorded_qs.values(
2661
- 'uuid', 'name', 'uom__name', 'inventory_received', 'inventory_received_value')
2873
+ 'uuid',
2874
+ 'name',
2875
+ 'uom__name',
2876
+ 'inventory_received',
2877
+ 'inventory_received_value',
2878
+ )
2662
2879
  return recorded_qs
2663
2880
 
2664
2881
  # COMMON TRANSACTIONS...
2665
- def deposit_capital(self,
2666
- amount: Union[Decimal, float],
2667
- cash_account: Optional[Union[AccountModel, BankAccountModel]] = None,
2668
- capital_account: Optional[AccountModel] = None,
2669
- description: Optional[str] = None,
2670
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2671
- ledger_model: Optional[Union[LedgerModel, UUID]] = None,
2672
- ledger_posted: bool = False,
2673
- je_timestamp: Optional[Union[datetime, date, str]] = None,
2674
- je_posted: bool = False):
2675
-
2882
+ def deposit_capital(
2883
+ self,
2884
+ amount: Union[Decimal, float],
2885
+ cash_account: Optional[Union[AccountModel, BankAccountModel]] = None,
2886
+ capital_account: Optional[AccountModel] = None,
2887
+ description: Optional[str] = None,
2888
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2889
+ ledger_model: Optional[Union[LedgerModel, UUID]] = None,
2890
+ ledger_posted: bool = False,
2891
+ je_timestamp: Optional[Union[datetime, date, str]] = None,
2892
+ je_posted: bool = False,
2893
+ ):
2676
2894
  if coa_model:
2677
2895
  self.validate_chart_of_accounts_for_entity(coa_model)
2678
2896
  else:
@@ -2697,16 +2915,28 @@ class EntityModelAbstract(MP_Node,
2697
2915
  if cash_account:
2698
2916
  if isinstance(cash_account, BankAccountModel):
2699
2917
  cash_account = cash_account.account_model
2700
- self.validate_account_model_for_coa(account_model=cash_account, coa_model=coa_model)
2701
- self.validate_account_model_for_role(cash_account, roles_module.ASSET_CA_CASH)
2918
+ self.validate_account_model_for_coa(
2919
+ account_model=cash_account, coa_model=coa_model
2920
+ )
2921
+ self.validate_account_model_for_role(
2922
+ cash_account, roles_module.ASSET_CA_CASH
2923
+ )
2702
2924
  else:
2703
- cash_account = account_model_qs.filter(role__exact=roles_module.ASSET_CA_CASH).get()
2925
+ cash_account = account_model_qs.filter(
2926
+ role__exact=roles_module.ASSET_CA_CASH
2927
+ ).get()
2704
2928
 
2705
2929
  if capital_account:
2706
- self.validate_account_model_for_coa(account_model=capital_account, coa_model=coa_model)
2707
- self.validate_account_model_for_role(capital_account, roles_module.EQUITY_CAPITAL)
2930
+ self.validate_account_model_for_coa(
2931
+ account_model=capital_account, coa_model=coa_model
2932
+ )
2933
+ self.validate_account_model_for_role(
2934
+ capital_account, roles_module.EQUITY_CAPITAL
2935
+ )
2708
2936
  else:
2709
- capital_account = account_model_qs.filter(role__exact=roles_module.EQUITY_CAPITAL).get()
2937
+ capital_account = account_model_qs.filter(
2938
+ role__exact=roles_module.EQUITY_CAPITAL
2939
+ ).get()
2710
2940
 
2711
2941
  if not je_timestamp:
2712
2942
  je_timestamp = get_localtime()
@@ -2715,36 +2945,42 @@ class EntityModelAbstract(MP_Node,
2715
2945
  description = f'Capital Deposit on {je_timestamp.isoformat()}...'
2716
2946
 
2717
2947
  txs = list()
2718
- txs.append({
2719
- 'account': cash_account,
2720
- 'tx_type': DEBIT,
2721
- 'amount': amount,
2722
- 'description': description
2723
- })
2724
- txs.append({
2725
- 'account': capital_account,
2726
- 'tx_type': CREDIT,
2727
- 'amount': amount,
2728
- 'description': description
2729
- })
2948
+ txs.append(
2949
+ {
2950
+ 'account': cash_account,
2951
+ 'tx_type': DEBIT,
2952
+ 'amount': amount,
2953
+ 'description': description,
2954
+ }
2955
+ )
2956
+ txs.append(
2957
+ {
2958
+ 'account': capital_account,
2959
+ 'tx_type': CREDIT,
2960
+ 'amount': amount,
2961
+ 'description': description,
2962
+ }
2963
+ )
2730
2964
 
2731
2965
  if not ledger_model:
2732
2966
  ledger_model = self.ledgermodel_set.create(
2733
2967
  name=f'Capital Deposit on {je_timestamp.isoformat()}.',
2734
- posted=ledger_posted
2968
+ posted=ledger_posted,
2735
2969
  )
2736
2970
  else:
2737
2971
  if isinstance(ledger_model, LedgerModel):
2738
2972
  self.validate_ledger_model_for_entity(ledger_model)
2739
2973
  else:
2740
- ledger_model_qs = LedgerModel.objects.filter(entity__uuid__exact=self.uuid)
2974
+ ledger_model_qs = LedgerModel.objects.filter(
2975
+ entity__uuid__exact=self.uuid
2976
+ )
2741
2977
  ledger_model = ledger_model_qs.get(uuid__exact=ledger_model)
2742
2978
 
2743
2979
  self.commit_txs(
2744
2980
  je_timestamp=je_timestamp,
2745
2981
  je_txs=txs,
2746
2982
  je_posted=je_posted,
2747
- je_ledger_model=ledger_model
2983
+ je_ledger_model=ledger_model,
2748
2984
  )
2749
2985
 
2750
2986
  return ledger_model
@@ -2756,14 +2992,22 @@ class EntityModelAbstract(MP_Node,
2756
2992
  def get_closing_entries(self):
2757
2993
  return self.closingentrymodel_set.all()
2758
2994
 
2759
- def get_closing_entry_dates_list_meta(self, as_iso: bool = True) -> List[Union[date, str]]:
2995
+ def get_closing_entry_dates_list_meta(
2996
+ self, as_iso: bool = True
2997
+ ) -> List[Union[date, str]]:
2760
2998
  date_list = self.meta[self.META_KEY_CLOSING_ENTRY_DATES]
2761
2999
  if as_iso:
2762
3000
  return date_list
2763
3001
  return [date.fromisoformat(d) for d in date_list]
2764
3002
 
2765
- def compute_closing_entry_dates_list(self, as_iso: bool = True) -> List[Union[date, str]]:
2766
- closing_entry_qs = self.closingentrymodel_set.order_by('-closing_date').only('closing_date').posted()
3003
+ def compute_closing_entry_dates_list(
3004
+ self, as_iso: bool = True
3005
+ ) -> List[Union[date, str]]:
3006
+ closing_entry_qs = (
3007
+ self.closingentrymodel_set.order_by('-closing_date')
3008
+ .only('closing_date')
3009
+ .posted()
3010
+ )
2767
3011
  if as_iso:
2768
3012
  return [ce.closing_date.isoformat() for ce in closing_entry_qs]
2769
3013
  return [ce.closing_date for ce in closing_entry_qs]
@@ -2776,14 +3020,11 @@ class EntityModelAbstract(MP_Node,
2776
3020
  except IndexError:
2777
3021
  self.last_closing_date = None
2778
3022
 
2779
- self.meta[self.META_KEY_CLOSING_ENTRY_DATES] = [d.isoformat() for d in date_list]
3023
+ self.meta[self.META_KEY_CLOSING_ENTRY_DATES] = [
3024
+ d.isoformat() for d in date_list
3025
+ ]
2780
3026
  if commit:
2781
- self.save(
2782
- update_fields=[
2783
- 'last_closing_date',
2784
- 'updated',
2785
- 'meta'
2786
- ])
3027
+ self.save(update_fields=['last_closing_date', 'updated', 'meta'])
2787
3028
  return date_list
2788
3029
 
2789
3030
  def fetch_closing_entry_dates_meta(self, as_date: bool = True) -> List[date]:
@@ -2796,7 +3037,9 @@ class EntityModelAbstract(MP_Node,
2796
3037
  return self._CLOSING_ENTRY_DATES
2797
3038
  return date_list
2798
3039
 
2799
- def get_closing_entry_for_date(self, io_date: Union[date, datetime], inclusive: bool = True) -> Optional[date]:
3040
+ def get_closing_entry_for_date(
3041
+ self, io_date: Union[date, datetime], inclusive: bool = True
3042
+ ) -> Optional[date]:
2800
3043
  if io_date is None:
2801
3044
  return
2802
3045
  ce_date_list = self.fetch_closing_entry_dates_meta()
@@ -2811,7 +3054,9 @@ class EntityModelAbstract(MP_Node,
2811
3054
  if ce_lookup in ce_date_list:
2812
3055
  return ce_lookup
2813
3056
 
2814
- def get_nearest_next_closing_entry(self, io_date: Union[date, datetime]) -> Optional[date]:
3057
+ def get_nearest_next_closing_entry(
3058
+ self, io_date: Union[date, datetime]
3059
+ ) -> Optional[date]:
2815
3060
  if io_date is None:
2816
3061
  return
2817
3062
 
@@ -2819,10 +3064,12 @@ class EntityModelAbstract(MP_Node,
2819
3064
  if not len(ce_date_list):
2820
3065
  return
2821
3066
 
2822
- if all([
2823
- isinstance(io_date, date),
2824
- isinstance(io_date, datetime),
2825
- ]):
3067
+ if all(
3068
+ [
3069
+ isinstance(io_date, date),
3070
+ isinstance(io_date, datetime),
3071
+ ]
3072
+ ):
2826
3073
  io_date = io_date.date()
2827
3074
 
2828
3075
  if io_date > ce_date_list[0]:
@@ -2832,35 +3079,42 @@ class EntityModelAbstract(MP_Node,
2832
3079
  if p and p <= io_date < f:
2833
3080
  return p
2834
3081
 
2835
- def close_entity_books(self,
2836
- closing_date: Optional[date] = None,
2837
- closing_entry_model=None,
2838
- force_update: bool = False,
2839
- post_closing_entry: bool = True):
2840
-
3082
+ def close_entity_books(
3083
+ self,
3084
+ closing_date: Optional[date] = None,
3085
+ closing_entry_model=None,
3086
+ force_update: bool = False,
3087
+ post_closing_entry: bool = True,
3088
+ ):
2841
3089
  if closing_entry_model and closing_date:
2842
3090
  raise EntityModelValidationError(
2843
- message=_('Closing books must be called by providing closing_date or closing_entry_model, not both.')
3091
+ message=_(
3092
+ 'Closing books must be called by providing closing_date or closing_entry_model, not both.'
3093
+ )
2844
3094
  )
2845
3095
  elif not closing_date and not closing_entry_model:
2846
3096
  raise EntityModelValidationError(
2847
- message=_('Closing books must be called by providing closing_date or closing_entry_model.')
3097
+ message=_(
3098
+ 'Closing books must be called by providing closing_date or closing_entry_model.'
3099
+ )
2848
3100
  )
2849
3101
 
2850
3102
  closing_entry_exists = False
2851
3103
 
2852
3104
  if closing_entry_model:
2853
3105
  closing_date = closing_entry_model.closing_date
2854
- self.validate_closing_entry_model(closing_entry_model, closing_date=closing_date)
3106
+ self.validate_closing_entry_model(
3107
+ closing_entry_model, closing_date=closing_date
3108
+ )
2855
3109
  closing_entry_exists = True
2856
3110
  else:
2857
3111
  try:
2858
- closing_entry_model = self.closingentrymodel_set.select_related(
2859
- 'ledger_model',
2860
- 'ledger_model__entity'
2861
- ).defer(
2862
- 'markdown_notes').get(
2863
- closing_date__exact=closing_date
3112
+ closing_entry_model = (
3113
+ self.closingentrymodel_set.select_related(
3114
+ 'ledger_model', 'ledger_model__entity'
3115
+ )
3116
+ .defer('markdown_notes')
3117
+ .get(closing_date__exact=closing_date)
2864
3118
  )
2865
3119
 
2866
3120
  closing_entry_exists = True
@@ -2879,27 +3133,44 @@ class EntityModelAbstract(MP_Node,
2879
3133
  self.save_closing_entry_dates_meta(commit=True)
2880
3134
 
2881
3135
  return closing_entry_model, ce_txs
2882
- raise EntityModelValidationError(message=f'Closing Entry for Period {closing_date} already exists.')
3136
+ raise EntityModelValidationError(
3137
+ message=f'Closing Entry for Period {closing_date} already exists.'
3138
+ )
2883
3139
 
2884
- def close_books_for_month(self, year: int, month: int, force_update: bool = False, post_closing_entry: bool = True):
3140
+ def close_books_for_month(
3141
+ self,
3142
+ year: int,
3143
+ month: int,
3144
+ force_update: bool = False,
3145
+ post_closing_entry: bool = True,
3146
+ ):
2885
3147
  _, day = monthrange(year, month)
2886
3148
  closing_dt = date(year, month, day)
2887
3149
  return self.close_entity_books(
2888
3150
  closing_date=closing_dt,
2889
3151
  force_update=force_update,
2890
3152
  post_closing_entry=post_closing_entry,
2891
- closing_entry_model=None
3153
+ closing_entry_model=None,
2892
3154
  )
2893
3155
 
2894
- def close_books_for_fiscal_year(self, fiscal_year: int, force_update: bool = False,
2895
- post_closing_entry: bool = True):
3156
+ def close_books_for_fiscal_year(
3157
+ self,
3158
+ fiscal_year: int,
3159
+ force_update: bool = False,
3160
+ post_closing_entry: bool = True,
3161
+ ):
2896
3162
  closing_dt = self.get_fy_end(year=fiscal_year)
2897
- return self.close_entity_books(closing_date=closing_dt, force_update=force_update,
2898
- post_closing_entry=post_closing_entry)
3163
+ return self.close_entity_books(
3164
+ closing_date=closing_dt,
3165
+ force_update=force_update,
3166
+ post_closing_entry=post_closing_entry,
3167
+ )
2899
3168
 
2900
3169
  # ### RANDOM DATA GENERATION ####
2901
3170
 
2902
- def populate_random_data(self, start_date: date, days_forward=180, tx_quantity: int = 25):
3171
+ def populate_random_data(
3172
+ self, start_date: date, days_forward=180, tx_quantity: int = 25
3173
+ ):
2903
3174
  EntityDataGenerator = lazy_loader.get_entity_data_generator()
2904
3175
  data_generator = EntityDataGenerator(
2905
3176
  user_model=self.admin,
@@ -2907,16 +3178,15 @@ class EntityModelAbstract(MP_Node,
2907
3178
  start_dttm=start_date,
2908
3179
  entity_model=self,
2909
3180
  capital_contribution=Decimal.from_float(50000.00),
2910
- tx_quantity=tx_quantity
3181
+ tx_quantity=tx_quantity,
2911
3182
  )
2912
3183
  data_generator.populate_entity()
2913
3184
 
2914
3185
  # URLS ----
2915
3186
  def get_absolute_url(self):
2916
- return reverse(viewname='django_ledger:entity-dashboard',
2917
- kwargs={
2918
- 'entity_slug': self.slug
2919
- })
3187
+ return reverse(
3188
+ viewname='django_ledger:entity-dashboard', kwargs={'entity_slug': self.slug}
3189
+ )
2920
3190
 
2921
3191
  def get_dashboard_url(self) -> str:
2922
3192
  """
@@ -2927,10 +3197,9 @@ class EntityModelAbstract(MP_Node,
2927
3197
  str
2928
3198
  EntityModel dashboard URL as a string.
2929
3199
  """
2930
- return reverse('django_ledger:entity-dashboard',
2931
- kwargs={
2932
- 'entity_slug': self.slug
2933
- })
3200
+ return reverse(
3201
+ 'django_ledger:entity-dashboard', kwargs={'entity_slug': self.slug}
3202
+ )
2934
3203
 
2935
3204
  def get_manage_url(self) -> str:
2936
3205
  """
@@ -2941,10 +3210,7 @@ class EntityModelAbstract(MP_Node,
2941
3210
  str
2942
3211
  EntityModel manage URL as a string.
2943
3212
  """
2944
- return reverse('django_ledger:entity-update',
2945
- kwargs={
2946
- 'entity_slug': self.slug
2947
- })
3213
+ return reverse('django_ledger:entity-update', kwargs={'entity_slug': self.slug})
2948
3214
 
2949
3215
  def get_ledgers_url(self) -> str:
2950
3216
  """
@@ -2955,10 +3221,7 @@ class EntityModelAbstract(MP_Node,
2955
3221
  str
2956
3222
  EntityModel ledger list URL as a string.
2957
3223
  """
2958
- return reverse('django_ledger:ledger-list',
2959
- kwargs={
2960
- 'entity_slug': self.slug
2961
- })
3224
+ return reverse('django_ledger:ledger-list', kwargs={'entity_slug': self.slug})
2962
3225
 
2963
3226
  def get_bills_url(self) -> str:
2964
3227
  """
@@ -2969,10 +3232,7 @@ class EntityModelAbstract(MP_Node,
2969
3232
  str
2970
3233
  EntityModel bill list URL as a string.
2971
3234
  """
2972
- return reverse('django_ledger:bill-list',
2973
- kwargs={
2974
- 'entity_slug': self.slug
2975
- })
3235
+ return reverse('django_ledger:bill-list', kwargs={'entity_slug': self.slug})
2976
3236
 
2977
3237
  def get_invoices_url(self) -> str:
2978
3238
  """
@@ -2983,10 +3243,7 @@ class EntityModelAbstract(MP_Node,
2983
3243
  str
2984
3244
  EntityModel invoice list URL as a string.
2985
3245
  """
2986
- return reverse('django_ledger:invoice-list',
2987
- kwargs={
2988
- 'entity_slug': self.slug
2989
- })
3246
+ return reverse('django_ledger:invoice-list', kwargs={'entity_slug': self.slug})
2990
3247
 
2991
3248
  def get_banks_url(self) -> str:
2992
3249
  """
@@ -2997,10 +3254,9 @@ class EntityModelAbstract(MP_Node,
2997
3254
  str
2998
3255
  EntityModel bank account list URL as a string.
2999
3256
  """
3000
- return reverse('django_ledger:bank-account-list',
3001
- kwargs={
3002
- 'entity_slug': self.slug
3003
- })
3257
+ return reverse(
3258
+ 'django_ledger:bank-account-list', kwargs={'entity_slug': self.slug}
3259
+ )
3004
3260
 
3005
3261
  def get_balance_sheet_url(self) -> str:
3006
3262
  """
@@ -3011,10 +3267,7 @@ class EntityModelAbstract(MP_Node,
3011
3267
  str
3012
3268
  EntityModel Balance Sheet Statement URL as a string.
3013
3269
  """
3014
- return reverse('django_ledger:entity-bs',
3015
- kwargs={
3016
- 'entity_slug': self.slug
3017
- })
3270
+ return reverse('django_ledger:entity-bs', kwargs={'entity_slug': self.slug})
3018
3271
 
3019
3272
  def get_income_statement_url(self) -> str:
3020
3273
  """
@@ -3025,10 +3278,7 @@ class EntityModelAbstract(MP_Node,
3025
3278
  str
3026
3279
  EntityModel Income Statement URL as a string.
3027
3280
  """
3028
- return reverse('django_ledger:entity-ic',
3029
- kwargs={
3030
- 'entity_slug': self.slug
3031
- })
3281
+ return reverse('django_ledger:entity-ic', kwargs={'entity_slug': self.slug})
3032
3282
 
3033
3283
  def get_cashflow_statement_url(self) -> str:
3034
3284
  """
@@ -3039,10 +3289,7 @@ class EntityModelAbstract(MP_Node,
3039
3289
  str
3040
3290
  EntityModel Cashflow Statement URL as a string.
3041
3291
  """
3042
- return reverse('django_ledger:entity-cf',
3043
- kwargs={
3044
- 'entity_slug': self.slug
3045
- })
3292
+ return reverse('django_ledger:entity-cf', kwargs={'entity_slug': self.slug})
3046
3293
 
3047
3294
  def get_data_import_url(self) -> str:
3048
3295
  """
@@ -3053,33 +3300,24 @@ class EntityModelAbstract(MP_Node,
3053
3300
  str
3054
3301
  EntityModel transaction import URL as a string.
3055
3302
  """
3056
- return reverse('django_ledger:data-import-jobs-list',
3057
- kwargs={
3058
- 'entity_slug': self.slug
3059
- })
3303
+ return reverse(
3304
+ 'django_ledger:data-import-jobs-list', kwargs={'entity_slug': self.slug}
3305
+ )
3060
3306
 
3061
3307
  def get_coa_list_url(self) -> str:
3062
3308
  return reverse(
3063
- viewname='django_ledger:coa-list',
3064
- kwargs={
3065
- 'entity_slug': self.slug
3066
- }
3309
+ viewname='django_ledger:coa-list', kwargs={'entity_slug': self.slug}
3067
3310
  )
3068
3311
 
3069
3312
  def get_coa_list_inactive_url(self) -> str:
3070
3313
  return reverse(
3071
3314
  viewname='django_ledger:coa-list-inactive',
3072
- kwargs={
3073
- 'entity_slug': self.slug
3074
- }
3315
+ kwargs={'entity_slug': self.slug},
3075
3316
  )
3076
3317
 
3077
3318
  def get_coa_create_url(self) -> str:
3078
3319
  return reverse(
3079
- viewname='django_ledger:coa-create',
3080
- kwargs={
3081
- 'entity_slug': self.slug
3082
- }
3320
+ viewname='django_ledger:coa-create', kwargs={'entity_slug': self.slug}
3083
3321
  )
3084
3322
 
3085
3323
  def get_accounts_url(self) -> str:
@@ -3091,10 +3329,12 @@ class EntityModelAbstract(MP_Node,
3091
3329
  str
3092
3330
  EntityModel Code of Accounts llist import URL as a string.
3093
3331
  """
3094
- return reverse('django_ledger:account-list',
3095
- kwargs={
3096
- 'entity_slug': self.slug,
3097
- })
3332
+ return reverse(
3333
+ 'django_ledger:account-list',
3334
+ kwargs={
3335
+ 'entity_slug': self.slug,
3336
+ },
3337
+ )
3098
3338
 
3099
3339
  def get_customers_url(self) -> str:
3100
3340
  """
@@ -3105,10 +3345,12 @@ class EntityModelAbstract(MP_Node,
3105
3345
  str
3106
3346
  EntityModel customers list URL as a string.
3107
3347
  """
3108
- return reverse('django_ledger:customer-list',
3109
- kwargs={
3110
- 'entity_slug': self.slug,
3111
- })
3348
+ return reverse(
3349
+ 'django_ledger:customer-list',
3350
+ kwargs={
3351
+ 'entity_slug': self.slug,
3352
+ },
3353
+ )
3112
3354
 
3113
3355
  def get_vendors_url(self) -> str:
3114
3356
  """
@@ -3119,10 +3361,12 @@ class EntityModelAbstract(MP_Node,
3119
3361
  str
3120
3362
  EntityModel vendors list URL as a string.
3121
3363
  """
3122
- return reverse('django_ledger:vendor-list',
3123
- kwargs={
3124
- 'entity_slug': self.slug,
3125
- })
3364
+ return reverse(
3365
+ 'django_ledger:vendor-list',
3366
+ kwargs={
3367
+ 'entity_slug': self.slug,
3368
+ },
3369
+ )
3126
3370
 
3127
3371
  def get_delete_url(self) -> str:
3128
3372
  """
@@ -3133,10 +3377,7 @@ class EntityModelAbstract(MP_Node,
3133
3377
  str
3134
3378
  EntityModel delete URL as a string.
3135
3379
  """
3136
- return reverse('django_ledger:entity-delete',
3137
- kwargs={
3138
- 'entity_slug': self.slug
3139
- })
3380
+ return reverse('django_ledger:entity-delete', kwargs={'entity_slug': self.slug})
3140
3381
 
3141
3382
  def clean(self):
3142
3383
  if not self.slug:
@@ -3163,6 +3404,7 @@ class EntityStateModelAbstract(Model):
3163
3404
  KEY_VENDOR = 'vendor'
3164
3405
  KEY_CUSTOMER = 'customer'
3165
3406
  KEY_ITEM = 'item'
3407
+ KEY_RECEIPT = 'receipt'
3166
3408
 
3167
3409
  KEY_CHOICES = [
3168
3410
  (KEY_JOURNAL_ENTRY, _('Journal Entry')),
@@ -3173,38 +3415,36 @@ class EntityStateModelAbstract(Model):
3173
3415
  ]
3174
3416
 
3175
3417
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
3176
- entity_model = models.ForeignKey('django_ledger.EntityModel',
3177
- on_delete=models.CASCADE,
3178
- verbose_name=_('Entity Model'))
3179
- entity_unit = models.ForeignKey('django_ledger.EntityUnitModel',
3180
- on_delete=models.RESTRICT,
3181
- verbose_name=_('Entity Unit'),
3182
- blank=True,
3183
- null=True)
3418
+ entity_model = models.ForeignKey(
3419
+ 'django_ledger.EntityModel',
3420
+ on_delete=models.CASCADE,
3421
+ verbose_name=_('Entity Model'),
3422
+ )
3423
+ entity_unit = models.ForeignKey(
3424
+ 'django_ledger.EntityUnitModel',
3425
+ on_delete=models.RESTRICT,
3426
+ verbose_name=_('Entity Unit'),
3427
+ blank=True,
3428
+ null=True,
3429
+ )
3184
3430
  fiscal_year = models.SmallIntegerField(
3185
3431
  verbose_name=_('Fiscal Year'),
3186
3432
  validators=[MinValueValidator(limit_value=1900)],
3187
3433
  null=True,
3188
- blank=True
3434
+ blank=True,
3189
3435
  )
3190
3436
  key = models.CharField(choices=KEY_CHOICES, max_length=10)
3191
- sequence = models.BigIntegerField(default=0, validators=[MinValueValidator(limit_value=0)])
3437
+ sequence = models.BigIntegerField(
3438
+ default=0, validators=[MinValueValidator(limit_value=0)]
3439
+ )
3192
3440
 
3193
3441
  class Meta:
3194
3442
  abstract = True
3195
3443
  indexes = [
3196
3444
  models.Index(fields=['key']),
3197
- models.Index(
3198
- fields=[
3199
- 'entity_model',
3200
- 'fiscal_year',
3201
- 'entity_unit',
3202
- 'key'
3203
- ])
3204
- ]
3205
- unique_together = [
3206
- ('entity_model', 'entity_unit', 'fiscal_year', 'key')
3445
+ models.Index(fields=['entity_model', 'fiscal_year', 'entity_unit', 'key']),
3207
3446
  ]
3447
+ unique_together = [('entity_model', 'entity_unit', 'fiscal_year', 'key')]
3208
3448
 
3209
3449
  def __str__(self):
3210
3450
  return f'{self.__class__.__name__} {self.entity_model_id}: FY: {self.fiscal_year}, KEY: {self.get_key_display()}'
@@ -3224,31 +3464,38 @@ class EntityManagementModelAbstract(CreateUpdateMixIn):
3224
3464
  """
3225
3465
  Entity Management Model responsible for manager permissions to read/write.
3226
3466
  """
3467
+
3227
3468
  PERMISSIONS = [
3228
3469
  ('read', _('Read Permissions')),
3229
3470
  ('write', _('Read/Write Permissions')),
3230
- ('suspended', _('No Permissions'))
3471
+ ('suspended', _('No Permissions')),
3231
3472
  ]
3232
3473
 
3233
3474
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
3234
- entity = models.ForeignKey('django_ledger.EntityModel',
3235
- on_delete=models.CASCADE,
3236
- verbose_name=_('Entity'),
3237
- related_name='entity_permissions')
3238
- user = models.ForeignKey(UserModel,
3239
- on_delete=models.CASCADE,
3240
- verbose_name=_('Manager'),
3241
- related_name='entity_permissions')
3242
- permission_level = models.CharField(max_length=10,
3243
- default='read',
3244
- choices=PERMISSIONS,
3245
- verbose_name=_('Permission Level'))
3475
+ entity = models.ForeignKey(
3476
+ 'django_ledger.EntityModel',
3477
+ on_delete=models.CASCADE,
3478
+ verbose_name=_('Entity'),
3479
+ related_name='entity_permissions',
3480
+ )
3481
+ user = models.ForeignKey(
3482
+ UserModel,
3483
+ on_delete=models.CASCADE,
3484
+ verbose_name=_('Manager'),
3485
+ related_name='entity_permissions',
3486
+ )
3487
+ permission_level = models.CharField(
3488
+ max_length=10,
3489
+ default='read',
3490
+ choices=PERMISSIONS,
3491
+ verbose_name=_('Permission Level'),
3492
+ )
3246
3493
 
3247
3494
  class Meta:
3248
3495
  abstract = True
3249
3496
  indexes = [
3250
3497
  models.Index(fields=['entity', 'user']),
3251
- models.Index(fields=['user', 'entity'])
3498
+ models.Index(fields=['user', 'entity']),
3252
3499
  ]
3253
3500
 
3254
3501