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

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

Potentially problematic release.


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

Files changed (56) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/forms/account.py +45 -46
  3. django_ledger/forms/data_import.py +182 -64
  4. django_ledger/io/io_core.py +507 -374
  5. django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
  6. django_ledger/models/__init__.py +2 -1
  7. django_ledger/models/bill.py +337 -300
  8. django_ledger/models/customer.py +47 -34
  9. django_ledger/models/data_import.py +770 -289
  10. django_ledger/models/entity.py +882 -637
  11. django_ledger/models/mixins.py +421 -282
  12. django_ledger/models/receipt.py +1083 -0
  13. django_ledger/models/transactions.py +105 -41
  14. django_ledger/models/unit.py +42 -30
  15. django_ledger/models/utils.py +12 -2
  16. django_ledger/models/vendor.py +85 -66
  17. django_ledger/settings.py +1 -0
  18. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  19. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +1 -13
  20. django_ledger/templates/django_ledger/bills/bill_update.html +1 -1
  21. django_ledger/templates/django_ledger/components/period_navigator.html +5 -3
  22. django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
  23. django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
  24. django_ledger/templates/django_ledger/customer/tags/customer_table.html +3 -1
  25. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
  26. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
  27. django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
  28. django_ledger/templates/django_ledger/invoice/invoice_update.html +1 -1
  29. django_ledger/templates/django_ledger/layouts/base.html +3 -1
  30. django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
  31. django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
  32. django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
  33. django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
  34. django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
  35. django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
  36. django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +3 -2
  37. django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
  38. django_ledger/templatetags/django_ledger.py +338 -191
  39. django_ledger/urls/__init__.py +1 -0
  40. django_ledger/urls/customer.py +3 -0
  41. django_ledger/urls/data_import.py +3 -0
  42. django_ledger/urls/receipt.py +102 -0
  43. django_ledger/urls/vendor.py +1 -0
  44. django_ledger/views/__init__.py +1 -0
  45. django_ledger/views/customer.py +56 -14
  46. django_ledger/views/data_import.py +119 -66
  47. django_ledger/views/mixins.py +112 -86
  48. django_ledger/views/receipt.py +294 -0
  49. django_ledger/views/vendor.py +53 -14
  50. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/METADATA +1 -1
  51. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/RECORD +55 -45
  52. django_ledger/static/django_ledger/bundle/styles.bundle.js +0 -1
  53. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/WHEEL +0 -0
  54. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/licenses/AUTHORS.md +0 -0
  55. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/licenses/LICENSE +0 -0
  56. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/top_level.txt +0 -0
@@ -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, Self
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()
@@ -110,13 +131,13 @@ class EntityModelManager(MP_NodeManager):
110
131
 
111
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,33 +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
491
  def get_closing_entry_digest(
445
- self,
446
- to_date: date,
447
- from_date: Optional[date] = None,
448
- user_model: Optional[UserModel] = None,
449
- closing_entry_model=None,
450
- **kwargs: Dict) -> Tuple:
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:
451
499
  ClosingEntryModel = lazy_loader.get_closing_entry_model()
452
500
  ClosingEntryTransactionModel = lazy_loader.get_closing_entry_transaction_model()
453
501
 
454
502
  if not closing_entry_model:
455
503
  closing_entry_model = ClosingEntryModel(
456
- entity_model=self,
457
- closing_date=to_date
504
+ entity_model=self, closing_date=to_date
458
505
  )
459
506
  closing_entry_model.clean()
460
507
  else:
@@ -467,7 +514,7 @@ class EntityModelClosingEntryMixIn:
467
514
  by_unit=True,
468
515
  by_activity=True,
469
516
  signs=False,
470
- **kwargs
517
+ **kwargs,
471
518
  )
472
519
  ce_data = io_digest.get_closing_entry_data()
473
520
 
@@ -478,8 +525,9 @@ class EntityModelClosingEntryMixIn:
478
525
  unit_model_id=ce['unit_uuid'],
479
526
  tx_type=ce['balance_type'],
480
527
  activity=ce['activity'],
481
- balance=ce['balance']
482
- ) for ce in ce_data
528
+ balance=ce['balance'],
529
+ )
530
+ for ce in ce_data
483
531
  ]
484
532
 
485
533
  for ce in ce_txs_list:
@@ -487,25 +535,25 @@ class EntityModelClosingEntryMixIn:
487
535
 
488
536
  return closing_entry_model, ce_txs_list
489
537
 
490
- def get_closing_entry_digest_for_date(self,
491
- closing_date: date,
492
- closing_entry_model=None,
493
- **kwargs) -> Tuple:
538
+ def get_closing_entry_digest_for_date(
539
+ self, closing_date: date, closing_entry_model=None, **kwargs
540
+ ) -> Tuple:
494
541
  return self.get_closing_entry_digest(
495
- to_date=closing_date,
496
- closing_entry_model=closing_entry_model,
497
- **kwargs
542
+ to_date=closing_date, closing_entry_model=closing_entry_model, **kwargs
498
543
  )
