django-ledger 0.6.2__py3-none-any.whl → 0.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 +1 -2
  3. django_ledger/admin/ledger.py +1 -0
  4. django_ledger/forms/account.py +22 -11
  5. django_ledger/forms/data_import.py +2 -1
  6. django_ledger/io/ofx.py +3 -2
  7. django_ledger/io/roles.py +6 -0
  8. django_ledger/models/accounts.py +523 -196
  9. django_ledger/models/bill.py +28 -14
  10. django_ledger/models/closing_entry.py +8 -0
  11. django_ledger/models/entity.py +12 -5
  12. django_ledger/models/estimate.py +93 -21
  13. django_ledger/models/invoice.py +44 -14
  14. django_ledger/models/journal_entry.py +112 -49
  15. django_ledger/models/ledger.py +32 -0
  16. django_ledger/models/purchase_order.py +34 -3
  17. django_ledger/models/signals.py +58 -0
  18. django_ledger/report/cash_flow_statement.py +1 -1
  19. django_ledger/report/core.py +1 -1
  20. django_ledger/static/.DS_Store +0 -0
  21. django_ledger/static/django_ledger/.DS_Store +0 -0
  22. django_ledger/static/django_ledger/logo_2/.DS_Store +0 -0
  23. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark.png +0 -0
  24. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@0.5x.png +0 -0
  25. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@2x.png +0 -0
  26. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@3x.png +0 -0
  27. django_ledger/static/django_ledger/logo_2/djl-full-vert.png +0 -0
  28. django_ledger/static/django_ledger/logo_2/djl-full-vert@0.5x.png +0 -0
  29. django_ledger/static/django_ledger/logo_2/djl-full-vert@2x.png +0 -0
  30. django_ledger/static/django_ledger/logo_2/djl-full-vert@3x.png +0 -0
  31. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz.png +0 -0
  32. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@0.5x.png +0 -0
  33. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@2x.png +0 -0
  34. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@3x.png +0 -0
  35. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert.png +0 -0
  36. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@0.5x.png +0 -0
  37. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@2x.png +0 -0
  38. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@3x.png +0 -0
  39. django_ledger/static/django_ledger/logo_2/djl-logo.png +0 -0
  40. django_ledger/static/django_ledger/logo_2/djl-logo@0.5x.png +0 -0
  41. django_ledger/static/django_ledger/logo_2/djl-logo@2x.png +0 -0
  42. django_ledger/static/django_ledger/logo_2/djl-logo@3x.png +0 -0
  43. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz.png +0 -0
  44. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@0.5x.png +0 -0
  45. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@2x.png +0 -0
  46. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@3x.png +0 -0
  47. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert.png +0 -0
  48. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@0.5x.png +0 -0
  49. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@2x.png +0 -0
  50. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@3x.png +0 -0
  51. django_ledger/static/django_ledger/logo_2/djl-txt-horiz.png +0 -0
  52. django_ledger/static/django_ledger/logo_2/djl-txt-horiz@0.5x.png +0 -0
  53. django_ledger/static/django_ledger/logo_2/djl-txt-horiz@2x.png +0 -0
  54. django_ledger/static/django_ledger/logo_2/djl-txt-horiz@3x.png +0 -0
  55. django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +2 -2
  56. django_ledger/tests/test_io_ofx/__init__.py +0 -0
  57. django_ledger/tests/test_io_ofx/tests.py +52 -0
  58. django_ledger/views/account.py +1 -1
  59. django_ledger/views/bill.py +1 -1
  60. {django_ledger-0.6.2.dist-info → django_ledger-0.6.4.dist-info}/METADATA +1 -1
  61. {django_ledger-0.6.2.dist-info → django_ledger-0.6.4.dist-info}/RECORD +65 -27
  62. {django_ledger-0.6.2.dist-info → django_ledger-0.6.4.dist-info}/top_level.txt +0 -1
  63. {django_ledger-0.6.2.dist-info → django_ledger-0.6.4.dist-info}/AUTHORS.md +0 -0
  64. {django_ledger-0.6.2.dist-info → django_ledger-0.6.4.dist-info}/LICENSE +0 -0
  65. {django_ledger-0.6.2.dist-info → django_ledger-0.6.4.dist-info}/WHEEL +0 -0
@@ -6,24 +6,52 @@ Contributions to this module:
6
6
  * Miguel Sanda <msanda@arrobalytics.com>
7
7
  * Pranav P Tulshyan <ptulshyan77@gmail.com>
8
8
 
9
- The AccountModel groups and sorts transactions involving the company's assets, liabilities and equities.
10
- Per accounting principles, an Account must be either a DEBIT-type balance account or a CREDIT-type balance account,
11
- depending on its purpose.
12
9
 
13
- The AccountModel plays a major role when creating Journal Entries in a double entry accounting systems where
14
- a DEBIT to a DEBIT-type AccountModel will increase its balance, and a CREDIT to a DEBIT-type AccountModel will
15
- reduce its balance. Conversely, a CREDIT to a CREDIT-type AccountModel will increase its balance, and a
16
- DEBIT to a CREDIT-type AccountModel will reduce its balance.
10
+ AccountModel
11
+ ------------
17
12
 
18
- It is entirely up to the user to adopt the chart of accounts that best suits the EntityModel.
19
- The user may choose to use the default Chart of Accounts provided by Django Ledger when creating a new EntityModel.
13
+ The AccountModel is a fundamental component of the Django Ledger system, responsible for categorizing and organizing
14
+ financial transactions related to an entity's assets, liabilities, and equity.
20
15
 
21
- In Django Ledger, all account models must be assigned a role from
22
- :func:`ACCOUNT_ROLES <django_ledger.io.roles.ACCOUNT_ROLES>`. Roles are a way to group accounts to a common namespace,
23
- regardless of its user-defined fields. Roles are an integral part to Django Ledger since they are critical when
24
- requesting and producing financial statements and financial ratio calculations.
16
+ Account Types
17
+ -------------
25
18
 
