django-ledger 0.7.10__py3-none-any.whl → 0.8.0__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 (115) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/context.py +12 -0
  3. django_ledger/forms/bill.py +0 -4
  4. django_ledger/forms/closing_entry.py +13 -1
  5. django_ledger/forms/data_import.py +1 -1
  6. django_ledger/forms/estimate.py +3 -6
  7. django_ledger/forms/invoice.py +3 -7
  8. django_ledger/forms/item.py +10 -18
  9. django_ledger/forms/purchase_order.py +2 -4
  10. django_ledger/io/io_core.py +25 -32
  11. django_ledger/io/io_generator.py +7 -6
  12. django_ledger/io/io_library.py +1 -2
  13. django_ledger/migrations/0024_billmodel_entity_model_invoicemodel_entity_model.py +24 -0
  14. django_ledger/migrations/0025_alter_billmodel_cash_account_and_more.py +70 -0
  15. django_ledger/models/accounts.py +109 -69
  16. django_ledger/models/bank_account.py +40 -23
  17. django_ledger/models/bill.py +89 -63
  18. django_ledger/models/chart_of_accounts.py +173 -105
  19. django_ledger/models/closing_entry.py +99 -48
  20. django_ledger/models/customer.py +60 -39
  21. django_ledger/models/data_import.py +55 -41
  22. django_ledger/models/deprecations.py +61 -0
  23. django_ledger/models/entity.py +18 -16
  24. django_ledger/models/estimate.py +57 -28
  25. django_ledger/models/invoice.py +58 -28
  26. django_ledger/models/items.py +503 -142
  27. django_ledger/models/journal_entry.py +61 -47
  28. django_ledger/models/ledger.py +106 -42
  29. django_ledger/models/mixins.py +16 -10
  30. django_ledger/models/purchase_order.py +39 -17
  31. django_ledger/models/transactions.py +152 -113
  32. django_ledger/models/unit.py +57 -30
  33. django_ledger/models/vendor.py +75 -43
  34. django_ledger/report/core.py +2 -14
  35. django_ledger/settings.py +56 -71
  36. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  37. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +25 -0
  38. django_ledger/static/django_ledger/bundle/styles.bundle.js +1 -1
  39. django_ledger/static/django_ledger/css/djl_styles.css +273 -0
  40. django_ledger/templates/django_ledger/bills/includes/card_bill.html +2 -2
  41. django_ledger/templates/django_ledger/components/menu.html +41 -26
  42. django_ledger/templates/django_ledger/customer/tags/customer_table.html +5 -5
  43. django_ledger/templates/django_ledger/entity/includes/card_entity.html +12 -6
  44. django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +1 -1
  45. django_ledger/templates/django_ledger/financial_statements/cash_flow.html +4 -1
  46. django_ledger/templates/django_ledger/financial_statements/income_statement.html +4 -1
  47. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +27 -3
  48. django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +16 -4
  49. django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +73 -18
  50. django_ledger/templates/django_ledger/includes/widget_ratios.html +18 -24
  51. django_ledger/templates/django_ledger/invoice/includes/card_invoice.html +3 -3
  52. django_ledger/templates/django_ledger/layouts/base.html +6 -1
  53. django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +9 -5
  54. django_ledger/tests/test_accounts.py +1 -2
  55. django_ledger/tests/test_io.py +17 -0
  56. django_ledger/tests/test_purchase_order.py +3 -3
  57. django_ledger/tests/test_transactions.py +1 -2
  58. django_ledger/urls/__init__.py +0 -4
  59. django_ledger/views/bill.py +8 -13
  60. django_ledger/views/chart_of_accounts.py +6 -4
  61. django_ledger/views/closing_entry.py +11 -7
  62. django_ledger/views/customer.py +13 -17
  63. django_ledger/views/data_import.py +7 -6
  64. django_ledger/views/djl_api.py +3 -5
  65. django_ledger/views/entity.py +2 -4
  66. django_ledger/views/estimate.py +3 -7
  67. django_ledger/views/inventory.py +3 -5
  68. django_ledger/views/invoice.py +4 -6
  69. django_ledger/views/item.py +7 -11
  70. django_ledger/views/journal_entry.py +1 -2
  71. django_ledger/views/mixins.py +25 -19
  72. django_ledger/views/purchase_order.py +24 -35
  73. django_ledger/views/unit.py +1 -2
  74. django_ledger/views/vendor.py +1 -2
  75. {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info}/METADATA +43 -75
  76. {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info}/RECORD +80 -108
  77. {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info}/WHEEL +1 -1
  78. django_ledger-0.8.0.dist-info/top_level.txt +1 -0
  79. django_ledger/contrib/django_ledger_graphene/__init__.py +0 -0
  80. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +0 -33
  81. django_ledger/contrib/django_ledger_graphene/api.py +0 -42
  82. django_ledger/contrib/django_ledger_graphene/apps.py +0 -6
  83. django_ledger/contrib/django_ledger_graphene/auth/mutations.py +0 -49
  84. django_ledger/contrib/django_ledger_graphene/auth/schema.py +0 -6
  85. django_ledger/contrib/django_ledger_graphene/bank_account/mutations.py +0 -61
  86. django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +0 -34
  87. django_ledger/contrib/django_ledger_graphene/bill/mutations.py +0 -0
  88. django_ledger/contrib/django_ledger_graphene/bill/schema.py +0 -34
  89. django_ledger/contrib/django_ledger_graphene/coa/mutations.py +0 -0
  90. django_ledger/contrib/django_ledger_graphene/coa/schema.py +0 -30
  91. django_ledger/contrib/django_ledger_graphene/customers/__init__.py +0 -0
  92. django_ledger/contrib/django_ledger_graphene/customers/mutations.py +0 -71
  93. django_ledger/contrib/django_ledger_graphene/customers/schema.py +0 -43
  94. django_ledger/contrib/django_ledger_graphene/data_import/mutations.py +0 -0
  95. django_ledger/contrib/django_ledger_graphene/data_import/schema.py +0 -0
  96. django_ledger/contrib/django_ledger_graphene/entity/mutations.py +0 -0
  97. django_ledger/contrib/django_ledger_graphene/entity/schema.py +0 -94
  98. django_ledger/contrib/django_ledger_graphene/item/mutations.py +0 -0
  99. django_ledger/contrib/django_ledger_graphene/item/schema.py +0 -31
  100. django_ledger/contrib/django_ledger_graphene/journal_entry/mutations.py +0 -0
  101. django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +0 -35
  102. django_ledger/contrib/django_ledger_graphene/ledger/mutations.py +0 -0
  103. django_ledger/contrib/django_ledger_graphene/ledger/schema.py +0 -32
  104. django_ledger/contrib/django_ledger_graphene/purchase_order/mutations.py +0 -0
  105. django_ledger/contrib/django_ledger_graphene/purchase_order/schema.py +0 -31
  106. django_ledger/contrib/django_ledger_graphene/transaction/mutations.py +0 -0
  107. django_ledger/contrib/django_ledger_graphene/transaction/schema.py +0 -36
  108. django_ledger/contrib/django_ledger_graphene/unit/mutations.py +0 -0
  109. django_ledger/contrib/django_ledger_graphene/unit/schema.py +0 -27
  110. django_ledger/contrib/django_ledger_graphene/vendor/mutations.py +0 -0
  111. django_ledger/contrib/django_ledger_graphene/vendor/schema.py +0 -37
  112. django_ledger/contrib/django_ledger_graphene/views.py +0 -12
  113. django_ledger-0.7.10.dist-info/top_level.txt +0 -4
  114. {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info/licenses}/AUTHORS.md +0 -0
  115. {django_ledger-0.7.10.dist-info → django_ledger-0.8.0.dist-info/licenses}/LICENSE +0 -0
