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
|
@@ -2,9 +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
|
-
|
|
8
5
|
A Journal Entry (JE) is the foundation of all double entry accounting and financial data of any EntityModel.
|
|
9
6
|
A JE encapsulates a collection of TransactionModel, which must contain two transactions at a minimum. Each transaction
|
|
10
7
|
must perform a DEBIT or a CREDIT to an AccountModel. The JE Model performs additional validation to make sure that
|
|
@@ -37,7 +34,7 @@ from uuid import uuid4, UUID
|
|
|
37
34
|
|
|
38
35
|
from django.core.exceptions import FieldError, ObjectDoesNotExist, ValidationError
|
|
39
36
|
from django.db import models, transaction, IntegrityError
|
|
40
|
-
from django.db.models import Q, Sum, QuerySet, F
|
|
37
|
+
from django.db.models import Q, Sum, QuerySet, F, Manager
|
|
41
38
|
from django.db.models.functions import Coalesce
|
|
42
39
|
from django.db.models.signals import pre_save
|
|
43
40
|
from django.urls import reverse
|
|
@@ -144,7 +141,7 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
144
141
|
return self.filter(locked=False)
|
|
145
142
|
|
|
146
143
|
|
|
147
|
-
class JournalEntryModelManager(
|
|
144
|
+
class JournalEntryModelManager(Manager):
|
|
148
145
|
"""
|
|
149
146
|
A custom defined Journal Entry Model Manager that supports additional complex initial Queries based on the
|
|
150
147
|
EntityModel and authenticated UserModel.
|
|
@@ -490,15 +487,18 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
490
487
|
"""
|
|
491
488
|
return self._verified
|
|
492
489
|
|
|
493
|
-
|
|
490
|
+
# Transaction QuerySet
|
|
491
|
+
def is_balance_valid(self, txs_qs: TransactionModelQuerySet, raise_exception: bool = True) -> bool:
|
|
494
492
|
"""
|
|
495
493
|
Checks if CREDITs and DEBITs are equal.
|
|
496
494
|
|
|
497
495
|
Parameters
|
|
498
496
|
----------
|
|
499
497
|
txs_qs: TransactionModelQuerySet
|
|
500
|
-
Optional pre-fetched JE instance TransactionModelQuerySet. Will be validated if provided.
|
|
501
498
|
|
|
499
|
+
raise_exception: bool
|
|
500
|
+
Raises JournalEntryValidationError if TransactionModelQuerySet is not valid.
|
|
501
|
+
|
|
502
502
|
Returns
|
|
503
503
|
-------
|
|
504
504
|
bool
|
|
@@ -506,32 +506,38 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
506
506
|
"""
|
|
507
507
|
if len(txs_qs) > 0:
|
|
508
508
|
balances = self.get_txs_balances(txs_qs=txs_qs, as_dict=True)
|
|
509
|
-
|
|
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
|
|
510
520
|
return True
|
|
511
521
|
|
|
512
|
-
def
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
|
519
531
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
self.FINANCING_DIVIDENDS,
|
|
525
|
-
self.FINANCING_STD,
|
|
526
|
-
self.FINANCING_OTHER
|
|
527
|
-
]
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
True if Transaction CoAs are valid, otherwise False.
|
|
535
|
+
"""
|
|
528
536
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
self.INVESTING_OTHER
|
|
534
|
-
]
|
|
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
|
|
535
541
|
|
|
536
542
|
def is_txs_qs_valid(self, txs_qs: TransactionModelQuerySet, raise_exception: bool = True) -> bool:
|
|
537
543
|
"""
|
|
@@ -563,6 +569,30 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
563
569
|
f'associated with LedgerModel {self.uuid}')
|
|
564
570
|
return is_valid
|
|
565
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
|
+
|
|
566
596
|
def get_entity_unit_name(self, no_unit_name: str = ''):
|
|
567
597
|
if self.entity_unit_id:
|
|
568
598
|
return self.entity_unit.name
|
|
@@ -1158,6 +1188,15 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
1158
1188
|
except JournalEntryValidationError as e:
|
|
1159
1189
|
raise e
|
|
1160
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
|
+
|
|
1161
1200
|
# if not len(txs_qs):
|
|
1162
1201
|
# if raise_exception:
|
|
1163
1202
|
# raise JournalEntryValidationError('Journal entry has no transactions.')
|
|
@@ -1166,7 +1205,7 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
1166
1205
|
# if raise_exception:
|
|
1167
1206
|
# raise JournalEntryValidationError('At least two transactions required.')
|
|
1168
1207
|
|
|
1169
|
-
if all([is_balance_valid, is_txs_qs_valid]):
|
|
1208
|
+
if all([is_balance_valid, is_txs_qs_valid, is_coa_valid]):
|
|
1170
1209
|
# activity flag...
|
|
1171
1210
|
self.generate_activity(txs_qs=txs_qs, raise_exception=raise_exception)
|
|
1172
1211
|
self._verified = True
|
|
@@ -1353,6 +1392,10 @@ class JournalEntryModel(JournalEntryModelAbstract):
|
|
|
1353
1392
|
Journal Entry Model Base Class From Abstract
|
|
1354
1393
|
"""
|
|
1355
1394
|
|
|
1395
|
+
class Meta(JournalEntryModelAbstract.Meta):
|
|
1396
|
+
swappable = 'DJANGO_LEDGER_JOURNAL_ENTRY_MODEL'
|
|
1397
|
+
abstract = False
|
|
1398
|
+
|
|
1356
1399
|
|
|
1357
1400
|
def journalentrymodel_presave(instance: JournalEntryModel, **kwargs):
|
|
1358
1401
|
if instance._state.adding and not instance.ledger.can_edit_journal_entries():
|
django_ledger/models/ledger.py
CHANGED
|
@@ -2,9 +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
|
-
|
|
8
5
|
The LedgerModel is the heart of Django Ledger. It is a self-contained unit of accounting that implements a
|
|
9
6
|
double-entry accounting system capable of creating and managing transactions into the ledger and producing any financial
|
|
10
7
|
statements. In essence, an EntityModel is made of a collection of LedgerModels that drive the whole bookkeeping process.
|
|
@@ -23,7 +20,7 @@ which is the class responsible for making accounting queries to the Database in
|
|
|
23
20
|
The digest() method executes all necessary aggregations and optimizations in order to push as much work to the Database
|
|
24
21
|
layer as possible in order to minimize the amount of data being pulled for analysis into the Python memory.
|
|
25
22
|
|
|
26
|
-
The Django Ledger core model follows the following structure:
|
|
23
|
+
The Django Ledger core model follows the following structure:
|
|
27
24
|
EntityModel -< LedgerModel -< JournalEntryModel -< TransactionModel
|
|
28
25
|
"""
|
|
29
26
|
from datetime import date
|
|
@@ -37,6 +34,10 @@ from django.db import models
|
|
|
37
34
|
from django.db.models import Q, Min, F, Count
|
|
38
35
|
from django.urls import reverse
|
|
39
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
|
|
40
41
|
from django_ledger.models.signals import (
|
|
41
42
|
ledger_posted,
|
|
42
43
|
ledger_unposted,
|
|
@@ -46,10 +47,6 @@ from django_ledger.models.signals import (
|
|
|
46
47
|
ledger_unhidden
|
|
47
48
|
)
|
|
48
49
|
|
|
49
|
-
from django_ledger.io.io_core import IOMixIn
|
|
50
|
-
from django_ledger.models import lazy_loader
|
|
51
|
-
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
52
|
-
|
|
53
50
|
LEDGER_ID_CHARS = ascii_lowercase + digits
|
|
54
51
|
|
|
55
52
|
|
|
@@ -728,6 +725,9 @@ class LedgerModel(LedgerModelAbstract):
|
|
|
728
725
|
"""
|
|
729
726
|
Base LedgerModel from Abstract.
|
|
730
727
|
"""
|
|
728
|
+
class Meta(LedgerModelAbstract.Meta):
|
|
729
|
+
swappable = 'DJANGO_LEDGER_LEDGER_MODEL'
|
|
730
|
+
abstract = False
|
|
731
731
|
|
|
732
732
|
|
|
733
733
|
def ledgermodel_presave(instance: LedgerModel, **kwargs):
|
django_ledger/models/mixins.py
CHANGED
|
@@ -2,9 +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
|
-
|
|
8
5
|
This module implements the different model MixIns used on different Django Ledger Models to implement common
|
|
9
6
|
functionality.
|
|
10
7
|
"""
|
|
@@ -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 purchase order is a commercial source document that is issued by a business purchasing department when placing an
|
|
10
6
|
order with its vendors or suppliers. The document indicates the details on the items that are to be purchased, such as
|
|
11
7
|
the types of goods, quantity, and price. In simple terms, it is the contract drafted by the buyer when purchasing goods
|
|
@@ -25,7 +21,7 @@ from django.contrib.auth import get_user_model
|
|
|
25
21
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
26
22
|
from django.core.validators import MinLengthValidator
|
|
27
23
|
from django.db import models, transaction, IntegrityError
|
|
28
|
-
from django.db.models import Q, Sum, Count, F
|
|
24
|
+
from django.db.models import Q, Sum, Count, F, Manager, QuerySet
|
|
29
25
|
from django.db.models.functions import Coalesce
|
|
30
26
|
from django.db.models.signals import pre_save
|
|
31
27
|
from django.shortcuts import get_object_or_404
|
|
@@ -37,8 +33,6 @@ from django_ledger.models.bill import BillModel, BillModelQuerySet
|
|
|
37
33
|
from django_ledger.models.entity import EntityModel
|
|
38
34
|
from django_ledger.models.items import ItemTransactionModel, ItemTransactionModelQuerySet, ItemModelQuerySet, ItemModel
|
|
39
35
|
from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn, ItemizeMixIn
|
|
40
|
-
from django_ledger.models.utils import lazy_loader
|
|
41
|
-
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX
|
|
42
36
|
from django_ledger.models.signals import (
|
|
43
37
|
po_status_draft,
|
|
44
38
|
po_status_void,
|
|
@@ -47,6 +41,8 @@ from django_ledger.models.signals import (
|
|
|
47
41
|
po_status_canceled,
|
|
48
42
|
po_status_in_review
|
|
49
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
|
|
50
46
|
|
|
51
47
|
PO_NUMBER_CHARS = ascii_uppercase + digits
|
|
52
48
|
|
|
@@ -57,7 +53,7 @@ class PurchaseOrderModelValidationError(ValidationError):
|
|
|
57
53
|
pass
|
|
58
54
|
|
|
59
55
|
|
|
60
|
-
class PurchaseOrderModelQuerySet(
|
|
56
|
+
class PurchaseOrderModelQuerySet(QuerySet):
|
|
61
57
|
"""
|
|
62
58
|
A custom defined PurchaseOrderModel QuerySet.
|
|
63
59
|
"""
|
|
@@ -104,7 +100,7 @@ class PurchaseOrderModelQuerySet(models.QuerySet):
|
|
|
104
100
|
return self.filter(po_status__exact=PurchaseOrderModel.PO_STATUS_DRAFT)
|
|
105
101
|
|
|
106
102
|
|
|
107
|
-
class PurchaseOrderModelManager(
|
|
103
|
+
class PurchaseOrderModelManager(Manager):
|
|
108
104
|
"""
|
|
109
105
|
A custom defined PurchaseOrderModel Manager.
|
|
110
106
|
"""
|
|
@@ -1237,6 +1233,10 @@ class PurchaseOrderModel(PurchaseOrderModelAbstract):
|
|
|
1237
1233
|
Purchase Order Base Model
|
|
1238
1234
|
"""
|
|
1239
1235
|
|
|
1236
|
+
class Meta(PurchaseOrderModelAbstract.Meta):
|
|
1237
|
+
swappable = 'DJANGO_LEDGER_PURCHASE_ORDER_MODEL'
|
|
1238
|
+
abstract = False
|
|
1239
|
+
|
|
1240
1240
|
|
|
1241
1241
|
def purchaseordermodel_presave(instance: PurchaseOrderModel, **kwargs):
|
|
1242
1242
|
if instance.can_generate_po_number():
|
django_ledger/models/signals.py
CHANGED
|
@@ -2,9 +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
|
-
|
|
8
5
|
The signals module provide the means to notify listeners about important events or states in the models,
|
|
9
6
|
such as a ledger model being posted or a bill status changing.
|
|
10
7
|
"""
|
|
@@ -2,9 +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
|
-
|
|
8
5
|
The TransactionModel is the lowest accounting level where financial information is recorded. Every transaction with a
|
|
9
6
|
financial implication must be part of a JournalEntryModel, which encapsulates a collection of TransactionModels.
|
|
10
7
|
Transaction models cannot exist without being part of a validated JournalEntryModel. Orphan TransactionModels are not
|
|
@@ -24,7 +21,7 @@ from django.contrib.auth import get_user_model
|
|
|
24
21
|
from django.core.exceptions import ValidationError
|
|
25
22
|
from django.core.validators import MinValueValidator
|
|
26
23
|
from django.db import models
|
|
27
|
-
from django.db.models import Q, QuerySet, F
|
|
24
|
+
from django.db.models import Q, QuerySet, Manager, F
|
|
28
25
|
from django.db.models.signals import pre_save
|
|
29
26
|
from django.utils.translation import gettext_lazy as _
|
|
30
27
|
|
|
@@ -209,14 +206,16 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
209
206
|
return self.filter(journal_entry__is_closing_entry=True)
|
|
210
207
|
|
|
211
208
|
|
|
212
|
-
class TransactionModelManager(
|
|
209
|
+
class TransactionModelManager(Manager):
|
|
213
210
|
"""
|
|
214
211
|
A manager class for the TransactionModel.
|
|
215
212
|
"""
|
|
216
213
|
|
|
217
214
|
def get_queryset(self) -> TransactionModelQuerySet:
|
|
218
215
|
qs = TransactionModelQuerySet(self.model, using=self._db)
|
|
219
|
-
return qs.
|
|
216
|
+
return qs.annotate(
|
|
217
|
+
_coa_id=F('account__coa_model_id'),
|
|
218
|
+
).select_related(
|
|
220
219
|
'journal_entry',
|
|
221
220
|
'account',
|
|
222
221
|
'account__coa_model',
|
|
@@ -500,6 +499,9 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
500
499
|
verbose_name=_('Tx Description'),
|
|
501
500
|
help_text=_('A description to be included with this individual transaction'))
|
|
502
501
|
|
|
502
|
+
cleared = models.BooleanField(default=False, verbose_name=_('Cleared'))
|
|
503
|
+
reconciled = models.BooleanField(default=False, verbose_name=_('Reconciled'))
|
|
504
|
+
|
|
503
505
|
objects = TransactionModelManager()
|
|
504
506
|
|
|
505
507
|
class Meta:
|
|
@@ -512,7 +514,9 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
512
514
|
models.Index(fields=['account']),
|
|
513
515
|
models.Index(fields=['journal_entry']),
|
|
514
516
|
models.Index(fields=['created']),
|
|
515
|
-
models.Index(fields=['updated'])
|
|
517
|
+
models.Index(fields=['updated']),
|
|
518
|
+
models.Index(fields=['cleared']),
|
|
519
|
+
models.Index(fields=['reconciled']),
|
|
516
520
|
]
|
|
517
521
|
|
|
518
522
|
def __str__(self):
|
|
@@ -522,6 +526,15 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
522
526
|
x4=self.tx_type,
|
|
523
527
|
x5=self.account.balance_type)
|
|
524
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
|
+
|
|
525
538
|
def clean(self):
|
|
526
539
|
if self.account_id and self.account.is_root_account():
|
|
527
540
|
raise TransactionModelValidationError(
|
|
@@ -534,6 +547,10 @@ class TransactionModel(TransactionModelAbstract):
|
|
|
534
547
|
Base Transaction Model From Abstract.
|
|
535
548
|
"""
|
|
536
549
|
|
|
550
|
+
class Meta(TransactionModelAbstract.Meta):
|
|
551
|
+
abstract = False
|
|
552
|
+
swappable = 'DJANGO_LEDGER_TRANSACTION_MODEL'
|
|
553
|
+
|
|
537
554
|
|
|
538
555
|
def transactionmodel_presave(instance: TransactionModel, **kwargs):
|
|
539
556
|
"""
|
django_ledger/models/unit.py
CHANGED
|
@@ -2,9 +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
|
-
|
|
8
5
|
An EntityUnit is a logical, user-defined grouping which is assigned to JournalEntryModels to help segregate business
|
|
9
6
|
operations into separate components. Examples of business units may include Departments (i.e. Human Resources, IT, etc.)
|
|
10
7
|
office locations, a real estate property, or any other label relevant to the business.
|
|
@@ -226,3 +223,7 @@ class EntityUnitModel(EntityUnitModelAbstract):
|
|
|
226
223
|
"""
|
|
227
224
|
Base Model Class for EntityUnitModel
|
|
228
225
|
"""
|
|
226
|
+
|
|
227
|
+
class Meta(EntityUnitModelAbstract.Meta):
|
|
228
|
+
swappable = 'DJANGO_LEDGER_ENTITY_UNIT_MODEL'
|
|
229
|
+
abstract = False
|
django_ledger/models/utils.py
CHANGED
django_ledger/models/vendor.py
CHANGED
|
@@ -2,9 +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
|
-
|
|
8
5
|
A Vendor refers to the person or entity that provides products and services to the business for a fee.
|
|
9
6
|
Vendors are an integral part of the billing process as they are the providers of goods and services for the
|
|
10
7
|
business.
|
|
@@ -326,3 +323,7 @@ class VendorModel(VendorModelAbstract):
|
|
|
326
323
|
"""
|
|
327
324
|
Base Vendor Model Implementation
|
|
328
325
|
"""
|
|
326
|
+
|
|
327
|
+
class Meta(VendorModelAbstract.Meta):
|
|
328
|
+
swappable = 'DJANGO_LEDGER_VENDOR_MODEL'
|
|
329
|
+
abstract = False
|
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)
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
</div>
|
|
14
14
|
<div class="column is-8-tablet is-6-desktop">
|
|
15
15
|
<div class="box">
|
|
16
|
-
<form method="post">
|
|
16
|
+
<form method="post" id="{{ form.form_id }}">
|
|
17
17
|
{% csrf_token %}
|
|
18
18
|
{{ form.as_p }}
|
|
19
19
|
<button type="submit" class="button is-primary is-fullwidth djetler_my_1">Submit</button>
|
|
20
20
|
<a class="button is-dark is-small is-fullwidth"
|
|
21
|
-
href="{
|
|
21
|
+
href="{{ coa_model.get_account_list_url }}">Back</a>
|
|
22
22
|
</form>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
{{ form.as_p }}
|
|
19
19
|
<button type="submit" class="button is-primary is-fullwidth djetler_my_1">Submit</button>
|
|
20
20
|
<a class="button is-dark is-small is-fullwidth"
|
|
21
|
-
href="{
|
|
21
|
+
href="{{ coa_model.get_account_list_url }}">Back</a>
|
|
22
22
|
</form>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<div class="dropdown-menu" id="dropdown-menu-{{ tx.uuid }}" role="menu">
|
|
34
34
|
<div class="dropdown-content">
|
|
35
|
+
{# TODO: These URLs need to be replaced with the future mode method that generates it. #}
|
|
35
36
|
<a href="{% url 'django_ledger:je-detail' entity_slug=entity_slug ledger_pk=tx.journal_entry.ledger.uuid je_pk=tx.journal_entry.uuid %}"
|
|
36
37
|
class="dropdown-item has-text-success">View JE</a>
|
|
37
38
|
{% if tx.journal_entry.ledger.billmodel %}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<td></td>
|
|
16
16
|
<td></td>
|
|
17
17
|
<td></td>
|
|
18
|
-
|
|
18
|
+
<td></td>
|
|
19
19
|
<td></td>
|
|
20
20
|
</tr>
|
|
21
21
|
{% endif %}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<th>{% trans 'CoA' %}</th>
|
|
26
26
|
<th>{% trans 'Balance Type' %}</th>
|
|
27
27
|
<th>{% trans 'Active' %}</th>
|
|
28
|
+
<th>{% trans 'Locked' %}</th>
|
|
28
29
|
<th>{% trans 'CoA Role Default' %}</th>
|
|
29
30
|
<th>{% trans 'Actions' %}</th>
|
|
30
31
|
</tr>
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
<td></td>
|
|
38
39
|
<td></td>
|
|
39
40
|
<td></td>
|
|
40
|
-
|
|
41
|
+
<td></td>
|
|
41
42
|
<td></td>
|
|
42
43
|
</tr>
|
|
43
44
|
|
|
@@ -49,28 +50,29 @@
|
|
|
49
50
|
<td>{{ account.coa_model.name }}</td>
|
|
50
51
|
<td>{{ account.get_balance_type_display }}</td>
|
|
51
52
|
<td class="has-text-centered">
|
|
52
|
-
{% if account.
|
|
53
|
+
{% if account.is_active %}
|
|
53
54
|
<span class="icon has-text-success-dark">
|
|
54
55
|
{% icon 'ant-design:check-circle-filled' 24 %}
|
|
55
56
|
</span>
|
|
56
|
-
{%
|
|
57
|
+
{% else %}
|
|
57
58
|
<span class="icon has-text-danger-dark">
|
|
58
59
|
{% icon 'mdi:dangerous' 24 %}
|
|
59
60
|
</span>
|
|
60
61
|
{% endif %}
|
|
61
62
|
</td>
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
<td class="has-text-centered">
|
|
65
|
+
{% if account.is_locked %}
|
|
66
|
+
<span class="icon has-text-success-dark">
|
|
67
|
+
{% icon 'ooui:lock' 24 %}
|
|
68
|
+
</span>
|
|
69
|
+
{% else %}
|
|
70
|
+
<span class="icon has-text-danger-dark">
|
|
71
|
+
{% icon 'ooui:un-lock' 24 %}
|
|
72
|
+
</span>
|
|
73
|
+
{% endif %}
|
|
74
|
+
</td>
|
|
75
|
+
|
|
74
76
|
<td class="has-text-centered">
|
|
75
77
|
{% if account.role_default %}
|
|
76
78
|
<span class="icon has-text-success-dark">
|
|
@@ -94,18 +96,26 @@
|
|
|
94
96
|
</div>
|
|
95
97
|
<div class="dropdown-menu" id="dropdown-menu-{{ account.uuid }}" role="menu">
|
|
96
98
|
<div class="dropdown-content">
|
|
97
|
-
<a href="{
|
|
99
|
+
<a href="{{ account.get_absolute_url }}"
|
|
98
100
|
class="dropdown-item has-text-success">{% trans 'Detail' %}</a>
|
|
99
|
-
<a href="{
|
|
101
|
+
<a href="{{ account.get_update_url }}"
|
|
100
102
|
class="dropdown-item has-text-warning">{% trans 'Update' %}</a>
|
|
101
103
|
{% if account.can_activate %}
|
|
102
|
-
<a href="{
|
|
104
|
+
<a href="{{ account.get_action_activate_url }}"
|
|
103
105
|
class="dropdown-item has-text-success has-text-weight-bold">{% trans 'Activate' %}</a>
|
|
104
106
|
{% endif %}
|
|
105
107
|
{% if account.can_deactivate %}
|
|
106
|
-
<a href="{
|
|
108
|
+
<a href="{{ account.get_action_deactivate_url }}"
|
|
107
109
|
class="dropdown-item has-text-danger has-text-weight-bold">{% trans 'Deactivate' %}</a>
|
|
108
110
|
{% endif %}
|
|
111
|
+
{% if account.can_lock %}
|
|
112
|
+
<a href="{{ account.get_action_lock_url }}"
|
|
113
|
+
class="dropdown-item has-text-success has-text-weight-bold">{% trans 'Lock' %}</a>
|
|
114
|
+
{% endif %}
|
|
115
|
+
{% if account.can_unlock %}
|
|
116
|
+
<a href="{{ account.get_action_unlock_url }}"
|
|
117
|
+
class="dropdown-item has-text-danger has-text-weight-bold">{% trans 'Unlock' %}</a>
|
|
118
|
+
{% endif %}
|
|
109
119
|
</div>
|
|
110
120
|
</div>
|
|
111
121
|
</div>
|