26
- AccountModels may also contain parent/child relationships as implemented by the Django Treebeard functionality.
19
+ In accordance with accounting principles, each AccountModel must be classified as either:
20
+
21
+ 1. **DEBIT-type balance account**
22
+ 2. **CREDIT-type balance account**
23
+
24
+ The account type determines how transactions affect the account's balance.
25
+
26
+ Double Entry Accounting
27
+ -----------------------
28
+
29
+ The AccountModel is crucial in implementing double entry accounting systems:
30
+
31
+ * For DEBIT-type accounts:
32
+ - A DEBIT increases the balance
33
+ - A CREDIT decreases the balance
34
+
35
+ * For CREDIT-type accounts:
36
+ - A CREDIT increases the balance
37
+ - A DEBIT decreases the balance
38
+
39
+ Chart of Accounts
40
+ -----------------
41
+
42
+ Users have the flexibility to adopt a chart of accounts that best suits their EntityModel. Django Ledger provides a
43
+ default Chart of Accounts when creating a new EntityModel, which can be customized as needed.
44
+
45
+ Account Roles
46
+ -------------
47
+
48
+ All AccountModels must be assigned a role from the `ACCOUNT_ROLES` function in `django_ledger.io.roles`.
49
+ Roles serve several purposes:
50
+
51
+ 1. Group accounts into common namespaces
52
+ 2. Provide consistency across user-defined fields
53
+ 3. Enable accurate generation of financial statements
54
+ 4. Facilitate financial ratio calculations
27
55
  """
28
56
  from itertools import groupby
29
57
  from random import randint
@@ -43,7 +71,7 @@ from django_ledger.io.roles import (ACCOUNT_ROLE_CHOICES, BS_ROLES, GROUP_INVOIC
43
71
  GROUP_ASSETS,
44
72
  GROUP_LIABILITIES, GROUP_CAPITAL, GROUP_INCOME, GROUP_EXPENSES, GROUP_COGS,
45
73
  ROOT_GROUP, BS_BUCKETS, ROOT_ASSETS, ROOT_LIABILITIES,
46
- ROOT_CAPITAL, ROOT_INCOME, ROOT_EXPENSES, ROOT_COA)
74
+ ROOT_CAPITAL, ROOT_INCOME, ROOT_EXPENSES, ROOT_COA, VALID_PARENTS)
47
75
  from django_ledger.models.mixins import CreateUpdateMixIn
48
76
  from django_ledger.models.utils import lazy_loader
49
77
  from django_ledger.settings import DJANGO_LEDGER_ACCOUNT_CODE_GENERATE, DJANGO_LEDGER_ACCOUNT_CODE_USE_PREFIX
@@ -61,68 +89,71 @@ class AccountModelValidationError(ValidationError):
61
89
 
62
90
  class AccountModelQuerySet(MP_NodeQuerySet):
63
91
  """
64
- A custom defined QuerySet, which inherits from the Materialized Path Tree implementation
65
- of Django Treebeard for tree-like model implementation.
92
+ Custom QuerySet for AccountModel inheriting from MP_NodeQuerySet.
66
93
  """
67
94
 
68
95
  def active(self):
69
96
  """
70
- Active accounts which can be used to create new transactions that show on drop-down menus and forms.
97
+ Filters the queryset to include only active items.
71
98
 
72
99
  Returns
73
- _______
100
+ -------
74
101
  AccountModelQuerySet
75
- A filtered AccountModelQuerySet of active accounts.
102
+ A filtered queryset containing only the items marked as active.
76
103
  """
77
104
  return self.filter(active=True)
78
105
 
79
106
  def inactive(self):
80
107
  """
81
- Inactive accounts cannot be used to create new transactions and don't show on drop-down menus and forms.
108
+ Filters and returns queryset entries where the active field is set to False.
82
109
 
83
110
  Returns
84
- _______
111
+ -------
85
112
  AccountModelQuerySet
86
- A filtered AccountModelQuerySet of inactive accounts.
113
+ A queryset containing entries with active=False.
87
114
  """
88
115
  return self.filter(active=False)
89
116
 
90
117
  def locked(self):
91
118
  """
92
- Filter locked elements.
93
-
94
- This method filters the elements based on the `locked` attribute and returns a filtered queryset.
119
+ Filters the queryset to include only locked AccountModels.
95
120
 
96
- Returns:
97
- A filtered queryset containing the locked elements.
121
+ Returns
122
+ -------
123
+ AccountModelQuerySet
124
+ A queryset containing only the objects with locked set to True.
98
125
  """
99
126
  return self.filter(locked=True)
100
127
 
101
128
  def unlocked(self):
102
129
  """
103
- Returns a filtered version of an object, excluding any locked items.
130
+ Returns a filtered list of items where the 'locked' attribute is set to False.
104
131
 
105
- Returns:
106
- A filtered version of the object, excluding any locked items.
132
+ Returns
133
+ -------
134
+ AccountModelQuerySet
135
+ A queryset of items with 'locked' attribute set to False
107
136
  """
108
137
  return self.filter(locked=False)
109
138
 
110
139
  def with_roles(self, roles: Union[List, str]):
111
140
  """
112
- This method is used to make query of accounts with a certain role. For instance, the fixed assets like
113
- Buildings have all been assigned the role of "asset_ppe_build" role is basically an aggregation of the
114
- accounts under a similar category. So, to query the list of all accounts under the role "asset_ppe_build",
115
- we can use this function.
141
+ Filter the accounts based on the specified roles. This method helps to retrieve accounts associated
142
+ with a particular role or a list of roles.
143
+
144
+ For example, to get all accounts categorized under the role "asset_ppe_build" (which might include
145
+ fixed assets like Buildings), you can utilize this method.
116
146
 
117
147
  Parameters
118
- __________
119
- roles: list or str
120
- Function accepts a single str instance of a role or a list of roles. For a list of roles , refer io.roles.py
148
+ ----------
149
+ roles : Union[List[str], str]
150
+ The role or a list of roles to filter the accounts by. If a single string is provided, it is converted
151
+ into a list containing that role.
121
152
 
122
153
  Returns
123
- _______
154
+ -------
124
155
  AccountModelQuerySet