@@ -47,10 +47,11 @@ Roles serve several purposes:
47
47
  3. Enable accurate generation of financial statements
48
48
  4. Facilitate financial ratio calculations
49
49
  """
50
+ import warnings
50
51
  from itertools import groupby
51
52
  from random import randint
52
53
  from typing import Union, List, Optional
53
- from uuid import uuid4
54
+ from uuid import uuid4, UUID
54
55
 
55
56
  from django.core.exceptions import ValidationError
56
57
  from django.db import models
@@ -60,6 +61,7 @@ from django.urls import reverse
60
61
  from django.utils.translation import gettext_lazy as _
61
62
  from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
62
63
 
64
+ from django_ledger.io import DEBIT, CREDIT
63
65
  from django_ledger.io.roles import (
64
66
  ACCOUNT_ROLE_CHOICES, BS_ROLES, GROUP_INVOICE, GROUP_BILL, validate_roles,
65
67
  GROUP_ASSETS, GROUP_LIABILITIES, GROUP_CAPITAL, GROUP_INCOME, GROUP_EXPENSES, GROUP_COGS,
@@ -67,15 +69,14 @@ from django_ledger.io.roles import (
67
69
  ROOT_CAPITAL, ROOT_INCOME, ROOT_EXPENSES, ROOT_COA, VALID_PARENTS,
68
70
  ROLES_ORDER_ALL, ASSET_CA_CASH
69
71
  )
72
+ from django_ledger.models.deprecations import deprecated_entity_slug_behavior
70
73
  from django_ledger.models.mixins import CreateUpdateMixIn
71
74
  from django_ledger.models.utils import lazy_loader
72
- from django_ledger.settings import DJANGO_LEDGER_ACCOUNT_CODE_GENERATE, DJANGO_LEDGER_ACCOUNT_CODE_USE_PREFIX
73
-
74
- DEBIT = 'debit'
75
- """A constant, identifying a DEBIT Account or DEBIT transaction in the respective database fields"""
76
-
77
- CREDIT = 'credit'
78
- """A constant, identifying a CREDIT Account or CREDIT transaction in the respective database fields"""
75
+ from django_ledger.settings import (
76
+ DJANGO_LEDGER_ACCOUNT_CODE_GENERATE,
77
+ DJANGO_LEDGER_ACCOUNT_CODE_USE_PREFIX,
78
+ DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
79
+ )
79
80
 
80
81
 
81
82
  class AccountModelValidationError(ValidationError):
@@ -87,7 +88,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
87
88
  Custom QuerySet for AccountModel inheriting from MP_NodeQuerySet.
88
89
  """
