django-ledger 0.5.6.2__py3-none-any.whl → 0.5.6.4__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 (65) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/admin/coa.py +3 -3
  3. django_ledger/forms/account.py +2 -0
  4. django_ledger/forms/coa.py +1 -6
  5. django_ledger/forms/transactions.py +3 -1
  6. django_ledger/io/io_core.py +95 -79
  7. django_ledger/io/io_digest.py +4 -5
  8. django_ledger/io/io_generator.py +5 -4
  9. django_ledger/io/io_library.py +241 -16
  10. django_ledger/io/roles.py +32 -10
  11. django_ledger/migrations/0015_remove_chartofaccountmodel_locked_and_more.py +22 -0
  12. django_ledger/models/accounts.py +13 -9
  13. django_ledger/models/bill.py +3 -3
  14. django_ledger/models/closing_entry.py +39 -28
  15. django_ledger/models/coa.py +244 -35
  16. django_ledger/models/entity.py +119 -51
  17. django_ledger/models/invoice.py +3 -2
  18. django_ledger/models/journal_entry.py +8 -4
  19. django_ledger/models/ledger.py +63 -11
  20. django_ledger/models/mixins.py +2 -2
  21. django_ledger/models/transactions.py +20 -11
  22. django_ledger/report/balance_sheet.py +1 -1
  23. django_ledger/report/cash_flow_statement.py +5 -5
  24. django_ledger/report/core.py +2 -2
  25. django_ledger/report/income_statement.py +2 -2
  26. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  27. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +10 -11
  28. django_ledger/templates/django_ledger/account/account_create.html +17 -11
  29. django_ledger/templates/django_ledger/account/account_list.html +12 -9
  30. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +6 -1
  31. django_ledger/templates/django_ledger/account/tags/accounts_table.html +97 -93
  32. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +17 -0
  33. django_ledger/templates/django_ledger/{code_of_accounts → chart_of_accounts}/coa_update.html +1 -4
  34. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +74 -0
  35. django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +1 -1
  36. django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +6 -6
  37. django_ledger/templates/django_ledger/includes/widget_ic.html +1 -1
  38. django_ledger/templates/django_ledger/invoice/invoice_list.html +91 -94
  39. django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +16 -7
  40. django_ledger/templates/django_ledger/journal_entry/je_detail.html +1 -1
  41. django_ledger/templates/django_ledger/ledger/tags/ledgers_table.html +10 -0
  42. django_ledger/templatetags/django_ledger.py +9 -8
  43. django_ledger/tests/base.py +134 -8
  44. django_ledger/tests/test_accounts.py +16 -0
  45. django_ledger/tests/test_auth.py +5 -7
  46. django_ledger/tests/test_bill.py +1 -1
  47. django_ledger/tests/test_chart_of_accounts.py +46 -0
  48. django_ledger/tests/test_closing_entry.py +16 -19
  49. django_ledger/tests/test_entity.py +3 -3
  50. django_ledger/tests/test_io.py +192 -2
  51. django_ledger/tests/test_transactions.py +290 -0
  52. django_ledger/urls/account.py +18 -3
  53. django_ledger/urls/chart_of_accounts.py +21 -1
  54. django_ledger/urls/ledger.py +7 -0
  55. django_ledger/views/account.py +29 -4
  56. django_ledger/views/coa.py +79 -4
  57. django_ledger/views/djl_api.py +4 -1
  58. django_ledger/views/journal_entry.py +1 -1
  59. django_ledger/views/mixins.py +3 -0
  60. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/METADATA +1 -1
  61. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/RECORD +65 -59
  62. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/AUTHORS.md +0 -0
  63. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/LICENSE +0 -0
  64. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/WHEEL +0 -0
  65. {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/top_level.txt +0 -0
django_ledger/__init__.py CHANGED
@@ -9,7 +9,7 @@ Contributions to this module:
9
9
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
10
10
 
11
11
  """Django Ledger"""
12
- __version__ = '0.5.6.2'
12
+ __version__ = '0.5.6.4'
13
13
  __license__ = 'GPLv3 License'
14
14
 
15
15
  __author__ = 'Miguel Sanda'
@@ -84,7 +84,7 @@ class ChartOfAccountsInLine(TabularInline):
84
84
  show_change_link = True
85
85
  fields = [
86
86
  'name',
87
- 'locked',
87
+ 'active',
88
88
  'assign_as_default'
89
89
  ]
90
90
 
@@ -92,13 +92,13 @@ class ChartOfAccountsInLine(TabularInline):
92
92
  class ChartOfAccountsModelAdmin(ModelAdmin):
93
93
  list_filter = [
94
94
  'entity__name',
95
- 'locked'
95
+ 'active'
96
96
  ]
97
97
  list_display = [
98
98
  'entity_name',
99
99
  'name',
100
100
  'slug',
101
- 'locked',
101
+ 'active',
102
102
  'account_model_count'
103
103
  ]
104
104
  search_fields = [
@@ -52,6 +52,8 @@ class AccountModelCreateForm(ModelForm):
52
52
  'role',
53
53
  'role_default',
54
54
  'balance_type',
55
+ 'active',
56
+ 'active'
55
57
  ]
56
58
  widgets = {
57
59
  'code': TextInput(attrs={
@@ -9,19 +9,14 @@ class ChartOfAccountsModelForm(ModelForm):
9
9
  class Meta:
10
10
  model = ChartOfAccountModel
11
11
  fields = [
12
- # 'slug',
13
12
  'name',
14
13
  'description'
15
14
  ]
16
15
  labels = {
17
- 'slug': _('CoA ID'),
18
16
  'name': _('Name'),
19
17
  'description': _('Description'),
20
18
  }
21
19
  widgets = {
22
- # 'slug': TextInput(attrs={
23
- # 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
24
- # }),
25
20
  'name': TextInput(attrs={
26
21
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
27
22
  }),
@@ -36,7 +31,7 @@ class ChartOfAccountsModelUpdateForm(ModelForm):
36
31
  model = ChartOfAccountModel
37
32
  fields = [
38
33
  'name',
39
- 'locked'
34
+ 'active'
40
35
  ]
41
36
  labels = {
42
37
  'name': _('Name'),
@@ -50,7 +50,6 @@ class TransactionModelFormSet(BaseModelFormSet):
50
50
  self.JE_MODEL: JournalEntryModel = je_model
51
51
  self.LEDGER_PK = ledger_pk
52
52
  self.ENTITY_SLUG = entity_slug
53
- self.queryset = self.JE_MODEL.transactionmodel_set.all().order_by('account__code')
54
53
 
55
54
  account_qs = AccountModel.objects.for_entity_available(
56
55
  user_model=self.USER_MODEL,
@@ -64,6 +63,9 @@ class TransactionModelFormSet(BaseModelFormSet):
64
63
  form.fields['tx_type'].disabled = True
65
64
  form.fields['amount'].disabled = True
66
65
 
66
+ def get_queryset(self):
67
+ return self.JE_MODEL.transactionmodel_set.all().order_by('account__code')
68
+
67
69
  def clean(self):
68
70
  if any(self.errors):
69
71
  return
@@ -26,7 +26,7 @@ from zoneinfo import ZoneInfo
26
26
  from django.conf import settings as global_settings
27
27
  from django.contrib.auth import get_user_model
28
28
  from django.core.exceptions import ValidationError, ObjectDoesNotExist
29
- from django.db.models import Sum, QuerySet
29
+ from django.db.models import Sum, QuerySet, F, DecimalField, When, Case
30
30
  from django.db.models.functions import TruncMonth
31
31
  from django.http import Http404
32
32
  from django.utils.dateparse import parse_date, parse_datetime
@@ -142,16 +142,14 @@ def get_localdate() -> date:
142
142
  return datetime.today()
143
143
 
144
144
 
145
- def validate_io_date(
145
+ def validate_io_timestamp(
146
146
  dt: Union[str, date, datetime],
147
- no_parse_localdate: bool = True) -> Optional[Union[datetime, date]]:
147
+ no_parse_localdate: bool = True
148
+ ) -> Optional[Union[datetime, date]]:
148
149
  if not dt:
149
150
  return
150
151
 
151
- if isinstance(dt, date):
152
- return dt
153
-
154
- elif isinstance(dt, datetime):
152
+ if isinstance(dt, datetime):
155
153
  if is_naive(dt):
156
154
  return make_aware(
157
155
  value=dt,
@@ -178,15 +176,23 @@ def validate_io_date(
178
176
  ))
179
177
  return datetime.combine(fdt, datetime.min.time())
180
178
 
179
+ elif isinstance(dt, date):
180
+ if global_settings.USE_TZ:
181
+ return make_aware(
182
+ value=datetime.combine(dt, datetime.min.time())
183
+ )
184
+ return datetime.combine(dt, datetime.min.time())
185
+
181
186
  if no_parse_localdate:
182
187
  return localtime()
183
188
 
184
189
 
185
190
  def validate_dates(
186
191
  from_date: Union[str, datetime, date] = None,
187
- to_date: Union[str, datetime, date] = None) -> Tuple[date, date]:
188
- from_date = validate_io_date(from_date, no_parse_localdate=False)
189
- to_date = validate_io_date(to_date)
192
+ to_date: Union[str, datetime, date] = None
193
+ ) -> Tuple[date, date]:
194
+ from_date = validate_io_timestamp(from_date, no_parse_localdate=False)
195
+ to_date = validate_io_timestamp(to_date)
190
196
  return from_date, to_date
191
197
 
192
198
 
@@ -226,6 +232,13 @@ class IOResult:
226
232
  # the aggregated account balance...
227
233
  accounts_digest: Optional[List[Dict]] = None
228
234
 
235
+ @property
236
+ def is_bounded(self) -> bool:
237
+ return all([
238
+ self.ce_from_date is not None,
239
+ self.ce_to_date is not None,
240
+ ])
241
+
229
242
 
230
243
  class IODatabaseMixIn:
231
244
  """
@@ -275,7 +288,7 @@ class IODatabaseMixIn:
275
288
  accounts: Optional[Union[str, List[str], Set[str]]] = None,
276
289
  posted: bool = True,
277
290
  exclude_zero_bal: bool = True,
278
- force_closing_entry_use: bool = False,
291
+ use_closing_entries: bool = False,
279
292
  **kwargs) -> IOResult:
280
293
  """
281
294
  Performs the appropriate database aggregation query for a given request.
@@ -314,57 +327,67 @@ class IODatabaseMixIn:
314
327
  Returns results aggregated by accounting if needed. Defaults to False.
315
328
  by_unit: bool
316
329
  Returns results aggregated by unit if needed. Defaults to False.
317
- force_closing_entry_use: bool
318
- Forces the use of closing entries if DJANGO_LEDGER_USE_CLOSING_ENTRIES setting is set to False.
330
+ use_closing_entry: bool
331
+ Overrides the DJANGO_LEDGER_USE_CLOSING_ENTRIES setting.
319
332
  Returns
320
333
  -------
321
334
  IOResult
322
335
  """
323
336
 
324
337
  TransactionModel = lazy_loader.get_txs_model()
325
- io_result = IOResult(db_to_date=to_date, db_from_date=from_date)
326
- txs_queryset_closing_entry = TransactionModel.objects.none()
327
338
 
328
- # where the IO model is operating from??...
339
+ # get_initial txs_queryset... where the IO model is operating from??...
329
340
  if self.is_entity_model():
330
341
  if entity_slug:
331
342
  if entity_slug != self.slug:
332
343
  raise IOValidationError('Inconsistent entity_slug. '
333
344
  f'Provided {entity_slug} does not match actual {self.slug}')
334
345
  if unit_slug:
335
- txs_queryset = TransactionModel.objects.for_unit(
346
+ txs_queryset_init = TransactionModel.objects.for_unit(
336
347
  user_model=user_model,
337
348
  entity_slug=entity_slug or self.slug,
338
349
  unit_slug=unit_slug
339
350
  )
340
351
  else:
341
- txs_queryset = TransactionModel.objects.for_entity(
352
+ txs_queryset_init = TransactionModel.objects.for_entity(
342
353
  user_model=user_model,
343
354
  entity_slug=self
344
355
  )
345
- elif self.is_ledger_model():
356
+ elif self.is_entity_unit_model():
346
357
  if not entity_slug:
347
358
  raise IOValidationError(
348
- 'Calling digest from Ledger Model requires entity_slug explicitly for safety')
349
- txs_queryset = TransactionModel.objects.for_ledger(
359
+ 'Calling digest from Entity Unit requires entity_slug explicitly for safety')
360
+ txs_queryset_init = TransactionModel.objects.for_unit(
350
361
  user_model=user_model,
351
362
  entity_slug=entity_slug,
352
- ledger_model=self
363
+ unit_slug=unit_slug or self
353
364
  )
354
- elif self.is_entity_unit_model():
365
+ elif self.is_ledger_model():
355
366
  if not entity_slug:
356
367
  raise IOValidationError(
357
- 'Calling digest from Entity Unit requires entity_slug explicitly for safety')
358
- txs_queryset = TransactionModel.objects.for_unit(
368
+ 'Calling digest from Ledger Model requires entity_slug explicitly for safety')
369
+ txs_queryset_init = TransactionModel.objects.for_ledger(
359
370
  user_model=user_model,
360
371
  entity_slug=entity_slug,
361
- unit_slug=unit_slug or self
372
+ ledger_model=self
362
373
  )
363
374
  else:
364
- txs_queryset = TransactionModel.objects.none()
375
+ raise IOValidationError(
376
+ message=f'Cannot call digest from {self.__class__.__name__}'
377
+ )
378
+
379
+ io_result = IOResult(db_to_date=to_date, db_from_date=from_date)
380
+ txs_queryset_agg = txs_queryset_init.not_closing_entry()
381
+ txs_queryset_from_closing_entry = txs_queryset_init.none()
382
+ txs_queryset_to_closing_entry = txs_queryset_init.none()
383
+
384
+ USE_CLOSING_ENTRIES = settings.DJANGO_LEDGER_USE_CLOSING_ENTRIES
385
+ if use_closing_entries is not None:
386
+ USE_CLOSING_ENTRIES = use_closing_entries
365
387
 
366
- # use closing entries to minimize DB aggregation if activated...
367
- if settings.DJANGO_LEDGER_USE_CLOSING_ENTRIES or force_closing_entry_use:
388
+ # use closing entries to minimize DB aggregation if possible and activated...
389
+ if USE_CLOSING_ENTRIES:
390
+ txs_queryset_closing_entry = txs_queryset_init.is_closing_entry()
368
391
  entity_model = self.get_entity_model_from_io()
369
392
 
370
393
  # looking up available dates...
@@ -378,57 +401,55 @@ class IODatabaseMixIn:
378
401
 
379
402
  # if there's a suitable closing entry...
380
403
  if ce_alt_from_date:
381
- txs_queryset_closing_entry = txs_queryset.is_closing_entry().filter(
382
- journal_entry__timestamp__date=ce_alt_from_date
383
- )
404
+ txs_queryset_from_closing_entry = txs_queryset_closing_entry.filter(
405
+ journal_entry__timestamp__date=ce_alt_from_date)
384
406
  io_result.ce_match = True
385
407
  io_result.ce_from_date = ce_alt_from_date
408
+
386
409
  # limit db aggregation to unclosed entries...
387
410
  io_result.db_from_date = ce_alt_from_date + timedelta(days=1)
388
411
  io_result.db_to_date = to_date
412
+
389
413
  # print(f'Unbounded lookup no date match. Closest from_dt: {ce_alt_from_date}...')
390
414
 
391
415
  # unbounded lookup, exact to_date match...
392
- elif not ce_from_date and ce_to_date:
393
- txs_queryset_closing_entry = txs_queryset.is_closing_entry().filter(
416
+ elif not from_date and ce_to_date:
417
+ txs_queryset_to_closing_entry = txs_queryset_closing_entry.filter(
394
418
  journal_entry__timestamp__date=ce_to_date)
395
419
  io_result.ce_match = True
396
420
  io_result.ce_to_date = ce_to_date
397
421
 
398
- # no need to DB aggregate...
422
+ # no need to DB aggregate, just use closing entry...
399
423
  io_result.db_from_date = None
400
424
  io_result.db_to_date = None
401
- # print(f'Unbounded lookup EXACT date match. Closest to_dt: {ce_to_date}...')
425
+ txs_queryset_agg = TransactionModel.objects.none()
402
426
 
403
427
  # bounded exact from_date and to_date match...
404
428
  elif ce_from_date and ce_to_date:
405
- txs_queryset_closing_entry = txs_queryset.is_closing_entry().filter(
406
- journal_entry__timestamp__date__in=[
407
- ce_from_date,
408
- ce_to_date
409
- ])
429
+ txs_queryset_from_closing_entry = txs_queryset_closing_entry.filter(
430
+ journal_entry__timestamp__date=ce_from_date)
431
+
432
+ txs_queryset_to_closing_entry = txs_queryset_closing_entry.filter(
433
+ journal_entry__timestamp__date=ce_to_date)
434
+
410
435
  io_result.ce_match = True
411
436
  io_result.ce_from_date = ce_from_date
412
437
  io_result.ce_to_date = ce_to_date
413
438
 
414
- # no need to aggregate...
439
+ # no need to aggregate, use both closing entries...
415
440
  io_result.db_from_date = None
416
441
  io_result.db_to_date = None
417
- # print(f'Bounded lookup EXACT date match. Closest from_dt: {ce_from_date} '
418
- # f'| to_dt: {ce_to_date}...')
442
+ txs_queryset_agg = TransactionModel.objects.none()
419
443
 
420
- # no suitable closing entries to use...
421
- else:
422
- txs_queryset = txs_queryset.not_closing_entry()
423
- else:
424
- # not using closing entries...
425
- txs_queryset = txs_queryset.not_closing_entry()
444
+ txs_queryset_closing_entry = txs_queryset_from_closing_entry | txs_queryset_to_closing_entry
426
445
 
427
446
  if io_result.db_from_date:
428
- txs_queryset = txs_queryset.from_date(from_date=io_result.db_from_date)
447
+ txs_queryset_agg = txs_queryset_agg.from_date(from_date=io_result.db_from_date)
429
448
 
430
449
  if io_result.db_to_date:
431
- txs_queryset = txs_queryset.to_date(to_date=io_result.db_to_date)
450
+ txs_queryset_agg = txs_queryset_agg.to_date(to_date=io_result.db_to_date)
451
+
452
+ txs_queryset = txs_queryset_agg | txs_queryset_closing_entry
432
453
 
433
454
  if exclude_zero_bal:
434
455
  txs_queryset = txs_queryset.filter(amount__gt=0.00)
@@ -449,6 +470,16 @@ class IODatabaseMixIn:
449
470
  if role:
450
471
  txs_queryset = txs_queryset.for_roles(role_list=role)
451
472
 
473
+ if io_result.is_bounded:
474
+ txs_queryset = txs_queryset.annotate(
475
+ amount_io=Case(
476
+ When(
477
+ journal_entry__timestamp__date=ce_from_date,
478
+ then=-F('amount')),
479
+ default=F('amount'),
480
+ output_field=DecimalField()
481
+ ))
482
+
452
483
  VALUES = [
453
484
  'account__uuid',
454
485
  'account__balance_type',
@@ -457,7 +488,11 @@ class IODatabaseMixIn:
457
488
  'account__name',
458
489
  'account__role',
459
490
  ]
491
+
460
492
  ANNOTATE = {'balance': Sum('amount')}
493
+ if io_result.is_bounded:
494
+ ANNOTATE = {'balance': Sum('amount_io')}
495
+
461
496
  ORDER_BY = ['account__uuid']
462
497
 
463
498
  if by_unit:
@@ -476,8 +511,6 @@ class IODatabaseMixIn:
476
511
  ORDER_BY.append('tx_type')
477
512
  VALUES.append('tx_type')
478
513
 
479
- txs_queryset = txs_queryset | txs_queryset_closing_entry
480
-
481
514
  io_result.txs_queryset = txs_queryset.values(*VALUES).annotate(**ANNOTATE).order_by(*ORDER_BY)
482
515
  return io_result
483
516
 
@@ -496,7 +529,7 @@ class IODatabaseMixIn:
496
529
  by_activity: bool = False,
497
530
  by_tx_type: bool = False,
498
531
  by_period: bool = False,
499
- force_closing_entry_use: bool = False,
532
+ use_closing_entries: bool = False,
500
533
  force_queryset_sorting: bool = False,
501
534
  **kwargs) -> IOResult:
502
535
  """
@@ -565,7 +598,7 @@ class IODatabaseMixIn:
565
598
  activity=activity,
566
599
  role=role,
567
600
  accounts=accounts,
568
- force_closing_entry_use=force_closing_entry_use,
601
+ use_closing_entries=use_closing_entries,
569
602
  **kwargs)
570
603
 
571
604
  for tx_model in io_result.txs_queryset:
@@ -649,6 +682,7 @@ class IODatabaseMixIn:
649
682
  balance_sheet_statement: bool = False,
650
683
  income_statement: bool = False,
651
684
  cash_flow_statement: bool = False,
685
+ use_closing_entry: Optional[bool] = None,
652
686
  **kwargs) -> IODigestContextManager:
653
687
 
654
688
  if balance_sheet_statement:
@@ -690,6 +724,7 @@ class IODatabaseMixIn:
690
724
  by_unit=by_unit,
691
725
  by_activity=by_activity,
692
726
  by_tx_type=by_tx_type,
727
+ use_closing_entry=use_closing_entry,
693
728
  **kwargs
694
729
  )
695
730
 
@@ -759,35 +794,15 @@ class IODatabaseMixIn:
759
794
  je_unit_model=None,
760
795
  je_desc=None,
761
796
  je_origin=None,
762
- force_je_retrieval: bool = False):
763
- """
764
- Creates JE from TXS list using provided account_id.
765
-
766
- TXS = List[{
767
- 'account': Account Database UUID
768
- 'tx_type': credit/debit,
769
- 'amount': Decimal/Float/Integer,
770
- 'description': string,
771
- 'staged_tx_model': StagedTransactionModel or None
772
- }]
773
-
774
- :param je_timestamp:
775
- :param je_txs:
776
- :param je_activity:
777
- :param je_posted:
778
- :param je_ledger_model:
779
- :param je_desc:
780
- :param je_origin:
781
- :param je_parent:
782
- :return:
783
- """
797
+ force_je_retrieval: bool = False,
798
+ **kwargs):
784
799
 
785
800
  JournalEntryModel = lazy_loader.get_journal_entry_model()
786
801
  TransactionModel = lazy_loader.get_txs_model()
787
802
 
788
803
  # Validates that credits/debits balance.
789
804
  check_tx_balance(je_txs, perform_correction=False)
790
- je_timestamp = validate_io_date(dt=je_timestamp)
805
+ je_timestamp = validate_io_timestamp(dt=je_timestamp)
791
806
 
792
807
  entity_model = self.get_entity_model_from_io()
793
808
 
@@ -861,6 +876,7 @@ class IODatabaseMixIn:
861
876
  )