125
- Returns a QuerySet filtered by user-provided list of Roles.
156
+ A QuerySet of accounts filtered by the provided roles.
126
157
  """
127
158
  if isinstance(roles, str):
128
159
  roles = [roles]
@@ -131,27 +162,58 @@ class AccountModelQuerySet(MP_NodeQuerySet):
131
162
 
132
163
  def expenses(self):
133
164
  """
134
- Return the expenses filtered by the roles specified in GROUP_EXPENSES.
165
+ Retrieve a queryset containing expenses filtered by specified roles.
135
166
 
136
- Returns:
137
- QuerySet: A queryset containing the expenses filtered by the GROUP_EXPENSES roles..
167
+ This method filters the expenses based on roles defined in the
168
+ `GROUP_EXPENSES` constant. It ensures that only the relevant expenses
169
+ associated with the specified roles are included in the queryset.
170
+
171
+ Returns
172
+ -------
173
+ AccountModelQuerySet
174
+ A queryset consisting of expenses filtered according to the roles in `GROUP_EXPENSES`.
138
175
  """
139
176
  return self.filter(role__in=GROUP_EXPENSES)
140
177
 
141
178
  def is_coa_root(self):
142
179
  """
143
- Check if the account model instance is the Chart of Account Root.
180
+ Retrieves the Chart of Accounts (CoA) root node queryset.
181
+
182
+ A Chart of Accounts Root is a foundational element indicating the primary node in the
183
+ account hierarchy. This method filters the queryset to include only the Chart of Accounts (CoA)
184
+ root node.
144
185
 
145
- Returns:
146
- bool: True if the Account is the CoA Root, False otherwise.
186
+ Returns
187
+ -------
188
+ AccountModelQuerySet
147
189
  """
148
190
  return self.filter(role__in=ROOT_GROUP)
149
191
 
150
192
  def not_coa_root(self):
193
+ """
194
+ Exclude AccountModels with ROOT_GROUP role from the QuerySet.
151
195
 
196
+ Returns
197
+ -------
198
+ AccountModelQuerySet
199
+ A QuerySet excluding users with role in ROOT_GROUP.
200
+ """
152
201
  return self.exclude(role__in=ROOT_GROUP)
153
202
 
154
203
  def for_entity(self, entity_slug, user_model):
204
+ """
205
+ Parameters
206
+ ----------
207
+ entity_slug : str
208
+ The slug identifier for the entity.
209
+ user_model : UserModel
210
+ The user model instance to use for filtering.
211
+
212
+ Returns
213
+ -------
214
+ AccountModelQuerySet
215
+ A Django QuerySet filtered by the specified entity and user permissions, ordered by 'code'.
216
+ """
155
217
  if isinstance(self, lazy_loader.get_entity_model()):
156
218
  return self.filter(
157
219
  Q(coa_model__entity=entity_slug) &
@@ -169,6 +231,16 @@ class AccountModelQuerySet(MP_NodeQuerySet):
169
231
  ).order_by('code')
170
232
 
171
233
  def gb_bs_role(self):
234
+ """
235
+ Groups accounts by Balance Sheet Bucket and then further groups them by role.
236
+
237
+ Returns
238
+ -------
239
+ List[Tuple]
240
+ A list where each element is a tuple. The first element of the tuple is the BS bucket,
241
+ and the second element is a list of tuples where each sub-tuple contains a role display
242
+ and a list of accounts that fall into that role within the BS bucket.
243
+ """
172
244
  accounts_gb = list((r, list(gb)) for r, gb in groupby(self, key=lambda acc: acc.get_bs_bucket()))
173
245
  return [
174
246
  (bsr, [
@@ -177,9 +249,26 @@ class AccountModelQuerySet(MP_NodeQuerySet):
177
249
  ]
178
250
 
179
251
  def is_role_default(self):
252
+ """
253
+ Filter the queryset to include only entries where `role_default`
254
+ is set to True, excluding entries marked as 'coa_root'.
255
+
256
+ Returns
257
+ -------
258
+ AccountModelQuerySet
259
+ Filtered queryset with `role_default` set to True and excluding 'coa_root' entries.
260
+ """
180
261
  return self.not_coa_root().filter(role_default=True)
181
262
 
182
263
  def can_transact(self):
264
+ """
265
+ Filter the queryset to include only accounts that can accept new transactions.
266
+
267
+ Returns
268
+ -------
269
+ QuerySet
270
+ A QuerySet containing the filtered results.
271
+ """
183
272
  return self.filter(
184
273
  Q(locked=False) & Q(active=True)
185
274
  )
@@ -193,14 +282,34 @@ class AccountModelManager(MP_NodeManager):
193
282
 
194
283
  def get_queryset(self) -> AccountModelQuerySet:
195
284
  """
196
- Sets the custom queryset as the default.
285
+ Retrieve and return athe default AccountModel QuerySet.
286
+
287
+ The query set is ordered by the 'path' field and uses 'select_related' to reduce the number of database queries
288
+ by retrieving the related 'coa_model'.
289
+
290
+ Returns
291
+ -------
292
+ AccountModelQuerySet
293
+ An instance of AccountModelQuerySet ordered by 'path' and prefetching related 'coa_model'.
197
294
  """
198
295
  return AccountModelQuerySet(
199
296
  self.model,
200
297
  using=self._db
201
298
  ).order_by('path').select_related('coa_model')
202
299
 
203
- def for_user(self, user_model):
300
+ def for_user(self, user_model) -> AccountModelQuerySet:
301
+ """
302
+ Parameters
303
+ ----------
304
+ user_model : UserModel
305
+ The user model instance to use for filtering.
306
+
307
+ Returns
308
+ -------
309
+ AccountModelQuerySet
310
+ The filtered queryset based on the user's permissions. Superusers get the complete queryset whereas other
311
+ users get a filtered queryset based on their role as admin or manager in the entity.
312
+ """
204
313
  qs = self.get_queryset()
205
314
  if user_model.is_superuser:
206
315
  return qs
@@ -210,32 +319,33 @@ class AccountModelManager(MP_NodeManager):
210
319
  )
211
320
 
212
321
  # todo: search for uses and pass EntityModel whenever possible.
213
- def for_entity(self,
214
- user_model,
215
- entity_slug,
216
- coa_slug: Optional[str] = None,
217
- select_coa_model: bool = True) -> AccountModelQuerySet:
322
+ def for_entity(
323
+ self,
324
+ user_model,
325
+ entity_slug,
326
+ coa_slug: Optional[str] = None,
327
+ select_coa_model: bool = True
328
+ ) -> AccountModelQuerySet:
218
329
  """