89
90
 
90
- def active(self):
91
+ def active(self) -> 'AccountModelQuerySet':
91
92
  """
92
93
  Filters the queryset to include only active items.
93
94
 
@@ -98,7 +99,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
98
99
  """
99
100
  return self.filter(active=True)
100
101
 
101
- def inactive(self):
102
+ def inactive(self) -> 'AccountModelQuerySet':
102
103
  """
103
104
  Filters and returns queryset entries where the active field is set to False.
104
105
 
@@ -109,7 +110,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
109
110
  """
110
111
  return self.filter(active=False)
111
112
 
112
- def locked(self):
113
+ def locked(self) -> 'AccountModelQuerySet':
113
114
  """
114
115
  Filters the queryset to include only locked AccountModels.
115
116
 
@@ -120,7 +121,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
120
121
  """
121
122
  return self.filter(locked=True)
122
123
 
123
- def unlocked(self):
124
+ def unlocked(self) -> 'AccountModelQuerySet':
124
125
  """
125
126
  Returns a filtered list of items where the 'locked' attribute is set to False.
126
127
 
@@ -131,7 +132,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
131
132
  """
132
133
  return self.filter(locked=False)
133
134
 
134
- def with_roles(self, roles: Union[List, str]):
135
+ def with_roles(self, roles: Union[List, str]) -> 'AccountModelQuerySet':
135
136
  """
136
137
  Filter the accounts based on the specified roles. This method helps to retrieve accounts associated
137
138
  with a particular role or a list of roles.
@@ -156,16 +157,16 @@ class AccountModelQuerySet(MP_NodeQuerySet):
156
157
  roles = validate_roles(roles)
157
158
  return self.filter(role__in=roles)
158
159
 
159
- def with_codes(self, codes: Union[List, str]):
160
+ def with_codes(self, codes: Union[List, str]) -> 'AccountModelQuerySet':
160
161
  if isinstance(codes, str):
161
162
  codes = [codes]
162
163
  return self.filter(code__in=codes)
163
164
 
164
- def cash(self):
165
+ def cash(self) -> 'AccountModelQuerySet':
165
166
  """Retrieve accounts that are of type ASSET_CA_CASH."""
166
167
  return self.filter(role__exact=ASSET_CA_CASH)
167
168
 
168
- def expenses(self):
169
+ def expenses(self) -> 'AccountModelQuerySet':
169
170
  """
