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
@@ -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': '00000',
122
+ 'code': '00000000',
123
123
  'title': 'CoA Root Node',
124
124
  'balance_type': DEBIT
125
125
  },
126
126
  ROOT_ASSETS: {
127
- 'code': '01000',
127
+ 'code': '01000000',
128
128
  'title': 'Asset Accounts Root Node',
129
129
  'balance_type': DEBIT
130
130
  },
131
131
  ROOT_LIABILITIES: {
132
- 'code': '02000',
132
+ 'code': '02000000',
133
133
  'title': 'Liability Accounts Root Node',
134
134
  'balance_type': CREDIT
135
135
  },
136
136
  ROOT_CAPITAL: {
137
- 'code': '03000',
137
+ 'code': '03000000',
138
138
  'title': 'Capital Accounts Root Node',
139
139
  'balance_type': CREDIT
140
140
  },
141
141
  ROOT_INCOME: {
142
- 'code': '04000',
142
+ 'code': '04000000',
143
143
  'title': 'Income Accounts Root Node',
144
144
  'balance_type': CREDIT
145
145
  },
146
146
  ROOT_COGS: {
147
- 'code': '05000',
147
+ 'code': '05000000',
148
148
  'title': 'COGS Accounts Root Node',
149
149
  'balance_type': DEBIT
150
150
  },
151
151
  ROOT_EXPENSES: {
152
- 'code': '06000',
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 = GROUP_CURRENT_ASSETS + GROUP_NON_CURRENT_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 = GROUP_CURRENT_LIABILITIES + GROUP_LT_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
+ ]
@@ -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 in ROOT_GROUP
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
- elif self.is_coa_root():
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
- else:
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(
@@ -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