219
- Ensures that only accounts associated with the given EntityModel are returned.
330
+ Retrieves accounts associated with the specified EntityModel.
220
331
 
221
332
  Parameters
222
333
  ----------
223
- entity_slug: EntityModel or str
224
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
225
- result in an additional Database query to determine the default code of accounts.
226
- coa_slug: str
227
- Explicitly specify which chart of accounts to use. If None, will pull default Chart of Accounts.
228
- Discussed in detail in the CoA Model CoA slug, basically helps in identifying the complete Chart of
229
- Accounts for a particular EntityModel.
230
- user_model:
334
+ user_model: User
231
335
  The Django User Model making the request to check for permissions.
232
- select_coa_model: bool
233
- Pre fetches the CoA Model information in the QuerySet. Defaults to True.
336
+ entity_slug: Union[EntityModel, str]
337
+ The EntityModel instance or its slug to filter accounts by. If a slug is provided and `coa_slug` is None,
338
+ an additional
339
+ database query will be executed to determine the default Chart of Accounts.
340
+ coa_slug: Optional[str], default=None
341
+ The slug of the specific Chart of Accounts to use. If None, the default Chart of Accounts is selected.
342
+ select_coa_model: bool, default=True
343
+ If True, prefetches the CoA Model information in the QuerySet.
234
344
 
235
345
  Returns
236
346
  -------
237
347
  AccountModelQuerySet
238
- A QuerySet of all requested EntityModel Chart of Accounts.
348
+ A QuerySet containing accounts associated with the specified EntityModel and Chart of Accounts.
239
349
  """
240
350
  qs = self.for_user(user_model)
241
351
  if select_coa_model:
@@ -256,26 +366,28 @@ class AccountModelManager(MP_NodeManager):
256
366
 
257
367
  def for_entity_available(self, user_model, entity_slug, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
258
368
  """
259
- Convenience method to pull only available and unlocked AccountModels for a specific EntityModel.
369
+ Retrieve available and unlocked AccountModels for a specific EntityModel.
370
+
371
+ This method filters AccountModels associated with the specified EntityModel
372
+ that are active, not locked, and have an active Chart of Accounts.
260
373
 
261
374
  Parameters
262
375
  ----------
263
- entity_slug: EntityModel or str
264
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
265
- result in an additional Database query to determine the default code of accounts.
376
+ user_model: User
377
+ The Django User Model instance making the request, used to validate permissions.
266
378
 
267
- coa_slug: str
268
- Explicitly specify which chart of accounts to use. If None, will pull default Chart of Accounts.
269
- Discussed in detail in the CoA Model CoA slug, basically helps in identifying the complete Chart of
270
- Accounts for a particular EntityModel.
379
+ entity_slug: EntityModel or str
380
+ The EntityModel instance or its slug to pull accounts from. If entity_slug is passed
381
+ and coa_slug is None, an additional database query will be performed to determine
382
+ the default Chart of Accounts.
271
383
 
272
- user_model:
273
- The Django User Model making the request to check for permissions.
384
+ coa_slug: str, optional
385
+ The specific Chart of Accounts to use. If None, the default Chart of Accounts will be pulled.
274
386
 
275
387
  Returns
276
388
  -------
277
389
  AccountModelQuerySet
278
- A QuerySet of all requested EntityModel Chart of Accounts.
390
+ A QuerySet containing available and unlocked AccountModels for the specified EntityModel and Chart of Accounts.
279
391
  """
280
392
  qs = self.for_entity(
281
393
  user_model=user_model,
@@ -289,24 +401,27 @@ class AccountModelManager(MP_NodeManager):
289
401
 
290
402
  def with_roles(self, roles: Union[list, str], entity_slug, user_model) -> AccountModelQuerySet:
291
403
  """
292
- This method is used to make query of accounts with a certain role. For instance, the fixed assets like
293
- Buildings have all been assigned the role of "asset_ppe_build" role is basically an aggregation of the
294
- accounts under a similar category. So, to query the list of all accounts under the role "asset_ppe_build",
295
- we can use this function.
404
+ Retrieve accounts based on specific roles.
405
+
406
+ This method filters accounts associated with a given role or a list of roles. For example, if you need to
407
+ find all accounts under the "asset_ppe_build" role, which includes all buildings fixed assets, this method
408
+ can be used.
296
409
 
297
410
  Parameters
298
411
  ----------
299
412
  entity_slug: EntityModel or str
300
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
301
- result in an additional Database query to determine the default code of accounts.
302
- user_model
303
- The Django User Model making the request to check for permissions.
413
+ The EntityModel instance or its slug to fetch accounts from. If only the slug is provided and coa_slug is
414
+ not specified, an additional database query will be performed to determine the default chart of accounts.
415
+ user_model: User
416
+ The Django User model instance making the request to ensure appropriate permissions are checked.
304
417
  roles: list or str
305
- Function accepts a single str instance of a role or a list of roles. For a list of roles , refer io.roles.py
418
+ Accepts either a single role as a string or a list of roles. Refer to io.roles.py for a comprehensive
419
+ list of roles.
420
+
306
421
  Returns
307
422
  -------
308
423
  AccountModelQuerySet
309
- Returns a QuerySet filtered by user-provided list of Roles.
424
+ A QuerySet of accounts filtered by the specified roles.
310
425
  """
311
426
  roles = validate_roles(roles)
312
427
  if isinstance(roles, str):
@@ -319,27 +434,26 @@ class AccountModelManager(MP_NodeManager):
319
434
  user_model,
320
435
  coa_slug: Optional[str]) -> AccountModelQuerySet:
321
436
  """