170
171
  Retrieve a queryset containing expenses filtered by specified roles.
171
172
 
@@ -180,7 +181,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
180
181
  """
181
182
  return self.filter(role__in=GROUP_EXPENSES)
182
183
 
183
- def is_coa_root(self):
184
+ def is_coa_root(self) -> 'AccountModelQuerySet':
184
185
  """
185
186
  Retrieves the Chart of Accounts (CoA) root node queryset.
186
187
 
@@ -194,7 +195,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
194
195
  """
195
196
  return self.filter(role__in=ROOT_GROUP)
196
197
 
197
- def not_coa_root(self):
198
+ def not_coa_root(self) -> 'AccountModelQuerySet':
198
199
  """
199
200
  Exclude AccountModels with ROOT_GROUP role from the QuerySet.
200
201
 
@@ -205,7 +206,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
205
206
  """
206
207
  return self.exclude(role__in=ROOT_GROUP)
207
208
 
208
- def gb_bs_role(self):
209
+ def gb_bs_role(self) -> 'AccountModelQuerySet':
209
210
  """
210
211
  Groups accounts by Balance Sheet Bucket and then further groups them by role.
211
212
 
@@ -227,7 +228,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
227
228
  ]) for bsr, gb in accounts_gb
228
229
  ]
229
230
 
230
- def is_role_default(self):
231
+ def is_role_default(self) -> 'AccountModelQuerySet':
231
232
  """
232
233
  Filter the queryset to include only entries where `role_default`
233
234
  is set to True, excluding entries marked as 'coa_root'.
@@ -239,7 +240,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
239
240
  """
240
241
  return self.not_coa_root().filter(role_default=True)
241
242
 
242
- def can_transact(self):
243
+ def can_transact(self) -> 'AccountModelQuerySet':
243
244
  """
244
245
  Filter the queryset to include only accounts that can accept new transactions.
245
246
 
@@ -254,14 +255,14 @@ class AccountModelQuerySet(MP_NodeQuerySet):
254
255
  Q(coa_model__active=True)
255
256
  )
256
257
 
257
- def available(self):
258
+ def available(self) -> 'AccountModelQuerySet':
258
259
  return self.filter(
259
260
  Q(locked=False) &
260
261
  Q(active=True) &
261
262
  Q(coa_model__active=True)
262
263
  )
263
264
 
264
- def for_bill(self):
265
+ def for_bill(self) -> 'AccountModelQuerySet':
265
266
  """
266
267
  Retrieves only available and unlocked AccountModels for a specific EntityModel,
267
268
  specifically for the creation and management of Bills. Roles within the 'GROUP_BILL'
@@ -274,7 +275,7 @@ class AccountModelQuerySet(MP_NodeQuerySet):
274
275
  """
275
276
  return self.available().filter(role__in=GROUP_BILL)
276
277
 
277
- def for_invoice(self):
278
+ def for_invoice(self) -> 'AccountModelQuerySet':
278
279
  """
279
280
  Retrieves available and unlocked AccountModels for a specific EntityModel, specifically for the creation
280
281
  and management of Invoices.
@@ -290,6 +291,27 @@ class AccountModelQuerySet(MP_NodeQuerySet):
290
291
  """
291
292
  return self.available().filter(role__in=GROUP_INVOICE)
292
293
 
294
+ def for_user(self, user_model) -> 'AccountModelQuerySet':
295
+ """
296
+ Parameters
297
+ ----------
298
+ user_model : UserModel
299
+ The user model instance to use for filtering.
300
+
301
+ Returns
302
+ -------
303
+ AccountModelQuerySet
304
+ The filtered queryset based on the user's permissions. Superusers get the complete queryset whereas other
305
+ users get a filtered queryset based on their role as admin or manager in the entity.
306
+ """
307
+ if user_model.is_superuser:
308
+ return self
309
+
310
+ return self.filter(
311
+ Q(coa_model__entity__admin=user_model) |
312
+ Q(coa_model__entity__managers__in=[user_model])
313
+ )
314
+
293
315
 
294
316
  class AccountModelManager(MP_NodeManager):
295
317
  """
