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.

Files changed (93) hide show
  1. django_ledger/__init__.py +1 -4
  2. django_ledger/admin/__init__.py +1 -1
  3. django_ledger/admin/{coa.py → chart_of_accounts.py} +1 -1
  4. django_ledger/admin/entity.py +1 -1
  5. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
  6. django_ledger/forms/account.py +43 -38
  7. django_ledger/forms/bank_account.py +5 -2
  8. django_ledger/forms/bill.py +24 -36
  9. django_ledger/forms/chart_of_accounts.py +82 -0
  10. django_ledger/forms/customer.py +1 -1
  11. django_ledger/forms/data_import.py +3 -3
  12. django_ledger/forms/estimate.py +1 -1
  13. django_ledger/forms/invoice.py +5 -7
  14. django_ledger/forms/item.py +24 -15
  15. django_ledger/forms/transactions.py +3 -3
  16. django_ledger/io/io_core.py +4 -2
  17. django_ledger/io/io_library.py +1 -1
  18. django_ledger/io/io_middleware.py +5 -0
  19. django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
  20. django_ledger/migrations/0018_transactionmodel_cleared_transactionmodel_reconciled_and_more.py +37 -0
  21. django_ledger/models/__init__.py +1 -1
  22. django_ledger/models/accounts.py +229 -265
  23. django_ledger/models/bank_account.py +6 -6
  24. django_ledger/models/bill.py +7 -6
  25. django_ledger/models/{coa.py → chart_of_accounts.py} +187 -72
  26. django_ledger/models/closing_entry.py +5 -10
  27. django_ledger/models/coa_default.py +10 -9
  28. django_ledger/models/customer.py +6 -6
  29. django_ledger/models/data_import.py +12 -8
  30. django_ledger/models/entity.py +96 -39
  31. django_ledger/models/estimate.py +6 -10
  32. django_ledger/models/invoice.py +14 -11
  33. django_ledger/models/items.py +23 -14
  34. django_ledger/models/journal_entry.py +73 -30
  35. django_ledger/models/ledger.py +8 -8
  36. django_ledger/models/mixins.py +0 -3
  37. django_ledger/models/purchase_order.py +9 -9
  38. django_ledger/models/signals.py +0 -3
  39. django_ledger/models/transactions.py +24 -7
  40. django_ledger/models/unit.py +4 -3
  41. django_ledger/models/utils.py +0 -3
  42. django_ledger/models/vendor.py +4 -3
  43. django_ledger/settings.py +28 -3
  44. django_ledger/templates/django_ledger/account/account_create.html +2 -2
  45. django_ledger/templates/django_ledger/account/account_update.html +1 -1
  46. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
  47. django_ledger/templates/django_ledger/account/tags/accounts_table.html +29 -19
  48. django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
  49. django_ledger/templates/django_ledger/chart_of_accounts/coa_create.html +25 -0
  50. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +25 -6
  51. django_ledger/templates/django_ledger/chart_of_accounts/coa_update.html +2 -2
  52. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +10 -4
  53. django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
  54. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +2 -2
  55. django_ledger/templates/django_ledger/includes/footer.html +2 -2
  56. django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
  57. django_ledger/templatetags/django_ledger.py +7 -1
  58. django_ledger/tests/base.py +23 -7
  59. django_ledger/tests/test_accounts.py +145 -9
  60. django_ledger/urls/account.py +17 -24
  61. django_ledger/urls/chart_of_accounts.py +6 -0
  62. django_ledger/utils.py +9 -36
  63. django_ledger/views/__init__.py +2 -2
  64. django_ledger/views/account.py +91 -116
  65. django_ledger/views/auth.py +1 -1
  66. django_ledger/views/bank_account.py +9 -11
  67. django_ledger/views/bill.py +91 -80
  68. django_ledger/views/{coa.py → chart_of_accounts.py} +49 -44
  69. django_ledger/views/closing_entry.py +8 -0
  70. django_ledger/views/customer.py +1 -1
  71. django_ledger/views/data_import.py +1 -1
  72. django_ledger/views/entity.py +1 -1
  73. django_ledger/views/estimate.py +13 -8
  74. django_ledger/views/feedback.py +1 -1
  75. django_ledger/views/financial_statement.py +1 -1
  76. django_ledger/views/home.py +1 -1
  77. django_ledger/views/inventory.py +9 -0
  78. django_ledger/views/invoice.py +5 -2
  79. django_ledger/views/item.py +58 -68
  80. django_ledger/views/journal_entry.py +1 -1
  81. django_ledger/views/ledger.py +3 -1
  82. django_ledger/views/mixins.py +25 -13
  83. django_ledger/views/purchase_order.py +1 -1
  84. django_ledger/views/transactions.py +1 -1
  85. django_ledger/views/unit.py +9 -0
  86. django_ledger/views/vendor.py +1 -1
  87. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +8 -2
  88. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/METADATA +33 -44
  89. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/RECORD +92 -89
  90. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +1 -1
  91. django_ledger/forms/coa.py +0 -47
  92. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
  93. {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(models.Manager):
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
- def is_balance_valid(self, txs_qs: Optional[TransactionModelQuerySet] = None) -> bool:
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
- return balances[CREDIT] == balances[DEBIT]
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 is_cash_involved(self, txs_qs=None):
513
- return ASSET_CA_CASH in self.get_txs_roles(txs_qs=None)
514
-
515
- def is_operating(self):
516
- return self.activity in [
517
- self.OPERATING_ACTIVITY
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
- def is_financing(self):
521
- return self.activity in [
522
- self.FINANCING_EQUITY,
523
- self.FINANCING_LTD,
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
- def is_investing(self):
530
- return self.activity in [
531
- self.INVESTING_SECURITIES,
532
- self.INVESTING_PPE,
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():
@@ -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: \n
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):
@@ -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(models.QuerySet):
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(models.Manager):
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():
@@ -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(models.Manager):
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.select_related(
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
  """
@@ -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
@@ -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
 
9
6
  from django.apps import apps
@@ -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="{% url 'django_ledger:account-list' entity_slug=view.kwargs.entity_slug %}">Back</a>
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="{% url 'django_ledger:account-list' entity_slug=view.kwargs.entity_slug %}">Back</a>
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
- {# <td></td>#}
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
- {# <td></td>#}
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.active %}
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
- {% elif not account.active %}
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
- {# <td class="has-text-centered">#}
64
- {# {% if account.locked %}#}
65
- {# <span class="icon has-text-success-dark">#}
66
- {# {% icon 'bi:lock-fill' 24 %}#}
67
- {# </span>#}
68
- {# {% elif not account.locked %}#}
69
- {# <span class="icon has-text-danger-dark">#}
70
- {# {% icon 'bx:bx-lock-open-alt' 24 %}#}
71
- {# </span>#}
72
- {# {% endif %}#}
73
- {# </td>#}
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="{% url 'django_ledger:account-detail' entity_slug=entity_slug account_pk=account.uuid %}"
99
+ <a href="{{ account.get_absolute_url }}"
98
100
  class="dropdown-item has-text-success">{% trans 'Detail' %}</a>
99
- <a href="{% url 'django_ledger:account-update' entity_slug=entity_slug account_pk=account.uuid %}"
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="{% url 'django_ledger:account-action-activate' entity_slug=entity_slug account_pk=account.uuid %}"
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="{% url 'django_ledger:account-action-deactivate' entity_slug=entity_slug account_pk=account.uuid %}"
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>