322
- Convenience method to pull only available and unlocked AccountModels for a specific EntityModel and for a
323
- specific list of roles.
437
+ Retrieve available and unlocked AccountModels for a specified EntityModel and list of roles.
324
438
 
325
439
  Parameters
326
440
  ----------
327
- entity_slug: EntityModel or str
328
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
329
- result in an additional Database query to determine the default code of accounts.
330
- coa_slug: str
331
- Explicitly specify which chart of accounts to use. If None, will pull default Chart of Accounts.
332
- Discussed in detail in the CoA Model CoA slug, basically helps in identifying the complete Chart of
333
- Accounts for a particular EntityModel.
334
- user_model:
335
- The Django User Model making the request to check for permissions.
336
- roles: list or str
337
- Function accepts a single str instance of a role or a list of roles. For a list of roles , refer io.roles.py
441
+ roles : Union[list, str]
442
+ A single role as a string or a list of roles.
443
+ entity_slug : Union[str, 'EntityModel']
444
+ The EntityModel object or its slug. If a slug is provided and `coa_slug` is None, an additional
445
+ database query will be executed to fetch the default Chart of Accounts.
446
+ user_model : 'UserModel'
447
+ The Django UserModel instance making the request, used to check permissions.
448
+ coa_slug : Optional[str], default None
449
+ The specific Chart of Accounts slug. If None, the default Chart of Accounts will be used.
450
+ This parameter assists in identifying the complete Chart of Accounts for the EntityModel.
338
451
 
339
452
  Returns
340
453
  -------
341
454
  AccountModelQuerySet
342
- A QuerySet of all requested EntityModel Chart of Accounts.
455
+ A QuerySet containing available and unlocked AccountModel instances for the specified
456
+ EntityModel and roles.
343
457
  """
344
458
 
345
459
  if isinstance(roles, str):
@@ -350,53 +464,53 @@ class AccountModelManager(MP_NodeManager):
350
464
 
351
465
  def coa_roots(self, user_model, entity_slug, coa_slug) -> AccountModelQuerySet:
352
466
  """
353
- Fetches the Code of Account Root Accounts.
467
+ Retrieves the root accounts of a specified Code of Accounts (CoA).
354
468
 
355
469
  Parameters
356
470
  ----------
357
- entity_slug: EntityModel or str
358
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
359
- result in an additional Database query to determine the default code of accounts.
360
- coa_slug: str
361
- Explicitly specify which chart of accounts to use. If None, will pull default Chart of Accounts.
362
- Discussed in detail in the CoA Model CoA slug, basically helps in identifying the complete Chart of
363
- Accounts for a particular EntityModel.
364
- user_model:
365
- The Django User Model making the request to check for permissions.
471
+ user_model: object
472
+ The Django User model instance requesting the data, used for permission checking.
473
+ entity_slug: Union[EntityModel, str]
474
+ The entity or its slug from which to fetch accounts. If a slug is provided and `coa_slug` is None,
475
+ an additional database query is performed to determine the default Code of Accounts.
476
+ coa_slug: Optional[str]
477
+ The specific chart of accounts to retrieve. If None, the default chart of accounts for the entity
478
+ will be used. This is crucial for identifying the complete set of accounts for a given entity.
366
479
 
367
480
  Returns
368
481
  -------
369
-
482
+ AccountModelQuerySet
483
+ A queryset of root accounts for the specified Code of Accounts.
370
484
  """
371
485
  qs = self.for_entity(user_model=user_model, entity_slug=entity_slug, coa_slug=coa_slug)
372
486
  return qs.is_coa_root()
373
487
 
374
488
  def for_invoice(self, user_model, entity_slug: str, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
375
489
  """
376
- Convenience method to pull only available and unlocked AccountModels for a specific EntityModel relevant only
377
- for creating and management of Invoices. See :func:`GROUP_INVOICE <django_ledger.io.roles.GROUP_INVOICE>`.
490
+ Retrieves available and unlocked AccountModels for a specific EntityModel, specifically for the creation
491
+ and management of Invoices.
378
492
 
379
- Roles in GROUP_INVOICE: ASSET_CA_CASH, ASSET_CA_RECEIVABLES, LIABILITY_CL_DEFERRED_REVENUE.
493
+ This method ensures that only relevant accounts are pulled, as defined under the roles in `GROUP_INVOICE`.
494
+ These roles include: ASSET_CA_CASH, ASSET_CA_RECEIVABLES, and LIABILITY_CL_DEFERRED_REVENUE.
380
495
 
381
496
  Parameters
382
- __________
383
-
384
- entity_slug: EntityModel or str
385
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
386
- result in an additional Database query to determine the default code of accounts.
497
+ ----------
498
+ user_model: User
499
+ The Django User Model instance requesting access. It is used to check the necessary permissions.
387
500
 
388
- coa_slug: str
389
- Explicitly specify which chart of accounts to use. If None, will pull default Chart of Accounts.
390
- Discussed in detail in the CoA Model CoA slug, basically helps in identifying the complete Chart of
391
- Accounts for a particular EntityModel.
501
+ entity_slug: Union[EntityModel, str]
502
+ Specifies the EntityModel or its slug to pull accounts from. If a slug is provided and `coa_slug` is `None`,
503
+ the method will perform an additional database query to determine the default chart of accounts.
392
504
 
393
- user_model:
394
- The Django User Model making the request to check for permissions.
505
+ coa_slug: Optional[str], default=None
506
+ Explicitly specifies which chart of accounts to use. If `None`, the method will default to using
507
+ the EntityModel's default chart of accounts.
395
508
 
396
509
  Returns
397
- _______
510
+ -------
398
511
  AccountModelQuerySet
399
- A QuerySet of all requested EntityModel Chart of Accounts.
512
+ A QuerySet containing the AccountModels relevant for the specified EntityModel and the roles defined
513
+ in `GROUP_INVOICE`.
400
514
  """
401
515
  qs = self.for_entity_available(
402
516
  user_model=user_model,
@@ -406,30 +520,26 @@ class AccountModelManager(MP_NodeManager):
406
520
 
407
521
  def for_bill(self, user_model, entity_slug, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
408
522
  """
