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.
- django_ledger/__init__.py +1 -1
- django_ledger/admin/coa.py +3 -3
- django_ledger/forms/account.py +2 -0
- django_ledger/forms/coa.py +1 -6
- django_ledger/forms/transactions.py +3 -1
- django_ledger/io/io_core.py +95 -79
- django_ledger/io/io_digest.py +4 -5
- django_ledger/io/io_generator.py +5 -4
- django_ledger/io/io_library.py +241 -16
- django_ledger/io/roles.py +32 -10
- django_ledger/migrations/0015_remove_chartofaccountmodel_locked_and_more.py +22 -0
- django_ledger/models/accounts.py +13 -9
- django_ledger/models/bill.py +3 -3
- django_ledger/models/closing_entry.py +39 -28
- django_ledger/models/coa.py +244 -35
- django_ledger/models/entity.py +119 -51
- django_ledger/models/invoice.py +3 -2
- django_ledger/models/journal_entry.py +8 -4
- django_ledger/models/ledger.py +63 -11
- django_ledger/models/mixins.py +2 -2
- django_ledger/models/transactions.py +20 -11
- django_ledger/report/balance_sheet.py +1 -1
- django_ledger/report/cash_flow_statement.py +5 -5
- django_ledger/report/core.py +2 -2
- django_ledger/report/income_statement.py +2 -2
- django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
- django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +10 -11
- django_ledger/templates/django_ledger/account/account_create.html +17 -11
- django_ledger/templates/django_ledger/account/account_list.html +12 -9
- django_ledger/templates/django_ledger/account/tags/account_txs_table.html +6 -1
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +97 -93
- django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +17 -0
- django_ledger/templates/django_ledger/{code_of_accounts → chart_of_accounts}/coa_update.html +1 -4
- django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +74 -0
- django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +1 -1
- django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +6 -6
- django_ledger/templates/django_ledger/includes/widget_ic.html +1 -1
- django_ledger/templates/django_ledger/invoice/invoice_list.html +91 -94
- django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +16 -7
- django_ledger/templates/django_ledger/journal_entry/je_detail.html +1 -1
- django_ledger/templates/django_ledger/ledger/tags/ledgers_table.html +10 -0
- django_ledger/templatetags/django_ledger.py +9 -8
- django_ledger/tests/base.py +134 -8
- django_ledger/tests/test_accounts.py +16 -0
- django_ledger/tests/test_auth.py +5 -7
- django_ledger/tests/test_bill.py +1 -1
- django_ledger/tests/test_chart_of_accounts.py +46 -0
- django_ledger/tests/test_closing_entry.py +16 -19
- django_ledger/tests/test_entity.py +3 -3
- django_ledger/tests/test_io.py +192 -2
- django_ledger/tests/test_transactions.py +290 -0
- django_ledger/urls/account.py +18 -3
- django_ledger/urls/chart_of_accounts.py +21 -1
- django_ledger/urls/ledger.py +7 -0
- django_ledger/views/account.py +29 -4
- django_ledger/views/coa.py +79 -4
- django_ledger/views/djl_api.py +4 -1
- django_ledger/views/journal_entry.py +1 -1
- django_ledger/views/mixins.py +3 -0
- {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/METADATA +1 -1
- {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/RECORD +65 -59
- {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.6.2.dist-info → django_ledger-0.5.6.4.dist-info}/WHEEL +0 -0
- {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
django_ledger/admin/coa.py
CHANGED
|
@@ -84,7 +84,7 @@ class ChartOfAccountsInLine(TabularInline):
|
|
|
84
84
|
show_change_link = True
|
|
85
85
|
fields = [
|
|
86
86
|
'name',
|
|
87
|
-
'
|
|
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
|
-
'
|
|
95
|
+
'active'
|
|
96
96
|
]
|
|
97
97
|
list_display = [
|
|
98
98
|
'entity_name',
|
|
99
99
|
'name',
|
|
100
100
|
'slug',
|
|
101
|
-
'
|
|
101
|
+
'active',
|
|
102
102
|
'account_model_count'
|
|
103
103
|
]
|
|
104
104
|
search_fields = [
|
django_ledger/forms/account.py
CHANGED
django_ledger/forms/coa.py
CHANGED
|
@@ -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
|
-
'
|
|
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
|
django_ledger/io/io_core.py
CHANGED
|
@@ -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
|
|
145
|
+
def validate_io_timestamp(
|
|
146
146
|
dt: Union[str, date, datetime],
|
|
147
|
-
no_parse_localdate: bool = True
|
|
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,
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
352
|
+
txs_queryset_init = TransactionModel.objects.for_entity(
|
|
342
353
|
user_model=user_model,
|
|
343
354
|
entity_slug=self
|
|
344
355
|
)
|
|
345
|
-
elif self.
|
|
356
|
+
elif self.is_entity_unit_model():
|
|
346
357
|
if not entity_slug:
|
|
347
358
|
raise IOValidationError(
|
|
348
|
-
'Calling digest from
|
|
349
|
-
|
|
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
|
-
|
|
363
|
+
unit_slug=unit_slug or self
|
|
353
364
|
)
|
|
354
|
-
elif self.
|
|
365
|
+
elif self.is_ledger_model():
|
|
355
366
|
if not entity_slug:
|
|
356
367
|
raise IOValidationError(
|
|
357
|
-
'Calling digest from
|
|
358
|
-
|
|
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
|
-
|
|
372
|
+
ledger_model=self
|
|
362
373
|
)
|
|
363
374
|
else:
|
|
364
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
393
|
-
|
|
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
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
418
|
-
# f'| to_dt: {ce_to_date}...')
|
|
442
|
+
txs_queryset_agg = TransactionModel.objects.none()
|
|
419
443
|
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
django_ledger/io/io_digest.py
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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()
|
django_ledger/io/io_generator.py
CHANGED
|
@@ -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
|
-
|
|
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...')
|