@@ -299,7 +321,7 @@ class AccountModelManager(MP_NodeManager):
299
321
 
300
322
  def get_queryset(self) -> AccountModelQuerySet:
301
323
  """
302
- Retrieve and return athe default AccountModel QuerySet.
324
+ Retrieve and return the default AccountModel QuerySet.
303
325
 
304
326
  The query set is ordered by the 'path' field and uses 'select_related' to reduce the number of database queries
305
327
  by retrieving the related 'coa_model'.
@@ -313,80 +335,98 @@ class AccountModelManager(MP_NodeManager):
313
335
  self.model,
314
336
  using=self._db
315
337
  ).order_by('path').select_related(
316
- 'coa_model').annotate(
338
+ 'coa_model'
339
+ ).annotate(
317
340
  _coa_slug=F('coa_model__slug'),
318
341
  _coa_active=F('coa_model__active'),
319
342
  _entity_slug=F('coa_model__entity__slug'),
320
343
  )
321
344
 
322
- def for_user(self, user_model) -> AccountModelQuerySet:
323
- """
324
- Parameters
325
- ----------
326
- user_model : UserModel
327
- The user model instance to use for filtering.
328
-
329
- Returns
330
- -------
331
- AccountModelQuerySet
332
- The filtered queryset based on the user's permissions. Superusers get the complete queryset whereas other
333
- users get a filtered queryset based on their role as admin or manager in the entity.
334
- """
335
- qs = self.get_queryset()
336
- if user_model.is_superuser:
337
- return qs
338
- return qs.filter(
339
- Q(coa_model__entity__admin=user_model) |
340
- Q(coa_model__entity__managers__in=[user_model])
341
- )
342
-
345
+ @deprecated_entity_slug_behavior
343
346
  def for_entity(
344
347
  self,
345
- user_model,
346
- entity_model,
347
- coa_slug: Optional[str] = None
348
+ entity_model: Union['EntityModel | str | UUID'] = None,
349
+ coa_model: Optional['ChartOfAccountModel | str | UUID'] = None,
350
+ **kwargs
348
351
  ) -> AccountModelQuerySet:
349
352
  """
350
- Retrieve accounts associated with a specified EntityModel and Chart of Accounts.
353
+ Filters the queryset for an entity and, optionally, a chart of account (COA) model.
354
+
355
+ The method refines the queryset based on the provided `entity_model` and, optionally,
356
+ the `coa_model`. If a deprecated `user_model` is specified in keyword arguments,
357
+ a warning is issued. The method supports `EntityModel`, `str`, and `UUID` types
358
+ for both `entity_model` and `coa_model`. A validation error is raised for unsupported types.
351
359
 
352
360
  Parameters
353
361
  ----------
354
- user_model : User
355
- The Django User instance initiating the request. Used to check for required permissions.
356
- entity_model : Union[EntityModel, str]
357
- An instance of EntityModel or its slug. This determines the entity whose accounts are being retrieved.
358
- A database query will be carried out to identify the default Chart of Accounts.
359
- coa_slug : Optional[str], default=None
360
- The slug for a specific Chart of Accounts to be used. If None, the default Chart of Accounts will be selected.
362
+ entity_model : Union['EntityModel', str, UUID]
363
+ The entity model used for filtering the queryset. Could be an instance of
364
+ `EntityModel`, a string (slug), or a UUID.
365
+ coa_model : Optional[Union['ChartOfAccountModel', str, UUID]], optional
366
+ The COA model used for filtering the queryset. Can be an instance of
367
+ `ChartOfAccountModel`, a string (slug), or a UUID. If None, default Entity ChartOfAccounts is used.
368
+ Defaults to None.
369
+ **kwargs : dict
370
+ Additional keyword arguments. A deprecated argument `user_model` can be passed
371
+ for backward compatibility.
361
372
 
362
373
  Returns
363
374
  -------
364
375
  AccountModelQuerySet