409
- Convenience method to pull only available and unlocked AccountModels for a specific EntityModel relevant only
410
- for creating and management of Bills. See :func:`GROUP_BILL <django_ledger.io.roles.GROUP_BILL>`.
411
-
412
- Roles in GROUP_BILL: ASSET_CA_CASH, ASSET_CA_PREPAID, LIABILITY_CL_ACC_PAYABLE.
523
+ Retrieves only available and unlocked AccountModels for a specific EntityModel,
524
+ specifically for the creation and management of Bills. Roles within the 'GROUP_BILL'
525
+ context include: ASSET_CA_CASH, ASSET_CA_PREPAID, and LIABILITY_CL_ACC_PAYABLE.
413
526
 
414
527
  Parameters
415
- __________
416
-
417
- entity_slug: EntityModel or str
418
- The EntityModel or EntityModel slug to pull accounts from. If slug is passed and coa_slug is None will
419
- result in an additional Database query to determine the default code of accounts.
528
+ ----------
529
+ user_model : Django User Model
530
+ The Django User Model that is making the request, used to check for permissions.
420
531
 
421
- coa_slug: str
422
- Explicitly specify which chart of accounts to use. If None, will pull default Chart of Accounts.
423
- Discussed in detail in the CoA Model CoA slug, basically helps in identifying the complete Chart of
424
- Accounts for a particular EntityModel.
532
+ entity_slug : Union[EntityModel, str]
533
+ The EntityModel or EntityModel slug from which to pull accounts. If given a slug and coa_slug
534
+ is None, an additional database query will be made to determine the default chart of accounts.
425
535
 
426
- user_model:
427
- The Django User Model making the request to check for permissions.
536
+ coa_slug : Optional[str]
537
+ The specific chart of accounts to use. If None, it will default to the EntityModel's default chart of accounts.
428
538
 
429
539
  Returns
430
- _______
540
+ -------
431
541
  AccountModelQuerySet
432
- A QuerySet of all requested EntityModel Chart of Accounts.
542
+ A QuerySet of the requested EntityModel's chart of accounts.
433
543
  """
434
544
  qs = self.for_entity_available(
435
545
  user_model=user_model,
@@ -444,42 +554,33 @@ def account_code_validator(value: str):
444
554
 
445
555
 
446
556
  class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
447
- """ AccountModelAbstract
448
-
557
+ """
449
558
  Abstract class representing an Account Model.
450
559
 
451
560
  Attributes
452
561
  ----------
453
562
  BALANCE_TYPE : list
454
- List of choices for the balance type of the account.
455
-
563
+ List of choices for the balance type of the account. Options include 'Credit' and 'Debit'.
456
564
  uuid : UUIDField
457
- UUID field representing the primary key of the account.
458
-
565
+ Unique identifier for each account instance.
459
566
  code : CharField
460
- CharField representing the account code.
461
-
567
+ Code representing the account, constrained by length and specific validation rules.
462
568
  name : CharField
463
- CharField representing the account name.
464
-
569
+ Name of the account, constrained by length.
465
570
  role : CharField
466
- CharField representing the account role.
467
-
571
+ Role associated with the account, with specific predefined choices.
468
572
  role_default : BooleanField
469
- BooleanField representing whether the account is a default account for the role.
470
-
573
+ Flag indicating if this account is the default for its role.
471
574
  balance_type : CharField
472
- CharField representing the balance type of the account. Must be 'debit' or 'credit'.
473
-
575
+ Type of balance the account holds, must be either 'debit' or 'credit'.
474
576
  locked : BooleanField
475
- BooleanField representing whether the account is locked.
476
-
577
+ Indicates whether the account is locked.
477
578
  active : BooleanField
478
- BooleanField representing whether the account is active.
479
-
579
+ Indicates whether the account is active.
480
580
  coa_model : ForeignKey
481
- ForeignKey representing the associated ChartOfAccountModel.
581
+ Reference to the associated ChartOfAccountModel.
482
582
  """
583
+
483
584
  BALANCE_TYPE = [
484
585
  (CREDIT, _('Credit')),
485
586
  (DEBIT, _('Debit'))
@@ -537,30 +638,33 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
537
638
  active: bool = False,
538
639
  **kwargs):
539
640
  """
540
- Convenience Method to Create a new Account Model. This is the preferred method to create new Accounts in order
541
- to properly handle parent/child relationships between models.
641
+ Create a new AccountModel instance, managing parent/child relationships properly.
642
+
643
+ This convenience method ensures correct creation of new accounts, handling the intricate logic needed for
644
+ maintaining hierarchical relationships between accounts.
542
645
 
543
646
  Parameters
544
647
  ----------
545
- name: str
546
- The name of the new Entity.
547
- role: str
548
- Account role.
549
- balance_type: str
550
- Account Balance Type. Must be 'debit' or 'credit'.
551
- is_role_default: bool
552
- If True, assigns account as default for role. Only once default account per role is permitted.
553
- locked: bool
554
- Marks account as Locked. Defaults to False.
555
- active: bool
556
- Marks account as Active. Defaults to True.
557
-
648
+ name : str
649
+ Name of the new account entity.
650
+ role : str
651
+ Role assigned to the account.
652
+ balance_type : str
653
+ Type of balance associated with the account. Must be either 'debit' or 'credit'.
654
+ is_role_default : bool, optional
655
+ Indicates if the account should be the default for its role. Only one default account per role is allowed.
656
+ Defaults to False.
657
+ locked : bool, optional
658
+ Flags the account as locked. Defaults to False.
659
+ active : bool, optional
660
+ Flags the account as active. Defaults to True.
661
+ **kwargs : dict, optional
662
+ Additional attributes for account creation.
558
663
 
559
664
  Returns
560
665
  -------
561
666
  AccountModel
562
- The newly created AccountModel instance.
563
-
667
+ The newly created `AccountModel` instance.
564
668
  """