499
544
 
500
- def get_closing_entry_digest_for_month(self,
501
- year: int,
502
- month: int,
503
- **kwargs: Dict) -> Tuple:
545
+ def get_closing_entry_digest_for_month(
546
+ self, year: int, month: int, **kwargs: Dict
547
+ ) -> Tuple:
504
548
  _, day_end = monthrange(year, month)
505
549
  closing_date = date(year=year, month=month, day=day_end)
506
- 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
+ )
507
553
 
508
- 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:
509
557
  closing_date = getattr(self, 'get_fy_end')(year=fiscal_year)
510
558
  return self.get_closing_entry_digest_for_date(to_date=closing_date, **kwargs)
511
559
 
@@ -526,17 +574,19 @@ class EntityModelClosingEntryMixIn:
526
574
  return self.get_closing_entry_queryset_for_date(closing_date=closing_date)
527
575
 
528
576
  # ----> Create Closing Entries <----
529
- def create_closing_entry_for_date(self,
530
- closing_date: date,
531
- closing_entry_model=None,
532
- closing_entry_exists=True):
533
-
577
+ def create_closing_entry_for_date(
578
+ self, closing_date: date, closing_entry_model=None, closing_entry_exists=True
579
+ ):
534
580
  if closing_entry_model:
535
- 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
+ )
536
584
 
537
585
  if closing_date > get_localdate():
538
586
  raise EntityModelValidationError(
539
- 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
+ )
540
590
  )
541
591
 
542
592
  if closing_entry_model is None:
@@ -545,8 +595,7 @@ class EntityModelClosingEntryMixIn:
545
595
  closing_entry_model.closingentrytransactionmodel_set.all().delete()
546
596
 
547
597
  closing_entry_model, ce_txs_list = self.get_closing_entry_digest_for_date(
548
- closing_date=closing_date,
549
- closing_entry_model=closing_entry_model
598
+ closing_date=closing_date, closing_entry_model=closing_entry_model
550
599
  )
551
600
 
552
601
  if closing_entry_model is not None:
@@ -554,8 +603,7 @@ class EntityModelClosingEntryMixIn:
554
603
 
555
604
  ClosingEntryTransactionModel = lazy_loader.get_closing_entry_transaction_model()
556
605
  return closing_entry_model, ClosingEntryTransactionModel.objects.bulk_create(
557
- objs=ce_txs_list,
558
- batch_size=100
606
+ objs=ce_txs_list, batch_size=100
559
607
  )
560
608
 
561
609
  def create_closing_entry_for_month(self, year: int, month: int):
@@ -584,21 +632,26 @@ class EntityModelClosingEntryMixIn:
584
632
  return f'closing_entry_{end_dt_str}_{self.uuid}'
585
633
 
586
634
  # ----> Closing Entry Caching Month < -----
587
- def get_closing_entry_cache_for_date(self,
588
- closing_date: date,
589
- cache_name: str = 'default',
590
- force_cache_update: bool = False,
591
- cache_timeout: Optional[int] = None,
592
- **kwargs):
593
-
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
+ ):
594
643
  if not force_cache_update:
595
644
  cache_system = caches[cache_name]
596
- 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
+ )
597
648
  ce_ser = cache_system.get(ce_cache_key)
598
649
 
599
650
  # if closing entry is in cache...
600
651
  if ce_ser:
601
- 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
+ )
602
655
  return list(ce.object for ce in ce_qs_serde_gen)
603
656
  return
604
657
 
@@ -606,16 +659,18 @@ class EntityModelClosingEntryMixIn:
606
659
  closing_date=closing_date,
607
660
  cache_name=cache_name,
608
661
  cache_timeout=cache_timeout,
609
- **kwargs)
610
-
611
- def get_closing_entry_cache_for_month(self,
612
- year: int,
613
- month: int,
614
- cache_name: str = 'default',
615
- force_cache_update: bool = False,
616
- cache_timeout: Optional[int] = None,
617
- **kwargs):
662
+ **kwargs,
663
+ )
618
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
+ ):
619
674
  _, day = monthrange(year, month)
620
675
  closing_date = date(year, month, day)
621
676
  return self.get_closing_entry_cache_for_date(
@@ -623,33 +678,39 @@ class EntityModelClosingEntryMixIn:
623
678
  cache_name=cache_name,
624
679
  force_cache_update=force_cache_update,
625
680
  cache_timeout=cache_timeout,
626
- **kwargs
681
+ **kwargs,
627
682
  )
628
683
 
629
- def get_closing_entry_cache_for_fiscal_year(self,
630
- fiscal_year: int,
631
- cache_name: str = 'default',
632
- force_cache_update: bool = False,
633
- cache_timeout: Optional[int] = None,
634
- **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
+ ):
635
692
  closing_date: date = getattr(self, 'get_fy_end')(year=fiscal_year)
