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.
- django_ledger/__init__.py +1 -1
- django_ledger/forms/closing_entry.py +2 -1
- django_ledger/forms/journal_entry.py +1 -0
- django_ledger/io/io_context.py +10 -0
- django_ledger/io/io_core.py +22 -0
- django_ledger/migrations/0021_alter_bankaccountmodel_account_model_and_more.py +2 -3
- django_ledger/models/accounts.py +40 -1
- django_ledger/models/bank_account.py +0 -1
- django_ledger/models/bill.py +0 -1
- django_ledger/models/chart_of_accounts.py +0 -1
- django_ledger/models/closing_entry.py +27 -13
- django_ledger/models/customer.py +0 -1
- django_ledger/models/data_import.py +0 -2
- django_ledger/models/entity.py +15 -3
- django_ledger/models/estimate.py +0 -1
- django_ledger/models/invoice.py +0 -1
- django_ledger/models/items.py +0 -3
- django_ledger/models/journal_entry.py +5 -3
- django_ledger/models/ledger.py +0 -1
- django_ledger/models/purchase_order.py +0 -1
- django_ledger/models/transactions.py +27 -4
- django_ledger/models/unit.py +0 -1
- django_ledger/models/vendor.py +0 -1
- django_ledger/settings.py +22 -21
- django_ledger/templates/django_ledger/layouts/base.html +6 -1
- django_ledger/urls/ledger.py +1 -1
- django_ledger/views/closing_entry.py +47 -40
- django_ledger/views/mixins.py +6 -0
- django_ledger/views/unit.py +11 -14
- django_ledger/views/vendor.py +10 -8
- {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/METADATA +11 -32
- {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/RECORD +36 -39
- {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/WHEEL +1 -1
- {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/top_level.txt +0 -1
- django_ledger/static/.DS_Store +0 -0
- django_ledger/static/django_ledger/.DS_Store +0 -0
- django_ledger/static/django_ledger/logo_2/.DS_Store +0 -0
- {django_ledger-0.7.5.1.dist-info → django_ledger-0.7.6.dist-info}/AUTHORS.md +0 -0
- {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.
|
|
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 = {
|
django_ledger/io/io_context.py
CHANGED
|
@@ -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
|
django_ledger/io/io_core.py
CHANGED
|
@@ -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=
|
|
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=
|
|
28
|
+
to='django_ledger.BankAccountModel',
|
|
30
29
|
verbose_name='Associated Bank Account Model'
|
|
31
30
|
),
|
|
32
31
|
),
|
django_ledger/models/accounts.py
CHANGED
|
@@ -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
|
|
django_ledger/models/bill.py
CHANGED
|
@@ -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
|
-
|
|
122
|
-
|
|
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 = {
|
|
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=
|
|
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 =
|
|
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 =
|
|
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)
|
django_ledger/models/customer.py
CHANGED
|
@@ -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
|
django_ledger/models/entity.py
CHANGED
|
@@ -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
|
|
django_ledger/models/estimate.py
CHANGED
django_ledger/models/invoice.py
CHANGED
django_ledger/models/items.py
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
django_ledger/models/ledger.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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):
|
django_ledger/models/unit.py
CHANGED
django_ledger/models/vendor.py
CHANGED
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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>
|
django_ledger/urls/ledger.py
CHANGED
|
@@ -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/',
|