django-ledger 0.7.0__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 -1
- 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/forms/account.py +1 -1
- django_ledger/forms/bank_account.py +2 -0
- django_ledger/forms/chart_of_accounts.py +82 -0
- django_ledger/io/io_library.py +1 -1
- 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 +4 -0
- django_ledger/models/bank_account.py +6 -2
- django_ledger/models/bill.py +7 -3
- django_ledger/models/{coa.py → chart_of_accounts.py} +131 -27
- django_ledger/models/closing_entry.py +5 -7
- django_ledger/models/coa_default.py +1 -1
- django_ledger/models/customer.py +6 -2
- django_ledger/models/data_import.py +12 -5
- django_ledger/models/entity.py +27 -3
- django_ledger/models/estimate.py +6 -1
- django_ledger/models/invoice.py +14 -8
- django_ledger/models/items.py +19 -8
- django_ledger/models/journal_entry.py +71 -25
- django_ledger/models/ledger.py +8 -5
- django_ledger/models/purchase_order.py +9 -5
- django_ledger/models/transactions.py +23 -3
- django_ledger/models/unit.py +4 -0
- django_ledger/models/vendor.py +4 -0
- django_ledger/settings.py +28 -3
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +3 -2
- 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/financial_statements/tags/balance_sheet_statement.html +2 -2
- django_ledger/templates/django_ledger/includes/footer.html +2 -2
- django_ledger/urls/chart_of_accounts.py +6 -0
- django_ledger/utils.py +1 -36
- django_ledger/views/__init__.py +1 -1
- django_ledger/views/account.py +16 -3
- django_ledger/views/{coa.py → chart_of_accounts.py} +48 -44
- django_ledger/views/mixins.py +16 -5
- {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/METADATA +1 -3
- {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/RECORD +48 -46
- django_ledger/forms/coa.py +0 -47
- {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
- {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +0 -0
- {django_ledger-0.7.0.dist-info → django_ledger-0.7.1.dist-info}/top_level.txt +0 -0
|
@@ -9,27 +9,26 @@ from uuid import uuid4
|
|
|
9
9
|
|
|
10
10
|
from django.core.exceptions import ValidationError
|
|
11
11
|
from django.db import models
|
|
12
|
-
from django.db.models import Q, Count, Sum, Case, When, F, Value, DecimalField, BooleanField
|
|
12
|
+
from django.db.models import Q, Count, Sum, Case, When, F, Value, DecimalField, BooleanField, Manager, QuerySet
|
|
13
13
|
from django.db.models.functions import Coalesce
|
|
14
14
|
from django.db.models.signals import pre_save
|
|
15
15
|
from django.utils.translation import gettext_lazy as _
|
|
16
16
|
|
|
17
17
|
from django_ledger.io import ASSET_CA_CASH, CREDIT, DEBIT
|
|
18
|
+
from django_ledger.models import JournalEntryModel
|
|
18
19
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
19
20
|
from django_ledger.models.utils import lazy_loader
|
|
20
21
|
|
|
21
|
-
from django_ledger.models import JournalEntryModel
|
|
22
|
-
|
|
23
22
|
|
|
24
23
|
class ImportJobModelValidationError(ValidationError):
|
|
25
24
|
pass
|
|
26
25
|
|
|
27
26
|
|
|
28
|
-
class ImportJobModelQuerySet(
|
|
27
|
+
class ImportJobModelQuerySet(QuerySet):
|
|
29
28
|
pass
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
class ImportJobModelManager(
|
|
31
|
+
class ImportJobModelManager(Manager):
|
|
33
32
|
|
|
34
33
|
def get_queryset(self):
|
|
35
34
|
qs = super().get_queryset()
|
|
@@ -502,6 +501,10 @@ class ImportJobModel(ImportJobModelAbstract):
|
|
|
502
501
|
Transaction Import Job Model Base Class.
|
|
503
502
|
"""
|
|
504
503
|
|
|
504
|
+
class Meta(ImportJobModelAbstract.Meta):
|
|
505
|
+
swappable = 'DJANGO_LEDGER_IMPORT_JOB_MODEL'
|
|
506
|
+
abstract = False
|
|
507
|
+
|
|
505
508
|
|
|
506
509
|
def importjobmodel_presave(instance: ImportJobModel, **kwargs):
|
|
507
510
|
if instance.is_configured():
|
|
@@ -518,3 +521,7 @@ class StagedTransactionModel(StagedTransactionModelAbstract):
|
|
|
518
521
|
"""
|
|
519
522
|
Staged Transaction Model Base Class.
|
|
520
523
|
"""
|
|
524
|
+
|
|
525
|
+
class Meta(StagedTransactionModelAbstract.Meta):
|
|
526
|
+
swappable = 'DJANGO_LEDGER_STAGED_TRANSACTION_MODEL'
|
|
527
|
+
abstract = False
|
django_ledger/models/entity.py
CHANGED
|
@@ -33,7 +33,7 @@ from django.core.cache import caches
|
|
|
33
33
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
34
34
|
from django.core.validators import MinValueValidator
|
|
35
35
|
from django.db import models
|
|
36
|
-
from django.db.models import Q, F
|
|
36
|
+
from django.db.models import Q, F, Model
|
|
37
37
|
from django.db.models.signals import pre_save
|
|
38
38
|
from django.urls import reverse
|
|
39
39
|
from django.utils.text import slugify
|
|
@@ -44,7 +44,7 @@ from django_ledger.io import roles as roles_module, validate_roles, IODigestCont
|
|
|
44
44
|
from django_ledger.io.io_core import IOMixIn, get_localtime, get_localdate
|
|
45
45
|
from django_ledger.models.accounts import AccountModel, AccountModelQuerySet, DEBIT, CREDIT
|
|
46
46
|
from django_ledger.models.bank_account import BankAccountModelQuerySet, BankAccountModel
|
|
47
|
-
from django_ledger.models.
|
|
47
|
+
from django_ledger.models.chart_of_accounts import ChartOfAccountModel, ChartOfAccountModelQuerySet
|
|
48
48
|
from django_ledger.models.coa_default import CHART_OF_ACCOUNTS_ROOT_MAP
|
|
49
49
|
from django_ledger.models.customer import CustomerModelQueryset, CustomerModel
|
|
50
50
|
from django_ledger.models.items import (ItemModelQuerySet, ItemTransactionModelQuerySet,
|
|
@@ -3038,6 +3038,22 @@ class EntityModelAbstract(MP_Node,
|
|
|
3038
3038
|
}
|
|
3039
3039
|
)
|
|
3040
3040
|
|
|
3041
|
+
def get_coa_list_inactive_url(self) -> str:
|
|
3042
|
+
return reverse(
|
|
3043
|
+
viewname='django_ledger:coa-list-inactive',
|
|
3044
|
+
kwargs={
|
|
3045
|
+
'entity_slug': self.slug
|
|
3046
|
+
}
|
|
3047
|
+
)
|
|
3048
|
+
|
|
3049
|
+
def get_coa_create_url(self) -> str:
|
|
3050
|
+
return reverse(
|
|
3051
|
+
viewname='django_ledger:coa-create',
|
|
3052
|
+
kwargs={
|
|
3053
|
+
'entity_slug': self.slug
|
|
3054
|
+
}
|
|
3055
|
+
)
|
|
3056
|
+
|
|
3041
3057
|
def get_accounts_url(self) -> str:
|
|
3042
3058
|
"""
|
|
3043
3059
|
The EntityModel Code of Accounts llist import URL.
|
|
@@ -3104,10 +3120,14 @@ class EntityModel(EntityModelAbstract):
|
|
|
3104
3120
|
"""
|
|
3105
3121
|
Entity Model Base Class From Abstract
|
|
3106
3122
|
"""
|
|
3123
|
+
class Meta(EntityModelAbstract.Meta):
|
|
3124
|
+
swappable = 'DJANGO_LEDGER_ENTITY_MODEL'
|
|
3125
|
+
abstract = False
|
|
3126
|
+
|
|
3107
3127
|
|
|
3108
3128
|
|
|
3109
3129
|
# ## ENTITY STATE....
|
|
3110
|
-
class EntityStateModelAbstract(
|
|
3130
|
+
class EntityStateModelAbstract(Model):
|
|
3111
3131
|
KEY_JOURNAL_ENTRY = 'je'
|
|
3112
3132
|
KEY_PURCHASE_ORDER = 'po'
|
|
3113
3133
|
KEY_BILL = 'bill'
|
|
@@ -3168,6 +3188,10 @@ class EntityStateModel(EntityStateModelAbstract):
|
|
|
3168
3188
|
Entity State Model Base Class from Abstract.
|
|
3169
3189
|
"""
|
|
3170
3190
|
|
|
3191
|
+
class Meta(EntityStateModelAbstract.Meta):
|
|
3192
|
+
swappable = 'DJANGO_LEDGER_ENTITY_STATE_MODEL'
|
|
3193
|
+
abstract = False
|
|
3194
|
+
|
|
3171
3195
|
|
|
3172
3196
|
# ## ENTITY MANAGEMENT.....
|
|
3173
3197
|
class EntityManagementModelAbstract(CreateUpdateMixIn):
|
django_ledger/models/estimate.py
CHANGED
|
@@ -1231,7 +1231,8 @@ class EstimateModelAbstract(CreateUpdateMixIn,
|
|
|
1231
1231
|
'updated'
|
|
1232
1232
|
])
|
|
1233
1233
|
|
|
1234
|
-
def update_state(self,
|
|
1234
|
+
def update_state(self,
|
|
1235
|
+
itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None):
|
|
1235
1236
|
itemtxs_qs, _ = self.get_itemtxs_data(queryset=itemtxs_qs)
|
|
1236
1237
|
self.update_cost_estimate(itemtxs_qs)
|
|
1237
1238
|
self.update_revenue_estimate(itemtxs_qs)
|
|
@@ -1611,3 +1612,7 @@ class EstimateModel(EstimateModelAbstract):
|
|
|
1611
1612
|
"""
|
|
1612
1613
|
Base EstimateModel Class.
|
|
1613
1614
|
"""
|
|
1615
|
+
|
|
1616
|
+
class Meta(EstimateModelAbstract.Meta):
|
|
1617
|
+
swappable = 'DJANGO_LEDGER_ESTIMATE_MODEL'
|
|
1618
|
+
abstract = False
|
django_ledger/models/invoice.py
CHANGED
|
@@ -33,14 +33,16 @@ from django.utils.translation import gettext_lazy as _
|
|
|
33
33
|
|
|
34
34
|
from django_ledger.io import ASSET_CA_CASH, ASSET_CA_RECEIVABLES, LIABILITY_CL_DEFERRED_REVENUE
|
|
35
35
|
from django_ledger.io.io_core import get_localtime, get_localdate
|
|
36
|
-
from django_ledger.models import
|
|
36
|
+
from django_ledger.models import (
|
|
37
|
+
lazy_loader, ItemTransactionModelQuerySet,
|
|
38
|
+
ItemModelQuerySet, ItemModel, QuerySet, Manager
|
|
39
|
+
)
|
|
37
40
|
from django_ledger.models.entity import EntityModel
|
|
38
41
|
from django_ledger.models.mixins import (
|
|
39
42
|
CreateUpdateMixIn, AccrualMixIn,
|
|
40
43
|
MarkdownNotesMixIn, PaymentTermsMixIn,
|
|
41
44
|
ItemizeMixIn
|
|
42
45
|
)
|
|
43
|
-
|
|
44
46
|
from django_ledger.models.signals import (
|
|
45
47
|
invoice_status_draft,
|
|
46
48
|
invoice_status_in_review,
|
|
@@ -49,7 +51,6 @@ from django_ledger.models.signals import (
|
|
|
49
51
|
invoice_status_canceled,
|
|
50
52
|
invoice_status_void
|
|
51
53
|
)
|
|
52
|
-
|
|
53
54
|
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_INVOICE_NUMBER_PREFIX
|
|
54
55
|
|
|
55
56
|
UserModel = get_user_model()
|
|
@@ -59,7 +60,7 @@ class InvoiceModelValidationError(ValidationError):
|
|
|
59
60
|
pass
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
class InvoiceModelQuerySet(
|
|
63
|
+
class InvoiceModelQuerySet(QuerySet):
|
|
63
64
|
"""
|
|
64
65
|
A custom defined QuerySet for the InvoiceModel.
|
|
65
66
|
This implements multiple methods or queries that we need to run to get a status of Invoices raised by the entity.
|
|
@@ -176,7 +177,7 @@ class InvoiceModelQuerySet(models.QuerySet):
|
|
|
176
177
|
return self.filter(invoice_status__exact=InvoiceModel.INVOICE_STATUS_APPROVED)
|
|
177
178
|
|
|
178
179
|
|
|
179
|
-
class InvoiceModelManager(
|
|
180
|
+
class InvoiceModelManager(Manager):
|
|
180
181
|
"""
|
|
181
182
|
A custom defined InvoiceModel Manager that will act as an interface to handling the DB queries to the InvoiceModel.
|
|
182
183
|
The default "get_queryset" has been overridden to refer the custom defined "InvoiceModelQuerySet"
|
|
@@ -572,9 +573,10 @@ class InvoiceModelAbstract(
|
|
|
572
573
|
else:
|
|
573
574
|
self.validate_itemtxs_qs(queryset)
|
|
574
575
|
|
|
575
|
-
return queryset.select_related('item_model').order_by(
|
|
576
|
-
|
|
577
|
-
|
|
576
|
+
return queryset.select_related('item_model').order_by(
|
|
577
|
+
'item_model__earnings_account__uuid',
|
|
578
|
+
'entity_unit__uuid',
|
|
579
|
+
'item_model__earnings_account__balance_type').values(
|
|
578
580
|
'item_model__earnings_account__uuid',
|
|
579
581
|
'item_model__earnings_account__balance_type',
|
|
580
582
|
'item_model__cogs_account__uuid',
|
|
@@ -1817,6 +1819,10 @@ class InvoiceModel(InvoiceModelAbstract):
|
|
|
1817
1819
|
Base Invoice Model from Abstract.
|
|
1818
1820
|
"""
|
|
1819
1821
|
|
|
1822
|
+
class Meta:
|
|
1823
|
+
swappable = 'DJANGO_LEDGER_INVOICE_MODEL'
|
|
1824
|
+
abstract = False
|
|
1825
|
+
|
|
1820
1826
|
|
|
1821
1827
|
def invoicemodel_presave(instance: InvoiceModel, **kwargs):
|
|
1822
1828
|
if instance.can_generate_invoice_number():
|
django_ledger/models/items.py
CHANGED
|
@@ -25,7 +25,7 @@ from uuid import uuid4, UUID
|
|
|
25
25
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
26
26
|
from django.core.validators import MinValueValidator
|
|
27
27
|
from django.db import models, transaction, IntegrityError
|
|
28
|
-
from django.db.models import Q, Sum, F, ExpressionWrapper, DecimalField, Value, Case, When, QuerySet
|
|
28
|
+
from django.db.models import Q, Sum, F, ExpressionWrapper, DecimalField, Value, Case, When, QuerySet, Manager
|
|
29
29
|
from django.db.models.functions import Coalesce
|
|
30
30
|
from django.utils.translation import gettext_lazy as _
|
|
31
31
|
|
|
@@ -42,12 +42,12 @@ class ItemModelValidationError(ValidationError):
|
|
|
42
42
|
pass
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
class UnitOfMeasureModelQuerySet(
|
|
45
|
+
class UnitOfMeasureModelQuerySet(QuerySet):
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
# UNIT OF MEASURES MODEL....
|
|
50
|
-
class UnitOfMeasureModelManager(
|
|
50
|
+
class UnitOfMeasureModelManager(Manager):
|
|
51
51
|
"""
|
|
52
52
|
A custom defined QuerySet Manager for the UnitOfMeasureModel.
|
|
53
53
|
"""
|
|
@@ -149,7 +149,7 @@ class UnitOfMeasureModelAbstract(CreateUpdateMixIn):
|
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
# ITEM MODEL....
|
|
152
|
-
class ItemModelQuerySet(
|
|
152
|
+
class ItemModelQuerySet(QuerySet):
|
|
153
153
|
"""
|
|
154
154
|
A custom-defined ItemModelQuerySet that implements custom QuerySet methods related to the ItemModel.
|
|
155
155
|
"""
|
|
@@ -287,7 +287,7 @@ class ItemModelQuerySet(models.QuerySet):
|
|
|
287
287
|
return self.inventory_all()
|
|
288
288
|
|
|
289
289
|
|
|
290
|
-
class ItemModelManager(
|
|
290
|
+
class ItemModelManager(Manager):
|
|
291
291
|
"""
|
|
292
292
|
A custom defined ItemModelManager that implement custom QuerySet methods related to the ItemModel
|
|
293
293
|
"""
|
|
@@ -849,7 +849,7 @@ class ItemModelAbstract(CreateUpdateMixIn):
|
|
|
849
849
|
|
|
850
850
|
|
|
851
851
|
# ITEM TRANSACTION MODELS...
|
|
852
|
-
class ItemTransactionModelQuerySet(
|
|
852
|
+
class ItemTransactionModelQuerySet(QuerySet):
|
|
853
853
|
|
|
854
854
|
def is_received(self):
|
|
855
855
|
return self.filter(po_item_status=ItemTransactionModel.STATUS_RECEIVED)
|
|
@@ -875,7 +875,7 @@ class ItemTransactionModelQuerySet(models.QuerySet):
|
|
|
875
875
|
}
|
|
876
876
|
|
|
877
877
|
|
|
878
|
-
class ItemTransactionModelManager(
|
|
878
|
+
class ItemTransactionModelManager(Manager):
|
|
879
879
|
|
|
880
880
|
def for_user(self, user_model):
|
|
881
881
|
qs = self.get_queryset()
|
|
@@ -1404,20 +1404,31 @@ class ItemTransactionModelAbstract(CreateUpdateMixIn):
|
|
|
1404
1404
|
|
|
1405
1405
|
|
|
1406
1406
|
# FINAL MODEL CLASSES....
|
|
1407
|
-
|
|
1408
1407
|
class UnitOfMeasureModel(UnitOfMeasureModelAbstract):
|
|
1409
1408
|
"""
|
|
1410
1409
|
Base UnitOfMeasureModel from Abstract.
|
|
1411
1410
|
"""
|
|
1412
1411
|
|
|
1412
|
+
class Meta(UnitOfMeasureModelAbstract.Meta):
|
|
1413
|
+
abstract = False
|
|
1414
|
+
swappable = 'DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL'
|
|
1415
|
+
|
|
1413
1416
|
|
|
1414
1417
|
class ItemTransactionModel(ItemTransactionModelAbstract):
|
|
1415
1418
|
"""
|
|
1416
1419
|
Base ItemTransactionModel from Abstract.
|
|
1417
1420
|
"""
|
|
1418
1421
|
|
|
1422
|
+
class Meta(ItemTransactionModelAbstract.Meta):
|
|
1423
|
+
abstract = False
|
|
1424
|
+
swappable = 'DJANGO_LEDGER_ITEM_TRANSACTION_MODEL'
|
|
1425
|
+
|
|
1419
1426
|
|
|
1420
1427
|
class ItemModel(ItemModelAbstract):
|
|
1421
1428
|
"""
|
|
1422
1429
|
Base ItemModel from Abstract.
|
|
1423
1430
|
"""
|
|
1431
|
+
|
|
1432
|
+
class Meta(ItemModelAbstract.Meta):
|
|
1433
|
+
abstract = False
|
|
1434
|
+
swappable = 'DJANGO_LEDGER_ITEM_MODEL'
|
|
@@ -487,15 +487,18 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
487
487
|
"""
|
|
488
488
|
return self._verified
|
|
489
489
|
|
|
490
|
-
|
|
490
|
+
# Transaction QuerySet
|
|
491
|
+
def is_balance_valid(self, txs_qs: TransactionModelQuerySet, raise_exception: bool = True) -> bool:
|
|
491
492
|
"""
|
|
492
493
|
Checks if CREDITs and DEBITs are equal.
|
|
493
494
|
|
|
494
495
|
Parameters
|
|
495
496
|
----------
|
|
496
497
|
txs_qs: TransactionModelQuerySet
|
|
497
|
-
Optional pre-fetched JE instance TransactionModelQuerySet. Will be validated if provided.
|
|
498
498
|
|
|
499
|
+
raise_exception: bool
|
|
500
|
+
Raises JournalEntryValidationError if TransactionModelQuerySet is not valid.
|
|
501
|
+
|
|
499
502
|
Returns
|
|
500
503
|
-------
|
|
501
504
|
bool
|
|
@@ -503,32 +506,38 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
503
506
|
"""
|
|
504
507
|
if len(txs_qs) > 0:
|
|
505
508
|
balances = self.get_txs_balances(txs_qs=txs_qs, as_dict=True)
|
|
506
|
-
|
|
509
|
+
is_valid = balances[CREDIT] == balances[DEBIT]
|
|
510
|
+
if not is_valid:
|
|
511
|
+
if raise_exception:
|
|
512
|
+
raise JournalEntryValidationError(
|
|
513
|
+
message='Balance of {0} CREDITs are {1} does not match DEBITs {2}.'.format(
|
|
514
|
+
self,
|
|
515
|
+
balances[CREDIT],
|
|
516
|
+
balances[DEBIT]
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
return is_valid
|
|
507
520
|
return True
|
|
508
521
|
|
|
509
|
-
def
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
522
|
+
def is_txs_qs_coa_valid(self, txs_qs: TransactionModelQuerySet) -> bool:
|
|
523
|
+
"""
|
|
524
|
+
Validates that the Chart of Accounts (COA) is valid for the transactions.
|
|
525
|
+
Journal Entry transactions can only be associated with one Chart of Accounts (COA).
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
Parameters
|
|
529
|
+
----------
|
|
530
|
+
txs_qs: TransactionModelQuerySet
|
|
516
531
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
self.FINANCING_DIVIDENDS,
|
|
522
|
-
self.FINANCING_STD,
|
|
523
|
-
self.FINANCING_OTHER
|
|
524
|
-
]
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
True if Transaction CoAs are valid, otherwise False.
|
|
535
|
+
"""
|
|
525
536
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
self.INVESTING_OTHER
|
|
531
|
-
]
|
|
537
|
+
if len(txs_qs) > 0:
|
|
538
|
+
coa_count = len(set(tx.coa_id for tx in txs_qs))
|
|
539
|
+
return coa_count == 1
|
|
540
|
+
return True
|
|
532
541
|
|
|
533
542
|
def is_txs_qs_valid(self, txs_qs: TransactionModelQuerySet, raise_exception: bool = True) -> bool:
|
|
534
543
|
"""
|
|
@@ -560,6 +569,30 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
560
569
|
f'associated with LedgerModel {self.uuid}')
|
|
561
570
|
return is_valid
|
|
562
571
|
|
|
572
|
+
def is_cash_involved(self, txs_qs=None):
|
|
573
|
+
return ASSET_CA_CASH in self.get_txs_roles(txs_qs=None)
|
|
574
|
+
|
|
575
|
+
def is_operating(self):
|
|
576
|
+
return self.activity in [
|
|
577
|
+
self.OPERATING_ACTIVITY
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
def is_financing(self):
|
|
581
|
+
return self.activity in [
|
|
582
|
+
self.FINANCING_EQUITY,
|
|
583
|
+
self.FINANCING_LTD,
|
|
584
|
+
self.FINANCING_DIVIDENDS,
|
|
585
|
+
self.FINANCING_STD,
|
|
586
|
+
self.FINANCING_OTHER
|
|
587
|
+
]
|
|
588
|
+
|
|
589
|
+
def is_investing(self):
|
|
590
|
+
return self.activity in [
|
|
591
|
+
self.INVESTING_SECURITIES,
|
|
592
|
+
self.INVESTING_PPE,
|
|
593
|
+
self.INVESTING_OTHER
|
|
594
|
+
]
|
|
595
|
+
|
|
563
596
|
def get_entity_unit_name(self, no_unit_name: str = ''):
|
|
564
597
|
if self.entity_unit_id:
|
|
565
598
|
return self.entity_unit.name
|
|
@@ -1155,6 +1188,15 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
1155
1188
|
except JournalEntryValidationError as e:
|
|
1156
1189
|
raise e
|
|
1157
1190
|
|
|
1191
|
+
# Transaction CoA if valid
|
|
1192
|
+
|
|
1193
|
+
try:
|
|
1194
|
+
is_coa_valid = self.is_txs_qs_coa_valid(txs_qs=txs_qs)
|
|
1195
|
+
if not is_coa_valid:
|
|
1196
|
+
raise JournalEntryValidationError('Transaction COA is not valid!')
|
|
1197
|
+
except JournalEntryValidationError as e:
|
|
1198
|
+
raise e
|
|
1199
|
+
|
|
1158
1200
|
# if not len(txs_qs):
|
|
1159
1201
|
# if raise_exception:
|
|
1160
1202
|
# raise JournalEntryValidationError('Journal entry has no transactions.')
|
|
@@ -1163,7 +1205,7 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
1163
1205
|
# if raise_exception:
|
|
1164
1206
|
# raise JournalEntryValidationError('At least two transactions required.')
|
|
1165
1207
|
|
|
1166
|
-
if all([is_balance_valid, is_txs_qs_valid]):
|
|
1208
|
+
if all([is_balance_valid, is_txs_qs_valid, is_coa_valid]):
|
|
1167
1209
|
# activity flag...
|
|
1168
1210
|
self.generate_activity(txs_qs=txs_qs, raise_exception=raise_exception)
|
|
1169
1211
|
self._verified = True
|
|
@@ -1350,6 +1392,10 @@ class JournalEntryModel(JournalEntryModelAbstract):
|
|
|
1350
1392
|
Journal Entry Model Base Class From Abstract
|
|
1351
1393
|
"""
|
|
1352
1394
|
|
|
1395
|
+
class Meta(JournalEntryModelAbstract.Meta):
|
|
1396
|
+
swappable = 'DJANGO_LEDGER_JOURNAL_ENTRY_MODEL'
|
|
1397
|
+
abstract = False
|
|
1398
|
+
|
|
1353
1399
|
|
|
1354
1400
|
def journalentrymodel_presave(instance: JournalEntryModel, **kwargs):
|
|
1355
1401
|
if instance._state.adding and not instance.ledger.can_edit_journal_entries():
|
django_ledger/models/ledger.py
CHANGED
|
@@ -20,7 +20,7 @@ which is the class responsible for making accounting queries to the Database in
|
|
|
20
20
|
The digest() method executes all necessary aggregations and optimizations in order to push as much work to the Database
|
|
21
21
|
layer as possible in order to minimize the amount of data being pulled for analysis into the Python memory.
|
|
22
22
|
|
|
23
|
-
The Django Ledger core model follows the following structure:
|
|
23
|
+
The Django Ledger core model follows the following structure:
|
|
24
24
|
EntityModel -< LedgerModel -< JournalEntryModel -< TransactionModel
|
|
25
25
|
"""
|
|
26
26
|
from datetime import date
|
|
@@ -34,6 +34,10 @@ from django.db import models
|
|
|
34
34
|
from django.db.models import Q, Min, F, Count
|
|
35
35
|
from django.urls import reverse
|
|
36
36
|
from django.utils.translation import gettext_lazy as _
|
|
37
|
+
|
|
38
|
+
from django_ledger.io.io_core import IOMixIn
|
|
39
|
+
from django_ledger.models import lazy_loader
|
|
40
|
+
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
37
41
|
from django_ledger.models.signals import (
|
|
38
42
|
ledger_posted,
|
|
39
43
|
ledger_unposted,
|
|
@@ -43,10 +47,6 @@ from django_ledger.models.signals import (
|
|
|
43
47
|
ledger_unhidden
|
|
44
48
|
)
|
|
45
49
|
|
|
46
|
-
from django_ledger.io.io_core import IOMixIn
|
|
47
|
-
from django_ledger.models import lazy_loader
|
|
48
|
-
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
49
|
-
|
|
50
50
|
LEDGER_ID_CHARS = ascii_lowercase + digits
|
|
51
51
|
|
|
52
52
|
|
|
@@ -725,6 +725,9 @@ class LedgerModel(LedgerModelAbstract):
|
|
|
725
725
|
"""
|
|
726
726
|
Base LedgerModel from Abstract.
|
|
727
727
|
"""
|
|
728
|
+
class Meta(LedgerModelAbstract.Meta):
|
|
729
|
+
swappable = 'DJANGO_LEDGER_LEDGER_MODEL'
|
|
730
|
+
abstract = False
|
|
728
731
|
|
|
729
732
|
|
|
730
733
|
def ledgermodel_presave(instance: LedgerModel, **kwargs):
|
|
@@ -21,7 +21,7 @@ from django.contrib.auth import get_user_model
|
|
|
21
21
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
22
22
|
from django.core.validators import MinLengthValidator
|
|
23
23
|
from django.db import models, transaction, IntegrityError
|
|
24
|
-
from django.db.models import Q, Sum, Count, F
|
|
24
|
+
from django.db.models import Q, Sum, Count, F, Manager, QuerySet
|
|
25
25
|
from django.db.models.functions import Coalesce
|
|
26
26
|
from django.db.models.signals import pre_save
|
|
27
27
|
from django.shortcuts import get_object_or_404
|
|
@@ -33,8 +33,6 @@ from django_ledger.models.bill import BillModel, BillModelQuerySet
|
|
|
33
33
|
from django_ledger.models.entity import EntityModel
|
|
34
34
|
from django_ledger.models.items import ItemTransactionModel, ItemTransactionModelQuerySet, ItemModelQuerySet, ItemModel
|
|
35
35
|
from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn, ItemizeMixIn
|
|
36
|
-
from django_ledger.models.utils import lazy_loader
|
|
37
|
-
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX
|
|
38
36
|
from django_ledger.models.signals import (
|
|
39
37
|
po_status_draft,
|
|
40
38
|
po_status_void,
|
|
@@ -43,6 +41,8 @@ from django_ledger.models.signals import (
|
|
|
43
41
|
po_status_canceled,
|
|
44
42
|
po_status_in_review
|
|
45
43
|
)
|
|
44
|
+
from django_ledger.models.utils import lazy_loader
|
|
45
|
+
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX
|
|
46
46
|
|
|
47
47
|
PO_NUMBER_CHARS = ascii_uppercase + digits
|
|
48
48
|
|
|
@@ -53,7 +53,7 @@ class PurchaseOrderModelValidationError(ValidationError):
|
|
|
53
53
|
pass
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
class PurchaseOrderModelQuerySet(
|
|
56
|
+
class PurchaseOrderModelQuerySet(QuerySet):
|
|
57
57
|
"""
|
|
58
58
|
A custom defined PurchaseOrderModel QuerySet.
|
|
59
59
|
"""
|
|
@@ -100,7 +100,7 @@ class PurchaseOrderModelQuerySet(models.QuerySet):
|
|
|
100
100
|
return self.filter(po_status__exact=PurchaseOrderModel.PO_STATUS_DRAFT)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
class PurchaseOrderModelManager(
|
|
103
|
+
class PurchaseOrderModelManager(Manager):
|
|
104
104
|
"""
|
|
105
105
|
A custom defined PurchaseOrderModel Manager.
|
|
106
106
|
"""
|
|
@@ -1233,6 +1233,10 @@ class PurchaseOrderModel(PurchaseOrderModelAbstract):
|
|
|
1233
1233
|
Purchase Order Base Model
|
|
1234
1234
|
"""
|
|
1235
1235
|
|
|
1236
|
+
class Meta(PurchaseOrderModelAbstract.Meta):
|
|
1237
|
+
swappable = 'DJANGO_LEDGER_PURCHASE_ORDER_MODEL'
|
|
1238
|
+
abstract = False
|
|
1239
|
+
|
|
1236
1240
|
|
|
1237
1241
|
def purchaseordermodel_presave(instance: PurchaseOrderModel, **kwargs):
|
|
1238
1242
|
if instance.can_generate_po_number():
|
|
@@ -21,7 +21,7 @@ from django.contrib.auth import get_user_model
|
|
|
21
21
|
from django.core.exceptions import ValidationError
|
|
22
22
|
from django.core.validators import MinValueValidator
|
|
23
23
|
from django.db import models
|
|
24
|
-
from django.db.models import Q, QuerySet, Manager
|
|
24
|
+
from django.db.models import Q, QuerySet, Manager, F
|
|
25
25
|
from django.db.models.signals import pre_save
|
|
26
26
|
from django.utils.translation import gettext_lazy as _
|
|
27
27
|
|
|
@@ -213,7 +213,9 @@ class TransactionModelManager(Manager):
|
|
|
213
213
|
|
|
214
214
|
def get_queryset(self) -> TransactionModelQuerySet:
|
|
215
215
|
qs = TransactionModelQuerySet(self.model, using=self._db)
|
|
216
|
-
return qs.
|
|
216
|
+
return qs.annotate(
|
|
217
|
+
_coa_id=F('account__coa_model_id'),
|
|
218
|
+
).select_related(
|
|
217
219
|
'journal_entry',
|
|
218
220
|
'account',
|
|
219
221
|
'account__coa_model',
|
|
@@ -497,6 +499,9 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
497
499
|
verbose_name=_('Tx Description'),
|
|
498
500
|
help_text=_('A description to be included with this individual transaction'))
|
|
499
501
|
|
|
502
|
+
cleared = models.BooleanField(default=False, verbose_name=_('Cleared'))
|
|
503
|
+
reconciled = models.BooleanField(default=False, verbose_name=_('Reconciled'))
|
|
504
|
+
|
|
500
505
|
objects = TransactionModelManager()
|
|
501
506
|
|
|
502
507
|
class Meta:
|
|
@@ -509,7 +514,9 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
509
514
|
models.Index(fields=['account']),
|
|
510
515
|
models.Index(fields=['journal_entry']),
|
|
511
516
|
models.Index(fields=['created']),
|
|
512
|
-
models.Index(fields=['updated'])
|
|
517
|
+
models.Index(fields=['updated']),
|
|
518
|
+
models.Index(fields=['cleared']),
|
|
519
|
+
models.Index(fields=['reconciled']),
|
|
513
520
|
]
|
|
514
521
|
|
|
515
522
|
def __str__(self):
|
|
@@ -519,6 +526,15 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
519
526
|
x4=self.tx_type,
|
|
520
527
|
x5=self.account.balance_type)
|
|
521
528
|
|
|
529
|
+
@property
|
|
530
|
+
def coa_id(self):
|
|
531
|
+
try:
|
|
532
|
+
return getattr(self, '_coa_id')
|
|
533
|
+
except AttributeError:
|
|
534
|
+
if self.account is None:
|
|
535
|
+
return None
|
|
536
|
+
return self.account.coa_model_id
|
|
537
|
+
|
|
522
538
|
def clean(self):
|
|
523
539
|
if self.account_id and self.account.is_root_account():
|
|
524
540
|
raise TransactionModelValidationError(
|
|
@@ -531,6 +547,10 @@ class TransactionModel(TransactionModelAbstract):
|
|
|
531
547
|
Base Transaction Model From Abstract.
|
|
532
548
|
"""
|
|
533
549
|
|
|
550
|
+
class Meta(TransactionModelAbstract.Meta):
|
|
551
|
+
abstract = False
|
|
552
|
+
swappable = 'DJANGO_LEDGER_TRANSACTION_MODEL'
|
|
553
|
+
|
|
534
554
|
|
|
535
555
|
def transactionmodel_presave(instance: TransactionModel, **kwargs):
|
|
536
556
|
"""
|
django_ledger/models/unit.py
CHANGED
django_ledger/models/vendor.py
CHANGED
django_ledger/settings.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
|
-
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
4
|
"""
|
|
8
5
|
import logging
|
|
9
6
|
from decimal import Decimal
|
|
@@ -31,6 +28,34 @@ except ImportError:
|
|
|
31
28
|
|
|
32
29
|
logger.info(f'Django Ledger GraphQL Enabled: {DJANGO_LEDGER_GRAPHQL_SUPPORT_ENABLED}')
|
|
33
30
|
|
|
31
|
+
|
|
32
|
+
## MODEL ABSTRACTS ##
|
|
33
|
+
DJANGO_LEDGER_ACCOUNT_MODEL = getattr(settings, 'DJANGO_LEDGER_ACCOUNT_MODEL', 'django_ledger.AccountModel')
|
|
34
|
+
DJANGO_LEDGER_CHART_OF_ACCOUNTS_MODEL = getattr(settings, 'DJANGO_LEDGER_ACCOUNT_MODEL', 'django_ledger.ChartOfAccountModelAbstract')
|
|
35
|
+
DJANGO_LEDGER_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_TRANSACTION_MODEL', 'django_ledger.TransactionModelAbstract')
|
|
36
|
+
DJANGO_LEDGER_JOURNAL_ENTRY_MODEL = getattr(settings, 'DJANGO_LEDGER_JOURNAL_ENTRY_MODEL', 'django_ledger.JournalEntryModelAbstract')
|
|
37
|
+
DJANGO_LEDGER_LEDGER_MODEL = getattr(settings, 'DJANGO_LEDGER_LEDGER_MODEL', 'django_ledger.LedgerModel')
|
|
38
|
+
DJANGO_LEDGER_ENTITY_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_MODEL', 'django_ledger.EntityModelAbstract')
|
|
39
|
+
DJANGO_LEDGER_ENTITY_STATE_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_STATE_MODEL', 'django_ledger.EntityStateModel')
|
|
40
|
+
|
|
41
|
+
DJANGO_LEDGER_ENTITY_UNIT_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_UNIT_MODEL', 'django_ledger.EntityUnitModelAbstract')
|
|
42
|
+
|
|
43
|
+
DJANGO_LEDGER_ESTIMATE_MODEL = getattr(settings, 'DJANGO_LEDGER_ESTIMATE_MODEL', 'django_ledger.EstimateModelAbstract')
|
|
44
|
+
DJANGO_LEDGER_BILL_MODEL = getattr(settings, 'DJANGO_LEDGER_BILL_MODEL', 'django_ledger.BillModelAbstract')
|
|
45
|
+
DJANGO_LEDGER_INVOICE_MODEL = getattr(settings, 'DJANGO_LEDGER_INVOICE_MODEL', 'django_ledger.InvoiceModelAbstract')
|
|
46
|
+
DJANGO_LEDGER_PURCHASE_ORDER_MODEL = getattr(settings, 'DJANGO_LEDGER_PURCHASE_ORDER_MODEL', 'django_ledger.PurchaseOrderModelAbstract')
|
|
47
|
+
|
|
48
|
+
DJANGO_LEDGER_CUSTOMER_MODEL = getattr(settings, 'DJANGO_LEDGER_CUSTOMER_MODEL', 'django_ledger.CustomerModelAbstract')
|
|
49
|
+
DJANGO_LEDGER_VENDOR_MODEL = getattr(settings, 'DJANGO_LEDGER_VENDOR_MODEL', 'django_ledger.VendorModelAbstract')
|
|
50
|
+
|
|
51
|
+
DJANGO_LEDGER_BANK_ACCOUNT_MODEL = getattr(settings, 'DJANGO_LEDGER_BANK_ACCOUNT_MODEL', 'django_ledger.BackAccountModelAbstract')
|
|
52
|
+
DJANGO_LEDGER_CLOSING_ENTRY_MODEL = getattr(settings, 'DJANGO_LEDGER_CLOSING_ENTRY_MODEL', 'django_ledger.ClosingEntryTransactionModelAbstract')
|
|
53
|
+
DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL = getattr(settings, 'DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL', 'django_ledger.UnitOfMeasureModelAbstract')
|
|
54
|
+
DJANGO_LEDGER_ITEM_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_ITEM_TRANSACTION_MODEL', 'django_ledger.ItemTransactionModelAbstract')
|
|
55
|
+
DJANGO_LEDGER_ITEM_MODEL = getattr(settings, 'DJANGO_LEDGER_ITEM_MODEL', 'django_ledger.ItemModelAbstract')
|
|
56
|
+
DJANGO_LEDGER_STAGED_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_STAGED_TRANSACTION_MODEL', 'django_ledger.StagedTransactionModelAbstract')
|
|
57
|
+
DJANGO_LEDGER_IMPORT_JOB_MODEL = getattr(settings, 'DJANGO_LEDGER_IMPORT_JOB_MODEL', 'django_ledger.ImportJobModelAbstract')
|
|
58
|
+
|
|
34
59
|
DJANGO_LEDGER_USE_CLOSING_ENTRIES = getattr(settings, 'DJANGO_LEDGER_USE_CLOSING_ENTRIES', True)
|
|
35
60
|
DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT = getattr(settings,
|
|
36
61
|
'DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT', 3600)
|