565
669
  account_model = cls(
566
670
  name=name,
@@ -578,25 +682,35 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
578
682
  @property
579
683
  def role_bs(self) -> str:
580
684
  """
581
- The principal role of the account on the balance sheet.
582
- Options are:
583
- * asset
584
- * liability
585
- * equity
685
+ Returns the principal role of the account on the balance sheet.
686
+
687
+ The principal role can be one of the following:
688
+ - 'asset'
689
+ - 'liability'
690
+ - 'equity'
586
691
 
587
692
  Returns
588
693
  -------
589
694
  str
590
- A String representing the principal role of the account on the balance sheet.
695
+ A string representing the principal role of the account on the balance sheet.
591
696
  """
592
697
  return BS_ROLES.get(self.role)
593
698
 
594
699
  def is_root_account(self):
700
+ """
701
+ Checks if the current user's role belongs to the ROOT_GROUP.
702
+
703
+ Returns
704
+ -------
705
+ bool
706
+ True if the role is in the ROOT_GROUP, False otherwise
707
+ """
595
708
  return self.role in ROOT_GROUP
596
709
 
597
710
  def is_debit(self) -> bool:
598
711
  """
599
- Checks if the account has a DEBIT balance.
712
+ Checks if the account has a DEBIT balance type.
713
+
600
714
  Returns
601
715
  -------
602
716
  bool
@@ -606,7 +720,8 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
606
720
 
607
721
  def is_credit(self):
608
722
  """
609
- Checks if the account has a CREDIT balance.
723
+ Checks if the Account Model has a CREDIT balance type.
724
+
610
725
  Returns
611
726
  -------
612
727
  bool
@@ -615,43 +730,156 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
615
730
  return self.balance_type == CREDIT
616
731
 
617
732
  def is_coa_root(self):
733
+ """
734
+ Check if the current Account Model role is 'ROOT_COA'.
735
+
736
+ Returns
737
+ -------
738
+ bool
739
+ True if the role is 'ROOT_COA', False otherwise.
740
+ """
618
741
  return self.role == ROOT_COA
619
742
 
620
743
  def is_asset(self) -> bool:
744
+ """
745
+ Determines if the current Account Model role of the instance is considered an asset.
746
+
747
+ Returns
748
+ -------
749
+ bool
750
+ True if the role is part of the GROUP_ASSETS, False otherwise.
751
+ """
621
752
  return self.role in GROUP_ASSETS
622
753
 
623
754
  def is_liability(self) -> bool:
755
+ """
756
+ Determines if the current Account Model role is considered a liability.
757
+
758
+ Returns
759
+ -------
760
+ bool
761
+ True if the role is part of GROUP_LIABILITIES, otherwise False.
762
+ """
624
763
  return self.role in GROUP_LIABILITIES
625
764
 
626
765
  def is_capital(self) -> bool:
766
+ """
767
+ Checks if the current Account Model role is in the capital group.
768
+
769
+ Returns
770
+ -------
771
+ bool
772
+ True if the role is in GROUP_CAPITAL, otherwise False.
773
+ """
627
774
  return self.role in GROUP_CAPITAL
628
775
 
629
776
  def is_income(self) -> bool:
777
+ """
778
+ Determines whether the current Account Model role belongs to the income group.
779
+
780
+ Parameters
781
+ ----------
782
+ self : object
783
+ The instance of the class containing attribute 'role'.
784
+
785
+ Returns
786
+ -------
787
+ bool
788
+ True if the role is in the GROUP_INCOME list, False otherwise.
789
+ """
630
790
  return self.role in GROUP_INCOME
631
791
 
632
792
  def is_cogs(self) -> bool:
793
+ """
794
+ Determines if the role of the object is part of the GROUP_COGS.
795
+
796
+ Returns
797
+ -------
798
+ bool
799
+ True if the object's role is part of the GROUP_COGS, False otherwise.
800
+ """
633
801
  return self.role in GROUP_COGS
634
802
 
635
803
  def is_expense(self) -> bool:
804
+ """
805
+ Checks if the current Account Model `role` is categorized under `GROUP_EXPENSES`.
806
+
807
+ Parameters
808
+ ----------
809
+ None
810
+
811
+ Returns
812
+ -------
813
+ bool
814
+ True if `role` is in `GROUP_EXPENSES`, otherwise False.
815
+ """
636
816
  return self.role in GROUP_EXPENSES
637
817
 
638
818
  def is_active(self) -> bool:
819
+ """
820
+ Determines if the current instance is active.
821
+
822
+ Returns
823
+ -------
824
+ bool
825
+ True if the instance is active, otherwise False
826
+ """
639
827
  return self.active is True
640
828
 
641
829
  def is_locked(self) -> bool:
830
+ """
831
+ Determines if the current object is locked.
832
+
833
+ Returns
834
+ -------
835
+ bool
836
+ True if the object is locked, False otherwise.
837
+
838
+ """
642
839
  return self.locked is True
643
840
 
644
841
  def can_activate(self):
842
+ """
843
+ Determines if the object can be activated.
844
+
845
+ Returns
846
+ -------
847
+ bool
848
+ True if the object is inactive, otherwise False.
849
+ """
645
850
  return all([
646
851
  self.active is False
647
852
  ])
648
853
 
649
854
  def can_deactivate(self):
855
+ """
856
+ Determine if the object can be deactivated.
857
+
858
+ Checks if the `active` attribute is set to `True`.
859
+
860
+ Returns
861
+ -------
862
+ bool
863
+ True if the object is currently active and can be deactivated, otherwise False.
864
+ """
650
865
  return all([
651
866
  self.active is True
652
867
  ])
653
868
 
654
869
  def activate(self, commit: bool = True, raise_exception: bool = True, **kwargs):
870
+ """
871
+ Checks if the Account Model instance can be activated, then Activates the AccountModel instance.
872
+ Raises exception if AccountModel cannot be activated.
873
+
874
+ Parameters
875
+ ----------
876
+ commit : bool, optional
877
+ If True, commit the changes to the database by calling the save method.
878
+ raise_exception : bool, optional
879
+ If True, raises an AccountModelValidationError if the account cannot be activated.
880
+ kwargs : dict
881
+ Additional parameters that can be passed for further customization.
882
+ """
655
883
  if not self.can_activate():
656
884
  if raise_exception:
657
885
  raise AccountModelValidationError(
@@ -666,6 +894,19 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
666
894
  ])