636
693
  return self.get_closing_entry_cache_for_date(
637
694
  closing_date=closing_date,
638
695
  cache_name=cache_name,
639
696
  force_cache_update=force_cache_update,
640
697
  cache_timeout=cache_timeout,
641
- **kwargs
698
+ **kwargs,
642
699
  )
643
700
 
644
701
  # ---> SAVE CLOSING ENTRY <---
645
- def save_closing_entry_cache_for_date(self,
646
- closing_date: date,
647
- cache_name: str = 'default',
648
- cache_timeout: Optional[int] = None,
649
- **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
+ ):
650
709
  cache_system = caches[cache_name]
651
710
  ce_qs = self.get_closing_entry_queryset_for_date(closing_date=closing_date)
652
- 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
+ )
653
714
  ce_ser = serializers.serialize(format='json', queryset=ce_qs)
654
715
 
655
716
  if not cache_timeout:
@@ -658,43 +719,49 @@ class EntityModelClosingEntryMixIn:
658
719
  cache_system.set(ce_cache_key, ce_ser, cache_timeout, **kwargs)
659
720
  return list(ce_qs)
660
721
 
661
- def save_closing_entry_cache_for_month(self,
662
- year: int,
663
- month: int,
664
- cache_name: str = 'default',
665
- cache_timeout: Optional[int] = None,
666
- **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
+ ):
667
730
  _, day = monthrange(year, month)
668
731
  closing_date = date(year, month, day)
669
732
  return self.save_closing_entry_cache_for_date(
670
733
  closing_date=closing_date,
671
734
  cache_name=cache_name,
672
735
  cache_timeout=cache_timeout,
673
- **kwargs
736
+ **kwargs,
674
737
  )
675
738
 
676
- def save_closing_entry_cache_for_fiscal_year(self,
677
- fiscal_year: int,
678
- cache_name: str = 'default',
679
- cache_timeout: Optional[int] = None,
680
- **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
+ ):
681
746
  closing_date: date = getattr(self, 'get_fy_end')(year=fiscal_year)
682
747
  return self.save_closing_entry_cache_for_date(
683
748
  closing_date=closing_date,
684
749
  cache_name=cache_name,
685
750
  cache_timeout=cache_timeout,
686
- **kwargs
751
+ **kwargs,
687
752
  )
688
753
 
689
754
 
690
- class EntityModelAbstract(MP_Node,
691
- SlugNameMixIn,
692
- CreateUpdateMixIn,
693
- ContactInfoMixIn,
694
- IOMixIn,
695
- LoggingMixIn,
696
- EntityModelFiscalPeriodMixIn,
697
- EntityModelClosingEntryMixIn):
755
+ class EntityModelAbstract(
756
+ MP_Node,
757
+ SlugNameMixIn,
758
+ CreateUpdateMixIn,
759
+ ContactInfoMixIn,
760
+ IOMixIn,
761
+ LoggingMixIn,
762
+ EntityModelFiscalPeriodMixIn,
763
+ EntityModelClosingEntryMixIn,
764
+ ):
698
765
  """
699
766
  The base implementation of the EntityModel. The EntityModel represents the Company, Corporation, Legal Entity,
700
767
  Enterprise or Person that engage and operate as a business. The base model inherit from the Materialized Path Node
@@ -766,24 +833,36 @@ class EntityModelAbstract(MP_Node,
766
833
 
767
834
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
768
835
  name = models.CharField(max_length=150, verbose_name=_('Entity Name'))
769
- default_coa = models.OneToOneField('django_ledger.ChartOfAccountModel',
770
- verbose_name=_('Default Chart of Accounts'),
771
- blank=True,
772
- null=True,
773
- on_delete=models.PROTECT)
774
- admin = models.ForeignKey(UserModel,
775
- on_delete=models.CASCADE,
776
- related_name='admin_of',
777
- verbose_name=_('Admin'))
778
- managers = models.ManyToManyField(UserModel,
779
- through='EntityManagementModel',
780
- related_name='managed_by',
781
- 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
+ )
782
855
 
783
856
  hidden = models.BooleanField(default=False)
784
- accrual_method = models.BooleanField(default=False, verbose_name=_('Use Accrual Method'))
785
- fy_start_month = models.IntegerField(choices=FY_MONTHS, default=1, verbose_name=_('Fiscal Year Start'))
786
- 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
+ )
787
866
  picture = models.ImageField(blank=True, null=True)
788
867
  meta = models.JSONField(default=dict, null=True, blank=True)
789
868
  objects = EntityModelManager.from_queryset(queryset_class=EntityModelQuerySet)()
@@ -793,9 +872,7 @@ class EntityModelAbstract(MP_Node,
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()
@@ -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,11 +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(
2607
- entity_model=self.slug
2816
+ counted_qs: ItemTransactionModelQuerySet = (
2817
+ ItemTransactionModel.objects.inventory_count(entity_model=self.slug)
2608
2818
  )
2609
2819
  recorded_qs: ItemModelQuerySet = self.recorded_inventory(as_values=False)
2610
- 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
+ )
2611
2823
 
2612
2824
  adj = self.inventory_adjustment(counted_qs, recorded_qs_values)
2613
2825
 
@@ -2620,18 +2832,16 @@ class EntityModelAbstract(MP_Node,
2620
2832
  updated_items.append(item_model)
2621
2833
 
2622
2834
  if commit:
2623
- ItemModel.objects.bulk_update(updated_items,
2624
- fields=[
2625
- 'inventory_received',
2626
- 'inventory_received_value',
2627
- 'updated'
2628
- ])
2835
+ ItemModel.objects.bulk_update(
2836
+ updated_items,
2837
+ fields=['inventory_received', 'inventory_received_value', 'updated'],
2838
+ )
2629
2839
 
2630
2840
  return adj, counted_qs, recorded_qs
2631
2841
 
2632
- def recorded_inventory(self,
2633
- item_qs: Optional[ItemModelQuerySet] = None,
2634
- as_values: bool = True) -> ItemModelQuerySet:
2842
+ def recorded_inventory(
2843
+ self, item_qs: Optional[ItemModelQuerySet] = None, as_values: bool = True
2844
+ ) -> ItemModelQuerySet:
2635
2845
  """
