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/io/io_library.py
CHANGED
|
@@ -4,13 +4,15 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
|
4
4
|
|
|
5
5
|
Contributions to this module:
|
|
6
6
|
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
+
|
|
8
|
+
This module contains classes and functions used to document, dispatch and commit new transaction into the database.
|
|
7
9
|
"""
|
|
8
10
|
from collections import defaultdict
|
|
9
11
|
from dataclasses import dataclass
|
|
10
12
|
from datetime import date, datetime
|
|
11
13
|
from decimal import Decimal
|
|
12
14
|
from itertools import chain
|
|
13
|
-
from typing import Union, Dict, Callable, Optional
|
|
15
|
+
from typing import Union, Dict, Callable, Optional, List
|
|
14
16
|
from uuid import UUID
|
|
15
17
|
|
|
16
18
|
from django.core.exceptions import ValidationError
|
|
@@ -62,6 +64,23 @@ class IOCursorValidationError(ValidationError):
|
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
class IOCursor:
|
|
67
|
+
"""
|
|
68
|
+
Represents a Django Ledger cursor capable of dispatching transactions to the database.
|
|
69
|
+
The Cursor class is responsible for coordinating the creation of new ledgers, journal entries and transactions
|
|
70
|
+
It is a low level interface to the IOBlueprint and IOLibrary classes.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
io_library: IOLibrary
|
|
75
|
+
The IOLibrary class that contains all the necessary instructions to dispatch the transactions.
|
|
76
|
+
entity_model: EntityModel
|
|
77
|
+
The EntityModel instance that will be used for the new transactions.
|
|
78
|
+
user_model: UserModel
|
|
79
|
+
The UserModel instance that will be used for the new transactions. Used for read permissions of QuerySets.
|
|
80
|
+
coa_model: ChartOfAccountModel or UUID or str.
|
|
81
|
+
The ChartOfAccountModel instance that contains the accounts to be used for transactions.
|
|
82
|
+
Instance, UUID or slug can be sued to retrieve the model.
|
|
83
|
+
"""
|
|
65
84
|
|
|
66
85
|
def __init__(self,
|
|
67
86
|
io_library,
|
|
@@ -81,25 +100,61 @@ class IOCursor:
|
|
|
81
100
|
self.instructions = None
|
|
82
101
|
|
|
83
102
|
def get_ledger_model_qs(self) -> LedgerModelQuerySet:
|
|
103
|
+
"""
|
|
104
|
+
Determines the ledger model queryset associated with the entity model and user model provided.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
LedgerModelQuerySet
|
|
109
|
+
"""
|
|
84
110
|
return LedgerModel.objects.for_entity(
|
|
85
111
|
self.ENTITY_MODEL,
|
|
86
112
|
self.USER_MODEL
|
|
87
113
|
)
|
|
88
114
|
|
|
89
115
|
def get_account_model_qs(self) -> AccountModelQuerySet:
|
|
116
|
+
"""
|
|
117
|
+
Determines the AccountModelQuerySet associated with the Chart of Accounts specified.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
AccountModelQuerySet
|
|
122
|
+
"""
|
|
90
123
|
return self.ENTITY_MODEL.get_coa_accounts(
|
|
91
124
|
coa_model=self.COA_MODEL
|
|
92
125
|
)
|
|
93
126
|
|
|
94
|
-
def resolve_account_model_qs(self, codes):
|
|
127
|
+
def resolve_account_model_qs(self, codes: List[str]) -> AccountModelQuerySet:
|
|
128
|
+
"""
|
|
129
|
+
Resolves the final AccountModelQuerySet associated with the given account codes used by the blueprint.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
codes: List[str]
|
|
134
|
+
List of codes used during the execution of the blueprint.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
AccountModelQuerySet
|
|
140
|
+
The resolved AccountModelQuerySet associated with the given codes.
|
|
141
|
+
"""
|
|
95
142
|
if self.account_model_qs is None:
|
|
96
|
-
# codes = self.get_account_codes()
|
|
97
143
|
qs = self.get_account_model_qs()
|
|
98
144
|
qs = qs.filter(code__in=codes)
|
|
99
145
|
self.account_model_qs = qs
|
|
100
146
|
return self.account_model_qs
|
|
101
147
|
|
|
102
|
-
def resolve_ledger_model_qs(self):
|
|
148
|
+
def resolve_ledger_model_qs(self) -> LedgerModelQuerySet:
|
|
149
|
+
"""
|
|
150
|
+
Resolves the final LedgerModelQuerySet associated with the provided ledger model identifiers used by the
|
|
151
|
+
blueprints.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
LedgerModelQuerySet
|
|
156
|
+
The resolved LedgerModelQuerySet associated with the given ledger model identifiers.
|
|
157
|
+
"""
|
|
103
158
|
if self.ledger_model_qs is None:
|
|
104
159
|
qs = self.get_ledger_model_qs()
|
|
105
160
|
by_uuid = [k for k in self.blueprints.keys() if isinstance(k, UUID)]
|
|
@@ -113,6 +168,20 @@ class IOCursor:
|
|
|
113
168
|
name,
|
|
114
169
|
ledger_model: Optional[Union[str, LedgerModel, UUID]] = None,
|
|
115
170
|
**kwargs):
|
|
171
|
+
"""
|
|
172
|
+
Stages the instructions to be processed by the IOCursor class. This method does not commit the transactions
|
|
173
|
+
into the database.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
name: str
|
|
178
|
+
The registered blueprint name to be staged.
|
|
179
|
+
ledger_model: Optional[Union[str, LedgerModel, UUID]]
|
|
180
|
+
Optional ledger model identifier to house the transactions associated with the blueprint. If none is
|
|
181
|
+
provided, a new ledger model will be created.
|
|
182
|
+
kwargs
|
|
183
|
+
The keyword arguments to be passed to the blueprint function.
|
|
184
|
+
"""
|
|
116
185
|
|
|
117
186
|
if not isinstance(ledger_model, (str, UUID, LedgerModel)):
|
|
118
187
|
raise IOCursorValidationError(
|
|
@@ -126,8 +195,15 @@ class IOCursor:
|
|
|
126
195
|
blueprint_txs = blueprint_func(**kwargs)
|
|
127
196
|
self.blueprints[ledger_model].append(blueprint_txs)
|
|
128
197
|
|
|
129
|
-
def compile_instructions(self):
|
|
198
|
+
def compile_instructions(self) -> Dict:
|
|
199
|
+
"""
|
|
200
|
+
Compiles the blueprint instructions into Journal Entries and Transactions to be committed to the ledger.
|
|
130
201
|
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
Dict
|
|
205
|
+
A dictionary containing the compiled instructions.
|
|
206
|
+
"""
|
|
131
207
|
if self.instructions is None:
|
|
132
208
|
instructions = {
|
|
133
209
|
ledger_model: list(chain.from_iterable(
|
|
@@ -139,8 +215,6 @@ class IOCursor:
|
|
|
139
215
|
total_credits = sum(t.amount for t in txs if t.tx_type == CREDIT)
|
|
140
216
|
total_debits = sum(t.amount for t in txs if t.tx_type == DEBIT)
|
|
141
217
|
|
|
142
|
-
# print("{} credits, {} debits".format(total_credits, total_debits))
|
|
143
|
-
|
|
144
218
|
if total_credits != total_debits:
|
|
145
219
|
raise IOCursorValidationError(
|
|
146
220
|
message=_('Total transactions Credits and Debits must be equal. '
|
|
@@ -151,12 +225,39 @@ class IOCursor:
|
|
|
151
225
|
return self.instructions
|
|
152
226
|
|
|
153
227
|
def is_committed(self) -> bool:
|
|
228
|
+
"""
|
|
229
|
+
Determines if the IOCursor instance has committed the transactions into the database.
|
|
230
|
+
A cursor can only commit transactions once.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
bool
|
|
235
|
+
True if committed, False otherwise.
|
|
236
|
+
"""
|
|
154
237
|
return self.__COMMITTED
|
|
155
238
|
|
|
156
239
|
def commit(self,
|
|
157
240
|
je_timestamp: Optional[Union[datetime, date, str]] = None,
|
|
158
241
|
post_new_ledgers: bool = False,
|
|
159
|
-
post_journal_entries: bool = False
|
|
242
|
+
post_journal_entries: bool = False,
|
|
243
|
+
**kwargs):
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
Commits the compiled blueprint transactions into the database. This action is irreversible and if journal
|
|
247
|
+
entries are posted, the books will be immediately impacted by the new transactions. It is encouraged NOT to
|
|
248
|
+
post anything until the transaction is reviews for accuracy.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
je_timestamp: Optional[Union[datetime, date, str]]
|
|
253
|
+
The date or timestamp used for the committed journal entries. If none, localtime will be used.
|
|
254
|
+
post_new_ledgers: bool
|
|
255
|
+
If a new ledger is created, the ledger model will be posted to the database.
|
|
256
|
+
post_journal_entries: bool
|
|
257
|
+
If new journal entries are created, the journal entry models will be posted to the database.
|
|
258
|
+
kwargs
|
|
259
|
+
Additional keyword arguments passed to the IO commit_txs function.
|
|
260
|
+
"""
|
|
160
261
|
if self.is_committed():
|
|
161
262
|
raise IOCursorValidationError(
|
|
162
263
|
message=_('Transactions already committed')
|
|
@@ -231,7 +332,8 @@ class IOCursor:
|
|
|
231
332
|
je, txs_models = ledger_model.commit_txs(
|
|
232
333
|
je_timestamp=je_timestamp if je_timestamp else get_localtime(),
|
|
233
334
|
je_txs=je_txs,
|
|
234
|
-
je_posted=post_journal_entries
|
|
335
|
+
je_posted=post_journal_entries,
|
|
336
|
+
**kwargs
|
|
235
337
|
)
|
|
236
338
|
|
|
237
339
|
results[ledger_model] = {
|
|
@@ -253,11 +355,41 @@ class IOBluePrintValidationError(ValidationError):
|
|
|
253
355
|
|
|
254
356
|
|
|
255
357
|
class IOBluePrint:
|
|
358
|
+
"""
|
|
359
|
+
This class documents instructions required to assemble and dispatch transactions into the ledger.
|
|
360
|
+
|
|
361
|
+
Parameters
|
|
362
|
+
----------
|
|
363
|
+
name: str, optional
|
|
364
|
+
The human-readable name of the IOBluePrint instance.
|
|
365
|
+
precision_decimals: int
|
|
366
|
+
The number of decimals to use when balancing transactions. Defaults to 2.
|
|
367
|
+
"""
|
|
256
368
|
|
|
257
|
-
def __init__(self, precision_decimals: int = 2):
|
|
369
|
+
def __init__(self, name: Optional[str] = None, precision_decimals: int = 2):
|
|
370
|
+
self.name = name
|
|
258
371
|
self.precision_decimals = precision_decimals
|
|
259
372
|
self.registry = list()
|
|
260
373
|
|
|
374
|
+
def get_name(self, entity_model: EntityModel) -> str:
|
|
375
|
+
"""
|
|
376
|
+
Determines the name of the blueprint if none provided.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
entity_model: EntityModel
|
|
381
|
+
The EntityModel instance where the resulting blueprint transactions will be stored.
|
|
382
|
+
|
|
383
|
+
Returns
|
|
384
|
+
-------
|
|
385
|
+
str
|
|
386
|
+
The name of the blueprint.
|
|
387
|
+
"""
|
|
388
|
+
if self.name is None:
|
|
389
|
+
l_dt = get_localtime()
|
|
390
|
+
return f'blueprint-{entity_model.slug}-{l_dt.strftime("%m%d%Y-%H%M%S")}'
|
|
391
|
+
return self.name
|
|
392
|
+
|
|
261
393
|
def _round_amount(self, amount: Decimal) -> Decimal:
|
|
262
394
|
return round(amount, self.precision_decimals)
|
|
263
395
|
|
|
@@ -281,7 +413,18 @@ class IOBluePrint:
|
|
|
281
413
|
)
|
|
282
414
|
|
|
283
415
|
def credit(self, account_code: str, amount: Union[float, Decimal], description: str = None):
|
|
284
|
-
|
|
416
|
+
"""
|
|
417
|
+
Registers a CREDIT to the specified account..
|
|
418
|
+
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
account_code: str
|
|
422
|
+
The account code to use for the transaction.
|
|
423
|
+
amount: float or Decimal
|
|
424
|
+
The amount of the transaction.
|
|
425
|
+
description: str
|
|
426
|
+
Description of the transaction.
|
|
427
|
+
"""
|
|
285
428
|
self.registry.append(
|
|
286
429
|
TransactionInstructionItem(
|
|
287
430
|
account_code=account_code,
|
|
@@ -291,7 +434,18 @@ class IOBluePrint:
|
|
|
291
434
|
))
|
|
292
435
|
|
|
293
436
|
def debit(self, account_code: str, amount: Union[float, Decimal], description: str = None):
|
|
294
|
-
|
|
437
|
+
"""
|
|
438
|
+
Registers a DEBIT to the specified account.
|
|
439
|
+
|
|
440
|
+
Parameters
|
|
441
|
+
----------
|
|
442
|
+
account_code: str
|
|
443
|
+
The account code to use for the transaction.
|
|
444
|
+
amount: float or Decimal
|
|
445
|
+
The amount of the transaction.
|
|
446
|
+
description: str
|
|
447
|
+
Description of the transaction.
|
|
448
|
+
"""
|
|
295
449
|
self.registry.append(
|
|
296
450
|
TransactionInstructionItem(
|
|
297
451
|
account_code=account_code,
|
|
@@ -306,20 +460,63 @@ class IOBluePrint:
|
|
|
306
460
|
ledger_model: Optional[Union[str, LedgerModel, UUID]] = None,
|
|
307
461
|
je_timestamp: Optional[Union[datetime, date, str]] = None,
|
|
308
462
|
post_new_ledgers: bool = False,
|
|
309
|
-
post_journal_entries: bool = False
|
|
463
|
+
post_journal_entries: bool = False,
|
|
464
|
+
**kwargs) -> Dict:
|
|
465
|
+
"""
|
|
466
|
+
Commits the blueprint transactions to the database.
|
|
467
|
+
|
|
468
|
+
Parameters
|
|
469
|
+
----------
|
|
470
|
+
entity_model: EntityModel
|
|
471
|
+
The entity model instance where transactions will be committed.
|
|
472
|
+
user_model: UserModel
|
|
473
|
+
The user model instance executing transactions to check for permissions.
|
|
474
|
+
ledger_model: Optional[Union[str, LedgerModel, UUID]]
|
|
475
|
+
The ledger model instance identifier to be used for the transactions. If none, a new ledger will be created.
|
|
476
|
+
je_timestamp: date or datetime or str, optional
|
|
477
|
+
The date and/or time to be used for the transactions. If none, localtime will be used.
|
|
478
|
+
post_new_ledgers: bool
|
|
479
|
+
If True, newly created ledgers will be posted. Defaults to False.
|
|
480
|
+
post_journal_entries: bool
|
|
481
|
+
If True, newly created journal entries will be posted. Defaults to False.
|
|
482
|
+
kwargs
|
|
483
|
+
Keyword arguments passed to the IO Library.
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
Dict
|
|
488
|
+
A dictionary containing the resulting models of the transactions.
|
|
489
|
+
"""
|
|
490
|
+
blueprint_lib = IOLibrary(
|
|
491
|
+
name=self.get_name(
|
|
492
|
+
entity_model=entity_model
|
|
493
|
+
))
|
|
494
|
+
|
|
495
|
+
cursor = blueprint_lib.get_cursor(
|
|
496
|
+
entity_model=entity_model,
|
|
497
|
+
user_model=user_model
|
|
498
|
+
)
|
|
310
499
|
|
|
311
|
-
blueprint_lib = IOLibrary(name='blueprint')
|
|
312
|
-
cursor = blueprint_lib.get_cursor(entity_model=entity_model, user_model=user_model)
|
|
313
500
|
cursor.blueprints[ledger_model].append(self)
|
|
314
501
|
|
|
315
502
|
return cursor.commit(
|
|
316
503
|
je_timestamp=je_timestamp,
|
|
317
504
|
post_new_ledgers=post_new_ledgers,
|
|
318
|
-
post_journal_entries=post_journal_entries
|
|
505
|
+
post_journal_entries=post_journal_entries,
|
|
506
|
+
**kwargs
|
|
319
507
|
)
|
|
320
508
|
|
|
321
509
|
|
|
322
510
|
class IOLibrary:
|
|
511
|
+
"""
|
|
512
|
+
The IO Library is a centralized interface for documenting commonly used operations. The library will register and
|
|
513
|
+
document the blueprints and their instructions so that they can be dispatched from anywhere in the application.
|
|
514
|
+
|
|
515
|
+
Parameters
|
|
516
|
+
----------
|
|
517
|
+
name: str
|
|
518
|
+
The human-readable name of the library (i.e. PayRoll, Expenses, Rentals, etc...)
|
|
519
|
+
"""
|
|
323
520
|
|
|
324
521
|
def __init__(self, name: str):
|
|
325
522
|
self.name = name
|
|
@@ -332,6 +529,18 @@ class IOLibrary:
|
|
|
332
529
|
self.registry[func.__name__] = func
|
|
333
530
|
|
|
334
531
|
def get_blueprint(self, name: str) -> Callable:
|
|
532
|
+
"""
|
|
533
|
+
Retrieves a blueprint by name.
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
name: str
|
|
538
|
+
The name of the blueprint to retrieve.
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
Callable
|
|
543
|
+
"""
|
|
335
544
|
if not self._check_func_name(name):
|
|
336
545
|
raise IOLibraryError(message=f'Function "{name}" is not registered in IO library {self.name}')
|
|
337
546
|
return self.registry[name]
|
|
@@ -342,6 +551,22 @@ class IOLibrary:
|
|
|
342
551
|
user_model,
|
|
343
552
|
coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None
|
|
344
553
|
) -> IOCursor:
|
|
554
|
+
"""
|
|
555
|
+
Creates a cursor instance for associated with the library, entity and user model.
|
|
556
|
+
|
|
557
|
+
Parameters
|
|
558
|
+
----------
|
|
559
|
+
entity_model: EntityModel
|
|
560
|
+
The entity model instance where transactions will be committed.
|
|
561
|
+
user_model: UserModel
|
|
562
|
+
The user model instance executing the transactions.
|
|
563
|
+
coa_model: ChartOfAccountModel or UUID or str, optional
|
|
564
|
+
The ChartOfAccountsModel instance or identifier used to determine the AccountModelQuerySet used for the transactions.
|
|
565
|
+
|
|
566
|
+
Returns
|
|
567
|
+
-------
|
|
568
|
+
IOCursor
|
|
569
|
+
"""
|
|
345
570
|
return IOCursor(
|
|
346
571
|
io_library=self,
|
|
347
572
|
entity_model=entity_model,
|
django_ledger/io/roles.py
CHANGED
|
@@ -119,48 +119,52 @@ ROOT_GROUP_LEVEL_2 = [
|
|
|
119
119
|
]
|
|
120
120
|
ROOT_GROUP_META = {
|
|
121
121
|
ROOT_COA: {
|
|
122
|
-
'code': '
|
|
122
|
+
'code': '00000000',
|
|
123
123
|
'title': 'CoA Root Node',
|
|
124
124
|
'balance_type': DEBIT
|
|
125
125
|
},
|
|
126
126
|
ROOT_ASSETS: {
|
|
127
|
-
'code': '
|
|
127
|
+
'code': '01000000',
|
|
128
128
|
'title': 'Asset Accounts Root Node',
|
|
129
129
|
'balance_type': DEBIT
|
|
130
130
|
},
|
|
131
131
|
ROOT_LIABILITIES: {
|
|
132
|
-
'code': '
|
|
132
|
+
'code': '02000000',
|
|
133
133
|
'title': 'Liability Accounts Root Node',
|
|
134
134
|
'balance_type': CREDIT
|
|
135
135
|
},
|
|
136
136
|
ROOT_CAPITAL: {
|
|
137
|
-
'code': '
|
|
137
|
+
'code': '03000000',
|
|
138
138
|
'title': 'Capital Accounts Root Node',
|
|
139
139
|
'balance_type': CREDIT
|
|
140
140
|
},
|
|
141
141
|
ROOT_INCOME: {
|
|
142
|
-
'code': '
|
|
142
|
+
'code': '04000000',
|
|
143
143
|
'title': 'Income Accounts Root Node',
|
|
144
144
|
'balance_type': CREDIT
|
|
145
145
|
},
|
|
146
146
|
ROOT_COGS: {
|
|
147
|
-
'code': '
|
|
147
|
+
'code': '05000000',
|
|
148
148
|
'title': 'COGS Accounts Root Node',
|
|
149
149
|
'balance_type': DEBIT
|
|
150
150
|
},
|
|
151
151
|
ROOT_EXPENSES: {
|
|
152
|
-
'code': '
|
|
152
|
+
'code': '06000000',
|
|
153
153
|
'title': 'Expense Accounts Root Node',
|
|
154
154
|
'balance_type': DEBIT
|
|
155
155
|
},
|
|
156
156
|
}
|
|
157
157
|
# ------> ROLE GROUPS <-------#
|
|
158
158
|
|
|
159
|
+
GROUP_ASSETS = list()
|
|
160
|
+
GROUP_ASSETS.append(ROOT_ASSETS)
|
|
161
|
+
|
|
159
162
|
# ASSET GROUPS...
|
|
160
163
|
GROUP_QUICK_ASSETS = [
|
|
161
164
|
ASSET_CA_CASH,
|
|
162
165
|
ASSET_CA_MKT_SECURITIES
|
|
163
166
|
]
|
|
167
|
+
GROUP_ASSETS += GROUP_QUICK_ASSETS
|
|
164
168
|
|
|
165
169
|
GROUP_CURRENT_ASSETS = [
|
|
166
170
|
ASSET_CA_CASH,
|
|
@@ -171,6 +175,7 @@ GROUP_CURRENT_ASSETS = [
|
|
|
171
175
|
ASSET_CA_UNCOLLECTIBLES,
|
|
172
176
|
ASSET_CA_OTHER
|
|
173
177
|
]
|
|
178
|
+
GROUP_ASSETS += GROUP_CURRENT_ASSETS
|
|
174
179
|
|
|
175
180
|
GROUP_NON_CURRENT_ASSETS = [
|
|
176
181
|
ASSET_LTI_NOTES_RECEIVABLE,
|
|
@@ -187,9 +192,13 @@ GROUP_NON_CURRENT_ASSETS = [
|
|
|
187
192
|
ASSET_ADJUSTMENTS
|
|
188
193
|
]
|
|
189
194
|
|
|
190
|
-
GROUP_ASSETS
|
|
195
|
+
GROUP_ASSETS += GROUP_NON_CURRENT_ASSETS
|
|
196
|
+
GROUP_ASSETS = list(set(GROUP_ASSETS))
|
|
191
197
|
|
|
192
198
|
# LIABILITY GROUPS....
|
|
199
|
+
GROUP_LIABILITIES = list()
|
|
200
|
+
GROUP_LIABILITIES.append(ROOT_LIABILITIES)
|
|
201
|
+
|
|
193
202
|
GROUP_CURRENT_LIABILITIES = [
|
|
194
203
|
LIABILITY_CL_ACC_PAYABLE,
|
|
195
204
|
LIABILITY_CL_DEFERRED_REVENUE,
|
|
@@ -200,17 +209,21 @@ GROUP_CURRENT_LIABILITIES = [
|
|
|
200
209
|
LIABILITY_CL_WAGES_PAYABLE,
|
|
201
210
|
LIABILITY_CL_TAXES_PAYABLE
|
|
202
211
|
]
|
|
212
|
+
GROUP_LIABILITIES += GROUP_CURRENT_LIABILITIES
|
|
203
213
|
|
|
204
214
|
GROUP_LT_LIABILITIES = [
|
|
205
215
|
LIABILITY_LTL_NOTES_PAYABLE,
|
|
206
216
|
LIABILITY_LTL_BONDS_PAYABLE,
|
|
207
217
|
LIABILITY_LTL_MORTGAGE_PAYABLE,
|
|
208
218
|
]
|
|
219
|
+
GROUP_LIABILITIES += GROUP_LT_LIABILITIES
|
|
209
220
|
|
|
210
|
-
GROUP_LIABILITIES =
|
|
221
|
+
GROUP_LIABILITIES = list(set(GROUP_LIABILITIES))
|
|
211
222
|
|
|
212
223
|
# CAPITAL/EQUITY...
|
|
224
|
+
|
|
213
225
|
GROUP_CAPITAL = [
|
|
226
|
+
ROOT_CAPITAL,
|
|
214
227
|
EQUITY_CAPITAL,
|
|
215
228
|
EQUITY_COMMON_STOCK,
|
|
216
229
|
EQUITY_PREFERRED_STOCK,
|
|
@@ -219,6 +232,7 @@ GROUP_CAPITAL = [
|
|
|
219
232
|
]
|
|
220
233
|
|
|
221
234
|
GROUP_INCOME = [
|
|
235
|
+
ROOT_INCOME,
|
|
222
236
|
INCOME_OPERATIONAL,
|
|
223
237
|
INCOME_PASSIVE,
|
|
224
238
|
INCOME_INTEREST,
|
|
@@ -227,10 +241,12 @@ GROUP_INCOME = [
|
|
|
227
241
|
]
|
|
228
242
|
|
|
229
243
|
GROUP_COGS = [
|
|
244
|
+
ROOT_COGS,
|
|
230
245
|
COGS
|
|
231
246
|
]
|
|
232
247
|
|
|
233
248
|
GROUP_EXPENSES = [
|
|
249
|
+
ROOT_EXPENSES,
|
|
234
250
|
EXPENSE_OPERATIONAL,
|
|
235
251
|
EXPENSE_INTEREST_ST,
|
|
236
252
|
EXPENSE_INTEREST_LT,
|
|
@@ -374,10 +390,14 @@ GROUP_CFS_FINANCING += GROUP_CFS_FIN_LT_DEBT_PAYMENTS
|
|
|
374
390
|
# Purchase of Assets....
|
|
375
391
|
GROUP_CFS_INV_PURCHASE_OR_SALE_OF_PPE = [
|
|
376
392
|
ASSET_PPE_BUILDINGS,
|
|
393
|
+
ASSET_PPE_BUILDINGS_ACCUM_DEPRECIATION,
|
|
377
394
|
ASSET_PPE_PLANT,
|
|
395
|
+
ASSET_PPE_PLANT_ACCUM_DEPRECIATION,
|
|
378
396
|
ASSET_PPE_EQUIPMENT,
|
|
397
|
+
ASSET_PPE_EQUIPMENT_ACCUM_DEPRECIATION,
|
|
379
398
|
INCOME_CAPITAL_GAIN_LOSS
|
|
380
399
|
]
|
|
400
|
+
|
|
381
401
|
GROUP_CFS_INV_LTD_OF_PPE = [
|
|
382
402
|
LIABILITY_LTL_NOTES_PAYABLE,
|
|
383
403
|
LIABILITY_LTL_MORTGAGE_PAYABLE,
|
|
@@ -386,14 +406,16 @@ GROUP_CFS_INV_LTD_OF_PPE = [
|
|
|
386
406
|
|
|
387
407
|
GROUP_CFS_INVESTING_PPE = GROUP_CFS_INV_PURCHASE_OR_SALE_OF_PPE + GROUP_CFS_INV_LTD_OF_PPE
|
|
388
408
|
|
|
389
|
-
# Purchase of Securities...
|
|
409
|
+
# Purchase/Sell of Securities...
|
|
390
410
|
GROUP_CFS_INV_PURCHASE_OF_SECURITIES = [
|
|
391
411
|
ASSET_CA_MKT_SECURITIES,
|
|
392
412
|
ASSET_LTI_NOTES_RECEIVABLE,
|
|
393
413
|
ASSET_LTI_SECURITIES,
|
|
394
414
|
INCOME_INTEREST,
|
|
395
415
|
INCOME_PASSIVE,
|
|
416
|
+
INCOME_CAPITAL_GAIN_LOSS
|
|
396
417
|
]
|
|
418
|
+
|
|
397
419
|
GROUP_CFS_INV_LTD_OF_SECURITIES = [
|
|
398
420
|
LIABILITY_LTL_NOTES_PAYABLE,
|
|
399
421
|
LIABILITY_LTL_BONDS_PAYABLE
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 5.0.3 on 2024-03-14 02:17
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('django_ledger', '0014_ledgermodel_ledger_xid_and_more'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RemoveField(
|
|
14
|
+
model_name='chartofaccountmodel',
|
|
15
|
+
name='locked',
|
|
16
|
+
),
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name='chartofaccountmodel',
|
|
19
|
+
name='active',
|
|
20
|
+
field=models.BooleanField(default=True, verbose_name='Is Active'),
|
|
21
|
+
),
|
|
22
|
+
]
|
django_ledger/models/accounts.py
CHANGED
|
@@ -164,6 +164,10 @@ class AccountModelManager(MP_NodeManager):
|
|
|
164
164
|
qs = self.get_queryset()
|
|
165
165
|
if user_model.is_superuser:
|
|
166
166
|
return qs
|
|
167
|
+
return qs.filter(
|
|
168
|
+
Q(coa_model__entity__admin=user_model) |
|
|
169
|
+
Q(coa_model__entity__managers__in=[user_model])
|
|
170
|
+
)
|
|
167
171
|
|
|
168
172
|
# todo: search for uses and pass EntityModel whenever possible.
|
|
169
173
|
def for_entity(self,
|
|
@@ -238,8 +242,9 @@ class AccountModelManager(MP_NodeManager):
|
|
|
238
242
|
entity_slug=entity_slug,
|
|
239
243
|
coa_slug=coa_slug)
|
|
240
244
|
return qs.filter(
|
|
241
|
-
active=True
|
|
242
|
-
locked=False
|
|
245
|
+
Q(active=True) &
|
|
246
|
+
Q(locked=False) &
|
|
247
|
+
Q(coa_model__active=True)
|
|
243
248
|
)
|
|
244
249
|
|
|
245
250
|
def with_roles(self, roles: Union[list, str], entity_slug, user_model) -> AccountModelQuerySet:
|
|
@@ -577,7 +582,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
577
582
|
return self.balance_type == CREDIT
|
|
578
583
|
|
|
579
584
|
def is_coa_root(self):
|
|
580
|
-
return self.role
|
|
585
|
+
return self.role == ROOT_COA
|
|
581
586
|
|
|
582
587
|
def is_asset(self) -> bool:
|
|
583
588
|
return self.role in GROUP_ASSETS
|
|
@@ -600,6 +605,9 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
600
605
|
def is_active(self) -> bool:
|
|
601
606
|
return self.active is True
|
|
602
607
|
|
|
608
|
+
def is_locked(self) -> bool:
|
|
609
|
+
return self.locked is True
|
|
610
|
+
|
|
603
611
|
def can_activate(self):
|
|
604
612
|
return all([
|
|
605
613
|
self.active is False
|
|
@@ -652,10 +660,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
652
660
|
return '5'
|
|
653
661
|
elif self.is_expense():
|
|
654
662
|
return '6'
|
|
655
|
-
|
|
656
|
-
return '0'
|
|
657
|
-
else:
|
|
658
|
-
raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
|
|
663
|
+
raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
|
|
659
664
|
|
|
660
665
|
def get_root_role(self) -> str:
|
|
661
666
|
if self.is_asset():
|
|
@@ -672,8 +677,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
672
677
|
return ROOT_EXPENSES
|
|
673
678
|
elif self.is_coa_root():
|
|
674
679
|
return ROOT_COA
|
|
675
|
-
|
|
676
|
-
raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
|
|
680
|
+
raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
|
|
677
681
|
|
|
678
682
|
def get_account_move_choice_queryset(self):
|
|
679
683
|
return self.coa_model.accountmodel_set.filter(
|
django_ledger/models/bill.py
CHANGED
|
@@ -481,14 +481,14 @@ class BillModelAbstract(
|
|
|
481
481
|
ledger_model.clean_fields()
|
|
482
482
|
self.ledger = ledger_model
|
|
483
483
|
|
|
484
|
+
if commit_ledger or commit:
|
|
485
|
+
self.ledger.save()
|
|
486
|
+
|
|
484
487
|
if self.can_generate_bill_number():
|
|
485
488
|
self.generate_bill_number(commit=commit)
|
|
486
489
|
|
|
487
490
|
self.clean()
|
|
488
491
|
|
|
489
|
-
if commit_ledger or commit:
|
|
490
|
-
self.ledger.save()
|
|
491
|
-
|
|
492
492
|
if commit:
|
|
493
493
|
self.save()
|
|
494
494
|
|