365
- A QuerySet containing accounts associated with the specified EntityModel and Chart of Accounts.
376
+ A queryset filtered by the input entity model and, optionally, the chart of
377
+ account model.
366
378
 
367
379
  Raises
368
380
  ------
369
381
  AccountModelValidationError
370
- If the entity_model is neither an instance of EntityModel nor a string.
382
+ If an invalid type is passed for either `entity_model` or `coa_model`.
383
+
384
+ Warns
385
+ -----
386
+ DeprecationWarning
387
+ If the `user_model` parameter is passed in the keyword arguments and the
388
+ application relies on deprecated behavior.
371
389
  """
372
- qs = self.for_user(user_model)
373
390
  EntityModel = lazy_loader.get_entity_model()
391
+ ChartOfAccountModel = lazy_loader.get_coa_model()
392
+
393
+ qs = self.get_queryset()
394
+
395
+ if 'user_model' in kwargs:
396
+ warnings.warn(
397
+ 'user_model parameter is deprecated and will be removed in a future release. '
398
+ 'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
399
+ DeprecationWarning,
400
+ stacklevel=2
401
+ )
402
+ if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
403
+ qs = qs.for_user(kwargs['user_model'])
374
404
 
375
405
  if isinstance(entity_model, EntityModel):
376
- entity_model = entity_model
377
406
  qs = qs.filter(coa_model__entity=entity_model)
378
407
  elif isinstance(entity_model, str):
379
408
  qs = qs.filter(coa_model__entity__slug__exact=entity_model)
409
+ elif isinstance(entity_model, UUID):
410
+ qs = qs.filter(coa_model__entity_id=entity_model)
380
411
  else:
381
412
  raise AccountModelValidationError(
382
- message='Must pass an instance of EntityModel or String for entity_slug.'
413
+ message='Must pass an instance of EntityModel, String or UUID for entity_model.'
383
414
  )
384
415
 
385
- return qs.filter(
386
- coa_model__slug__exact=coa_slug
387
- ) if coa_slug else qs.filter(
388
- coa_model__slug__exact=F('coa_model__entity__default_coa__slug')
389
- )
416
+ if coa_model:
417
+ if isinstance(coa_model, ChartOfAccountModel):
418
+ qs = qs.filter(coa_model=coa_model)
419
+ elif isinstance(coa_model, str):
420
+ qs = qs.filter(coa_model__slug__exact=coa_model)
421
+ elif isinstance(coa_model, UUID):
422
+ qs = qs.filter(coa_model__uuid__exact=coa_model)
423
+ else:
424
+ raise AccountModelValidationError(
425
+ message='Must pass an instance of ChartOfAccountModel, String or UUID for coa_model.'
426
+ )
427
+ return qs
428
+
429
+ return qs.filter(coa_model__slug__exact=F('coa_model__entity__default_coa__slug'))
390
430
 
391
431
 
392
432
  def account_code_validator(value: str):
@@ -438,7 +478,7 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
438
478
  coa_model = models.ForeignKey('django_ledger.ChartOfAccountModel',
439
479
  on_delete=models.CASCADE,
440
480
  verbose_name=_('Chart of Accounts'))
441
- objects = AccountModelManager()
481
+ objects = AccountModelManager.from_queryset(queryset_class=AccountModelQuerySet)()
442
482
 
443
483
  class Meta:
444
484
  abstract = True
@@ -6,8 +6,9 @@ A Bank Account refers to the financial institution which holds financial assets
6
6
  A bank account usually holds cash, which is a Current Asset. Transactions may be imported using the open financial
7
7
  format specification OFX into a staging area for final disposition into the EntityModel ledger.
8
8
  """
9
+ import warnings
9
10
  from typing import Optional
10
- from uuid import uuid4
11
+ from uuid import uuid4, UUID
11
12
 
12
13
  from django.contrib.auth import get_user_model
13
14
  from django.core.exceptions import ValidationError
@@ -17,7 +18,9 @@ from django.shortcuts import get_object_or_404
17
18
  from django.utils.translation import gettext_lazy as _
18
19
 
19
20
  from django_ledger.models import CreateUpdateMixIn, FinancialAccountInfoMixin