2636
2846
  Recorded inventory on the books marked as received. PurchaseOrderModel drives the ordering and receiving of
2637
2847
  inventory. Once inventory is marked as "received" recorded inventory of each item is updated by calling
@@ -2660,21 +2870,27 @@ class EntityModelAbstract(MP_Node,
2660
2870
  recorded_qs = item_qs
2661
2871
  if as_values:
2662
2872
  return recorded_qs.values(
2663
- 'uuid', 'name', 'uom__name', 'inventory_received', 'inventory_received_value')
2873
+ 'uuid',
2874
+ 'name',
2875
+ 'uom__name',
2876
+ 'inventory_received',
2877
+ 'inventory_received_value',
2878
+ )
2664
2879
  return recorded_qs
2665
2880
 
2666
2881
  # COMMON TRANSACTIONS...
2667
- def deposit_capital(self,
2668
- amount: Union[Decimal, float],
2669
- cash_account: Optional[Union[AccountModel, BankAccountModel]] = None,
2670
- capital_account: Optional[AccountModel] = None,
2671
- description: Optional[str] = None,
2672
- coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
2673
- ledger_model: Optional[Union[LedgerModel, UUID]] = None,
2674
- ledger_posted: bool = False,
2675
- je_timestamp: Optional[Union[datetime, date, str]] = None,
2676
- je_posted: bool = False):
2677
-
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
+ ):
2678
2894
  if coa_model:
2679
2895
  self.validate_chart_of_accounts_for_entity(coa_model)
2680
2896
  else:
@@ -2699,16 +2915,28 @@ class EntityModelAbstract(MP_Node,
2699
2915
  if cash_account:
2700
2916
  if isinstance(cash_account, BankAccountModel):
2701
2917
  cash_account = cash_account.account_model
2702
- self.validate_account_model_for_coa(account_model=cash_account, coa_model=coa_model)
2703
- 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
+ )
2704
2924
  else:
2705
- 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()
2706
2928
 
2707
2929
  if capital_account:
2708
- self.validate_account_model_for_coa(account_model=capital_account, coa_model=coa_model)
2709
- 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
+ )
2710
2936
  else:
2711
- 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()
2712
2940
 
2713
2941
  if not je_timestamp:
2714
2942
  je_timestamp = get_localtime()
@@ -2717,36 +2945,42 @@ class EntityModelAbstract(MP_Node,
2717
2945
  description = f'Capital Deposit on {je_timestamp.isoformat()}...'
2718
2946
 
2719
2947
  txs = list()
2720
- txs.append({
2721
- 'account': cash_account,
2722
- 'tx_type': DEBIT,
2723
- 'amount': amount,
2724
- 'description': description
2725
- })
2726
- txs.append({
2727
- 'account': capital_account,
2728
- 'tx_type': CREDIT,
2729
- 'amount': amount,
2730
- 'description': description
2731
- })
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
+ )
2732
2964
 
2733
2965
  if not ledger_model:
2734
2966
  ledger_model = self.ledgermodel_set.create(
2735
2967
  name=f'Capital Deposit on {je_timestamp.isoformat()}.',
2736
- posted=ledger_posted
2968
+ posted=ledger_posted,
2737
2969
  )
2738
2970
  else:
2739
2971
  if isinstance(ledger_model, LedgerModel):
2740
2972
  self.validate_ledger_model_for_entity(ledger_model)
2741
2973
  else:
2742
- 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
+ )
2743
2977
  ledger_model = ledger_model_qs.get(uuid__exact=ledger_model)
2744
2978
 
