django-ledger 0.6.4__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -4
- django_ledger/admin/__init__.py +1 -1
- django_ledger/admin/{coa.py → chart_of_accounts.py} +1 -1
- django_ledger/admin/entity.py +1 -1
- django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
- django_ledger/forms/account.py +43 -38
- django_ledger/forms/bank_account.py +5 -2
- django_ledger/forms/bill.py +24 -36
- django_ledger/forms/chart_of_accounts.py +82 -0
- django_ledger/forms/customer.py +1 -1
- django_ledger/forms/data_import.py +3 -3
- django_ledger/forms/estimate.py +1 -1
- django_ledger/forms/invoice.py +5 -7
- django_ledger/forms/item.py +24 -15
- django_ledger/forms/transactions.py +3 -3
- django_ledger/io/io_core.py +4 -2
- django_ledger/io/io_library.py +1 -1
- django_ledger/io/io_middleware.py +5 -0
- django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
- django_ledger/migrations/0018_transactionmodel_cleared_transactionmodel_reconciled_and_more.py +37 -0
- django_ledger/models/__init__.py +1 -1
- django_ledger/models/accounts.py +229 -265
- django_ledger/models/bank_account.py +6 -6
- django_ledger/models/bill.py +7 -6
- django_ledger/models/{coa.py → chart_of_accounts.py} +187 -72
- django_ledger/models/closing_entry.py +5 -10
- django_ledger/models/coa_default.py +10 -9
- django_ledger/models/customer.py +6 -6
- django_ledger/models/data_import.py +12 -8
- django_ledger/models/entity.py +96 -39
- django_ledger/models/estimate.py +6 -10
- django_ledger/models/invoice.py +14 -11
- django_ledger/models/items.py +23 -14
- django_ledger/models/journal_entry.py +73 -30
- django_ledger/models/ledger.py +8 -8
- django_ledger/models/mixins.py +0 -3
- django_ledger/models/purchase_order.py +9 -9
- django_ledger/models/signals.py +0 -3
- django_ledger/models/transactions.py +24 -7
- django_ledger/models/unit.py +4 -3
- django_ledger/models/utils.py +0 -3
- django_ledger/models/vendor.py +4 -3
- django_ledger/settings.py +28 -3
- django_ledger/templates/django_ledger/account/account_create.html +2 -2
- django_ledger/templates/django_ledger/account/account_update.html +1 -1
- django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +29 -19
- django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
- django_ledger/templates/django_ledger/chart_of_accounts/coa_create.html +25 -0
- django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +25 -6
- django_ledger/templates/django_ledger/chart_of_accounts/coa_update.html +2 -2
- django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +10 -4
- django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
- django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +2 -2
- django_ledger/templates/django_ledger/includes/footer.html +2 -2
- django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
- django_ledger/templatetags/django_ledger.py +7 -1
- django_ledger/tests/base.py +23 -7
- django_ledger/tests/test_accounts.py +145 -9
- django_ledger/urls/account.py +17 -24
- django_ledger/urls/chart_of_accounts.py +6 -0
- django_ledger/utils.py +9 -36
- django_ledger/views/__init__.py +2 -2
- django_ledger/views/account.py +91 -116
- django_ledger/views/auth.py +1 -1
- django_ledger/views/bank_account.py +9 -11
- django_ledger/views/bill.py +91 -80
- django_ledger/views/{coa.py → chart_of_accounts.py} +49 -44
- django_ledger/views/closing_entry.py +8 -0
- django_ledger/views/customer.py +1 -1
- django_ledger/views/data_import.py +1 -1
- django_ledger/views/entity.py +1 -1
- django_ledger/views/estimate.py +13 -8
- django_ledger/views/feedback.py +1 -1
- django_ledger/views/financial_statement.py +1 -1
- django_ledger/views/home.py +1 -1
- django_ledger/views/inventory.py +9 -0
- django_ledger/views/invoice.py +5 -2
- django_ledger/views/item.py +58 -68
- django_ledger/views/journal_entry.py +1 -1
- django_ledger/views/ledger.py +3 -1
- django_ledger/views/mixins.py +25 -13
- django_ledger/views/purchase_order.py +1 -1
- django_ledger/views/transactions.py +1 -1
- django_ledger/views/unit.py +9 -0
- django_ledger/views/vendor.py +1 -1
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +8 -2
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/METADATA +33 -44
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/RECORD +92 -89
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +1 -1
- django_ledger/forms/coa.py +0 -47
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/top_level.txt +0 -0
django_ledger/models/accounts.py
CHANGED
|
@@ -2,14 +2,8 @@
|
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
4
|
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
-
* Pranav P Tulshyan <ptulshyan77@gmail.com>
|
|
8
|
-
|
|
9
|
-
|
|
10
5
|
AccountModel
|
|
11
6
|
------------
|
|
12
|
-
|
|
13
7
|
The AccountModel is a fundamental component of the Django Ledger system, responsible for categorizing and organizing
|
|
14
8
|
financial transactions related to an entity's assets, liabilities, and equity.
|
|
15
9
|
|
|
@@ -60,18 +54,19 @@ from uuid import uuid4
|
|
|
60
54
|
|
|
61
55
|
from django.core.exceptions import ValidationError
|
|
62
56
|
from django.db import models
|
|
63
|
-
from django.db.models import Q
|
|
57
|
+
from django.db.models import Q, F, UniqueConstraint
|
|
64
58
|
from django.db.models.signals import pre_save
|
|
65
59
|
from django.urls import reverse
|
|
66
60
|
from django.utils.translation import gettext_lazy as _
|
|
67
61
|
from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
|
|
68
62
|
|
|
69
|
-
from django_ledger.io.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
from django_ledger.io.roles import (
|
|
64
|
+
ACCOUNT_ROLE_CHOICES, BS_ROLES, GROUP_INVOICE, GROUP_BILL, validate_roles,
|
|
65
|
+
GROUP_ASSETS, GROUP_LIABILITIES, GROUP_CAPITAL, GROUP_INCOME, GROUP_EXPENSES, GROUP_COGS,
|
|
66
|
+
ROOT_GROUP, BS_BUCKETS, ROOT_ASSETS, ROOT_LIABILITIES,
|
|
67
|
+
ROOT_CAPITAL, ROOT_INCOME, ROOT_EXPENSES, ROOT_COA, VALID_PARENTS,
|
|
68
|
+
ROLES_ORDER_ALL
|
|
69
|
+
)
|
|
75
70
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
76
71
|
from django_ledger.models.utils import lazy_loader
|
|
77
72
|
from django_ledger.settings import DJANGO_LEDGER_ACCOUNT_CODE_GENERATE, DJANGO_LEDGER_ACCOUNT_CODE_USE_PREFIX
|
|
@@ -155,11 +150,17 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
155
150
|
AccountModelQuerySet
|
|
156
151
|
A QuerySet of accounts filtered by the provided roles.
|
|
157
152
|
"""
|
|
153
|
+
roles = validate_roles(roles)
|
|
158
154
|
if isinstance(roles, str):
|
|
159
155
|
roles = [roles]
|
|
160
156
|
roles = validate_roles(roles)
|
|
161
157
|
return self.filter(role__in=roles)
|
|
162
158
|
|
|
159
|
+
def with_codes(self, codes: Union[List, str]):
|
|
160
|
+
if isinstance(codes, str):
|
|
161
|
+
codes = [codes]
|
|
162
|
+
return self.filter(code__in=codes)
|
|
163
|
+
|
|
163
164
|
def expenses(self):
|
|
164
165
|
"""
|
|
165
166
|
Retrieve a queryset containing expenses filtered by specified roles.
|
|
@@ -200,36 +201,6 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
200
201
|
"""
|
|
201
202
|
return self.exclude(role__in=ROOT_GROUP)
|
|
202
203
|
|
|
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
|
-
"""
|
|
217
|
-
if isinstance(self, lazy_loader.get_entity_model()):
|
|
218
|
-
return self.filter(
|
|
219
|
-
Q(coa_model__entity=entity_slug) &
|
|
220
|
-
(
|
|
221
|
-
Q(coa_model__entity__admin=user_model) |
|
|
222
|
-
Q(coa_model__entity__managers__in=[user_model])
|
|
223
|
-
)
|
|
224
|
-
).order_by('code')
|
|
225
|
-
return self.filter(
|
|
226
|
-
Q(coa_model__entity__slug__exact=entity_slug) &
|
|
227
|
-
(
|
|
228
|
-
Q(coa_model__entity__admin=user_model) |
|
|
229
|
-
Q(coa_model__entity__managers__in=[user_model])
|
|
230
|
-
)
|
|
231
|
-
).order_by('code')
|
|
232
|
-
|
|
233
204
|
def gb_bs_role(self):
|
|
234
205
|
"""
|
|
235
206
|
Groups accounts by Balance Sheet Bucket and then further groups them by role.
|
|
@@ -241,10 +212,14 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
241
212
|
and the second element is a list of tuples where each sub-tuple contains a role display
|
|
242
213
|
and a list of accounts that fall into that role within the BS bucket.
|
|
243
214
|
"""
|
|
244
|
-
accounts_gb = list(
|
|
215
|
+
accounts_gb = list(
|
|
216
|
+
(r, sorted(list(gb), key=lambda acc: ROLES_ORDER_ALL.index(acc.role))) for r, gb in
|
|
217
|
+
groupby(self, key=lambda acc: acc.get_bs_bucket())
|
|
218
|
+
)
|
|
245
219
|
return [
|
|
246
220
|
(bsr, [
|
|
247
|
-
(r, list(l)
|
|
221
|
+
(r, sorted(list(l), key=lambda acc: acc.code)) for r, l in
|
|
222
|
+
groupby(gb, key=lambda a: a.get_role_display())
|
|
248
223
|
]) for bsr, gb in accounts_gb
|
|
249
224
|
]
|
|
250
225
|
|
|
@@ -270,9 +245,47 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
270
245
|
A QuerySet containing the filtered results.
|
|
271
246
|
"""
|
|
272
247
|
return self.filter(
|
|
273
|
-
Q(locked=False) &
|
|
248
|
+
Q(locked=False) &
|
|
249
|
+
Q(active=True) &
|
|
250
|
+
Q(coa_model__active=True)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def available(self):
|
|
254
|
+
return self.filter(
|
|
255
|
+
Q(locked=False) &
|
|
256
|
+
Q(active=True) &
|
|
257
|
+
Q(coa_model__active=True)
|
|
274
258
|
)
|
|
275
259
|
|
|
260
|
+
def for_bill(self):
|
|
261
|
+
"""
|
|
262
|
+
Retrieves only available and unlocked AccountModels for a specific EntityModel,
|
|
263
|
+
specifically for the creation and management of Bills. Roles within the 'GROUP_BILL'
|
|
264
|
+
context include: ASSET_CA_CASH, ASSET_CA_PREPAID, and LIABILITY_CL_ACC_PAYABLE.
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
AccountModelQuerySet
|
|
269
|
+
A QuerySet of the requested EntityModel's chart of accounts.
|
|
270
|
+
"""
|
|
271
|
+
return self.available().filter(role__in=GROUP_BILL)
|
|
272
|
+
|
|
273
|
+
def for_invoice(self):
|
|
274
|
+
"""
|
|
275
|
+
Retrieves available and unlocked AccountModels for a specific EntityModel, specifically for the creation
|
|
276
|
+
and management of Invoices.
|
|
277
|
+
|
|
278
|
+
This method ensures that only relevant accounts are pulled, as defined under the roles in `GROUP_INVOICE`.
|
|
279
|
+
These roles include: ASSET_CA_CASH, ASSET_CA_RECEIVABLES, and LIABILITY_CL_DEFERRED_REVENUE.
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
AccountModelQuerySet
|
|
284
|
+
A QuerySet containing the AccountModels relevant for the specified EntityModel and the roles defined
|
|
285
|
+
in `GROUP_INVOICE`.
|
|
286
|
+
"""
|
|
287
|
+
return self.available().filter(role__in=GROUP_INVOICE)
|
|
288
|
+
|
|
276
289
|
|
|
277
290
|
class AccountModelManager(MP_NodeManager):
|
|
278
291
|
"""
|
|
@@ -295,7 +308,12 @@ class AccountModelManager(MP_NodeManager):
|
|
|
295
308
|
return AccountModelQuerySet(
|
|
296
309
|
self.model,
|
|
297
310
|
using=self._db
|
|
298
|
-
).order_by('path').select_related(
|
|
311
|
+
).order_by('path').select_related(
|
|
312
|
+
'coa_model').annotate(
|
|
313
|
+
_coa_slug=F('coa_model__slug'),
|
|
314
|
+
_coa_active=F('coa_model__active'),
|
|
315
|
+
_entity_slug=F('coa_model__entity__slug'),
|
|
316
|
+
)
|
|
299
317
|
|
|
300
318
|
def for_user(self, user_model) -> AccountModelQuerySet:
|
|
301
319
|
"""
|
|
@@ -318,235 +336,54 @@ class AccountModelManager(MP_NodeManager):
|
|
|
318
336
|
Q(coa_model__entity__managers__in=[user_model])
|
|
319
337
|
)
|
|
320
338
|
|
|
321
|
-
# todo: search for uses and pass EntityModel whenever possible.
|
|
322
339
|
def for_entity(
|
|
323
340
|
self,
|
|
324
341
|
user_model,
|
|
325
|
-
|
|
326
|
-
coa_slug: Optional[str] = None
|
|
327
|
-
select_coa_model: bool = True
|
|
342
|
+
entity_model,
|
|
343
|
+
coa_slug: Optional[str] = None
|
|
328
344
|
) -> AccountModelQuerySet:
|
|
329
345
|
"""
|
|
330
|
-
|
|
346
|
+
Retrieve accounts associated with a specified EntityModel and Chart of Accounts.
|
|
331
347
|
|
|
332
348
|
Parameters
|
|
333
349
|
----------
|
|
334
|
-
user_model: User
|
|
335
|
-
The Django User
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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.
|
|
350
|
+
user_model : User
|
|
351
|
+
The Django User instance initiating the request. Used to check for required permissions.
|
|
352
|
+
entity_model : Union[EntityModel, str]
|
|
353
|
+
An instance of EntityModel or its slug. This determines the entity whose accounts are being retrieved.
|
|
354
|
+
A database query will be carried out to identify the default Chart of Accounts.
|
|
355
|
+
coa_slug : Optional[str], default=None
|
|
356
|
+
The slug for a specific Chart of Accounts to be used. If None, the default Chart of Accounts will be selected.
|
|
344
357
|
|
|
345
358
|
Returns
|
|
346
359
|
-------
|
|
347
360
|
AccountModelQuerySet
|
|
348
361
|
A QuerySet containing accounts associated with the specified EntityModel and Chart of Accounts.
|
|
362
|
+
|
|
363
|
+
Raises
|
|
364
|
+
------
|
|
365
|
+
AccountModelValidationError
|
|
366
|
+
If the entity_model is neither an instance of EntityModel nor a string.
|
|
349
367
|
"""
|
|
350
368
|
qs = self.for_user(user_model)
|
|
351
|
-
if select_coa_model:
|
|
352
|
-
qs = qs.select_related('coa_model')
|
|
353
|
-
|
|
354
369
|
EntityModel = lazy_loader.get_entity_model()
|
|
355
|
-
|
|
356
|
-
|
|
370
|
+
|
|
371
|
+
if isinstance(entity_model, EntityModel):
|
|
372
|
+
entity_model = entity_model
|
|
357
373
|
qs = qs.filter(coa_model__entity=entity_model)
|
|
358
|
-
elif isinstance(
|
|
359
|
-
qs = qs.filter(coa_model__entity__slug__exact=
|
|
374
|
+
elif isinstance(entity_model, str):
|
|
375
|
+
qs = qs.filter(coa_model__entity__slug__exact=entity_model)
|
|
360
376
|
else:
|
|
361
|
-
raise AccountModelValidationError(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
qs = qs.filter(coa_model__slug__exact=coa_slug)
|
|
365
|
-
return qs.order_by('coa_model')
|
|
366
|
-
|
|
367
|
-
def for_entity_available(self, user_model, entity_slug, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
|
|
368
|
-
"""
|
|
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.
|
|
373
|
-
|
|
374
|
-
Parameters
|
|
375
|
-
----------
|
|
376
|
-
user_model: User
|
|
377
|
-
The Django User Model instance making the request, used to validate permissions.
|
|
378
|
-
|
|
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.
|
|
383
|
-
|
|
384
|
-
coa_slug: str, optional
|
|
385
|
-
The specific Chart of Accounts to use. If None, the default Chart of Accounts will be pulled.
|
|
377
|
+
raise AccountModelValidationError(
|
|
378
|
+
message='Must pass an instance of EntityModel or String for entity_slug.'
|
|
379
|
+
)
|
|
386
380
|
|
|
387
|
-
Returns
|
|
388
|
-
-------
|
|
389
|
-
AccountModelQuerySet
|
|
390
|
-
A QuerySet containing available and unlocked AccountModels for the specified EntityModel and Chart of Accounts.
|
|
391
|
-
"""
|
|
392
|
-
qs = self.for_entity(
|
|
393
|
-
user_model=user_model,
|
|
394
|
-
entity_slug=entity_slug,
|
|
395
|
-
coa_slug=coa_slug)
|
|
396
381
|
return qs.filter(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
382
|
+
coa_model__slug__exact=coa_slug
|
|
383
|
+
) if coa_slug else qs.filter(
|
|
384
|
+
coa_model__slug__exact=F('coa_model__entity__default_coa__slug')
|
|
400
385
|
)
|
|
401
386
|
|
|
402
|
-
def with_roles(self, roles: Union[list, str], entity_slug, user_model) -> AccountModelQuerySet:
|
|
403
|
-
"""
|
|
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.
|
|
409
|
-
|
|
410
|
-
Parameters
|
|
411
|
-
----------
|
|
412
|
-
entity_slug: EntityModel or str
|
|
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.
|
|
417
|
-
roles: list or str
|
|
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
|
-
|
|
421
|
-
Returns
|
|
422
|
-
-------
|
|
423
|
-
AccountModelQuerySet
|
|
424
|
-
A QuerySet of accounts filtered by the specified roles.
|
|
425
|
-
"""
|
|
426
|
-
roles = validate_roles(roles)
|
|
427
|
-
if isinstance(roles, str):
|
|
428
|
-
roles = [roles]
|
|
429
|
-
qs = self.for_entity(entity_slug=entity_slug, user_model=user_model)
|
|
430
|
-
return qs.filter(role__in=roles)
|
|
431
|
-
|
|
432
|
-
def with_roles_available(self, roles: Union[list, str],
|
|
433
|
-
entity_slug,
|
|
434
|
-
user_model,
|
|
435
|
-
coa_slug: Optional[str]) -> AccountModelQuerySet:
|
|
436
|
-
"""
|
|
437
|
-
Retrieve available and unlocked AccountModels for a specified EntityModel and list of roles.
|
|
438
|
-
|
|
439
|
-
Parameters
|
|
440
|
-
----------
|
|
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.
|
|
451
|
-
|
|
452
|
-
Returns
|
|
453
|
-
-------
|
|
454
|
-
AccountModelQuerySet
|
|
455
|
-
A QuerySet containing available and unlocked AccountModel instances for the specified
|
|
456
|
-
EntityModel and roles.
|
|
457
|
-
"""
|
|
458
|
-
|
|
459
|
-
if isinstance(roles, str):
|
|
460
|
-
roles = [roles]
|
|
461
|
-
roles = validate_roles(roles)
|
|
462
|
-
qs = self.for_entity_available(entity_slug=entity_slug, user_model=user_model)
|
|
463
|
-
return qs.filter(role__in=roles)
|
|
464
|
-
|
|
465
|
-
def coa_roots(self, user_model, entity_slug, coa_slug) -> AccountModelQuerySet:
|
|
466
|
-
"""
|
|
467
|
-
Retrieves the root accounts of a specified Code of Accounts (CoA).
|
|
468
|
-
|
|
469
|
-
Parameters
|
|
470
|
-
----------
|
|
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.
|
|
479
|
-
|
|
480
|
-
Returns
|
|
481
|
-
-------
|
|
482
|
-
AccountModelQuerySet
|
|
483
|
-
A queryset of root accounts for the specified Code of Accounts.
|
|
484
|
-
"""
|
|
485
|
-
qs = self.for_entity(user_model=user_model, entity_slug=entity_slug, coa_slug=coa_slug)
|
|
486
|
-
return qs.is_coa_root()
|
|
487
|
-
|
|
488
|
-
def for_invoice(self, user_model, entity_slug: str, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
|
|
489
|
-
"""
|
|
490
|
-
Retrieves available and unlocked AccountModels for a specific EntityModel, specifically for the creation
|
|
491
|
-
and management of Invoices.
|
|
492
|
-
|
|
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.
|
|
495
|
-
|
|
496
|
-
Parameters
|
|
497
|
-
----------
|
|
498
|
-
user_model: User
|
|
499
|
-
The Django User Model instance requesting access. It is used to check the necessary permissions.
|
|
500
|
-
|
|
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.
|
|
504
|
-
|
|
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.
|
|
508
|
-
|
|
509
|
-
Returns
|
|
510
|
-
-------
|
|
511
|
-
AccountModelQuerySet
|
|
512
|
-
A QuerySet containing the AccountModels relevant for the specified EntityModel and the roles defined
|
|
513
|
-
in `GROUP_INVOICE`.
|
|
514
|
-
"""
|
|
515
|
-
qs = self.for_entity_available(
|
|
516
|
-
user_model=user_model,
|
|
517
|
-
entity_slug=entity_slug,
|
|
518
|
-
coa_slug=coa_slug)
|
|
519
|
-
return qs.filter(role__in=GROUP_INVOICE)
|
|
520
|
-
|
|
521
|
-
def for_bill(self, user_model, entity_slug, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
|
|
522
|
-
"""
|
|
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.
|
|
526
|
-
|
|
527
|
-
Parameters
|
|
528
|
-
----------
|
|
529
|
-
user_model : Django User Model
|
|
530
|
-
The Django User Model that is making the request, used to check for permissions.
|
|
531
|
-
|
|
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.
|
|
535
|
-
|
|
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.
|
|
538
|
-
|
|
539
|
-
Returns
|
|
540
|
-
-------
|
|
541
|
-
AccountModelQuerySet
|
|
542
|
-
A QuerySet of the requested EntityModel's chart of accounts.
|
|
543
|
-
"""
|
|
544
|
-
qs = self.for_entity_available(
|
|
545
|
-
user_model=user_model,
|
|
546
|
-
entity_slug=entity_slug,
|
|
547
|
-
coa_slug=coa_slug)
|
|
548
|
-
return qs.filter(role__in=GROUP_BILL)
|
|
549
|
-
|
|
550
387
|
|
|
551
388
|
def account_code_validator(value: str):
|
|
552
389
|
if not value.isalnum():
|
|
@@ -596,7 +433,6 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
596
433
|
active = models.BooleanField(default=False, verbose_name=_('Active'))
|
|
597
434
|
coa_model = models.ForeignKey('django_ledger.ChartOfAccountModel',
|
|
598
435
|
on_delete=models.CASCADE,
|
|
599
|
-
editable=False,
|
|
600
436
|
verbose_name=_('Chart of Accounts'))
|
|
601
437
|
objects = AccountModelManager()
|
|
602
438
|
node_order_by = ['uuid']
|
|
@@ -606,9 +442,17 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
606
442
|
ordering = ['-created']
|
|
607
443
|
verbose_name = _('Account')
|
|
608
444
|
verbose_name_plural = _('Accounts')
|
|
609
|
-
|
|
610
|
-
(
|
|
611
|
-
|
|
445
|
+
constraints = [
|
|
446
|
+
UniqueConstraint(
|
|
447
|
+
fields=('coa_model', 'code'),
|
|
448
|
+
name='unique_code_for_coa_model',
|
|
449
|
+
violation_error_message=_('Account codes must be unique for each Chart of Accounts Model.')
|
|
450
|
+
),
|
|
451
|
+
UniqueConstraint(
|
|
452
|
+
fields=('coa_model', 'role', 'role_default'),
|
|
453
|
+
name='only_one_account_assigned_as_default_for_role',
|
|
454
|
+
violation_error_message=_('Only one default account for role permitted.')
|
|
455
|
+
)
|
|
612
456
|
]
|
|
613
457
|
indexes = [
|
|
614
458
|
models.Index(fields=['role']),
|
|
@@ -628,6 +472,17 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
628
472
|
x5=self.code
|
|
629
473
|
)
|
|
630
474
|
|
|
475
|
+
@property
|
|
476
|
+
def coa_slug(self):
|
|
477
|
+
try:
|
|
478
|
+
return getattr(self, '_coa_slug')
|
|
479
|
+
except AttributeError:
|
|
480
|
+
return self.coa_model.slug
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def entity_slug(self):
|
|
484
|
+
return getattr(self, '_entity_slug')
|
|
485
|
+
|
|
631
486
|
@classmethod
|
|
632
487
|
def create_account(cls,
|
|
633
488
|
name: str,
|
|
@@ -838,6 +693,13 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
838
693
|
"""
|
|
839
694
|
return self.locked is True
|
|
840
695
|
|
|
696
|
+
def is_coa_active(self) -> bool:
|
|
697
|
+
try:
|
|
698
|
+
return getattr(self, '_coa_active')
|
|
699
|
+
except AttributeError:
|
|
700
|
+
pass
|
|
701
|
+
return self.coa_model.active
|
|
702
|
+
|
|
841
703
|
def can_activate(self):
|
|
842
704
|
"""
|
|
843
705
|
Determines if the object can be activated.
|
|
@@ -866,6 +728,46 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
866
728
|
self.active is True
|
|
867
729
|
])
|
|
868
730
|
|
|
731
|
+
def can_lock(self):
|
|
732
|
+
return all([
|
|
733
|
+
self.locked is False
|
|
734
|
+
])
|
|
735
|
+
|
|
736
|
+
def can_unlock(self):
|
|
737
|
+
return all([
|
|
738
|
+
self.locked is True
|
|
739
|
+
])
|
|
740
|
+
|
|
741
|
+
def lock(self, commit: bool = True, raise_exception: bool = True, **kwargs):
|
|
742
|
+
if not self.can_lock():
|
|
743
|
+
if raise_exception:
|
|
744
|
+
raise AccountModelValidationError(
|
|
745
|
+
message=_(f'Cannot lock account {self.code}: {self.name}. Active: {self.is_active()}')
|
|
746
|
+
)
|
|
747
|
+
return
|
|
748
|
+
|
|
749
|
+
self.locked = True
|
|
750
|
+
if commit:
|
|
751
|
+
self.save(update_fields=[
|
|
752
|
+
'locked',
|
|
753
|
+
'updated'
|
|
754
|
+
])
|
|
755
|
+
|
|
756
|
+
def unlock(self, commit: bool = True, raise_exception: bool = True, **kwargs):
|
|
757
|
+
if not self.can_unlock():
|
|
758
|
+
if raise_exception:
|
|
759
|
+
raise AccountModelValidationError(
|
|
760
|
+
message=_(f'Cannot unlock account {self.code}: {self.name}. Active: {self.is_active()}')
|
|
761
|
+
)
|
|
762
|
+
return
|
|
763
|
+
|
|
764
|
+
self.locked = False
|
|
765
|
+
if commit:
|
|
766
|
+
self.save(update_fields=[
|
|
767
|
+
'locked',
|
|
768
|
+
'updated'
|
|
769
|
+
])
|
|
770
|
+
|
|
869
771
|
def activate(self, commit: bool = True, raise_exception: bool = True, **kwargs):
|
|
870
772
|
"""
|
|
871
773
|
Checks if the Account Model instance can be activated, then Activates the AccountModel instance.
|
|
@@ -936,9 +838,8 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
936
838
|
3. The entity itself must be active.
|
|
937
839
|
"""
|
|
938
840
|
return all([
|
|
939
|
-
self.
|
|
940
|
-
not self.is_locked()
|
|
941
|
-
self.is_active()
|
|
841
|
+
self.is_coa_active(),
|
|
842
|
+
not self.is_locked()
|
|
942
843
|
])
|
|
943
844
|
|
|
944
845
|
def get_code_prefix(self) -> str:
|
|
@@ -1072,16 +973,75 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
1072
973
|
ri = randint(10000, 99999)
|
|
1073
974
|
return f'{prefix}{ri}'
|
|
1074
975
|
|
|
1075
|
-
|
|
976
|
+
# URLS...
|
|
977
|
+
def get_absolute_url(self) -> str:
|
|
978
|
+
return reverse(
|
|
979
|
+
viewname='django_ledger:account-detail',
|
|
980
|
+
kwargs={
|
|
981
|
+
'account_pk': self.uuid,
|
|
982
|
+
'entity_slug': self.entity_slug,
|
|
983
|
+
'coa_slug': self.coa_slug
|
|
984
|
+
}
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
def get_update_url(self) -> str:
|
|
988
|
+
return reverse(
|
|
989
|
+
viewname='django_ledger:account-update',
|
|
990
|
+
kwargs={
|
|
991
|
+
'account_pk': self.uuid,
|
|
992
|
+
'entity_slug': self.entity_slug,
|
|
993
|
+
'coa_slug': self.coa_slug
|
|
994
|
+
}
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
def get_action_deactivate_url(self) -> str:
|
|
998
|
+
return reverse(
|
|
999
|
+
viewname='django_ledger:account-action-deactivate',
|
|
1000
|
+
kwargs={
|
|
1001
|
+
'account_pk': self.uuid,
|
|
1002
|
+
'entity_slug': self.entity_slug,
|
|
1003
|
+
'coa_slug': self.coa_slug
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
def get_action_activate_url(self) -> str:
|
|
1076
1008
|
return reverse(
|
|
1077
|
-
viewname='django_ledger:account-
|
|
1009
|
+
viewname='django_ledger:account-action-activate',
|
|
1078
1010
|
kwargs={
|
|
1079
1011
|
'account_pk': self.uuid,
|
|
1080
|
-
'entity_slug': self.
|
|
1081
|
-
'
|
|
1012
|
+
'entity_slug': self.entity_slug,
|
|
1013
|
+
'coa_slug': self.coa_slug
|
|
1082
1014
|
}
|
|
1083
1015
|
)
|
|
1084
1016
|
|
|
1017
|
+
def get_action_lock_url(self) -> str:
|
|
1018
|
+
return reverse(
|
|
1019
|
+
viewname='django_ledger:account-action-lock',
|
|
1020
|
+
kwargs={
|
|
1021
|
+
'account_pk': self.uuid,
|
|
1022
|
+
'entity_slug': self.entity_slug,
|
|
1023
|
+
'coa_slug': self.coa_slug
|
|
1024
|
+
}
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
def get_action_unlock_url(self) -> str:
|
|
1028
|
+
return reverse(
|
|
1029
|
+
viewname='django_ledger:account-action-unlock',
|
|
1030
|
+
kwargs={
|
|
1031
|
+
'account_pk': self.uuid,
|
|
1032
|
+
'entity_slug': self.entity_slug,
|
|
1033
|
+
'coa_slug': self.coa_slug
|
|
1034
|
+
}
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
def get_coa_account_list_url(self) -> str:
|
|
1038
|
+
return reverse(
|
|
1039
|
+
viewname='django_ledger:account-list',
|
|
1040
|
+
kwargs={
|
|
1041
|
+
'entity_slug': self.entity_slug,
|
|
1042
|
+
'coa_slug': self.coa_slug
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1085
1045
|
def clean(self):
|
|
1086
1046
|
if not self.code and DJANGO_LEDGER_ACCOUNT_CODE_GENERATE:
|
|
1087
1047
|
self.code = self.generate_random_code()
|
|
@@ -1098,6 +1058,10 @@ class AccountModel(AccountModelAbstract):
|
|
|
1098
1058
|
Base Account Model from Account Model Abstract Class
|
|
1099
1059
|
"""
|
|
1100
1060
|
|
|
1061
|
+
class Meta(AccountModelAbstract.Meta):
|
|
1062
|
+
swappable = 'DJANGO_LEDGER_ACCOUNT_MODEL'
|
|
1063
|
+
abstract = False
|
|
1064
|
+
|
|
1101
1065
|
|
|
1102
1066
|
def accountmodel_presave(instance: AccountModel, **kwargs):
|
|
1103
1067
|
if instance.role_default is False:
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
4
|
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
-
* Pranav P Tulshyan <ptulshyan77@gmail.com>
|
|
8
|
-
|
|
9
5
|
A Bank Account refers to the financial institution which holds financial assets for the EntityModel.
|
|
10
6
|
A bank account usually holds cash, which is a Current Asset. Transactions may be imported using the open financial
|
|
11
7
|
format specification OFX into a staging area for final disposition into the EntityModel ledger.
|
|
@@ -95,7 +91,7 @@ class BankAccountModelManager(models.Manager):
|
|
|
95
91
|
)
|
|
96
92
|
|
|
97
93
|
|
|
98
|
-
class
|
|
94
|
+
class BankAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
|
|
99
95
|
"""
|
|
100
96
|
This is the main abstract class which the BankAccountModel database will inherit from.
|
|
101
97
|
The BankAccountModel inherits functionality from the following MixIns:
|
|
@@ -208,7 +204,11 @@ class BackAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
|
|
|
208
204
|
])
|
|
209
205
|
|
|
210
206
|
|
|
211
|
-
class BankAccountModel(
|
|
207
|
+
class BankAccountModel(BankAccountModelAbstract):
|
|
212
208
|
"""
|
|
213
209
|
Base Bank Account Model Implementation
|
|
214
210
|
"""
|
|
211
|
+
|
|
212
|
+
class Meta(BankAccountModelAbstract.Meta):
|
|
213
|
+
swappable = 'DJANGO_LEDGER_BANK_ACCOUNT_MODEL'
|
|
214
|
+
abstract = False
|