21
+ from django_ledger.models.deprecations import deprecated_entity_slug_behavior
20
22
  from django_ledger.models.utils import lazy_loader
23
+ from django_ledger.settings import DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
21
24
 
22
25
  UserModel = get_user_model()
23
26
 
@@ -28,10 +31,18 @@ class BankAccountValidationError(ValidationError):
28
31
 
29
32
  class BankAccountModelQuerySet(QuerySet):
30
33
  """
31
- A custom defined QuerySet for the BankAccountModel.
34
+ A custom-defined QuerySet for the BankAccountModel.
32
35
  """
33
36
 
34
- def active(self) -> QuerySet:
37
+ def for_user(self, user_model) -> 'BankAccountModelQuerySet':
38
+ if user_model.is_superuser:
39
+ return self
40
+ return self.filter(
41
+ Q(entity_model__admin=user_model) |
42
+ Q(entity_model__managers__in=[user_model])
43
+ )
44
+
45
+ def active(self) -> 'BankAccountModelQuerySet':
35
46
  """
36
47
  Active bank accounts which can be used to create new transactions.
37
48
 
@@ -42,7 +53,7 @@ class BankAccountModelQuerySet(QuerySet):
42
53
  """
43
54
  return self.filter(active=True)
44
55
 
45
- def hidden(self) -> QuerySet:
56
+ def hidden(self) -> 'BankAccountModelQuerySet':
46
57
  """
47
58
  Hidden bank accounts which can be used to create new transactions. but will not show in drop down menus
48
59
  in the UI.
@@ -63,16 +74,8 @@ class BankAccountModelManager(Manager):
63
74
  def get_queryset(self) -> BankAccountModelQuerySet:
64
75
  return BankAccountModelQuerySet(self.model, using=self._db)
65
76
 
66
- def for_user(self, user_model):
67
- qs = self.get_queryset()
68
- if user_model.is_superuser:
69
- return qs
70
- return qs.filter(
71
- Q(entity_model__admin=user_model) |
72
- Q(entity_model__managers__in=[user_model])
73
- )
74
-
75
- def for_entity(self, entity_slug, user_model) -> BankAccountModelQuerySet:
77
+ @deprecated_entity_slug_behavior
78
+ def for_entity(self, entity_model: 'EntityModel | str | UUID' = None, **kwargs) -> BankAccountModelQuerySet:
76
79
  """
77
80
  Allows only the authorized user to query the BankAccountModel for a given EntityModel.
78
81
  This is the recommended initial QuerySet.
@@ -81,17 +84,31 @@ class BankAccountModelManager(Manager):
81
84
  __________
82
85
  entity_slug: str or EntityModel
83
86
  The entity slug or EntityModel used for filtering the QuerySet.
84
- user_model
85
- Logged in and authenticated django UserModel instance.
86
87
  """
87
- qs = self.for_user(user_model)
88
- if isinstance(entity_slug, lazy_loader.get_entity_model()):
89
- return qs.filter(
90
- Q(entity_model=entity_slug)
88
+ EntityModel = lazy_loader.get_entity_model()
89
+
90
+ qs = self.get_queryset()
91
+ if 'user_model' in kwargs:
92
+ warnings.warn(
93
+ 'user_model parameter is deprecated and will be removed in a future release. '
94
+ 'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
95
+ DeprecationWarning,
96
+ stacklevel=2
91
97
  )
92
- return qs.filter(
93
- Q(entity_model__slug__exact=entity_slug)
94
- )
98
+ if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
99
+ qs = qs.for_user(kwargs['user_model'])
100
+
101
+ if isinstance(entity_model, EntityModel):
102
+ qs = qs.filter(entity_model=entity_model)
103
+ elif isinstance(entity_model, str):
104
+ qs = qs.filter(entity_model__slug__exact=entity_model)
105
+ elif isinstance(entity_model, UUID):
106
+ qs = qs.filter(entity_model_id=entity_model)
107
+ else:
108
+ raise BankAccountValidationError(
109
+ message=_('Must pass EntityModel slug or EntityModel UUID'),
110
+ )
111
+ return qs
95
112
 
96
113
 
97
114
  class BankAccountModelAbstract(FinancialAccountInfoMixin, CreateUpdateMixIn):