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
@@ -6,19 +6,13 @@ This module implements the BillModel, which represents an Invoice received from
6
6
  the Vendor states the amount owed by the recipient for the purposes of supplying goods and/or services.
7
7
  In addition to tracking the bill amount, it tracks the paid and due amount.
8
8
 
9
- Examples
10
- ________
11
- >>> user_model = request.user # django UserModel
12
- >>> entity_slug = kwargs['entity_slug'] # may come from view kwargs
13
- >>> bill_model = BillModel()
14
- >>> ledger_model, bill_model = bill_model.configure(entity_slug=entity_slug, user_model=user_model)
15
- >>> bill_model.save()
16
9
  """
17
10
 
11
+ import warnings
18
12
  from datetime import date, datetime
19
13
  from decimal import Decimal
20
14
  from typing import Union, Optional, Tuple, Dict, List
21
- from uuid import uuid4
15
+ from uuid import uuid4, UUID
22
16
 
23
17
  from django.contrib.auth import get_user_model
24
18
  from django.core.exceptions import ValidationError, ObjectDoesNotExist
@@ -31,14 +25,20 @@ from django.utils.translation import gettext_lazy as _
31
25
 
32
26
  from django_ledger.io import ASSET_CA_CASH, ASSET_CA_PREPAID, LIABILITY_CL_ACC_PAYABLE
33
27
  from django_ledger.io.io_core import get_localtime, get_localdate
28
+ from django_ledger.models.deprecations import deprecated_entity_slug_behavior
34
29
  from django_ledger.models.entity import EntityModel
35
- from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel, ItemModel, ItemModelQuerySet
30
+ from django_ledger.models.items import (
31
+ ItemTransactionModelQuerySet,
32
+ ItemTransactionModel,
33
+ ItemModel,
34
+ ItemModelQuerySet,
35
+ )
36
36
  from django_ledger.models.mixins import (
37
37
  CreateUpdateMixIn,
38
38
  AccrualMixIn,
39
39
  MarkdownNotesMixIn,
40
40
  PaymentTermsMixIn,
41
- ItemizeMixIn
41
+ ItemizeMixIn,
42
42
  )
43
43
  from django_ledger.models.signals import (
44
44
  bill_status_draft,
@@ -49,7 +49,11 @@ from django_ledger.models.signals import (
49
49
  bill_status_void,
50
50
  )
51
51
  from django_ledger.models.utils import lazy_loader
52
- from django_ledger.settings import (DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_BILL_NUMBER_PREFIX)
52
+ from django_ledger.settings import (
53
+ DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
54
+ DJANGO_LEDGER_BILL_NUMBER_PREFIX,
55
+ DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR,
56
+ )
53
57
 
54
58
  UserModel = get_user_model()
55
59
 
@@ -66,6 +70,32 @@ class BillModelQuerySet(QuerySet):
66
70
  building customized reports.
67
71
  """
68
72
 
73
+ def for_user(self, user_model) -> 'BillModelQuerySet':
74
+ """
75
+ Fetches a QuerySet of BillModels that the UserModel as access to.
76
+ May include BillModels from multiple Entities.
77
+
78
+ The user has access to bills if:
79
+ 1. Is listed as Manager of Entity.
80
+ 2. Is the Admin of the Entity.
81
+
82
+ Parameters
83
+ __________
84
+ user_model
85
+ Logged in and authenticated django UserModel instance.
86
+
87
+ Returns
88
+ _______
89
+ BillModelQuerySet
90
+ Returns a BillModelQuerySet with applied filters.
91
+ """
92
+ if user_model.is_superuser:
93
+ return self
94
+ return self.filter(
95
+ Q(ledger__entity__admin=user_model)
96
+ | Q(ledger__entity__managers__in=[user_model])
97
+ )
98
+
69
99
  def draft(self):
70
100
  """
71
101
  Default status of any bill that is created.
@@ -147,8 +177,8 @@ class BillModelQuerySet(QuerySet):
147
177
  Returns a QuerySet of active bills only.
148
178
  """
149
179
  return self.filter(
150
- Q(bill_status__exact=BillModel.BILL_STATUS_APPROVED) |
151
- Q(bill_status__exact=BillModel.BILL_STATUS_PAID)
180
+ Q(bill_status__exact=BillModel.BILL_STATUS_APPROVED)
181
+ | Q(bill_status__exact=BillModel.BILL_STATUS_PAID)
152
182
  )
153
183
 
154
184
  def overdue(self):
@@ -162,7 +192,7 @@ class BillModelQuerySet(QuerySet):
162
192
  """
163
193
  return self.filter(date_due__lt=get_localdate())
164
194
 
165
- def unpaid(self):
195
+ def unpaid(self) -> 'BillModelQuerySet':
166
196
  """
167
197
  Unpaid bills are those that are approved but have not received 100% of the amount due.
168
198
  Equivalent to approved().
@@ -177,90 +207,57 @@ class BillModelQuerySet(QuerySet):
177
207
 
178
208
  class BillModelManager(Manager):
179
209
  """
180
- A custom defined BillModelManager that will act as an interface to handling the initial DB queries
210
+ A custom-defined BillModelManager that will act as an interface to handling the initial DB queries
181
211
  to the BillModel. The default "get_queryset" has been overridden to refer the custom defined
182
212
  "BillModelQuerySet".
183
213
  """
184
214
 
185
- def get_queryset(self):
186
- qs = super().get_queryset()
187
- return qs.select_related(
188
- 'ledger',
189
- 'ledger__entity'
190
- )
191
-
192
- def for_user(self, user_model) -> BillModelQuerySet:
193
- """
194
- Fetches a QuerySet of BillModels that the UserModel as access to.
195
- May include BillModels from multiple Entities.
196
-
197
- The user has access to bills if:
198
- 1. Is listed as Manager of Entity.
199
- 2. Is the Admin of the Entity.
200
-
201
- Parameters
202
- __________
203
- user_model
204
- Logged in and authenticated django UserModel instance.
205
-
206
- Examples
207
- ________
208
- >>> request_user = request.user
209
- >>> bill_model_qs = BillModel.objects.for_user(user_model=request_user)
210
-
211
- Returns
212
- _______
213
- BillModelQuerySet
214
- Returns a BillModelQuerySet with applied filters.
215
- """
216
- qs = self.get_queryset()
217
- if user_model.is_superuser:
218
- return qs
219
- return qs.filter(
220
- Q(ledger__entity__admin=user_model) |
221
- Q(ledger__entity__managers__in=[user_model])
222
- )
215
+ def get_queryset(self) -> BillModelQuerySet:
216
+ qs = BillModelQuerySet(self.model, using=self._db)
217
+ return qs.select_related('ledger', 'ledger__entity')
223
218
 
