django-ledger 0.7.5.1__py3-none-any.whl → 0.7.6__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 (39) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/forms/closing_entry.py +2 -1
  3. django_ledger/forms/journal_entry.py +1 -0
  4. django_ledger/io/io_context.py +10 -0
  5. django_ledger/io/io_core.py +22 -0
  6. django_ledger/migrations/0021_alter_bankaccountmodel_account_model_and_more.py +2 -3
  7. django_ledger/models/accounts.py +40 -1
  8. django_ledger/models/bank_account.py +0 -1
  9. django_ledger/models/bill.py +0 -1
  10. django_ledger/models/chart_of_accounts.py +0 -1
  11. django_ledger/models/closing_entry.py +27 -13
  12. django_ledger/models/customer.py +0 -1
  13. django_ledger/models/data_import.py +0 -2
  14. django_ledger/models/entity.py +15 -3
  15. django_ledger/models/estimate.py +0 -1
  16. django_ledger/models/invoice.py +0 -1
  17. django_ledger/models/items.py +0 -3
  18. django_ledger/models/journal_entry.py +5 -3
  19. django_ledger/models/ledger.py +0 -1
  20. django_ledger/models/purchase_order.py +0 -1
  21. django_ledger/models/transactions.py +27 -4
  22. django_ledger/models/unit.py +0 -1
  23. django_ledger/models/vendor.py +0 -1
  24. django_ledger/settings.py +22 -21
  25. django_ledger/templates/django_ledger/layouts/base.html +6 -1
  26. django_ledger/urls/ledger.py +1 -1
  27. django_ledger/views/closing_entry.py +47 -40
  28. django_ledger/views/mixins.py +6 -0
  29. django_ledger/views/unit.py +11 -14
  30. django_ledger/views/vendor.py +10 -8
  31. {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/METADATA +11 -32
  32. {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/RECORD +36 -39
  33. {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/WHEEL +1 -1
  34. {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/top_level.txt +0 -1
  35. django_ledger/static/.DS_Store +0 -0
  36. django_ledger/static/django_ledger/.DS_Store +0 -0
  37. django_ledger/static/django_ledger/logo_2/.DS_Store +0 -0
  38. {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/AUTHORS.md +0 -0
  39. {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/LICENSE +0 -0
django_ledger/__init__.py CHANGED
@@ -6,7 +6,7 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
6
6
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
7
7
 
8
8
  """Django Ledger"""
9
- __version__ = '0.7.5.1'
9
+ __version__ = '0.7.6'
10
10
  __license__ = 'GPLv3 License'
11
11
 
12
12
  __author__ = 'Miguel Sanda'
@@ -26,7 +26,8 @@ class ClosingEntryCreateForm(ModelForm):
26
26
  'closing_date': DateInput(attrs={
27
27
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-large',
28
28
  'placeholder': _('Closing Date (YYYY-MM-DD)...'),
29
- 'id': 'djl-datepicker'
29
+ 'id': 'djl-datepicker',
30
+ 'type': 'date'
30
31
  })
31
32
  }
32
33
  labels = {
@@ -53,6 +53,7 @@ class JournalEntryModelCreateForm(ModelForm):
53
53
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
54
54
  }),
55
55
  'timestamp': DateTimeInput(attrs={
56
+ 'type': 'datetime-local',
56
57
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
57
58
  }),
58
59
  'description': Textarea(attrs={
@@ -89,6 +89,16 @@ class IODigestContextManager:
89
89
  def is_by_activity(self) -> bool:
90
90
  return self.IO_DATA['by_activity']
91
91
 
92
+ # Account Information
93
+ def get_account_data(self, key_func=None) -> Dict:
94
+ if key_func:
95
+ return {
96
+ key_func(acc): acc for acc in self.IO_DATA['accounts']
97
+ }
98
+ return {
99
+ acc['account_uuid']: acc for acc in self.IO_DATA['accounts']
100
+ }
101
+
92
102
  # Balance Sheet Data...
93
103
  def has_balance_sheet(self) -> bool:
94
104
  return 'balance_sheet' in self.IO_DATA
@@ -847,6 +847,28 @@ class IODatabaseMixIn:
847
847
  if role:
848
848
  txs_queryset = txs_queryset.for_roles(role_list=role)
849
849
 
850
+ # Cleared transaction filter via KWARGS....
851
+ cleared_filter = kwargs.get('cleared')
852
+ if cleared_filter is not None:
853
+ if cleared_filter in [True, False]:
854
+ txs_queryset = txs_queryset.is_cleared() if cleared_filter else txs_queryset.not_cleared()
855
+ else:
856
+ raise IOValidationError(
857
+ message=f'Invalid value for cleared filter: {cleared_filter}. '
858
+ f'Valid values are True, False'
859
+ )
860
+
861
+ # Reconciled transaction filter via KWARGS....
862
+ reconciled_filter = kwargs.get('reconciled')
863
+ if reconciled_filter is not None:
864
+ if reconciled_filter in [True, False]:
865
+ txs_queryset = txs_queryset.is_reconciled() if reconciled_filter else txs_queryset.not_reconciled()
866
+ else:
867
+ raise IOValidationError(
868
+ message=f'Invalid value for reconciled filter: {reconciled_filter}. '
869
+ f'Valid values are True, False'
870
+ )
871
+
850
872
  if io_result.is_bounded:
851
873
  txs_queryset = txs_queryset.annotate(
852
874
  amount_io=Case(
@@ -1,7 +1,6 @@
1
1
  # Generated by Django 5.1.6 on 2025-02-18 22:47
2
2
 
3
3
  import django.db.models.deletion
4
- from django.conf import settings
5
4
  from django.db import migrations, models
6
5
 
7
6
 
@@ -17,7 +16,7 @@ class Migration(migrations.Migration):
17
16
  field=models.ForeignKey(
18
17
  help_text='Account model be used to map transactions from financial institution',
19
18
  on_delete=django.db.models.deletion.RESTRICT,
20
- to=settings.DJANGO_LEDGER_ACCOUNT_MODEL,
19
+ to='django_ledger.AccountModel',
21
20
  verbose_name='Associated Account Model'
22
21
  ),
23
22
  ),
@@ -26,7 +25,7 @@ class Migration(migrations.Migration):
26
25
  name='bank_account_model',
27
26
  field=models.ForeignKey(
28
27
  on_delete=django.db.models.deletion.CASCADE,
29
- to=settings.DJANGO_LEDGER_BANK_ACCOUNT_MODEL,
28
+ to='django_ledger.BankAccountModel',
30
29
  verbose_name='Associated Bank Account Model'
31
30
  ),
32
31
  ),
@@ -472,8 +472,35 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
472
472
  x5=self.code
473
473
  )
474
474
 
475
+ def alt_str(self):
476
+ """
477
+ Returns a formatted string representation of the object.
478
+
479
+ The formatted string includes the code, name, role, and balance type
480
+ of the object. The role is converted to uppercase for consistency,
481
+ and the balance type is displayed as is. This method provides a
482
+ concise textual representation for quick identification or display.
483
+
484
+ Returns:
485
+ str: A formatted string in the format 'code: name (ROLE/BALANCE_TYPE)'.
486
+ """
487
+ return f'{self.code}: {self.name} ({self.role.upper()}/{self.balance_type})'
488
+
475
489
  @property
476
490
  def coa_slug(self):
491
+ """
492
+ Property that retrieves the `coa_slug` attribute from the object. If the attribute
493
+ is not found, it fetches the `slug` attribute from the `coa_model`.
494
+
495
+ Attributes:
496
+ _coa_slug (str): Cached value of the `coa_slug` if it exists.
497
+ coa_model (Any): Object containing the `slug` attribute that serves
498
+ as a fallback when `_coa_slug` is not present.
499
+
500
+ Returns:
501
+ str: The value of `_coa_slug` if defined, or the `slug` attribute from
502
+ `coa_model` if `_coa_slug` is not available.
503
+ """
477
504
  try:
478
505
  return getattr(self, '_coa_slug')
479
506
  except AttributeError:
@@ -481,6 +508,19 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
481
508
 
482
509
  @property
483
510
  def entity_slug(self):
511
+ """
512
+ Retrieve the slug value associated with the entity.
513
+
514
+ This property method returns the value of the private attribute
515
+ '_entity_slug' for the current instance. The purpose of the
516
+ slug is typically to provide a URL-friendly string representing
517
+ the entity.
518
+
519
+ Returns
520
+ -------
521
+ Any
522
+ The value of the '_entity_slug' attribute.
523
+ """
484
524
  return getattr(self, '_entity_slug')
485
525
 
486
526
  @classmethod
@@ -1059,7 +1099,6 @@ class AccountModel(AccountModelAbstract):
1059
1099
  """
1060
1100
 
1061
1101
  class Meta(AccountModelAbstract.Meta):
1062
- swappable = 'DJANGO_LEDGER_ACCOUNT_MODEL'
1063
1102
  abstract = False
1064
1103
 
1065
1104
 
@@ -213,5 +213,4 @@ class BankAccountModel(BankAccountModelAbstract):
213
213
  """
214
214
 
215
215
  class Meta(BankAccountModelAbstract.Meta):
216
- swappable = 'DJANGO_LEDGER_BANK_ACCOUNT_MODEL'
217
216
  abstract = False
@@ -1920,7 +1920,6 @@ class BillModel(BillModelAbstract):
1920
1920
  """
1921
1921
 
1922
1922
  class Meta(BillModelAbstract.Meta):
1923
- swappable = 'DJANGO_LEDGER_BILL_MODEL'
1924
1923
  abstract = False
1925
1924
 
1926
1925
 
@@ -840,7 +840,6 @@ class ChartOfAccountModel(ChartOfAccountModelAbstract):
840
840
  Base ChartOfAccounts Model
841
841
  """
842
842
  class Meta(ChartOfAccountModelAbstract.Meta):
843
- swappable = 'DJANGO_LEDGER_CHART_OF_ACCOUNTS_MODEL'
844
843
  abstract = False
845
844
 
846
845
 
@@ -9,11 +9,10 @@ from itertools import groupby, chain
9
9
  from typing import Optional
10
10
  from uuid import uuid4, UUID
11
11
 
12
- from django.conf import settings
13
12
  from django.core.exceptions import ValidationError
14
13
  from django.core.validators import MinValueValidator
15
14
  from django.db import models
16
- from django.db.models import Q
15
+ from django.db.models import Q, Count
17
16
  from django.db.models.signals import pre_save
18
17
  from django.urls import reverse
19
18
  from django.utils.timezone import make_aware
@@ -24,7 +23,6 @@ from django_ledger.models.ledger import LedgerModel
24
23
  from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn
25
24
  from django_ledger.models.transactions import TransactionModel
26
25
  from django_ledger.models.utils import lazy_loader
27
- from django_ledger.settings import DJANGO_LEDGER_LEDGER_MODEL
28
26
 
29
27
 
30
28
  class ClosingEntryValidationError(ValidationError):
@@ -42,6 +40,12 @@ class ClosingEntryModelQuerySet(models.QuerySet):
42
40
 
43
41
  class ClosingEntryModelManager(models.Manager):
44
42
 
43
+ def get_queryset(self):
44
+ qs = ClosingEntryModelQuerySet(self.model, using=self._db)
45
+ return qs.annotate(
46
+ ce_txs_count=Count('closingentrytransactionmodel')
47
+ )
48
+
45
49
  def for_user(self, user_model):
46
50
  qs = self.get_queryset()
47
51
  if user_model.is_superuser:
@@ -102,6 +106,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
102
106
  return make_aware(datetime.combine(self.closing_date, time.max))
103
107
 
104
108
  def migrate(self):
109
+
105
110
  ce_txs = self.closingentrytransactionmodel_set.all().select_related(
106
111
  'account_model',
107
112
  'unit_model'
@@ -118,8 +123,9 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
118
123
  ce_txs_gb = groupby(ce_txs, key=lambda k: k.tx_type)
119
124
 
120
125
  # adding DEBITS and CREDITS...
121
- # ce_txs_gb = {k: list(l) for k, l in ce_txs_gb}
122
- ce_txs_sum = {k: sum(v.balance for v in l) for k, l in ce_txs_gb}
126
+ ce_txs_sum = {
127
+ k: sum(v.balance for v in l) for k, l in ce_txs_gb
128
+ }
123
129
 
124
130
  if len(ce_txs_sum) and ce_txs_sum[TransactionModel.DEBIT] != ce_txs_sum[TransactionModel.CREDIT]:
125
131
  raise ClosingEntryValidationError(
@@ -131,7 +137,9 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
131
137
 
132
138
  ce_txs.sort(key=key_func)
133
139
  ce_txs_gb = groupby(ce_txs, key=key_func)
134
- ce_txs_gb = {unit_model_id: list(je_txs) for unit_model_id, je_txs in ce_txs_gb}
140
+ ce_txs_gb = {
141
+ unit_model_id: list(je_txs) for unit_model_id, je_txs in ce_txs_gb
142
+ }
135
143
 
136
144
  ce_txs_journal_entries = {
137
145
  (unit_model_id, activity): JournalEntryModel(
@@ -164,6 +172,8 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
164
172
  for k, je_model in ce_txs_journal_entries.items():
165
173
  je_model.save(verify=True)
166
174
 
175
+ self.ledger_model.lock(commit=True, raise_exception=True)
176
+
167
177
  return ce_txs_journal_entries, ce_je_txs
168
178
 
169
179
  def create_entry_ledger(self, commit: bool = False):
@@ -172,7 +182,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
172
182
  name=f'Closing Entry {self.closing_date} Ledger',
173
183
  entity_id=self.entity_model_id,
174
184
  hidden=True,
175
- locked=True,
185
+ locked=False,
176
186
  posted=True
177
187
  )
178
188
  ledger_model.clean()
@@ -189,7 +199,7 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
189
199
  def can_post(self) -> bool:
190
200
  return not self.is_posted()
191
201
 
192
- def mark_as_posted(self, commit: bool = False, update_entity_meta: bool = False, **kwargs):
202
+ def mark_as_posted(self, commit: bool = False, update_entity_meta: bool = True, **kwargs):
193
203
  if not self.can_post():
194
204
  raise ClosingEntryValidationError(
195
205
  message=_(f'Closing Entry {self.closing_date} is already posted.')
@@ -226,11 +236,14 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
226
236
  def can_unpost(self) -> bool:
227
237
  return self.is_posted()
228
238
 
229
- def mark_as_unposted(self, commit: bool = False, update_entity_meta: bool = False, **kwargs):
239
+ def mark_as_unposted(self, commit: bool = False, update_entity_meta: bool = True, **kwargs):
230
240
  if not self.can_unpost():
231
241
  raise ClosingEntryValidationError(
232
242
  message=_(f'Closing Entry {self.closing_date} is not posted.')
233
243
  )
244
+
245
+ self.ledger_model.unlock(commit=False, raise_exception=True)
246
+ self.ledger_model.save(update_fields=['posted', 'locked', 'updated'])
234
247
  self.posted = False
235
248
 
236
249
  TransactionModel.objects.for_entity(
@@ -305,6 +318,8 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
305
318
  message=_('Cannot delete a posted Closing Entry')
306
319
  )
307
320
 
321
+ self.ledger_model.unpost(commit=True, raise_exception=True)
322
+
308
323
  TransactionModel.objects.for_entity(
309
324
  entity_slug=self.entity_model_id
310
325
  ).for_ledger(ledger_model=self.ledger_model).delete()
@@ -343,12 +358,9 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
343
358
 
344
359
  class ClosingEntryModel(ClosingEntryModelAbstract):
345
360
  class Meta(ClosingEntryModelAbstract.Meta):
346
- swappable = 'DJANGO_LEDGER_CLOSING_ENTRY_MODEL'
347
361
  abstract = False
348
362
 
349
363
 
350
- # todo: Remove this model!
351
-
352
364
  class ClosingEntryTransactionModelQuerySet(models.QuerySet):
353
365
  pass
354
366
 
@@ -433,7 +445,6 @@ class ClosingEntryTransactionModelAbstract(CreateUpdateMixIn):
433
445
  name='unique_ce_opt_3'
434
446
  )
435
447
  ]
436
-
437
448
  indexes = [
438
449
  models.Index(fields=['closing_entry_model']),
439
450
  models.Index(fields=['account_model'])
@@ -470,6 +481,9 @@ class ClosingEntryTransactionModel(ClosingEntryTransactionModelAbstract):
470
481
  Base ClosingEntryModel Class
471
482
  """
472
483
 
484
+ class Meta(ClosingEntryTransactionModelAbstract.Meta):
485
+ abstract = False
486
+
473
487
 
474
488
  def closingentrymodel_presave(instance: ClosingEntryModel, **kwargs):
475
489
  instance.create_entry_ledger(commit=False)
@@ -321,5 +321,4 @@ class CustomerModel(CustomerModelAbstract):
321
321
  """
322
322
 
323
323
  class Meta(CustomerModelAbstract.Meta):
324
- swappable = 'DJANGO_LEDGER_CUSTOMER_MODEL'
325
324
  abstract = False
@@ -1176,7 +1176,6 @@ class ImportJobModel(ImportJobModelAbstract):
1176
1176
  """
1177
1177
 
1178
1178
  class Meta(ImportJobModelAbstract.Meta):
1179
- swappable = 'DJANGO_LEDGER_IMPORT_JOB_MODEL'
1180
1179
  abstract = False
1181
1180
 
1182
1181
 
@@ -1197,5 +1196,4 @@ class StagedTransactionModel(StagedTransactionModelAbstract):
1197
1196
  """
1198
1197
 
1199
1198
  class Meta(StagedTransactionModelAbstract.Meta):
1200
- swappable = 'DJANGO_LEDGER_STAGED_TRANSACTION_MODEL'
1201
1199
  abstract = False
@@ -1517,6 +1517,20 @@ class EntityModelAbstract(MP_Node,
1517
1517
  # account_model.clean()
1518
1518
  return coa_model, coa_model.create_account(**account_model_kwargs)
1519
1519
 
1520
+ def get_account_balance(self,
1521
+ account_codes: List[str],
1522
+ to_date: Union[datetime, date, str],
1523
+ **kwargs):
1524
+
1525
+ io_context = self.digest(
1526
+ entity_model=self.slug,
1527
+ accounts=account_codes,
1528
+ to_date=to_date,
1529
+ **kwargs
1530
+ )
1531
+
1532
+ return io_context
1533
+
1520
1534
  # ### LEDGER MANAGEMENT ####
1521
1535
  def get_ledgers(self, posted: Optional[bool] = None):
1522
1536
  if posted is not None:
@@ -3135,12 +3149,11 @@ class EntityModel(EntityModelAbstract):
3135
3149
  """
3136
3150
  Entity Model Base Class From Abstract
3137
3151
  """
3152
+
3138
3153
  class Meta(EntityModelAbstract.Meta):
3139
- swappable = 'DJANGO_LEDGER_ENTITY_MODEL'
3140
3154
  abstract = False
3141
3155
 
3142
3156
 
3143
-
3144
3157
  # ## ENTITY STATE....
3145
3158
  class EntityStateModelAbstract(Model):
3146
3159
  KEY_JOURNAL_ENTRY = 'je'
@@ -3204,7 +3217,6 @@ class EntityStateModel(EntityStateModelAbstract):
3204
3217
  """
3205
3218
 
3206
3219
  class Meta(EntityStateModelAbstract.Meta):
3207
- swappable = 'DJANGO_LEDGER_ENTITY_STATE_MODEL'
3208
3220
  abstract = False
3209
3221
 
3210
3222
 
@@ -1614,5 +1614,4 @@ class EstimateModel(EstimateModelAbstract):
1614
1614
  """
1615
1615
 
1616
1616
  class Meta(EstimateModelAbstract.Meta):
1617
- swappable = 'DJANGO_LEDGER_ESTIMATE_MODEL'
1618
1617
  abstract = False
@@ -1828,7 +1828,6 @@ class InvoiceModel(InvoiceModelAbstract):
1828
1828
  """
1829
1829
 
1830
1830
  class Meta(InvoiceModelAbstract.Meta):
1831
- swappable = 'DJANGO_LEDGER_INVOICE_MODEL'
1832
1831
  abstract = False
1833
1832
 
1834
1833
 
@@ -1411,7 +1411,6 @@ class UnitOfMeasureModel(UnitOfMeasureModelAbstract):
1411
1411
 
1412
1412
  class Meta(UnitOfMeasureModelAbstract.Meta):
1413
1413
  abstract = False
1414
- swappable = 'DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL'
1415
1414
 
1416
1415
 
1417
1416
  class ItemTransactionModel(ItemTransactionModelAbstract):
@@ -1421,7 +1420,6 @@ class ItemTransactionModel(ItemTransactionModelAbstract):
1421
1420
 
1422
1421
  class Meta(ItemTransactionModelAbstract.Meta):
1423
1422
  abstract = False
1424
- swappable = 'DJANGO_LEDGER_ITEM_TRANSACTION_MODEL'
1425
1423
 
1426
1424
 
1427
1425
  class ItemModel(ItemModelAbstract):
@@ -1431,4 +1429,3 @@ class ItemModel(ItemModelAbstract):
1431
1429
 
1432
1430
  class Meta(ItemModelAbstract.Meta):
1433
1431
  abstract = False
1434
- swappable = 'DJANGO_LEDGER_ITEM_MODEL'
@@ -1243,7 +1243,10 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
1243
1243
  self.activity = None
1244
1244
  else:
1245
1245
  role_list = self.get_txs_roles(txs_qs, exclude_cash_role=True)
1246
- self.activity = self.get_activity_from_roles(role_set=role_list)
1246
+ self.activity = self.get_activity_from_roles(
1247
+ role_set=role_list,
1248
+ raise_exception=raise_exception
1249
+ )
1247
1250
  return self.activity
1248
1251
 
1249
1252
  # todo: add entity_model as parameter on all functions...
@@ -1534,7 +1537,7 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
1534
1537
 
1535
1538
  if verify:
1536
1539
  txs_qs, is_verified = self.clean(verify=True)
1537
- if self.is_verified() and post_on_verify:
1540
+ if is_verified and post_on_verify:
1538
1541
  # Mark as posted if verification succeeds and posting is requested
1539
1542
  self.mark_as_posted(commit=False, verify=False, force_lock=True, raise_exception=True)
1540
1543
 
@@ -1762,7 +1765,6 @@ class JournalEntryModel(JournalEntryModelAbstract):
1762
1765
  """
1763
1766
 
1764
1767
  class Meta(JournalEntryModelAbstract.Meta):
1765
- swappable = 'DJANGO_LEDGER_JOURNAL_ENTRY_MODEL'
1766
1768
  abstract = False
1767
1769
 
1768
1770
 
@@ -767,7 +767,6 @@ class LedgerModel(LedgerModelAbstract):
767
767
  """
768
768
 
769
769
  class Meta(LedgerModelAbstract.Meta):
770
- swappable = 'DJANGO_LEDGER_LEDGER_MODEL'
771
770
  abstract = False
772
771
 
773
772
 
@@ -1234,7 +1234,6 @@ class PurchaseOrderModel(PurchaseOrderModelAbstract):
1234
1234
  """
1235
1235
 
1236
1236
  class Meta(PurchaseOrderModelAbstract.Meta):
1237
- swappable = 'DJANGO_LEDGER_PURCHASE_ORDER_MODEL'
1238
1237
  abstract = False
1239
1238
 
1240
1239
 
@@ -67,7 +67,7 @@ class TransactionModelQuerySet(QuerySet):
67
67
  Q(journal_entry__ledger__posted=True)
68
68
  )
69
69
 
70
- def for_accounts(self, account_list: List[str or AccountModel]):
70
+ def for_accounts(self, account_list: List[Union[AccountModel, str, UUID]]):
71
71
  """
72
72
  Filters transactions based on the accounts they are associated with.
73
73
 
@@ -82,9 +82,20 @@ class TransactionModelQuerySet(QuerySet):
82
82
  TransactionModelQuerySet
83
83
  A QuerySet filtered for transactions associated with the specified accounts.
84
84
  """
85
- if isinstance(account_list, list) > 0 and isinstance(account_list[0], str):
85
+
86
+ if not isinstance(account_list, list) or not len(account_list) > 0:
87
+ raise TransactionModelValidationError(
88
+ message=_('Account list must be a list of AccountModel, UUID or str objects (codes).')
89
+ )
90
+ if isinstance(account_list[0], str):
86
91
  return self.filter(account__code__in=account_list)
87
- return self.filter(account__in=account_list)
92
+ elif isinstance(account_list[0], UUID):
93
+ return self.filter(account__uuid__in=account_list)
94
+ elif isinstance(account_list[0], AccountModel):
95
+ return self.filter(account__in=account_list)
96
+ raise TransactionModelValidationError(
97
+ message=_('Account list must be a list of AccountModel, UUID or str objects (codes).')
98
+ )
88
99
 
89
100
  def for_roles(self, role_list: Union[str, List[str], Set[str]]):
90
101
  """
@@ -294,6 +305,18 @@ class TransactionModelQuerySet(QuerySet):
294
305
  timestamp=F('journal_entry__timestamp'),
295
306
  )
296
307
 
308
+ def is_cleared(self):
309
+ return self.filter(cleared=True)
310
+
311
+ def not_cleared(self):
312
+ return self.filter(cleared=False)
313
+
314
+ def is_reconciled(self):
315
+ return self.filter(reconciled=True)
316
+
317
+ def not_reconciled(self):
318
+ return self.filter(reconciled=False)
319
+
297
320
 
298
321
  class TransactionModelManager(Manager):
299
322
  """
@@ -317,6 +340,7 @@ class TransactionModelManager(Manager):
317
340
  """
318
341
  qs = TransactionModelQuerySet(self.model, using=self._db)
319
342
  return qs.annotate(
343
+ timestamp=F('journal_entry__timestamp'),
320
344
  _coa_id=F('account__coa_model_id') # Annotates the `coa_model_id` from the related `account`.
321
345
  ).select_related(
322
346
  'journal_entry', # Pre-loads the related Journal Entry.
@@ -510,7 +534,6 @@ class TransactionModel(TransactionModelAbstract):
510
534
 
511
535
  class Meta(TransactionModelAbstract.Meta):
512
536
  abstract = False
513
- swappable = 'DJANGO_LEDGER_TRANSACTION_MODEL'
514
537
 
515
538
 
516
539
  def transactionmodel_presave(instance: TransactionModel, **kwargs):
@@ -245,5 +245,4 @@ class EntityUnitModel(EntityUnitModelAbstract):
245
245
  """
246
246
 
247
247
  class Meta(EntityUnitModelAbstract.Meta):
248
- swappable = 'DJANGO_LEDGER_ENTITY_UNIT_MODEL'
249
248
  abstract = False
@@ -325,5 +325,4 @@ class VendorModel(VendorModelAbstract):
325
325
  """
326
326
 
327
327
  class Meta(VendorModelAbstract.Meta):
328
- swappable = 'DJANGO_LEDGER_VENDOR_MODEL'
329
328
  abstract = False
django_ledger/settings.py CHANGED
@@ -30,27 +30,28 @@ logger.info(f'Django Ledger GraphQL Enabled: {DJANGO_LEDGER_GRAPHQL_SUPPORT_ENAB
30
30
 
31
31
 
32
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.ChartOfAccountModel')
35
- DJANGO_LEDGER_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_TRANSACTION_MODEL', 'django_ledger.TransactionModel')
36
- DJANGO_LEDGER_JOURNAL_ENTRY_MODEL = getattr(settings, 'DJANGO_LEDGER_JOURNAL_ENTRY_MODEL', 'django_ledger.JournalEntryModel')
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.EntityModel')
39
- DJANGO_LEDGER_ENTITY_STATE_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_STATE_MODEL', 'django_ledger.EntityStateModel')
40
- DJANGO_LEDGER_ENTITY_UNIT_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_UNIT_MODEL', 'django_ledger.EntityUnitModel')
41
- DJANGO_LEDGER_ESTIMATE_MODEL = getattr(settings, 'DJANGO_LEDGER_ESTIMATE_MODEL', 'django_ledger.EstimateModel')
42
- DJANGO_LEDGER_BILL_MODEL = getattr(settings, 'DJANGO_LEDGER_BILL_MODEL', 'django_ledger.BillModel')
43
- DJANGO_LEDGER_INVOICE_MODEL = getattr(settings, 'DJANGO_LEDGER_INVOICE_MODEL', 'django_ledger.InvoiceModel')
44
- DJANGO_LEDGER_PURCHASE_ORDER_MODEL = getattr(settings, 'DJANGO_LEDGER_PURCHASE_ORDER_MODEL', 'django_ledger.PurchaseOrderModel')
45
- DJANGO_LEDGER_CUSTOMER_MODEL = getattr(settings, 'DJANGO_LEDGER_CUSTOMER_MODEL', 'django_ledger.CustomerModel')
46
- DJANGO_LEDGER_VENDOR_MODEL = getattr(settings, 'DJANGO_LEDGER_VENDOR_MODEL', 'django_ledger.VendorModel')
47
- DJANGO_LEDGER_BANK_ACCOUNT_MODEL = getattr(settings, 'DJANGO_LEDGER_BANK_ACCOUNT_MODEL', 'django_ledger.BankAccountModel')
48
- DJANGO_LEDGER_CLOSING_ENTRY_MODEL = getattr(settings, 'DJANGO_LEDGER_CLOSING_ENTRY_MODEL', 'django_ledger.ClosingEntryModel')
49
- DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL = getattr(settings, 'DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL', 'django_ledger.UnitOfMeasureModel')
50
- DJANGO_LEDGER_ITEM_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_ITEM_TRANSACTION_MODEL', 'django_ledger.ItemTransactionModel')
51
- DJANGO_LEDGER_ITEM_MODEL = getattr(settings, 'DJANGO_LEDGER_ITEM_MODEL', 'django_ledger.ItemModel')
52
- DJANGO_LEDGER_STAGED_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_STAGED_TRANSACTION_MODEL', 'django_ledger.StagedTransactionModel')
53
- DJANGO_LEDGER_IMPORT_JOB_MODEL = getattr(settings, 'DJANGO_LEDGER_IMPORT_JOB_MODEL', 'django_ledger.ImportJobModel')
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.ChartOfAccountModel')
35
+ # DJANGO_LEDGER_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_TRANSACTION_MODEL', 'django_ledger.TransactionModel')
36
+ # DJANGO_LEDGER_JOURNAL_ENTRY_MODEL = getattr(settings, 'DJANGO_LEDGER_JOURNAL_ENTRY_MODEL', 'django_ledger.JournalEntryModel')
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.EntityModel')
39
+ # DJANGO_LEDGER_ENTITY_STATE_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_STATE_MODEL', 'django_ledger.EntityStateModel')
40
+ # DJANGO_LEDGER_ENTITY_UNIT_MODEL = getattr(settings, 'DJANGO_LEDGER_ENTITY_UNIT_MODEL', 'django_ledger.EntityUnitModel')
41
+ # DJANGO_LEDGER_ESTIMATE_MODEL = getattr(settings, 'DJANGO_LEDGER_ESTIMATE_MODEL', 'django_ledger.EstimateModel')
42
+ # DJANGO_LEDGER_BILL_MODEL = getattr(settings, 'DJANGO_LEDGER_BILL_MODEL', 'django_ledger.BillModel')
43
+ # DJANGO_LEDGER_INVOICE_MODEL = getattr(settings, 'DJANGO_LEDGER_INVOICE_MODEL', 'django_ledger.InvoiceModel')
44
+ # DJANGO_LEDGER_PURCHASE_ORDER_MODEL = getattr(settings, 'DJANGO_LEDGER_PURCHASE_ORDER_MODEL', 'django_ledger.PurchaseOrderModel')
45
+ # DJANGO_LEDGER_CUSTOMER_MODEL = getattr(settings, 'DJANGO_LEDGER_CUSTOMER_MODEL', 'django_ledger.CustomerModel')
46
+ # DJANGO_LEDGER_VENDOR_MODEL = getattr(settings, 'DJANGO_LEDGER_VENDOR_MODEL', 'django_ledger.VendorModel')
47
+ # DJANGO_LEDGER_BANK_ACCOUNT_MODEL = getattr(settings, 'DJANGO_LEDGER_BANK_ACCOUNT_MODEL', 'django_ledger.BankAccountModel')
48
+ # DJANGO_LEDGER_CLOSING_ENTRY_MODEL = getattr(settings, 'DJANGO_LEDGER_CLOSING_ENTRY_MODEL', 'django_ledger.ClosingEntryModel')
49
+ # DJANGO_LEDGER_CLOSING_ENTRY_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_CLOSING_ENTRY_TRANSACTION_MODEL', 'django_ledger.ClosingEntryTransactionModel')
50
+ # DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL = getattr(settings, 'DJANGO_LEDGER_UNIT_OF_MEASURE_MODEL', 'django_ledger.UnitOfMeasureModel')
51
+ # DJANGO_LEDGER_ITEM_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_ITEM_TRANSACTION_MODEL', 'django_ledger.ItemTransactionModel')
52
+ # DJANGO_LEDGER_ITEM_MODEL = getattr(settings, 'DJANGO_LEDGER_ITEM_MODEL', 'django_ledger.ItemModel')
53
+ # DJANGO_LEDGER_STAGED_TRANSACTION_MODEL = getattr(settings, 'DJANGO_LEDGER_STAGED_TRANSACTION_MODEL', 'django_ledger.StagedTransactionModel')
54
+ # DJANGO_LEDGER_IMPORT_JOB_MODEL = getattr(settings, 'DJANGO_LEDGER_IMPORT_JOB_MODEL', 'django_ledger.ImportJobModel')
54
55
 
55
56
  DJANGO_LEDGER_USE_CLOSING_ENTRIES = getattr(settings, 'DJANGO_LEDGER_USE_CLOSING_ENTRIES', True)
56
57
  DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT = getattr(settings,
@@ -10,6 +10,10 @@
10
10
  <title>{% block page_title %}{% session_entity_name %} | {{ page_title }}{% endblock %}</title>
11
11
  <script src="{% static 'django_ledger/bundle/styles.bundle.js' %}"></script>
12
12
  <link rel="shortcut icon" type="image/jpg" href="{% static 'django_ledger/logo/favicon.png' %}">
13
+
14
+ {% block header_extra_js %}{% endblock %}
15
+ {% block header_extra_css %}{% endblock %}
16
+
13
17
  </head>
14
18
 
15
19
  <body>
@@ -50,8 +54,9 @@
50
54
  datePickers.forEach(dp => djLedger.getCalendar(dp.attributes.id.value, dateNavigationUrl))
51
55
  {% endif %}
52
56
  </script>
53
-
54
57
  {% endblock %}
55
58
 
59
+ {% block bottom_extra_js %}{% endblock %}
60
+
56
61
  </body>
57
62
  </html>
@@ -14,7 +14,7 @@ urlpatterns = [
14
14
  path('<slug:entity_slug>/list/year/<int:year>/',
15
15
  views.LedgerModelYearListView.as_view(),
16
16
  name='ledger-list-year'),
17
- path('<slug:entity_slug>/list/month/<int:year>/<int:month>',
17
+ path('<slug:entity_slug>/list/month/<int:year>/<int:month>/',
18
18
  views.LedgerModelMonthListView.as_view(),
19
19
  name='ledger-list-month'),
20
20
  path('<slug:entity_slug>/create/',