2745
2979
  self.commit_txs(
2746
2980
  je_timestamp=je_timestamp,
2747
2981
  je_txs=txs,
2748
2982
  je_posted=je_posted,
2749
- je_ledger_model=ledger_model
2983
+ je_ledger_model=ledger_model,
2750
2984
  )
2751
2985
 
2752
2986
  return ledger_model
@@ -2758,14 +2992,22 @@ class EntityModelAbstract(MP_Node,
2758
2992
  def get_closing_entries(self):
2759
2993
  return self.closingentrymodel_set.all()
2760
2994
 
2761
- 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]]:
2762
2998
  date_list = self.meta[self.META_KEY_CLOSING_ENTRY_DATES]
2763
2999
  if as_iso:
2764
3000
  return date_list
2765
3001
  return [date.fromisoformat(d) for d in date_list]
2766
3002
 
2767
- def compute_closing_entry_dates_list(self, as_iso: bool = True) -> List[Union[date, str]]:
2768
- 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
+ )
2769
3011
  if as_iso:
2770
3012
  return [ce.closing_date.isoformat() for ce in closing_entry_qs]
2771
3013
  return [ce.closing_date for ce in closing_entry_qs]
@@ -2778,14 +3020,11 @@ class EntityModelAbstract(MP_Node,
2778
3020
  except IndexError:
2779
3021
  self.last_closing_date = None
2780
3022
 
2781
- 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
+ ]
2782
3026
  if commit:
2783
- self.save(
2784
- update_fields=[
2785
- 'last_closing_date',
2786
- 'updated',
2787
- 'meta'
2788
- ])
3027
+ self.save(update_fields=['last_closing_date', 'updated', 'meta'])
2789
3028
  return date_list
2790
3029
 
2791
3030
  def fetch_closing_entry_dates_meta(self, as_date: bool = True) -> List[date]:
@@ -2798,7 +3037,9 @@ class EntityModelAbstract(MP_Node,
2798
3037
  return self._CLOSING_ENTRY_DATES
2799
3038
  return date_list
2800
3039
 
2801
- 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]:
2802
3043
  if io_date is None:
2803
3044
  return
2804
3045
  ce_date_list = self.fetch_closing_entry_dates_meta()
@@ -2813,7 +3054,9 @@ class EntityModelAbstract(MP_Node,
2813
3054
  if ce_lookup in ce_date_list:
2814
3055
  return ce_lookup
2815
3056
 
2816
- 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]:
2817
3060
  if io_date is None:
2818
3061
  return
2819
3062
 
@@ -2821,10 +3064,12 @@ class EntityModelAbstract(MP_Node,
2821
3064
  if not len(ce_date_list):
2822
3065
  return
2823
3066
 
2824
- if all([
2825
- isinstance(io_date, date),
2826
- isinstance(io_date, datetime),
2827
- ]):
3067
+ if all(
3068
+ [
3069
+ isinstance(io_date, date),
3070
+ isinstance(io_date, datetime),
3071
+ ]
3072
+ ):
2828
3073
  io_date = io_date.date()
2829
3074
 
2830
3075
  if io_date > ce_date_list[0]:
@@ -2834,35 +3079,42 @@ class EntityModelAbstract(MP_Node,
2834
3079
  if p and p <= io_date < f:
2835
3080
  return p
2836
3081
 
2837
- def close_entity_books(self,
2838
- closing_date: Optional[date] = None,
2839
- closing_entry_model=None,
2840
- force_update: bool = False,
2841
- post_closing_entry: bool = True):
2842
-
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
+ ):
2843
3089
  if closing_entry_model and closing_date:
2844
3090
  raise EntityModelValidationError(
2845
- 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
+ )
2846
3094
  )
2847
3095
  elif not closing_date and not closing_entry_model:
2848
3096
  raise EntityModelValidationError(
2849
- 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
+ )
2850
3100
  )
2851
3101
 
2852
3102
  closing_entry_exists = False
2853
3103
 
2854
3104
  if closing_entry_model:
2855
3105
  closing_date = closing_entry_model.closing_date
2856
- 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
+ )
2857
3109
  closing_entry_exists = True
2858
3110
  else:
2859
3111
  try:
2860
- closing_entry_model = self.closingentrymodel_set.select_related(
2861
- 'ledger_model',
2862
- 'ledger_model__entity'
2863
- ).defer(
2864
- 'markdown_notes').get(
2865
- 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)
2866
3118
  )
2867
3119
 
2868
3120
  closing_entry_exists = True
@@ -2881,27 +3133,44 @@ class EntityModelAbstract(MP_Node,
2881
3133
  self.save_closing_entry_dates_meta(commit=True)
2882
3134
 
2883
3135
  return closing_entry_model, ce_txs
2884
- 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
+ )
2885
3139
 
2886
- 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
+ ):
2887
3147
  _, day = monthrange(year, month)
2888
3148
  closing_dt = date(year, month, day)