224
- def for_entity(self, entity_slug, user_model) -> BillModelQuerySet:
219
+ @deprecated_entity_slug_behavior
220
+ def for_entity(
221
+ self, entity_model: EntityModel | str | UUID = None, **kwargs
222
+ ) -> BillModelQuerySet:
225
223
  """
226
224
  Fetches a QuerySet of BillModels associated with a specific EntityModel & UserModel.
227
225
  May pass an instance of EntityModel or a String representing the EntityModel slug.
228
226
 
229
227
  Parameters
230
228
  __________
231
- entity_slug: str or EntityModel
229
+ entity_model: str or EntityModel
232
230
  The entity slug or EntityModel used for filtering the QuerySet.
233
- user_model
234
- Logged in and authenticated django UserModel instance.
235
-
236
- Examples
237
- ________
238
- >>> request_user = request.user
239
- >>> slug = kwargs['entity_slug'] # may come from request kwargs
240
- >>> bill_model_qs = BillModel.objects.for_entity(user_model=request_user, entity_slug=slug)
241
231
 
242
232
  Returns
243
233
  _______
244
234
  BillModelQuerySet
245
235
  Returns a BillModelQuerySet with applied filters.
246
236
  """
247
- qs = self.for_user(user_model)
248
- if isinstance(entity_slug, EntityModel):
249
- return qs.filter(
250
- Q(ledger__entity=entity_slug)
251
- )
252
- elif isinstance(entity_slug, str):
253
- return qs.filter(
254
- Q(ledger__entity__slug__exact=entity_slug)
237
+ qs = self.get_queryset()
238
+ if 'user_model' in kwargs:
239
+ warnings.warn(
240
+ 'user_model parameter is deprecated and will be removed in a future release. '
241
+ 'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
242
+ DeprecationWarning,
243
+ stacklevel=2,
255
244
  )
245
+ if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
246
+ qs = qs.for_user(kwargs['user_model'])
247
+
248
+ if isinstance(entity_model, EntityModel):
249
+ qs = qs.filter(ledger__entity=entity_model)
250
+ elif isinstance(entity_model, str):
251
+ qs = qs.filter(ledger__entity__slug__exact=entity_model)
252
+ elif isinstance(entity_model, UUID):
253
+ qs = qs.filter(ledger__entity_id=entity_model)
254
+ else:
255
+ raise BillModelValidationError('Must pass EntityModel, slug or UUID')
256
+ return qs
256
257
 
257
258
 
258
259
  class BillModelAbstract(
259
- AccrualMixIn,
260
- ItemizeMixIn,
261
- PaymentTermsMixIn,
262
- MarkdownNotesMixIn,
263
- CreateUpdateMixIn
260
+ AccrualMixIn, ItemizeMixIn, PaymentTermsMixIn, MarkdownNotesMixIn, CreateUpdateMixIn
264
261
  ):
265
262
  """
266
263
  This is the main abstract class which the BillModel database will inherit from.
@@ -328,6 +325,7 @@ class BillModelAbstract(
328
325
  The canceled date represents the date when the BillModel was canceled, if applicable.
329
326
  Will be null unless BillModel is canceled. Defaults to :func:`localdate <django.utils.timezone.localdate>`.
330
327
  """
328
+
331
329
  REL_NAME_PREFIX = 'bill'
332
330
  IS_DEBIT_BALANCE = False
333
331
  ALLOW_MIGRATE = True
@@ -345,7 +343,7 @@ class BillModelAbstract(
345
343
  (BILL_STATUS_APPROVED, _('Approved')),
346
344
  (BILL_STATUS_PAID, _('Paid')),
347
345
  (BILL_STATUS_CANCELED, _('Canceled')),
348
- (BILL_STATUS_VOID, _('Void'))
346
+ (BILL_STATUS_VOID, _('Void')),
349
347
  ]
350
348
  """
351
349
  The different bill status options and their representation in the Database.
@@ -358,38 +356,79 @@ class BillModelAbstract(
358
356
  on_delete=models.CASCADE,
359
357
  null=True,
360
358
  blank=True,
361
- editable=False
359
+ editable=False,
360
+ )
361
+ bill_number = models.SlugField(
362
+ max_length=20, verbose_name=_('Bill Number'), editable=False
363
+ )
364
+ bill_status = models.CharField(
365
+ max_length=10,
366
+ choices=BILL_STATUS,
367
+ default=BILL_STATUS[0][0],
368
+ verbose_name=_('Bill Status'),
369
+ )
370
+ xref = models.SlugField(
371
+ null=True, blank=True, verbose_name=_('External Reference Number')
372
+ )
373
+ vendor = models.ForeignKey(
374
+ 'django_ledger.VendorModel', on_delete=models.CASCADE, verbose_name=_('Vendor')
375
+ )
376
+
377
+ cash_account = models.ForeignKey(
378
+ 'django_ledger.AccountModel',
379
+ on_delete=models.RESTRICT,
380
+ null=True,
381
+ blank=True,
382
+ verbose_name=_('Cash Account'),
383
+ related_name=f'{REL_NAME_PREFIX}_cash_account',
384
+ )
385
+ prepaid_account = models.ForeignKey(
386
+ 'django_ledger.AccountModel',
387
+ on_delete=models.RESTRICT,
388
+ null=True,
389
+ blank=True,
390
+ verbose_name=_('Prepaid Account'),
391
+ related_name=f'{REL_NAME_PREFIX}_prepaid_account',
392
+ )
393
+ unearned_account = models.ForeignKey(
394
+ 'django_ledger.AccountModel',
395
+ on_delete=models.RESTRICT,
396
+ null=True,
397
+ blank=True,
398
+ verbose_name=_('Unearned Account'),
399
+ related_name=f'{REL_NAME_PREFIX}_unearned_account',
400
+ )
401
+
402
+ additional_info = models.JSONField(
403
+ blank=True, null=True, default=dict, verbose_name=_('Bill Additional Info')
404
+ )
405
+ bill_items = models.ManyToManyField(
406
+ 'django_ledger.ItemModel',
407
+ through='django_ledger.ItemTransactionModel',
408
+ through_fields=('bill_model', 'item_model'),
409
+ verbose_name=_('Bill Items'),
410
+ )
411
+
412
+ ce_model = models.ForeignKey(
413
+ 'django_ledger.EstimateModel',
414
+ on_delete=models.RESTRICT,
415
+ null=True,
416
+ blank=True,
417
+ verbose_name=_('Associated Customer Job/Estimate'),
362
418
  )
363
- bill_number = models.SlugField(max_length=20, verbose_name=_('Bill Number'), editable=False)
364
- bill_status = models.CharField(max_length=10,
365
- choices=BILL_STATUS,
366
- default=BILL_STATUS[0][0],
367
- verbose_name=_('Bill Status'))
368
- xref = models.SlugField(null=True, blank=True, verbose_name=_('External Reference Number'))
369
- vendor = models.ForeignKey('django_ledger.VendorModel',
370
- on_delete=models.CASCADE,
371
- verbose_name=_('Vendor'))
372
- additional_info = models.JSONField(blank=True,
373
- null=True,
374
- default=dict,
375
- verbose_name=_('Bill Additional Info'))
376
- bill_items = models.ManyToManyField('django_ledger.ItemModel',
377
- through='django_ledger.ItemTransactionModel',
378
- through_fields=('bill_model', 'item_model'),
379
- verbose_name=_('Bill Items'))
380
-
381
- ce_model = models.ForeignKey('django_ledger.EstimateModel',
382
- on_delete=models.RESTRICT,
383
- null=True,
384
- blank=True,
385
- verbose_name=_('Associated Customer Job/Estimate'))
386
419
 
387
420
  date_draft = models.DateField(null=True, blank=True, verbose_name=_('Draft Date'))
388
- date_in_review = models.DateField(null=True, blank=True, verbose_name=_('In Review Date'))
389
- date_approved = models.DateField(null=True, blank=True, verbose_name=_('Approved Date'))
421
+ date_in_review = models.DateField(
422
+ null=True, blank=True, verbose_name=_('In Review Date')
423
+ )
424
+ date_approved = models.DateField(
425
+ null=True, blank=True, verbose_name=_('Approved Date')
426
+ )
390
427
  date_paid = models.DateField(null=True, blank=True, verbose_name=_('Paid Date'))
391
428
  date_void = models.DateField(null=True, blank=True, verbose_name=_('Void Date'))
392
- date_canceled = models.DateField(null=True, blank=True, verbose_name=_('Canceled Date'))
429
+ date_canceled = models.DateField(
430
+ null=True, blank=True, verbose_name=_('Canceled Date')
431
+ )
393
432
 
394
433
  objects = BillModelManager.from_queryset(queryset_class=BillModelQuerySet)()
395
434
 
@@ -401,11 +440,9 @@ class BillModelAbstract(
401
440
  indexes = [
402
441
  models.Index(fields=['bill_status']),
403
442
  models.Index(fields=['terms']),
404
-
405
443
  models.Index(fields=['cash_account']),
406
444
  models.Index(fields=['prepaid_account']),
407
445
  models.Index(fields=['unearned_account']),
408
-
409
446
  models.Index(fields=['date_due']),
410
447
  models.Index(fields=['date_draft']),
411
448
  models.Index(fields=['date_in_review']),
@@ -413,7 +450,6 @@ class BillModelAbstract(
413
450
  models.Index(fields=['date_paid']),
414
451
  models.Index(fields=['date_canceled']),
415
452
  models.Index(fields=['date_void']),
416
-
417
453
  models.Index(fields=['vendor']),
418
454
  models.Index(fields=['bill_number']),
419
455
  ]
@@ -422,20 +458,19 @@ class BillModelAbstract(
422
458
  return f'Bill: {self.bill_number} | {self.get_bill_status_display()}'
423
459
 
424
460
  def is_configured(self) -> bool:
425
- return all([
426
- super().is_configured(),
427
- self.bill_status
428
- ])
461
+ return all([super().is_configured(), self.bill_status])
429
462
 
430
463
  # Configuration...
431
- def configure(self,
432
- entity_slug: Union[str, EntityModel],
433
- user_model: Optional[UserModel] = None,
434
- date_draft: Optional[Union[date, datetime]] = None,
435
- ledger_posted: bool = False,
436
- ledger_name: str = None,
437
- commit: bool = False,
438
- commit_ledger: bool = False):
464
+ def configure(
465
+ self,
466
+ entity_slug: Union[str, EntityModel],
467
+ user_model: Optional[UserModel] = None,
468
+ date_draft: Optional[Union[date, datetime]] = None,
469
+ ledger_posted: bool = False,
470
+ ledger_name: str = None,
471
+ commit: bool = False,
472
+ commit_ledger: bool = False,
473
+ ):
439
474
  """
440
475
  A configuration hook which executes all initial BillModel setup on to the LedgerModel and all initial
441
476
  values of the BillModel. Can only call this method once in the lifetime of a BillModel.
@@ -468,13 +503,19 @@ class BillModelAbstract(
468
503
  if not self.is_configured():
469
504
  if isinstance(entity_slug, str):
470
505
  if not user_model:
471
- raise BillModelValidationError(_('Must pass user_model when using entity_slug.'))
506
+ raise BillModelValidationError(
507
+ _('Must pass user_model when using entity_slug.')
508
+ )
472
509
  entity_qs = EntityModel.objects.for_user(user_model=user_model)
473
- entity_model: EntityModel = get_object_or_404(entity_qs, slug__exact=entity_slug)
510
+ entity_model: EntityModel = get_object_or_404(
511
+ entity_qs, slug__exact=entity_slug
512
+ )
474
513
  elif isinstance(entity_slug, EntityModel):
475
514
  entity_model = entity_slug
476
515
  else:
477
- raise BillModelValidationError('entity_slug must be an instance of str or EntityModel')
516
+ raise BillModelValidationError(
517
+ 'entity_slug must be an instance of str or EntityModel'
518
+ )
478
519
 
479
520
  if entity_model.is_accrual_method():
480
521
  self.accrue = True
@@ -489,7 +530,9 @@ class BillModelAbstract(
489
530
  self.date_draft = get_localdate() if not date_draft else date_draft
490
531
 
491
532
  LedgerModel = lazy_loader.get_ledger_model()
492
- ledger_model: LedgerModel = LedgerModel(entity=entity_model, posted=ledger_posted)
533
+ ledger_model: LedgerModel = LedgerModel(
534
+ entity=entity_model, posted=ledger_posted
535
+ )
493
536
  ledger_name = f'Bill {self.uuid}' if not ledger_name else ledger_name
494
537
  ledger_model.name = ledger_name
495
538
  ledger_model.configure_for_wrapper_model(model_instance=self)
@@ -517,24 +560,30 @@ class BillModelAbstract(
517
560
  return self.is_draft()
518
561
 
519
562
  def migrate_itemtxs(self, itemtxs: Dict, operation: str, commit: bool = False):
520
- itemtxs_batch = super().migrate_itemtxs(itemtxs=itemtxs, commit=commit, operation=operation)
563
+ itemtxs_batch = super().migrate_itemtxs(
564
+ itemtxs=itemtxs, commit=commit, operation=operation
565
+ )
521
566
  self.update_amount_due(itemtxs_qs=itemtxs_batch)
522
567
  self.get_state(commit=True)
523
568
 
524
569
  if commit:
525
- self.save(update_fields=['amount_due',
526
- 'amount_receivable',
527
- 'amount_unearned',
528
- 'amount_earned',
529
- 'updated'])
570
+ self.save(
571
+ update_fields=[
572
+ 'amount_due',
573
+ 'amount_receivable',
574
+ 'amount_unearned',
575
+ 'amount_earned',
576
+ 'updated',
577
+ ]
578
+ )
530
579
  return itemtxs_batch
531
580
 
532
581
  def get_item_model_qs(self) -> ItemModelQuerySet:
533
- return ItemModel.objects.filter(
534
- entity_id__exact=self.ledger.entity_id
535
- ).bills()
582
+ return ItemModel.objects.filter(entity_id__exact=self.ledger.entity_id).bills()
536
583
 
537
- def validate_itemtxs_qs(self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]):
584
+ def validate_itemtxs_qs(
585
+ self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]
586
+ ):
538
587
  """
539
588
  Validates that the entire ItemTransactionModelQuerySet is bound to the BillModel.
540
589
 
@@ -543,16 +592,18 @@ class BillModelAbstract(
543
592
  queryset: ItemTransactionModelQuerySet or list of ItemTransactionModel.
544
593
  ItemTransactionModelQuerySet to validate.
545
594
  """
546
- valid = all([
547
- i.bill_model_id == self.uuid for i in queryset
548
- ])
595
+ valid = all([i.bill_model_id == self.uuid for i in queryset])
549
596
  if not valid:
550
- raise BillModelValidationError(f'Invalid queryset. All items must be assigned to Bill {self.uuid}')
597
+ raise BillModelValidationError(
598
+ f'Invalid queryset. All items must be assigned to Bill {self.uuid}'
599
+ )
551
600
 
552
- def get_itemtxs_data(self,
553
- queryset: Optional[ItemTransactionModelQuerySet] = None,
554
- aggregate_on_db: bool = False,
555
- lazy_agg: bool = False) -> Tuple[ItemTransactionModelQuerySet, Dict]:
601
+ def get_itemtxs_data(
602
+ self,
603
+ queryset: Optional[ItemTransactionModelQuerySet] = None,
604
+ aggregate_on_db: bool = False,
605
+ lazy_agg: bool = False,
606
+ ) -> Tuple[ItemTransactionModelQuerySet, Dict]:
556
607
  """
557
608
  Fetches the BillModel Items and aggregates the QuerySet.
558
609
 
@@ -568,21 +619,18 @@ class BillModelAbstract(
568
619
  """
569
620
  if not queryset:
570
621
  queryset = self.itemtransactionmodel_set.all().select_related(
571
- 'item_model',
572
- 'entity_unit',
573
- 'po_model',
574
- 'bill_model')
622
+ 'item_model', 'entity_unit', 'po_model', 'bill_model'
623
+ )
575
624
  else:
576
625
  self.validate_itemtxs_qs(queryset)
577
626
 
578
627
  if aggregate_on_db and isinstance(queryset, ItemTransactionModelQuerySet):
579
628
  return queryset, queryset.aggregate(
580
- total_amount__sum=Sum('total_amount'),
581
- total_items=Count('uuid')
629
+ total_amount__sum=Sum('total_amount'), total_items=Count('uuid')
582
630
  )
583
631
  return queryset, {
584
632
  'total_amount__sum': sum(i.total_amount for i in queryset),
585
- 'total_items': len(queryset)
633
+ 'total_items': len(queryset),
586
634
  } if not lazy_agg else None
587
635
 
588
636
  # ### ItemizeMixIn implementation END...
@@ -592,7 +640,9 @@ class BillModelAbstract(
592
640
  Fetches the TransactionModelQuerySet associated with the BillModel instance.
593
641
  """
594
642
  TransactionModel = lazy_loader.get_txs_model()
595
- transaction_model_qs = TransactionModel.objects.all().for_ledger(ledger_model=self.ledger_id)
643
+ transaction_model_qs = TransactionModel.objects.all().for_ledger(
644
+ ledger_model=self.ledger_id
645
+ )
596
646
  if annotated:
597
647
  return transaction_model_qs.with_annotated_details()
598
648
  return transaction_model_qs
@@ -609,8 +659,9 @@ class BillModelAbstract(
609
659
  """
610
660
  return f'Bill {self.bill_number} account adjustment.'
611
661
 
612
- def get_migration_data(self,
613
- queryset: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet:
662
+ def get_migration_data(
663
+ self, queryset: Optional[ItemTransactionModelQuerySet] = None
664
+ ) -> ItemTransactionModelQuerySet:
614
665
  """
615
666
  Fetches necessary item transaction data to perform a migration into the LedgerModel.
616
667
 
@@ -625,21 +676,30 @@ class BillModelAbstract(
625
676
  else:
626
677
  self.validate_itemtxs_qs(queryset)
627
678
 
628
- return queryset.order_by('item_model__expense_account__uuid',
629
- 'entity_unit__uuid',
630
- 'item_model__expense_account__balance_type').values(
631
- 'item_model__expense_account__uuid',
632
- 'item_model__inventory_account__uuid',
633
- 'item_model__expense_account__balance_type',
634
- 'item_model__inventory_account__balance_type',
635
- 'entity_unit__slug',
636
- 'entity_unit__uuid',
637
- 'total_amount').annotate(
638
- account_unit_total=Sum('total_amount')
679
+ return (
680
+ queryset.order_by(
681
+ 'item_model__expense_account__uuid',
682
+ 'entity_unit__uuid',
683
+ 'item_model__expense_account__balance_type',
684
+ )
685
+ .values(
686
+ 'item_model__expense_account__uuid',
687
+ 'item_model__inventory_account__uuid',
688
+ 'item_model__expense_account__balance_type',
689
+ 'item_model__inventory_account__balance_type',
690
+ 'entity_unit__slug',
691
+ 'entity_unit__uuid',
692
+ 'total_amount',
693
+ )
694
+ .annotate(account_unit_total=Sum('total_amount'))
639
695
  )
640
696
 
641
- def update_amount_due(self, itemtxs_qs: Optional[
642
- Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None) -> ItemTransactionModelQuerySet:
697
+ def update_amount_due(
698
+ self,
699
+ itemtxs_qs: Optional[
700
+ Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]
701
+ ] = None,
702
+ ) -> ItemTransactionModelQuerySet:
643
703
  """
644
704
  Updates the BillModel amount due.
645
705
 
@@ -722,11 +782,7 @@ class BillModelAbstract(
722
782
  bool
723
783
  True if BillModel is Active, else False.
724
784
  """
725
- return any([
726
- self.is_paid(),
727
- self.is_approved(),
728
- self.is_void()
729
- ])
785
+ return any([self.is_paid(), self.is_approved(), self.is_void()])
730
786
 
731
787
  def is_void(self) -> bool:
732
788
  """
@@ -773,10 +829,7 @@ class BillModelAbstract(
773
829
  bool
774
830
  True if BillModel can be marked as in review, else False.
775
831
  """
776
- return all([
777
- self.is_configured(),
778
- self.is_draft()
779
- ])
832
+ return all([self.is_configured(), self.is_draft()])
780
833
 
781
834
  def can_approve(self) -> bool:
782
835
  """
@@ -812,11 +865,7 @@ class BillModelAbstract(
812
865
  bool
813
866
  True if BillModel can be deleted, else False.
814
867
  """
815
- return any([
816
- self.is_review(),
817
- self.is_draft(),
818
- not self.ledger.is_locked()
819
- ])
868
+ return any([self.is_review(), self.is_draft(), not self.ledger.is_locked()])
820
869
 
821
870
  def can_void(self) -> bool:
822
871
  """
@@ -827,10 +876,7 @@ class BillModelAbstract(
827
876
  bool
828
877
  True if BillModel can be marked as void, else False.
829
878
  """
830
- return all([
831
- self.is_approved(),
832
- float(self.amount_paid) == 0.00
833
- ])
879
+ return all([self.is_approved(), float(self.amount_paid) == 0.00])
834
880
 
835
881
  def can_cancel(self) -> bool:
836
882
  """
@@ -841,10 +887,7 @@ class BillModelAbstract(
841
887
  bool
842
888
  True if BillModel can be marked as canceled, else False.
843
889
  """
844
- return any([
845
- self.is_draft(),
846
- self.is_review()
847
- ])
890
+ return any([self.is_draft(), self.is_review()])
848
891
 
849
892
  def can_edit_items(self) -> bool:
850
893
  """
@@ -892,16 +935,16 @@ class BillModelAbstract(
892
935
  """
893
936
  if self.ce_model_id:
894
937
  if raise_exception:
895
- raise BillModelValidationError(f'Bill {self.bill_number} already bound to '
896
- f'Estimate {self.ce_model.estimate_number}')
938
+ raise BillModelValidationError(
939
+ f'Bill {self.bill_number} already bound to '
940
+ f'Estimate {self.ce_model.estimate_number}'
941
+ )
897
942
  return False
898
943
 
899
944
  is_approved = estimate_model.is_approved()
900
945
  if not is_approved and raise_exception:
901
- raise BillModelValidationError(f'Cannot bind estimate that is not approved.')
902
- return all([
903
- is_approved
904
- ])
946
+ raise BillModelValidationError('Cannot bind estimate that is not approved.')
947
+ return all([is_approved])
905
948
 
906
949
  def can_bind_po(self, po_model, raise_exception: bool = False) -> bool:
907
950
  """
@@ -924,12 +967,14 @@ class BillModelAbstract(
924
967
  """
925
968
  if not po_model.is_approved():
926
969
  if raise_exception:
927
- raise BillModelValidationError(f'Cannot bind an unapproved PO.')
970
+ raise BillModelValidationError('Cannot bind an unapproved PO.')
928
971
  return False
929
972
 
930
973
  if po_model.date_approved > self.date_draft:
931
974
  if raise_exception:
932
- raise BillModelValidationError(f'Approved PO date cannot be greater than Bill draft date.')
975
+ raise BillModelValidationError(
976
+ 'Approved PO date cannot be greater than Bill draft date.'
977
+ )
933
978
  return False
934
979
 
935
980
  return True
@@ -944,11 +989,7 @@ class BillModelAbstract(
944
989
  bool
945
990
  True if BillModel can generate its bill_number, else False.
946
991
  """
947
- return all([
948
- not self.bill_number,
949
- self.is_draft(),
950
- self.is_configured()
951
- ])
992
+ return all([not self.bill_number, self.is_draft(), self.is_configured()])
952
993
 
953
994
  # ACTIONS ---
954
995
 
@@ -965,11 +1006,13 @@ class BillModelAbstract(
965
1006
  """
966
1007
  return self.is_approved()
967
1008
 
968
- def make_payment(self,
969
- payment_amount: Union[Decimal, float, int],
970
- payment_date: Optional[Union[datetime, date]] = None,
971
- commit: bool = False,
972
- raise_exception: bool = True):
1009
+ def make_payment(
1010
+ self,
1011
+ payment_amount: Union[Decimal, float, int],
1012
+ payment_date: Optional[Union[datetime, date]] = None,
1013
+ commit: bool = False,
1014
+ raise_exception: bool = True,
1015
+ ):
973
1016
  """
974
1017
  Makes a payment to the BillModel.
975
1018
 
@@ -1020,7 +1063,7 @@ class BillModelAbstract(
1020
1063
  user_model=None,
1021
1064
  entity_slug=self.ledger.entity.slug,
1022
1065
  je_timestamp=payment_date,
1023
- raise_exception=True
1066
+ raise_exception=True,
1024
1067
  )
1025
1068
  self.save(
1026
1069
  update_fields=[
@@ -1028,10 +1071,13 @@ class BillModelAbstract(
1028
1071
  'amount_earned',
1029
1072
  'amount_unearned',
1030
1073
  'amount_receivable',
1031
- 'updated'
1032
- ])
1074
+ 'updated',
1075
+ ]
1076
+ )
1033
1077
 
1034
- def bind_estimate(self, estimate_model, commit: bool = False, raise_exception: bool = True):
1078
+ def bind_estimate(
1079
+ self, estimate_model, commit: bool = False, raise_exception: bool = True
1080
+ ):
1035
1081
  """
1036
1082
  Binds BillModel to a given EstimateModel. Raises ValueError if EstimateModel cannot be bound.
1037
1083
 
@@ -1055,12 +1101,11 @@ class BillModelAbstract(
1055
1101
  self.ce_model = estimate_model
1056
1102
  self.clean()
1057
1103
  if commit:
1058
- self.save(update_fields=[
1059
- 'ce_model',
1060
- 'updated'
1061
- ])
1104
+ self.save(update_fields=['ce_model', 'updated'])
1062
1105
 
1063
- def mark_as_draft(self, date_draft: Optional[date] = None, commit: bool = False, **kwargs):
1106
+ def mark_as_draft(
1107
+ self, date_draft: Optional[date] = None, commit: bool = False, **kwargs
1108
+ ):
1064
1109
  """
1065
1110
  Marks BillModel as Draft.
1066
1111
 
@@ -1089,16 +1134,10 @@ class BillModelAbstract(
1089
1134
 
1090
1135
  self.clean()
1091
1136
  if commit:
1092
- self.save(
1093
- update_fields=[
1094
- 'bill_status',
1095
- 'date_draft',
1096
- 'updated'
1097
- ]
1098
- )
1099
- bill_status_draft.send_robust(sender=self.__class__,
1100
- instance=self,
1101
- commited=commit, **kwargs)
1137
+ self.save(update_fields=['bill_status', 'date_draft', 'updated'])
1138
+ bill_status_draft.send_robust(
1139
+ sender=self.__class__, instance=self, commited=commit, **kwargs
1140
+ )
1102
1141
 
1103
1142
  def get_mark_as_draft_html_id(self) -> str:
1104
1143
  """
@@ -1128,11 +1167,10 @@ class BillModelAbstract(
1128
1167
  """
1129
1168
  if not entity_slug:
1130
1169
  entity_slug = self.ledger.entity.slug
1131
- return reverse('django_ledger:bill-action-mark-as-draft',
1132
- kwargs={
1133
- 'entity_slug': entity_slug,
1134
- 'bill_pk': self.uuid
1135
- })
1170
+ return reverse(
1171
+ 'django_ledger:bill-action-mark-as-draft',
1172
+ kwargs={'entity_slug': entity_slug, 'bill_pk': self.uuid},
1173
+ )
1136
1174
 
1137
1175
  def get_mark_as_draft_message(self) -> str:
1138
1176
  """
@@ -1146,12 +1184,14 @@ class BillModelAbstract(
1146
1184
  return _('Do you want to mark Bill %s as Draft?') % self.bill_number
1147
1185
 
1148
1186
  # IN REVIEW ACTIONS....
1149
- def mark_as_review(self,
1150
- commit: bool = False,
1151
- itemtxs_qs: ItemTransactionModelQuerySet = None,
1152
- date_in_review: Optional[date] = None,
1153
- raise_exception: bool = True,
1154
- **kwargs):
1187
+ def mark_as_review(
1188
+ self,
1189
+ commit: bool = False,
1190
+ itemtxs_qs: ItemTransactionModelQuerySet = None,
1191
+ date_in_review: Optional[date] = None,
1192
+ raise_exception: bool = True,
1193
+ **kwargs,
1194
+ ):
1155
1195
  """
1156
1196
  Marks BillModel as In Review.
1157
1197
 
@@ -1179,7 +1219,10 @@ class BillModelAbstract(
1179
1219
  self.validate_itemtxs_qs(queryset=itemtxs_qs)
1180
1220
 
1181
1221
  if not itemtxs_qs.count():
1182
- raise BillModelValidationError(message=f'Cannot review a {self.__class__.__name__} without items...')
1222
+ raise BillModelValidationError(
1223
+ message=f'Cannot review a {self.__class__.__name__} without items...'
1224
+ )
1225
+
1183
1226
  if not self.amount_due:
1184
1227
  raise BillModelValidationError(
1185
1228
  f'Bill {self.bill_number} cannot be marked as in review. Amount due must be greater than 0.'
@@ -1197,16 +1240,10 @@ class BillModelAbstract(
1197
1240
 
1198
1241
  self.clean()
1199
1242
  if commit:
1200
- self.save(
1201
- update_fields=[
1202
- 'date_in_review',
1203
- 'bill_status',
1204
- 'updated'
1205
- ]
1206
- )
1207
- bill_status_in_review.send_robust(sender=self.__class__,
1208
- instance=self,
1209
- commited=commit, **kwargs)
1243
+ self.save(update_fields=['date_in_review', 'bill_status', 'updated'])
1244
+ bill_status_in_review.send_robust(
1245
+ sender=self.__class__, instance=self, commited=commit, **kwargs
1246
+ )
1210
1247
 
1211
1248
  def get_mark_as_review_html_id(self) -> str:
1212
1249
  """
@@ -1236,11 +1273,10 @@ class BillModelAbstract(
1236
1273
  """
1237
1274
  if not entity_slug:
1238
1275
  entity_slug = self.ledger.entity.slug
1239
- return reverse('django_ledger:bill-action-mark-as-review',
1240
- kwargs={
1241
- 'entity_slug': entity_slug,
1242
- 'bill_pk': self.uuid
1243
- })
1276
+ return reverse(
1277
+ 'django_ledger:bill-action-mark-as-review',
1278
+ kwargs={'entity_slug': entity_slug, 'bill_pk': self.uuid},
1279
+ )
1244
1280
 
1245
1281
  def get_mark_as_review_message(self) -> str:
1246
1282
  """
@@ -1254,14 +1290,16 @@ class BillModelAbstract(
1254
1290
  return _('Do you want to mark Bill %s as In Review?') % self.bill_number
1255
1291
 
1256
1292
  # APPROVED ACTIONS....
1257
- def mark_as_approved(self,
1258
- user_model,
1259
- entity_slug: Optional[str] = None,
1260
- date_approved: Optional[Union[date, datetime]] = None,
1261
- commit: bool = False,
1262
- force_migrate: bool = False,
1263
- raise_exception: bool = True,
1264
- **kwargs):
1293
+ def mark_as_approved(
1294
+ self,
1295
+ user_model,
1296
+ entity_slug: Optional[str] = None,
1297
+ date_approved: Optional[Union[date, datetime]] = None,
1298
+ commit: bool = False,
1299
+ force_migrate: bool = False,
1300
+ raise_exception: bool = True,
1301
+ **kwargs,
1302
+ ):
1265
1303
  """
1266
1304
  Marks BillModel as Approved.
1267
1305
 
@@ -1311,12 +1349,12 @@ class BillModelAbstract(
1311
1349
  entity_slug=entity_slug,
1312
1350
  user_model=user_model,
1313
1351
  je_timestamp=date_approved,
1314
- force_migrate=self.accrue
1352
+ force_migrate=self.accrue,
1315
1353
  )
1316
1354
  self.ledger.post(commit=commit, raise_exception=raise_exception)
1317
- bill_status_approved.send_robust(sender=self.__class__,
1318
- instance=self,
1319
- commited=commit, **kwargs)
1355
+ bill_status_approved.send_robust(
1356
+ sender=self.__class__, instance=self, commited=commit, **kwargs
1357
+ )
1320
1358
 
1321
1359
  def get_mark_as_approved_html_id(self) -> str:
1322
1360
  """
@@ -1346,11 +1384,10 @@ class BillModelAbstract(
1346
1384
  """
1347
1385
  if not entity_slug:
1348
1386
  entity_slug = self.ledger.entity.slug
1349
- return reverse('django_ledger:bill-action-mark-as-approved',
1350
- kwargs={
1351
- 'entity_slug': entity_slug,
1352
- 'bill_pk': self.uuid
1353
- })
1387
+ return reverse(
1388
+ 'django_ledger:bill-action-mark-as-approved',
1389
+ kwargs={'entity_slug': entity_slug, 'bill_pk': self.uuid},
1390
+ )
1354
1391
 
1355
1392
  def get_mark_as_approved_message(self) -> str:
1356
1393
  """
@@ -1364,14 +1401,15 @@ class BillModelAbstract(
1364
1401
  return _('Do you want to mark Bill %s as Approved?') % self.bill_number
1365
1402
 
1366
1403
  # PAY ACTIONS....
1367
- def mark_as_paid(self,
1368
- user_model,
1369
- entity_slug: Optional[str] = None,
1370
- date_paid: Optional[Union[date, datetime]] = None,
1371
- itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None,
1372
- commit: bool = False,
1373
- **kwargs):
1374
-
1404
+ def mark_as_paid(
1405
+ self,
1406
+ user_model,
1407
+ entity_slug: Optional[str] = None,
1408
+ date_paid: Optional[Union[date, datetime]] = None,
1409
+ itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None,
1410
+ commit: bool = False,
1411
+ **kwargs,
1412
+ ):
1375
1413
  """
1376
1414
  Marks BillModel as Paid.
1377
1415
 
@@ -1394,7 +1432,9 @@ class BillModelAbstract(
1394
1432
  Commits transaction into the Database. Defaults to False.
1395
1433
  """
1396
1434
  if not self.can_pay():
1397
- raise BillModelValidationError(f'Cannot mark Bill {self.bill_number} as paid...')
1435
+ raise BillModelValidationError(
1436
+ f'Cannot mark Bill {self.bill_number} as paid...'
1437
+ )
1398
1438
 
1399
1439
  if date_paid:
1400
1440
  if isinstance(date_paid, datetime):
@@ -1408,10 +1448,13 @@ class BillModelAbstract(
1408
1448
  self.amount_paid = self.amount_due
1409
1449
 
1410
1450
  if self.date_paid > get_localdate():
1411
- raise BillModelValidationError(f'Cannot pay {self.__class__.__name__} in the future.')
1451
+ raise BillModelValidationError(
1452
+ f'Cannot pay {self.__class__.__name__} in the future.'
1453
+ )
1412
1454
  if self.date_paid < self.date_approved:
1413
1455
  raise BillModelValidationError(
1414
- f'Cannot pay {self.__class__.__name__} before approved date {self.date_approved}.')
1456
+ f'Cannot pay {self.__class__.__name__} before approved date {self.date_approved}.'
1457
+ )
1415
1458
 
1416
1459
  self.bill_status = self.BILL_STATUS_PAID
1417
1460
  self.get_state(commit=True)
@@ -1425,9 +1468,9 @@ class BillModelAbstract(
1425
1468
  if commit:
1426
1469
  self.save()
1427
1470
  ItemTransactionModel = lazy_loader.get_item_transaction_model()
1428
- itemtxs_qs.filter(
1429
- po_model_id__isnull=False
1430
- ).update(po_item_status=ItemTransactionModel.STATUS_ORDERED)
1471
+ itemtxs_qs.filter(po_model_id__isnull=False).update(
1472
+ po_item_status=ItemTransactionModel.STATUS_ORDERED
1473
+ )
1431
1474
 
1432
1475
  if not entity_slug:
1433
1476
  entity_slug = self.ledger.entity.slug
@@ -1437,12 +1480,12 @@ class BillModelAbstract(
1437
1480
  entity_slug=entity_slug,
1438
1481
  itemtxs_qs=itemtxs_qs,
1439
1482
  je_timestamp=date_paid,
1440
- force_migrate=True
1483
+ force_migrate=True,
1441
1484
  )
1442
1485
  self.lock_ledger(commit=True)
1443
- bill_status_paid.send_robust(sender=self.__class__,
1444
- instance=self,
1445
- commited=commit, **kwargs)
1486
+ bill_status_paid.send_robust(
1487
+ sender=self.__class__, instance=self, commited=commit, **kwargs
1488
+ )
1446
1489
 
1447
1490
  def get_mark_as_paid_html_id(self) -> str:
1448
1491
  """
@@ -1472,11 +1515,10 @@ class BillModelAbstract(
1472
1515
  """
1473
1516
  if not entity_slug:
1474
1517
  entity_slug = self.ledger.entity.slug
1475
- return reverse('django_ledger:bill-action-mark-as-paid',
1476
- kwargs={
1477
- 'entity_slug': entity_slug,
1478
- 'bill_pk': self.uuid
1479
- })
1518
+ return reverse(
1519
+ 'django_ledger:bill-action-mark-as-paid',
1520
+ kwargs={'entity_slug': entity_slug, 'bill_pk': self.uuid},
1521
+ )
1480
1522
 
1481
1523
  def get_mark_as_paid_message(self) -> str:
1482
1524
  """
@@ -1490,12 +1532,14 @@ class BillModelAbstract(
1490
1532
  return _('Do you want to mark Bill %s as Paid?') % self.bill_number
1491
1533
 
1492
1534
  # VOID Actions...
1493
- def mark_as_void(self,
1494
- user_model,
1495
- entity_slug: Optional[str] = None,
1496
- date_void: Optional[date] = None,
1497
- commit: bool = False,
1498
- **kwargs):
1535
+ def mark_as_void(
1536
+ self,
1537
+ user_model,
1538
+ entity_slug: Optional[str] = None,
1539
+ date_void: Optional[date] = None,
1540
+ commit: bool = False,
1541
+ **kwargs,
1542
+ ):
1499
1543
  """
1500
1544
  Marks BillModel as Void.
1501
1545
  When mark as void, all transactions associated with BillModel are reversed as of the void date.
@@ -1516,7 +1560,9 @@ class BillModelAbstract(
1516
1560
  Commits transaction into DB. Defaults to False.
1517
1561
  """
1518
1562
  if not self.can_void():
1519
- raise BillModelValidationError(f'Bill {self.bill_number} cannot be voided. Must be approved.')
1563
+ raise BillModelValidationError(
1564
+ f'Bill {self.bill_number} cannot be voided. Must be approved.'
1565
+ )
1520
1566
 
1521
1567
  if date_void:
1522
1568
  if isinstance(date_void, datetime):
@@ -1541,12 +1587,13 @@ class BillModelAbstract(
1541
1587
  void=True,
1542
1588
  void_date=self.date_void,
1543
1589
  raise_exception=False,
1544
- force_migrate=True)
1590
+ force_migrate=True,
1591
+ )
1545
1592
  self.save()
1546
1593
  self.lock_ledger(commit=False, raise_exception=False)
1547
- bill_status_void.send_robust(sender=self.__class__,
1548
- instance=self,
1549
- commited=commit, **kwargs)
1594
+ bill_status_void.send_robust(
1595
+ sender=self.__class__, instance=self, commited=commit, **kwargs
1596
+ )
1550
1597
 
1551
1598
  def get_mark_as_void_html_id(self) -> str:
1552
1599
  """
@@ -1575,11 +1622,10 @@ class BillModelAbstract(
1575
1622
  """
1576
1623
  if not entity_slug:
1577
1624
  entity_slug = self.ledger.entity.slug
1578
- return reverse('django_ledger:bill-action-mark-as-void',
1579
- kwargs={
1580
- 'entity_slug': entity_slug,
1581
- 'bill_pk': self.uuid
1582
- })
1625
+ return reverse(
1626
+ 'django_ledger:bill-action-mark-as-void',
1627
+ kwargs={'entity_slug': entity_slug, 'bill_pk': self.uuid},
1628
+ )
1583
1629
 
1584
1630
  def get_mark_as_void_message(self) -> str:
1585
1631
  """
@@ -1593,7 +1639,9 @@ class BillModelAbstract(
1593
1639
  return _('Do you want to void Bill %s?') % self.bill_number
1594
1640
 
1595
1641
  # Cancel Actions...
1596
- def mark_as_canceled(self, date_canceled: Optional[date] = None, commit: bool = False, **kwargs):
1642
+ def mark_as_canceled(
1643
+ self, date_canceled: Optional[date] = None, commit: bool = False, **kwargs
1644
+ ):
1597
1645
  """
1598
1646
  Mark BillModel as Canceled.
1599
1647
 
@@ -1607,16 +1655,18 @@ class BillModelAbstract(
1607
1655
  Commits transaction into the Database. Defaults to False.
1608
1656
  """
1609
1657
  if not self.can_cancel():
1610
- raise BillModelValidationError(f'Bill {self.bill_number} cannot be canceled. Must be draft or in review.')
1658
+ raise BillModelValidationError(
1659
+ f'Bill {self.bill_number} cannot be canceled. Must be draft or in review.'
1660
+ )
1611
1661
 
1612
1662
  self.date_canceled = get_localdate() if not date_canceled else date_canceled
1613
1663
  self.bill_status = self.BILL_STATUS_CANCELED
1614
1664
  self.clean()
1615
1665
  if commit:
1616
1666
  self.save()
1617
- bill_status_canceled.send_robust(sender=self.__class__,
1618
- instance=self,
1619
- commited=commit, **kwargs)
1667
+ bill_status_canceled.send_robust(
1668
+ sender=self.__class__, instance=self, commited=commit, **kwargs
1669
+ )
1620
1670
 
1621
1671
  def get_mark_as_canceled_html_id(self) -> str:
1622
1672
  """
@@ -1648,11 +1698,10 @@ class BillModelAbstract(
1648
1698
  if not entity_slug:
1649
1699
  entity_slug = self.ledger.entity.slug
1650
1700
 
1651
- return reverse('django_ledger:bill-action-mark-as-canceled',
1652
- kwargs={
1653
- 'entity_slug': entity_slug,
1654
- 'bill_pk': self.uuid
1655
- })
1701
+ return reverse(
1702
+ 'django_ledger:bill-action-mark-as-canceled',
1703
+ kwargs={'entity_slug': entity_slug, 'bill_pk': self.uuid},
1704
+ )
1656
1705
 
1657
1706
  def get_mark_as_canceled_message(self) -> str:
1658
1707
  """
@@ -1830,28 +1879,28 @@ class BillModelAbstract(
1830
1879
  'entity_model_id__exact': self.ledger.entity_id,
1831
1880
  'entity_unit_id__exact': None,
1832
1881
  'fiscal_year': fy_key,
1833
- 'key__exact': EntityStateModel.KEY_BILL
1882
+ 'key__exact': EntityStateModel.KEY_BILL,
1834
1883
  }
1835
1884
 
1836
- state_model_qs = EntityStateModel.objects.filter(**LOOKUP).select_related(
1837
- 'entity_model').select_for_update()
1885
+ state_model_qs = (
1886
+ EntityStateModel.objects.filter(**LOOKUP)
1887
+ .select_related('entity_model')
1888
+ .select_for_update()
1889
+ )
1838
1890
  state_model = state_model_qs.get()
1839
1891
  state_model.sequence = F('sequence') + 1
1840
1892
  state_model.save(update_fields=['sequence'])
1841
1893
  state_model.refresh_from_db()
1842
1894
  return state_model
1843
1895
  except ObjectDoesNotExist:
1844
- EntityModel = lazy_loader.get_entity_model()
1845
- entity_model = EntityModel.objects.get(uuid__exact=self.ledger.entity_id)
1846
- fy_key = entity_model.get_fy_for_date(dt=self.date_draft)
1847
-
1848
1896
  LOOKUP = {
1849
1897
  'entity_model_id': entity_model.uuid,
1850
1898
  'entity_unit_id': None,
1851
1899
  'fiscal_year': fy_key,
1852
1900
  'key': EntityStateModel.KEY_BILL,
1853
- 'sequence': 1
1901
+ 'sequence': 1,
1854
1902
  }
1903
+
1855
1904
  state_model = EntityStateModel.objects.create(**LOOKUP)
1856
1905
  return state_model
1857
1906
  except IntegrityError as e:
@@ -1876,12 +1925,13 @@ class BillModelAbstract(
1876
1925
  """
1877
1926
  if self.can_generate_bill_number():
1878
1927
  with transaction.atomic(durable=True):
1879
-
1880
1928
  state_model = None
1881
1929
  while not state_model:
1882
1930
  state_model = self._get_next_state_model(raise_exception=False)
1883
1931
 
1884
- seq = str(state_model.sequence).zfill(DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING)
1932
+ seq = str(state_model.sequence).zfill(
1933
+ DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING
1934
+ )
1885
1935
  self.bill_number = f'{DJANGO_LEDGER_BILL_NUMBER_PREFIX}-{state_model.fiscal_year}-{seq}'
1886
1936
 
1887
1937
  if commit:
@@ -1894,11 +1944,10 @@ class BillModelAbstract(
1894
1944
 
1895
1945
  # --> URLs <---
1896
1946
  def get_absolute_url(self):
1897
- return reverse('django_ledger:bill-detail',
1898
- kwargs={
1899
- 'entity_slug': self.ledger.entity.slug,
1900
- 'bill_pk': self.uuid
1901
- })
1947
+ return reverse(
1948
+ 'django_ledger:bill-detail',
1949
+ kwargs={'entity_slug': self.ledger.entity.slug, 'bill_pk': self.uuid},
1950
+ )
1902
1951
 
1903
1952
  def clean(self, commit: bool = True):
1904
1953
  """
@@ -1916,9 +1965,13 @@ class BillModelAbstract(
1916
1965
  if self.cash_account.role != ASSET_CA_CASH:
1917
1966
  raise ValidationError(f'Cash account must be of role {ASSET_CA_CASH}.')
1918
1967
  if self.prepaid_account.role != ASSET_CA_PREPAID:
1919
- raise ValidationError(f'Prepaid account must be of role {ASSET_CA_PREPAID}.')
1968
+ raise ValidationError(
1969
+ f'Prepaid account must be of role {ASSET_CA_PREPAID}.'
1970
+ )
1920
1971
  if self.unearned_account.role != LIABILITY_CL_ACC_PAYABLE:
1921
- raise ValidationError(f'Unearned account must be of role {LIABILITY_CL_ACC_PAYABLE}.')
1972
+ raise ValidationError(
1973
+ f'Unearned account must be of role {LIABILITY_CL_ACC_PAYABLE}.'
1974
+ )
1922
1975
 
1923
1976
 
1924
1977
  class BillModel(BillModelAbstract):