862
877
  je_model.save(verify=False)
863
878
 
879
+ # todo: add method to process list of transaction models...
864
880
  txs_models = [
865
881
  (
866
882
  TransactionModel(
@@ -6,7 +6,7 @@ Contributions to this module:
6
6
  * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
  from collections import defaultdict
9
- from datetime import date
9
+ from datetime import datetime
10
10
  from typing import Dict, Optional
11
11
 
12
12
  from django.core.exceptions import ValidationError
@@ -24,7 +24,6 @@ class IODigestContextManager:
24
24
  self.IO_DATA: Dict = io_state
25
25
  self.IO_RESULT = io_state['io_result']
26
26
  self.IO_MODEL = io_state['io_model']
27
- self.TXS_QS = io_state['io_result']
28
27
  self.STRFTIME_FORMAT = '%B %d, %Y'
29
28
 
30
29
  def get_io_data(self) -> Dict:
@@ -34,12 +33,12 @@ class IODigestContextManager:
34
33
  return self.IO_RESULT
35
34
 
36
35
  def get_io_txs_queryset(self):
37
- return self.TXS_QS
36
+ return self.IO_RESULT.txs_queryset
38
37
 
39
38
  def get_strftime_format(self):
40
39
  return self.STRFTIME_FORMAT
41
40
 
42
- def get_from_date(self, as_str: bool = False, fmt=None) -> Optional[date]:
41
+ def get_from_datetime(self, as_str: bool = False, fmt=None) -> Optional[datetime]:
43
42
  from_date = self.IO_DATA['from_date']
44
43
  if from_date:
45
44
  if as_str:
@@ -48,7 +47,7 @@ class IODigestContextManager:
48
47
  return from_date.strftime(fmt)
49
48
  return from_date
50
49
 
51
- def get_to_date(self, as_str: bool = False, fmt=None) -> date:
50
+ def get_to_datetime(self, as_str: bool = False, fmt=None) -> datetime:
52
51
  if as_str:
53
52
  if not fmt:
54
53
  fmt = self.get_strftime_format()
@@ -134,7 +134,7 @@ class EntityDataGenerator(LoggingMixIn):
134
134
  def get_logger_name(self):
135
135
  return self.entity_model.slug
136
136
 
137
- def populate_entity(self, force_populate: bool = False):
137
+ def populate_entity(self, create_closing_entry: bool = False, force_populate: bool = False):
138
138
 
139
139
  self.logger.info('Checking for existing transactions...')
140
140
  txs_qs = TransactionModel.objects.for_entity(
@@ -176,7 +176,8 @@ class EntityDataGenerator(LoggingMixIn):
176
176
  start_dttm = self.start_date + timedelta(days=randint(0, self.DAYS_FORWARD))
177
177
  self.create_invoice(date_draft=start_dttm)
178
178
 
179
- self.create_closing_entry()
179
+ if create_closing_entry:
180
+ self.create_closing_entry()
180
181
 
181
182
  def get_next_timestamp(self, prev_timestamp: Union[date, datetime] = None) -> date:
182
183
  if not prev_timestamp:
@@ -792,9 +793,9 @@ class EntityDataGenerator(LoggingMixIn):
792
793
  closing_date = self.start_date + timedelta(days=int(self.DAYS_FORWARD / 2))
793
794
  ce_model, ce_txs = self.entity_model.close_books_for_month(
794
795
  year=closing_date.year,
795
- month=closing_date.month
796
+ month=closing_date.month,
797
+ post_closing_entry=True
796
798
  )
797
- ce_model.mark_as_posted(commit=True)
798
799
 
799
800
  def recount_inventory(self):
800
801
  self.logger.info(f'Recounting inventory...')