2889
3149
  return self.close_entity_books(
2890
3150
  closing_date=closing_dt,
2891
3151
  force_update=force_update,
2892
3152
  post_closing_entry=post_closing_entry,
2893
- closing_entry_model=None
3153
+ closing_entry_model=None,
2894
3154
  )
2895
3155
 
2896
- def close_books_for_fiscal_year(self, fiscal_year: int, force_update: bool = False,
2897
- 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
+ ):
2898
3162
  closing_dt = self.get_fy_end(year=fiscal_year)
2899
- return self.close_entity_books(closing_date=closing_dt, force_update=force_update,
2900
- 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
+ )
2901
3168
 
2902
3169
  # ### RANDOM DATA GENERATION ####
2903
3170
 
2904
- 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
+ ):
2905
3174
  EntityDataGenerator = lazy_loader.get_entity_data_generator()
2906
3175
  data_generator = EntityDataGenerator(
2907
3176
  user_model=self.admin,
@@ -2909,16 +3178,15 @@ class EntityModelAbstract(MP_Node,
2909
3178
  start_dttm=start_date,
2910
3179
  entity_model=self,
2911
3180
  capital_contribution=Decimal.from_float(50000.00),
2912
- tx_quantity=tx_quantity
3181
+ tx_quantity=tx_quantity,
2913
3182
  )
2914
3183
  data_generator.populate_entity()
2915
3184
 
2916
3185
  # URLS ----
2917
3186
  def get_absolute_url(self):
2918
- return reverse(viewname='django_ledger:entity-dashboard',
2919
- kwargs={
2920
- 'entity_slug': self.slug
2921
- })
3187
+ return reverse(
3188
+ viewname='django_ledger:entity-dashboard', kwargs={'entity_slug': self.slug}
3189
+ )
2922
3190
 
2923
3191
  def get_dashboard_url(self) -> str:
2924
3192
  """
@@ -2929,10 +3197,9 @@ class EntityModelAbstract(MP_Node,
2929
3197
  str
2930
3198
  EntityModel dashboard URL as a string.
2931
3199
  """
2932
- return reverse('django_ledger:entity-dashboard',
2933
- kwargs={
2934
- 'entity_slug': self.slug
2935
- })
3200
+ return reverse(
3201
+ 'django_ledger:entity-dashboard', kwargs={'entity_slug': self.slug}
3202
+ )
2936
3203
 
2937
3204
  def get_manage_url(self) -> str:
2938
3205
  """
@@ -2943,10 +3210,7 @@ class EntityModelAbstract(MP_Node,
2943
3210
  str
2944
3211
  EntityModel manage URL as a string.
2945
3212
  """
2946
- return reverse('django_ledger:entity-update',
2947
- kwargs={
2948
- 'entity_slug': self.slug
2949
- })
3213
+ return reverse('django_ledger:entity-update', kwargs={'entity_slug': self.slug})
2950
3214
 
2951
3215
  def get_ledgers_url(self) -> str:
2952
3216
  """
@@ -2957,10 +3221,7 @@ class EntityModelAbstract(MP_Node,
2957
3221
  str
2958
3222
  EntityModel ledger list URL as a string.
2959
3223
  """
2960
- return reverse('django_ledger:ledger-list',
2961
- kwargs={
2962
- 'entity_slug': self.slug
2963
- })
3224
+ return reverse('django_ledger:ledger-list', kwargs={'entity_slug': self.slug})
2964
3225
 
2965
3226
  def get_bills_url(self) -> str:
2966
3227
  """
@@ -2971,10 +3232,7 @@ class EntityModelAbstract(MP_Node,
2971
3232
  str
2972
3233
  EntityModel bill list URL as a string.
2973
3234
  """
2974
- return reverse('django_ledger:bill-list',
2975
- kwargs={
2976
- 'entity_slug': self.slug
2977
- })
3235
+ return reverse('django_ledger:bill-list', kwargs={'entity_slug': self.slug})
2978
3236
 
2979
3237
  def get_invoices_url(self) -> str:
2980
3238
  """
@@ -2985,10 +3243,7 @@ class EntityModelAbstract(MP_Node,
2985
3243
  str
2986
3244
  EntityModel invoice list URL as a string.
2987
3245
  """
2988
- return reverse('django_ledger:invoice-list',
2989
- kwargs={
2990
- 'entity_slug': self.slug
2991
- })
3246
+ return reverse('django_ledger:invoice-list', kwargs={'entity_slug': self.slug})
2992
3247
 
2993
3248
  def get_banks_url(self) -> str:
2994
3249
  """
@@ -2999,10 +3254,9 @@ class EntityModelAbstract(MP_Node,
2999
3254
  str
3000
3255
  EntityModel bank account list URL as a string.
3001
3256
  """
3002
- return reverse('django_ledger:bank-account-list',
3003
- kwargs={
3004
- 'entity_slug': self.slug
3005
- })
3257
+ return reverse(
3258
+ 'django_ledger:bank-account-list', kwargs={'entity_slug': self.slug}
3259
+ )
3006
3260
 
3007
3261
  def get_balance_sheet_url(self) -> str:
3008
3262
  """
@@ -3013,10 +3267,7 @@ class EntityModelAbstract(MP_Node,
3013
3267
  str
3014
3268
  EntityModel Balance Sheet Statement URL as a string.
3015
3269
  """
3016
- return reverse('django_ledger:entity-bs',
3017
- kwargs={
3018
- 'entity_slug': self.slug
3019
- })
3270
+ return reverse('django_ledger:entity-bs', kwargs={'entity_slug': self.slug})
3020
3271
 
3021
3272
  def get_income_statement_url(self) -> str:
3022
3273
  """
@@ -3027,10 +3278,7 @@ class EntityModelAbstract(MP_Node,
3027
3278
  str
3028
3279
  EntityModel Income Statement URL as a string.
3029
3280
  """
3030
- return reverse('django_ledger:entity-ic',
3031
- kwargs={
3032
- 'entity_slug': self.slug
3033
- })
3281
+ return reverse('django_ledger:entity-ic', kwargs={'entity_slug': self.slug})
3034
3282
 
3035
3283
  def get_cashflow_statement_url(self) -> str:
3036
3284
  """
@@ -3041,10 +3289,7 @@ class EntityModelAbstract(MP_Node,
3041
3289
  str
3042
3290
  EntityModel Cashflow Statement URL as a string.
3043
3291
  """
3044
- return reverse('django_ledger:entity-cf',
3045
- kwargs={
3046
- 'entity_slug': self.slug
3047
- })
3292
+ return reverse('django_ledger:entity-cf', kwargs={'entity_slug': self.slug})
3048
3293
 
3049
3294
  def get_data_import_url(self) -> str:
3050
3295
  """
@@ -3055,33 +3300,24 @@ class EntityModelAbstract(MP_Node,
3055
3300
  str
3056
3301
  EntityModel transaction import URL as a string.
3057
3302
  """
3058
- return reverse('django_ledger:data-import-jobs-list',
3059
- kwargs={
3060
- 'entity_slug': self.slug
3061
- })
3303
+ return reverse(
3304
+ 'django_ledger:data-import-jobs-list', kwargs={'entity_slug': self.slug}
3305
+ )
3062
3306
 
3063
3307
  def get_coa_list_url(self) -> str:
3064
3308
  return reverse(
3065
- viewname='django_ledger:coa-list',
3066
- kwargs={
3067
- 'entity_slug': self.slug
3068
- }
3309
+ viewname='django_ledger:coa-list', kwargs={'entity_slug': self.slug}
3069
3310
  )
3070
3311
 
3071
3312
  def get_coa_list_inactive_url(self) -> str:
3072
3313
  return reverse(
3073
3314
  viewname='django_ledger:coa-list-inactive',
3074
- kwargs={
3075
- 'entity_slug': self.slug
3076
- }
3315
+ kwargs={'entity_slug': self.slug},
3077
3316
  )
3078
3317
 
3079
3318
  def get_coa_create_url(self) -> str:
3080
3319
  return reverse(
3081
- viewname='django_ledger:coa-create',
3082
- kwargs={
3083
- 'entity_slug': self.slug
3084
- }
3320
+ viewname='django_ledger:coa-create', kwargs={'entity_slug': self.slug}
3085
3321
  )
3086
3322
 
3087
3323
  def get_accounts_url(self) -> str:
@@ -3093,10 +3329,12 @@ class EntityModelAbstract(MP_Node,
3093
3329
  str
3094
3330
  EntityModel Code of Accounts llist import URL as a string.
3095
3331
  """
3096
- return reverse('django_ledger:account-list',
3097
- kwargs={
3098
- 'entity_slug': self.slug,
3099
- })
3332
+ return reverse(
3333
+ 'django_ledger:account-list',
3334
+ kwargs={
3335
+ 'entity_slug': self.slug,
3336
+ },
3337
+ )
3100
3338
 
3101
3339
  def get_customers_url(self) -> str:
3102
3340
  """
@@ -3107,10 +3345,12 @@ class EntityModelAbstract(MP_Node,
3107
3345
  str
3108
3346
  EntityModel customers list URL as a string.
3109
3347
  """
3110
- return reverse('django_ledger:customer-list',
3111
- kwargs={
3112
- 'entity_slug': self.slug,
3113
- })
3348
+ return reverse(
3349
+ 'django_ledger:customer-list',
3350
+ kwargs={
3351
+ 'entity_slug': self.slug,
3352
+ },
3353
+ )
3114
3354
 
3115
3355
  def get_vendors_url(self) -> str:
3116
3356
  """
@@ -3121,10 +3361,12 @@ class EntityModelAbstract(MP_Node,
3121
3361
  str
3122
3362
  EntityModel vendors list URL as a string.
3123
3363
  """
3124
- return reverse('django_ledger:vendor-list',
3125
- kwargs={
3126
- 'entity_slug': self.slug,
3127
- })
3364
+ return reverse(
3365
+ 'django_ledger:vendor-list',
3366
+ kwargs={
3367
+ 'entity_slug': self.slug,
3368
+ },
3369
+ )
3128
3370
 
3129
3371
  def get_delete_url(self) -> str:
3130
3372
  """
@@ -3135,10 +3377,7 @@ class EntityModelAbstract(MP_Node,
3135
3377
  str
3136
3378
  EntityModel delete URL as a string.
3137
3379
  """
3138
- return reverse('django_ledger:entity-delete',
3139
- kwargs={
3140
- 'entity_slug': self.slug
3141
- })
3380
+ return reverse('django_ledger:entity-delete', kwargs={'entity_slug': self.slug})
3142
3381
 
3143
3382
  def clean(self):
3144
3383
  if not self.slug:
@@ -3165,6 +3404,7 @@ class EntityStateModelAbstract(Model):
3165
3404
  KEY_VENDOR = 'vendor'
3166
3405
  KEY_CUSTOMER = 'customer'
3167
3406
  KEY_ITEM = 'item'
3407
+ KEY_RECEIPT = 'receipt'
3168
3408
 
3169
3409
  KEY_CHOICES = [
3170
3410
  (KEY_JOURNAL_ENTRY, _('Journal Entry')),
@@ -3175,38 +3415,36 @@ class EntityStateModelAbstract(Model):
3175
3415
  ]
3176
3416
 
3177
3417
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
3178
- entity_model = models.ForeignKey('django_ledger.EntityModel',
3179
- on_delete=models.CASCADE,
3180
- verbose_name=_('Entity Model'))
3181
- entity_unit = models.ForeignKey('django_ledger.EntityUnitModel',
3182
- on_delete=models.RESTRICT,
3183
- verbose_name=_('Entity Unit'),
3184
- blank=True,
3185
- 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
+ )
3186
3430
  fiscal_year = models.SmallIntegerField(
3187
3431
  verbose_name=_('Fiscal Year'),
3188
3432
  validators=[MinValueValidator(limit_value=1900)],
3189
3433
  null=True,
3190
- blank=True
3434
+ blank=True,
3191
3435
  )
3192
3436
  key = models.CharField(choices=KEY_CHOICES, max_length=10)
3193
- sequence = models.BigIntegerField(default=0, validators=[MinValueValidator(limit_value=0)])
3437
+ sequence = models.BigIntegerField(
3438
+ default=0, validators=[MinValueValidator(limit_value=0)]
3439
+ )
3194
3440
 
3195
3441
  class Meta:
3196
3442
  abstract = True
3197
3443
  indexes = [
3198
3444
  models.Index(fields=['key']),
3199
- models.Index(
3200
- fields=[
3201
- 'entity_model',
3202
- 'fiscal_year',
3203
- 'entity_unit',
3204
- 'key'
3205
- ])
3206
- ]
3207
- unique_together = [
3208
- ('entity_model', 'entity_unit', 'fiscal_year', 'key')
3445
+ models.Index(fields=['entity_model', 'fiscal_year', 'entity_unit', 'key']),
3209
3446
  ]
3447
+ unique_together = [('entity_model', 'entity_unit', 'fiscal_year', 'key')]
3210
3448
 
3211
3449
  def __str__(self):
3212
3450
  return f'{self.__class__.__name__} {self.entity_model_id}: FY: {self.fiscal_year}, KEY: {self.get_key_display()}'
@@ -3226,31 +3464,38 @@ class EntityManagementModelAbstract(CreateUpdateMixIn):
3226
3464
  """
3227
3465
  Entity Management Model responsible for manager permissions to read/write.
3228
3466
  """
3467
+
3229
3468
  PERMISSIONS = [
3230
3469
  ('read', _('Read Permissions')),
3231
3470
  ('write', _('Read/Write Permissions')),
3232
- ('suspended', _('No Permissions'))
3471
+ ('suspended', _('No Permissions')),
3233
3472
  ]
3234
3473
 
3235
3474
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
3236
- entity = models.ForeignKey('django_ledger.EntityModel',
3237
- on_delete=models.CASCADE,
3238
- verbose_name=_('Entity'),
3239
- related_name='entity_permissions')
3240
- user = models.ForeignKey(UserModel,
3241
- on_delete=models.CASCADE,
3242
- verbose_name=_('Manager'),
3243
- related_name='entity_permissions')
3244
- permission_level = models.CharField(max_length=10,
3245
- default='read',
3246
- choices=PERMISSIONS,
3247
- 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
+ )
3248
3493
 
3249
3494
  class Meta:
3250
3495
  abstract = True
3251
3496
  indexes = [
3252
3497
  models.Index(fields=['entity', 'user']),
3253
- models.Index(fields=['user', 'entity'])
3498
+ models.Index(fields=['user', 'entity']),
3254
3499
  ]
3255
3500
 
3256
3501