667
895
 
668
896
  def deactivate(self, commit: bool = True, raise_exception: bool = True, **kwargs):
897
+ """
898
+ Checks if the Account Model instance can be de-activated, then De-activates the AccountModel instance.
899
+ Raises exception if AccountModel cannot be de-activated.
900
+
901
+ Parameters
902
+ ----------
903
+ commit : bool, optional
904
+ If True, commit the changes to the database by calling the save method.
905
+ raise_exception : bool, optional
906
+ If True, raises an AccountModelValidationError if the account cannot be activated.
907
+ kwargs : dict
908
+ Additional parameters that can be passed for further customization.
909
+ """
669
910
  if not self.can_deactivate():
670
911
  if raise_exception:
671
912
  raise AccountModelValidationError(
@@ -674,12 +915,26 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
674
915
  return
675
916
  self.active = False
676
917
  if commit:
677
- self.save(update_fields=[
678
- 'active',
679
- 'updated'
680
- ])
918
+ self.save(
919
+ update_fields=[
920
+ 'active',
921
+ 'updated'
922
+ ])
681
923
 
682
924
  def can_transact(self) -> bool:
925
+ """
926
+ Determines if a transaction can be performed based on multiple conditions.
927
+
928
+ Returns
929
+ -------
930
+ bool
931
+ True if all conditions are met, enabling a transaction; False otherwise.
932
+
933
+ Conditions:
934
+ 1. The chart of accounts (coa_model) must be active.
935
+ 2. The entity must not be locked.
936
+ 3. The entity itself must be active.
937
+ """
683
938
  return all([
684
939
  self.coa_model.is_active(),
685
940
  not self.is_locked(),
@@ -687,7 +942,25 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
687
942
  ])
688
943
 
689
944
  def get_code_prefix(self) -> str:
945
+ """
946
+ Returns the code prefix based on the account type.
690
947
 
948
+ This method determines the account type by calling the respective
949
+ account type methods and returns the corresponding code prefix based on Accounting best practices..
950
+
951
+ Returns
952
+ -------
953
+ str
954
+ The code prefix for the account type. The possible values are:
955
+ '1' for assets, '2' for liabilities, '3' for capital,
956
+ '4' for income, '5' for cost of goods sold (COGS),
957
+ '6' for expenses.
958
+
959
+ Raises
960
+ ------
961
+ AccountModelValidationError
962
+ If the account role does not match any of the predefined categories.
963
+ """
691
964
  if self.is_asset():
692
965
  return '1'
693
966
  elif self.is_liability():
@@ -703,6 +976,19 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
703
976
  raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
704
977
 
705
978
  def get_root_role(self) -> str:
979
+ """
980
+ Returns the root role corresponding to the account type.
981
+
982
+ Returns
983
+ -------
984
+ str
985
+ The root role corresponding to the account type.
986
+
987
+ Raises
988
+ ------
989
+ AccountModelValidationError
990
+ If no valid role match is found for the account's role.
991
+ """
706
992
  if self.is_asset():
707
993
  return ROOT_ASSETS
708
994
  elif self.is_liability():
@@ -720,10 +1006,23 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
720
1006
  raise AccountModelValidationError(f'Invalid role match for role {self.role}...')
721
1007
 
722
1008
  def get_account_move_choice_queryset(self):
1009
+ """
1010
+ Retrieves a filtered queryset of account models that the current Account Model instance
1011
+ can be a child of.
1012
+
1013
+ The queryset is filtered based on the specified role and its hierarchical parent roles.
1014
+ Account models with a UUID matching the current instance's UUID are excluded from the results.
1015
+
1016
+ Returns
1017
+ -------
1018
+ QuerySet
1019
+ A filtered set of account models suitable for moving the current instance under.
1020
+ """
723
1021
  return self.coa_model.accountmodel_set.filter(
724
1022
  role__in=[
725
1023
  self.role,
726
- self.get_root_role()
1024
+ self.get_root_role(),
1025
+ *VALID_PARENTS.get(self.role, [])
727
1026
  ],
728
1027
  ).exclude(uuid__exact=self.uuid)
729
1028
 
@@ -731,12 +1030,41 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
731
1030
  return BS_BUCKETS[self.get_code_prefix()]
732
1031
 
733
1032
  def is_indented(self):
1033
+ """
1034
+ Check if the current depth level is greater than 2.
1035
+
1036
+ Returns
1037
+ -------
1038
+ bool
1039
+ True if the depth is greater than 2, False otherwise.
1040
+ """
734
1041
  return self.depth > 2
735
1042
 
736
1043
  def get_html_pixel_indent(self):
1044
+ """
1045
+ Calculates the pixel indentation for HTML elements based on the depth attribute for UI purposes
1046
+
1047
+ Returns
1048
+ -------
1049
+ str
1050
+ The calculated pixel indentation as a string with 'px' suffix.
1051
+ """
737
1052
  return f'{(self.depth - 2) * 40}px'
738
1053
 
739
1054
  def generate_random_code(self):
1055
+ """
1056
+ Generates a random code for the account adding a prefix 1-6 depending on account role.
1057
+
1058
+ Raises
1059
+ ------
1060
+ AccountModelValidationError
1061
+ If the account role is not assigned before code generation.
1062
+
1063
+ Returns
1064
+ -------
1065
+ str
1066
+ A randomly generated code prefixed with a role-based prefix.
1067
+ """
740
1068
  if not self.role:
741
1069
  raise AccountModelValidationError('Must assign account role before generate random code')
742
1070
 
@@ -755,7 +1083,6 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
755
1083
  )
756
1084
 
757
1085
  def clean(self):
758
-
759
1086
  if not self.code and DJANGO_LEDGER_ACCOUNT_CODE_GENERATE:
760
1087
  self.code = self.generate_random_code()
761
1088