django-ledger 0.5.5.4__py3-none-any.whl → 0.5.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -1
- django_ledger/admin/__init__.py +10 -0
- django_ledger/admin/coa.py +135 -0
- django_ledger/admin/entity.py +199 -0
- django_ledger/admin/ledger.py +283 -0
- django_ledger/forms/entity.py +4 -12
- django_ledger/forms/ledger.py +19 -0
- django_ledger/forms/transactions.py +1 -1
- django_ledger/io/__init__.py +4 -1
- django_ledger/io/{io_mixin.py → io_core.py} +95 -35
- django_ledger/io/io_digest.py +15 -0
- django_ledger/io/{data_generator.py → io_generator.py} +51 -8
- django_ledger/io/io_library.py +317 -0
- django_ledger/io/{io_context.py → io_middleware.py} +16 -9
- django_ledger/migrations/0001_initial.py +413 -132
- django_ledger/migrations/0014_ledgermodel_ledger_xid_and_more.py +22 -0
- django_ledger/models/accounts.py +68 -7
- django_ledger/models/bank_account.py +12 -11
- django_ledger/models/bill.py +5 -9
- django_ledger/models/closing_entry.py +14 -14
- django_ledger/models/coa.py +60 -18
- django_ledger/models/customer.py +5 -11
- django_ledger/models/data_import.py +12 -6
- django_ledger/models/entity.py +90 -12
- django_ledger/models/estimate.py +12 -9
- django_ledger/models/invoice.py +10 -4
- django_ledger/models/items.py +11 -6
- django_ledger/models/journal_entry.py +61 -18
- django_ledger/models/ledger.py +90 -24
- django_ledger/models/mixins.py +2 -3
- django_ledger/models/purchase_order.py +11 -7
- django_ledger/models/transactions.py +3 -1
- django_ledger/models/unit.py +22 -13
- django_ledger/models/vendor.py +12 -11
- django_ledger/report/cash_flow_statement.py +1 -1
- django_ledger/report/core.py +3 -2
- django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +1 -1
- django_ledger/templates/django_ledger/journal_entry/je_list.html +3 -0
- django_ledger/templatetags/django_ledger.py +1 -1
- django_ledger/tests/base.py +1 -1
- django_ledger/tests/test_entity.py +1 -1
- django_ledger/urls/ledger.py +3 -0
- django_ledger/views/entity.py +9 -3
- django_ledger/views/ledger.py +14 -7
- django_ledger/views/mixins.py +9 -1
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/METADATA +9 -9
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/RECORD +51 -46
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/top_level.txt +0 -1
- django_ledger/admin.py +0 -160
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/WHEEL +0 -0
django_ledger/models/estimate.py
CHANGED
|
@@ -111,6 +111,15 @@ class EstimateModelManager(models.Manager):
|
|
|
111
111
|
A custom defined EstimateModelManager that that implements custom QuerySet methods related to the EstimateModel.
|
|
112
112
|
"""
|
|
113
113
|
|
|
114
|
+
def for_user(self, user_model):
|
|
115
|
+
qs = self.get_queryset()
|
|
116
|
+
if user_model.is_superuser:
|
|
117
|
+
return qs
|
|
118
|
+
return qs.filter(
|
|
119
|
+
Q(entity__admin=user_model) |
|
|
120
|
+
Q(entity__managers__in=[user_model])
|
|
121
|
+
)
|
|
122
|
+
|
|
114
123
|
def for_entity(self, entity_slug: Union[EntityModel, str], user_model):
|
|
115
124
|
"""
|
|
116
125
|
Fetches a QuerySet of EstimateModels associated with a specific EntityModel & UserModel.
|
|
@@ -134,19 +143,13 @@ class EstimateModelManager(models.Manager):
|
|
|
134
143
|
EstimateModelQuerySet
|
|
135
144
|
Returns a EstimateModelQuerySet with applied filters.
|
|
136
145
|
"""
|
|
137
|
-
qs = self.
|
|
146
|
+
qs = self.for_user(user_model)
|
|
138
147
|
if isinstance(entity_slug, EntityModel):
|
|
139
148
|
return qs.filter(
|
|
140
|
-
Q(entity=entity_slug)
|
|
141
|
-
Q(entity__admin=user_model) |
|
|
142
|
-
Q(entity__managers__in=[user_model])
|
|
143
|
-
)
|
|
149
|
+
Q(entity=entity_slug)
|
|
144
150
|
)
|
|
145
151
|
return qs.filter(
|
|
146
|
-
Q(entity__slug__exact=entity_slug)
|
|
147
|
-
Q(entity__admin=user_model) |
|
|
148
|
-
Q(entity__managers__in=[user_model])
|
|
149
|
-
)
|
|
152
|
+
Q(entity__slug__exact=entity_slug)
|
|
150
153
|
)
|
|
151
154
|
|
|
152
155
|
|
django_ledger/models/invoice.py
CHANGED
|
@@ -179,6 +179,15 @@ class InvoiceModelManager(models.Manager):
|
|
|
179
179
|
'ledger__entity'
|
|
180
180
|
)
|
|
181
181
|
|
|
182
|
+
def for_user(self, user_model):
|
|
183
|
+
qs = self.get_queryset()
|
|
184
|
+
if user_model.is_superuser:
|
|
185
|
+
return qs
|
|
186
|
+
return qs.filter(
|
|
187
|
+
Q(ledger__entity__admin=user_model) |
|
|
188
|
+
Q(ledger__entity__managers__in=[user_model])
|
|
189
|
+
)
|
|
190
|
+
|
|
182
191
|
def for_entity(self, entity_slug, user_model) -> InvoiceModelQuerySet:
|
|
183
192
|
"""
|
|
184
193
|
Returns a QuerySet of InvoiceModels associated with a specific EntityModel & UserModel.
|
|
@@ -196,10 +205,7 @@ class InvoiceModelManager(models.Manager):
|
|
|
196
205
|
InvoiceModelQuerySet
|
|
197
206
|
A Filtered InvoiceModelQuerySet.
|
|
198
207
|
"""
|
|
199
|
-
qs = self.
|
|
200
|
-
Q(ledger__entity__admin=user_model) |
|
|
201
|
-
Q(ledger__entity__managers__in=[user_model])
|
|
202
|
-
)
|
|
208
|
+
qs = self.for_user(user_model)
|
|
203
209
|
if isinstance(entity_slug, EntityModel):
|
|
204
210
|
return qs.filter(ledger__entity=entity_slug)
|
|
205
211
|
elif isinstance(entity_slug, str):
|
django_ledger/models/items.py
CHANGED
|
@@ -879,14 +879,19 @@ class ItemTransactionModelQuerySet(models.QuerySet):
|
|
|
879
879
|
|
|
880
880
|
class ItemTransactionModelManager(models.Manager):
|
|
881
881
|
|
|
882
|
-
def
|
|
882
|
+
def for_user(self, user_model):
|
|
883
883
|
qs = self.get_queryset()
|
|
884
|
+
if user_model.is_superuser:
|
|
885
|
+
return qs
|
|
884
886
|
return qs.filter(
|
|
885
|
-
Q(
|
|
886
|
-
(
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
887
|
+
Q(item_model__entity__admin=user_model) |
|
|
888
|
+
Q(item_model__entity__managers__in=[user_model])
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def for_entity(self, user_model, entity_slug):
|
|
892
|
+
qs = self.for_user(user_model)
|
|
893
|
+
return qs.filter(
|
|
894
|
+
Q(item_model__entity__slug__exact=entity_slug)
|
|
890
895
|
)
|
|
891
896
|
|
|
892
897
|
def for_bill(self, user_model, entity_slug, bill_pk):
|
|
@@ -138,6 +138,15 @@ class JournalEntryModelManager(models.Manager):
|
|
|
138
138
|
EntityModel and authenticated UserModel.
|
|
139
139
|
"""
|
|
140
140
|
|
|
141
|
+
def for_user(self, user_model):
|
|
142
|
+
qs = self.get_queryset()
|
|
143
|
+
if user_model.is_superuser:
|
|
144
|
+
return qs
|
|
145
|
+
return qs.filter(
|
|
146
|
+
Q(ledger__entity__admin=user_model) |
|
|
147
|
+
Q(ledger__entity__managers__in=[user_model])
|
|
148
|
+
)
|
|
149
|
+
|
|
141
150
|
def for_entity(self, entity_slug, user_model):
|
|
142
151
|
"""
|
|
143
152
|
Fetches a QuerySet of JournalEntryModels associated with a specific EntityModel & UserModel.
|
|
@@ -161,22 +170,13 @@ class JournalEntryModelManager(models.Manager):
|
|
|
161
170
|
JournalEntryModelQuerySet
|
|
162
171
|
Returns a JournalEntryModelQuerySet with applied filters.
|
|
163
172
|
"""
|
|
173
|
+
qs = self.for_user(user_model)
|
|
164
174
|
if isinstance(entity_slug, lazy_loader.get_entity_model()):
|
|
165
|
-
return
|
|
166
|
-
Q(ledger__entity=entity_slug)
|
|
167
|
-
(
|
|
168
|
-
Q(ledger__entity__admin=user_model) |
|
|
169
|
-
Q(ledger__entity__managers__in=[user_model])
|
|
170
|
-
)
|
|
171
|
-
|
|
175
|
+
return qs.filter(
|
|
176
|
+
Q(ledger__entity=entity_slug)
|
|
172
177
|
)
|
|
173
178
|
return self.get_queryset().filter(
|
|
174
|
-
Q(ledger__entity__slug__iexact=entity_slug)
|
|
175
|
-
(
|
|
176
|
-
Q(ledger__entity__admin=user_model) |
|
|
177
|
-
Q(ledger__entity__managers__in=[user_model])
|
|
178
|
-
)
|
|
179
|
-
|
|
179
|
+
Q(ledger__entity__slug__iexact=entity_slug)
|
|
180
180
|
)
|
|
181
181
|
|
|
182
182
|
def for_ledger(self, ledger_pk: Union[str, UUID], entity_slug, user_model):
|
|
@@ -345,8 +345,18 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
345
345
|
|
|
346
346
|
def __str__(self):
|
|
347
347
|
if self.je_number:
|
|
348
|
-
return 'JE: {x1} - Desc: {x2}'.format(
|
|
349
|
-
|
|
348
|
+
return 'JE: {x1} (posted={p}, locked={l}) - Desc: {x2}'.format(
|
|
349
|
+
x1=self.je_number,
|
|
350
|
+
x2=self.description,
|
|
351
|
+
p=self.posted,
|
|
352
|
+
l=self.locked
|
|
353
|
+
)
|
|
354
|
+
return 'JE ID: {x1} (posted={p}, locked={l}) - Desc: {x2}'.format(
|
|
355
|
+
x1=self.pk,
|
|
356
|
+
x2=self.description,
|
|
357
|
+
p=self.posted,
|
|
358
|
+
l=self.locked
|
|
359
|
+
)
|
|
350
360
|
|
|
351
361
|
def __init__(self, *args, **kwargs):
|
|
352
362
|
super().__init__(*args, **kwargs)
|
|
@@ -1011,7 +1021,7 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
1011
1021
|
"""
|
|
1012
1022
|
if self.can_generate_je_number():
|
|
1013
1023
|
|
|
1014
|
-
with transaction.atomic(
|
|
1024
|
+
with transaction.atomic():
|
|
1015
1025
|
|
|
1016
1026
|
state_model = None
|
|
1017
1027
|
while not state_model:
|
|
@@ -1198,12 +1208,45 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
|
|
|
1198
1208
|
|
|
1199
1209
|
return reverse('django_ledger:je-detail',
|
|
1200
1210
|
kwargs={
|
|
1201
|
-
'je_pk': self.
|
|
1211
|
+
'je_pk': self.uuid,
|
|
1202
1212
|
'ledger_pk': self.ledger_id,
|
|
1203
|
-
# pylint: disable=no-member
|
|
1204
1213
|
'entity_slug': self.ledger.entity.slug
|
|
1205
1214
|
})
|
|
1206
1215
|
|
|
1216
|
+
def get_detail_url(self) -> str:
|
|
1217
|
+
"""
|
|
1218
|
+
Determines the update URL of the LedgerModel instance.
|
|
1219
|
+
Results in additional Database query if entity field is not selected in QuerySet.
|
|
1220
|
+
|
|
1221
|
+
Returns
|
|
1222
|
+
-------
|
|
1223
|
+
str
|
|
1224
|
+
URL as a string.
|
|
1225
|
+
"""
|
|
1226
|
+
return reverse('django_ledger:je-detail',
|
|
1227
|
+
kwargs={
|
|
1228
|
+
'entity_slug': self.ledger.entity.slug,
|
|
1229
|
+
'ledger_pk': self.ledger_id,
|
|
1230
|
+
'je_pk': self.uuid
|
|
1231
|
+
})
|
|
1232
|
+
|
|
1233
|
+
def get_detail_txs_url(self) -> str:
|
|
1234
|
+
"""
|
|
1235
|
+
Determines the update URL of the LedgerModel instance.
|
|
1236
|
+
Results in additional Database query if entity field is not selected in QuerySet.
|
|
1237
|
+
|
|
1238
|
+
Returns
|
|
1239
|
+
-------
|
|
1240
|
+
str
|
|
1241
|
+
URL as a string.
|
|
1242
|
+
"""
|
|
1243
|
+
return reverse('django_ledger:je-detail-txs',
|
|
1244
|
+
kwargs={
|
|
1245
|
+
'entity_slug': self.ledger.entity.slug,
|
|
1246
|
+
'ledger_pk': self.ledger_id,
|
|
1247
|
+
'je_pk': self.uuid
|
|
1248
|
+
})
|
|
1249
|
+
|
|
1207
1250
|
def get_unlock_url(self):
|
|
1208
1251
|
return reverse('django_ledger:je-mark-as-unlocked',
|
|
1209
1252
|
kwargs={
|
django_ledger/models/ledger.py
CHANGED
|
@@ -38,7 +38,7 @@ from django.db.models import Q, Min, F, Count
|
|
|
38
38
|
from django.urls import reverse
|
|
39
39
|
from django.utils.translation import gettext_lazy as _
|
|
40
40
|
|
|
41
|
-
from django_ledger.io import IOMixIn
|
|
41
|
+
from django_ledger.io.io_core import IOMixIn
|
|
42
42
|
from django_ledger.models import lazy_loader
|
|
43
43
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
44
44
|
|
|
@@ -54,6 +54,28 @@ class LedgerModelQuerySet(models.QuerySet):
|
|
|
54
54
|
Custom defined LedgerModel QuerySet.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
+
def locked(self):
|
|
58
|
+
"""
|
|
59
|
+
Filters the QuerySet to only locked LedgerModel.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
LedgerModelQuerySet
|
|
64
|
+
A QuerySet with applied filters.
|
|
65
|
+
"""
|
|
66
|
+
return self.filter(locked=True)
|
|
67
|
+
|
|
68
|
+
def unlocked(self):
|
|
69
|
+
"""
|
|
70
|
+
Filters the QuerySet to only un-locked LedgerModel.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
LedgerModelQuerySet
|
|
75
|
+
A QuerySet with applied filters.
|
|
76
|
+
"""
|
|
77
|
+
return self.filter(locked=False)
|
|
78
|
+
|
|
57
79
|
def posted(self):
|
|
58
80
|
"""
|
|
59
81
|
Filters the QuerySet to only posted LedgerModel.
|
|
@@ -65,6 +87,17 @@ class LedgerModelQuerySet(models.QuerySet):
|
|
|
65
87
|
"""
|
|
66
88
|
return self.filter(posted=True)
|
|
67
89
|
|
|
90
|
+
def unposted(self):
|
|
91
|
+
"""
|
|
92
|
+
Filters the QuerySet to only un-posted LedgerModel.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
LedgerModelQuerySet
|
|
97
|
+
A QuerySet with applied filters.
|
|
98
|
+
"""
|
|
99
|
+
return self.filter(posted=True)
|
|
100
|
+
|
|
68
101
|
def hidden(self):
|
|
69
102
|
return self.filter(hidden=True)
|
|
70
103
|
|
|
@@ -73,7 +106,8 @@ class LedgerModelQuerySet(models.QuerySet):
|
|
|
73
106
|
|
|
74
107
|
def current(self):
|
|
75
108
|
return self.filter(
|
|
76
|
-
earliest_timestamp__gt=F('entity__last_closing_date')
|
|
109
|
+
Q(earliest_timestamp__gt=F('entity__last_closing_date'))
|
|
110
|
+
| Q(earliest_timestamp__isnull=True)
|
|
77
111
|
)
|
|
78
112
|
|
|
79
113
|
|
|
@@ -90,6 +124,15 @@ class LedgerModelManager(models.Manager):
|
|
|
90
124
|
filter=Q(journal_entries__posted=True)),
|
|
91
125
|
)
|
|
92
126
|
|
|
127
|
+
def for_user(self, user_model):
|
|
128
|
+
qs = self.get_queryset()
|
|
129
|
+
if user_model.is_superuser:
|
|
130
|
+
return qs
|
|
131
|
+
return qs.filter(
|
|
132
|
+
Q(entity__admin=user_model) |
|
|
133
|
+
Q(entity__managers__in=[user_model])
|
|
134
|
+
)
|
|
135
|
+
|
|
93
136
|
def for_entity(self, entity_slug, user_model):
|
|
94
137
|
"""
|
|
95
138
|
Returns a QuerySet of LedgerModels associated with a specific EntityModel & UserModel.
|
|
@@ -107,21 +150,13 @@ class LedgerModelManager(models.Manager):
|
|
|
107
150
|
LedgerModelQuerySet
|
|
108
151
|
A Filtered LedgerModelQuerySet.
|
|
109
152
|
"""
|
|
110
|
-
qs = self.
|
|
153
|
+
qs = self.for_user(user_model)
|
|
111
154
|
if isinstance(entity_slug, lazy_loader.get_entity_model()):
|
|
112
155
|
return qs.filter(
|
|
113
|
-
Q(entity=entity_slug)
|
|
114
|
-
(
|
|
115
|
-
Q(entity__admin=user_model) |
|
|
116
|
-
Q(entity__managers__in=[user_model])
|
|
117
|
-
)
|
|
156
|
+
Q(entity=entity_slug)
|
|
118
157
|
)
|
|
119
158
|
return qs.filter(
|
|
120
|
-
Q(entity__slug__exact=entity_slug)
|
|
121
|
-
(
|
|
122
|
-
Q(entity__admin=user_model) |
|
|
123
|
-
Q(entity__managers__in=[user_model])
|
|
124
|
-
)
|
|
159
|
+
Q(entity__slug__exact=entity_slug)
|
|
125
160
|
)
|
|
126
161
|
|
|
127
162
|
|
|
@@ -135,6 +170,8 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
135
170
|
This is a unique primary key generated for the table. The default value of this field is uuid4().
|
|
136
171
|
name: str
|
|
137
172
|
Human-readable name of the LedgerModel. Maximum 150 characters.
|
|
173
|
+
ledger_xid: str
|
|
174
|
+
A unique user-defined identifier for the LedgerModel. Unique for the Entity Model.
|
|
138
175
|
entity: EntityModel
|
|
139
176
|
The EntityModel associated with the LedgerModel instance.
|
|
140
177
|
posted: bool
|
|
@@ -146,6 +183,9 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
146
183
|
"""
|
|
147
184
|
_WRAPPED_MODEL_KEY = 'wrapped_model'
|
|
148
185
|
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
|
186
|
+
ledger_xid = models.SlugField(allow_unicode=True, max_length=150, null=True, blank=True,
|
|
187
|
+
verbose_name=_('Ledger Slug'),
|
|
188
|
+
help_text=_('User Defined Ledger ID'))
|
|
149
189
|
name = models.CharField(max_length=150, null=True, blank=True, verbose_name=_('Ledger Name'))
|
|
150
190
|
|
|
151
191
|
# todo: rename to entity_model...
|
|
@@ -173,6 +213,9 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
173
213
|
models.Index(fields=['entity', 'posted']),
|
|
174
214
|
models.Index(fields=['entity', 'locked']),
|
|
175
215
|
]
|
|
216
|
+
unique_together = [
|
|
217
|
+
('entity', 'ledger_xid')
|
|
218
|
+
]
|
|
176
219
|
|
|
177
220
|
def __str__(self):
|
|
178
221
|
return self.name
|
|
@@ -192,6 +235,7 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
192
235
|
return getattr(self, model_id)
|
|
193
236
|
except ObjectDoesNotExist:
|
|
194
237
|
pass
|
|
238
|
+
return False
|
|
195
239
|
|
|
196
240
|
def remove_wrapped_model_info(self):
|
|
197
241
|
if self.has_wrapped_model_info():
|
|
@@ -375,7 +419,7 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
375
419
|
if not self.can_post():
|
|
376
420
|
if raise_exception:
|
|
377
421
|
raise LedgerModelValidationError(
|
|
378
|
-
message=_(f'Ledger {self.
|
|
422
|
+
message=_(f'Ledger {self.name} cannot be posted. UUID: {self.uuid}')
|
|
379
423
|
)
|
|
380
424
|
return
|
|
381
425
|
self.posted = True
|
|
@@ -417,7 +461,7 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
417
461
|
'updated'
|
|
418
462
|
])
|
|
419
463
|
|
|
420
|
-
def lock(self, commit: bool = False, **kwargs):
|
|
464
|
+
def lock(self, commit: bool = False, raise_exception: bool = True, **kwargs):
|
|
421
465
|
"""
|
|
422
466
|
Locks the LedgerModel.
|
|
423
467
|
|
|
@@ -425,14 +469,22 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
425
469
|
----------
|
|
426
470
|
commit: bool
|
|
427
471
|
If True, saves the LedgerModel instance instantly. Defaults to False.
|
|
472
|
+
raise_exception: bool
|
|
473
|
+
Raises LedgerModelValidationError if locking not allowed.
|
|
428
474
|
"""
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if
|
|
432
|
-
|
|
433
|
-
'locked'
|
|
434
|
-
|
|
435
|
-
|
|
475
|
+
|
|
476
|
+
if not self.can_lock():
|
|
477
|
+
if raise_exception:
|
|
478
|
+
raise LedgerModelValidationError(
|
|
479
|
+
message=_(f'Ledger {self.name} cannot be locked. UUID: {self.uuid}')
|
|
480
|
+
)
|
|
481
|
+
return
|
|
482
|
+
self.locked = True
|
|
483
|
+
if commit:
|
|
484
|
+
self.save(update_fields=[
|
|
485
|
+
'locked',
|
|
486
|
+
'updated'
|
|
487
|
+
])
|
|
436
488
|
|
|
437
489
|
def lock_journal_entries(self, commit: bool = True, **kwargs):
|
|
438
490
|
je_model_qs = self.journal_entries.unlocked()
|
|
@@ -493,9 +545,8 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
493
545
|
str
|
|
494
546
|
URL as a string.
|
|
495
547
|
"""
|
|
496
|
-
return reverse('django_ledger:
|
|
548
|
+
return reverse(viewname='django_ledger:je-list',
|
|
497
549
|
kwargs={
|
|
498
|
-
# pylint: disable=no-member
|
|
499
550
|
'entity_slug': self.entity.slug,
|
|
500
551
|
'ledger_pk': self.uuid
|
|
501
552
|
})
|
|
@@ -531,6 +582,21 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
531
582
|
'ledger_pk': self.uuid
|
|
532
583
|
})
|
|
533
584
|
|
|
585
|
+
def get_list_url(self) -> str:
|
|
586
|
+
"""
|
|
587
|
+
Determines the list URL of the LedgerModel instances.
|
|
588
|
+
Results in additional Database query if entity field is not selected in QuerySet.
|
|
589
|
+
|
|
590
|
+
Returns
|
|
591
|
+
-------
|
|
592
|
+
str
|
|
593
|
+
URL as a string.
|
|
594
|
+
"""
|
|
595
|
+
return reverse('django_ledger:ledger-list',
|
|
596
|
+
kwargs={
|
|
597
|
+
'entity_slug': self.entity.slug
|
|
598
|
+
})
|
|
599
|
+
|
|
534
600
|
def get_balance_sheet_url(self):
|
|
535
601
|
return reverse(
|
|
536
602
|
viewname='django_ledger:ledger-bs',
|
django_ledger/models/mixins.py
CHANGED
|
@@ -13,7 +13,7 @@ from collections import defaultdict
|
|
|
13
13
|
from datetime import timedelta, date, datetime
|
|
14
14
|
from decimal import Decimal
|
|
15
15
|
from itertools import groupby
|
|
16
|
-
from typing import Optional, Union, Dict
|
|
16
|
+
from typing import Optional, Union, Dict
|
|
17
17
|
from uuid import UUID
|
|
18
18
|
|
|
19
19
|
from django.conf import settings
|
|
@@ -27,8 +27,7 @@ from django.utils.timezone import localdate, localtime
|
|
|
27
27
|
from django.utils.translation import gettext_lazy as _
|
|
28
28
|
from markdown import markdown
|
|
29
29
|
|
|
30
|
-
from django_ledger.io import
|
|
31
|
-
validate_io_date)
|
|
30
|
+
from django_ledger.io.io_core import validate_io_date, check_tx_balance
|
|
32
31
|
from django_ledger.models.utils import lazy_loader
|
|
33
32
|
|
|
34
33
|
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
|
|
@@ -101,6 +101,15 @@ class PurchaseOrderModelManager(models.Manager):
|
|
|
101
101
|
A custom defined PurchaseOrderModel Manager.
|
|
102
102
|
"""
|
|
103
103
|
|
|
104
|
+
def for_user(self, user_model):
|
|
105
|
+
qs = self.get_queryset()
|
|
106
|
+
if user_model.is_superuser:
|
|
107
|
+
return qs
|
|
108
|
+
return qs.filter(
|
|
109
|
+
Q(entity__admin=user_model) |
|
|
110
|
+
Q(entity__managers__in=[user_model])
|
|
111
|
+
)
|
|
112
|
+
|
|
104
113
|
def for_entity(self, entity_slug, user_model) -> PurchaseOrderModelQuerySet:
|
|
105
114
|
"""
|
|
106
115
|
Fetches a QuerySet of PurchaseOrderModel associated with a specific EntityModel & UserModel.
|
|
@@ -111,15 +120,10 @@ class PurchaseOrderModelManager(models.Manager):
|
|
|
111
120
|
PurchaseOrderModelQuerySet
|
|
112
121
|
A PurchaseOrderModelQuerySet with applied filters.
|
|
113
122
|
"""
|
|
114
|
-
qs = self.
|
|
123
|
+
qs = self.for_user(user_model)
|
|
115
124
|
if isinstance(entity_slug, EntityModel):
|
|
116
125
|
qs = qs.filter(entity=entity_slug)
|
|
117
|
-
|
|
118
|
-
qs = qs.filter(entity__slug__exact=entity_slug)
|
|
119
|
-
return qs.filter(
|
|
120
|
-
Q(entity__admin=user_model) |
|
|
121
|
-
Q(entity__managers__in=[user_model])
|
|
122
|
-
)
|
|
126
|
+
return qs.filter(entity__slug__exact=entity_slug)
|
|
123
127
|
|
|
124
128
|
|
|
125
129
|
class PurchaseOrderModelAbstract(CreateUpdateMixIn,
|
|
@@ -26,7 +26,7 @@ from django.db import models
|
|
|
26
26
|
from django.db.models import Q, QuerySet
|
|
27
27
|
from django.utils.translation import gettext_lazy as _
|
|
28
28
|
|
|
29
|
-
from django_ledger.io import validate_io_date
|
|
29
|
+
from django_ledger.io.io_core import validate_io_date
|
|
30
30
|
from django_ledger.models.accounts import AccountModel
|
|
31
31
|
from django_ledger.models.bill import BillModel
|
|
32
32
|
from django_ledger.models.entity import EntityModel
|
|
@@ -220,6 +220,8 @@ class TransactionModelAdmin(models.Manager):
|
|
|
220
220
|
Returns a TransactionModelQuerySet with applied filters.
|
|
221
221
|
"""
|
|
222
222
|
qs = self.get_queryset()
|
|
223
|
+
if user_model.is_superuser:
|
|
224
|
+
return qs
|
|
223
225
|
return qs.filter(
|
|
224
226
|
Q(journal_entry__ledger__entity__admin=user_model) |
|
|
225
227
|
Q(journal_entry__ledger__entity__managers__in=[user_model])
|
django_ledger/models/unit.py
CHANGED
|
@@ -34,7 +34,7 @@ from django.utils.text import slugify
|
|
|
34
34
|
from django.utils.translation import gettext_lazy as _
|
|
35
35
|
from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
|
|
36
36
|
|
|
37
|
-
from django_ledger.io.
|
|
37
|
+
from django_ledger.io.io_core import IOMixIn
|
|
38
38
|
from django_ledger.models import lazy_loader
|
|
39
39
|
from django_ledger.models.mixins import CreateUpdateMixIn, SlugNameMixIn
|
|
40
40
|
|
|
@@ -53,6 +53,15 @@ class EntityUnitModelQuerySet(MP_NodeQuerySet):
|
|
|
53
53
|
|
|
54
54
|
class EntityUnitModelManager(MP_NodeManager):
|
|
55
55
|
|
|
56
|
+
def for_user(self, user_model):
|
|
57
|
+
qs = self.get_queryset()
|
|
58
|
+
if user_model.is_superuser:
|
|
59
|
+
return qs
|
|
60
|
+
return qs.filter(
|
|
61
|
+
Q(entity__admin=user_model) |
|
|
62
|
+
Q(entity__managers__in=[user_model])
|
|
63
|
+
)
|
|
64
|
+
|
|
56
65
|
def for_entity(self, entity_slug: str, user_model):
|
|
57
66
|
"""
|
|
58
67
|
Fetches a QuerySet of EntityUnitModels associated with a specific EntityModel & UserModel.
|
|
@@ -76,23 +85,14 @@ class EntityUnitModelManager(MP_NodeManager):
|
|
|
76
85
|
EntityUnitModelQuerySet
|
|
77
86
|
Returns a EntityUnitModelQuerySet with applied filters.
|
|
78
87
|
"""
|
|
79
|
-
qs = self.
|
|
88
|
+
qs = self.for_user(user_model)
|
|
80
89
|
if isinstance(entity_slug, lazy_loader.get_entity_model()):
|
|
81
90
|
return qs.filter(
|
|
82
|
-
Q(entity=entity_slug)
|
|
83
|
-
(
|
|
84
|
-
Q(entity__admin=user_model) |
|
|
85
|
-
Q(entity__managers__in=[user_model])
|
|
86
|
-
)
|
|
91
|
+
Q(entity=entity_slug)
|
|
87
92
|
|
|
88
93
|
)
|
|
89
94
|
return qs.filter(
|
|
90
|
-
Q(entity__slug__exact=entity_slug)
|
|
91
|
-
(
|
|
92
|
-
Q(entity__admin=user_model) |
|
|
93
|
-
Q(entity__managers__in=[user_model])
|
|
94
|
-
)
|
|
95
|
-
|
|
95
|
+
Q(entity__slug__exact=entity_slug)
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
|
|
@@ -212,6 +212,15 @@ class EntityUnitModelAbstract(MP_Node,
|
|
|
212
212
|
self.slug = unit_slug
|
|
213
213
|
return self.slug
|
|
214
214
|
|
|
215
|
+
def get_absolute_url(self):
|
|
216
|
+
return reverse(
|
|
217
|
+
viewname='django_ledger:unit-detail',
|
|
218
|
+
kwargs={
|
|
219
|
+
'entity_slug': self.entity.slug,
|
|
220
|
+
'unit_slug': self.slug
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
215
224
|
|
|
216
225
|
class EntityUnitModel(EntityUnitModelAbstract):
|
|
217
226
|
"""
|
django_ledger/models/vendor.py
CHANGED
|
@@ -91,6 +91,15 @@ class VendorModelManager(models.Manager):
|
|
|
91
91
|
Custom defined VendorModel Manager, which defines many methods for initial query of the Database.
|
|
92
92
|
"""
|
|
93
93
|
|
|
94
|
+
def for_user(self, user_model):
|
|
95
|
+
qs = self.get_queryset()
|
|
96
|
+
if user_model.is_superuser:
|
|
97
|
+
return qs
|
|
98
|
+
return qs.filter(
|
|
99
|
+
Q(entity_model__admin=user_model) |
|
|
100
|
+
Q(entity_model__managers__in=[user_model])
|
|
101
|
+
)
|
|
102
|
+
|
|
94
103
|
def for_entity(self, entity_slug, user_model) -> VendorModelQuerySet:
|
|
95
104
|
"""
|
|
96
105
|
Fetches a QuerySet of VendorModel associated with a specific EntityModel & UserModel.
|
|
@@ -114,21 +123,13 @@ class VendorModelManager(models.Manager):
|
|
|
114
123
|
VendorModelQuerySet
|
|
115
124
|
A filtered VendorModel QuerySet.
|
|
116
125
|
"""
|
|
117
|
-
qs = self.
|
|
126
|
+
qs = self.for_user(user_model)
|
|
118
127
|
if isinstance(entity_slug, lazy_loader.get_entity_model()):
|
|
119
128
|
return qs.filter(
|
|
120
|
-
Q(entity_model=entity_slug)
|
|
121
|
-
(
|
|
122
|
-
Q(entity_model__admin=user_model) |
|
|
123
|
-
Q(entity_model__managers__in=[user_model])
|
|
124
|
-
)
|
|
129
|
+
Q(entity_model=entity_slug)
|
|
125
130
|
)
|
|
126
131
|
return qs.filter(
|
|
127
|
-
Q(entity_model__slug__exact=entity_slug)
|
|
128
|
-
(
|
|
129
|
-
Q(entity_model__admin=user_model) |
|
|
130
|
-
Q(entity_model__managers__in=[user_model])
|
|
131
|
-
)
|
|
132
|
+
Q(entity_model__slug__exact=entity_slug)
|
|
132
133
|
)
|
|
133
134
|
|
|
134
135
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime, date
|
|
2
2
|
from typing import Optional, Dict, Union
|
|
3
3
|
|
|
4
|
-
from django_ledger.io import IODigestContextManager
|
|
4
|
+
from django_ledger.io.io_digest import IODigestContextManager
|
|
5
5
|
from django_ledger.report.core import BaseReportSupport, PDFReportValidationError
|
|
6
6
|
from django_ledger.settings import DJANGO_LEDGER_CURRENCY_SYMBOL
|
|
7
7
|
from django_ledger.templatetags.django_ledger import currency_format
|
django_ledger/report/core.py
CHANGED
|
@@ -3,8 +3,9 @@ from typing import Optional, Dict
|
|
|
3
3
|
from django.contrib.staticfiles import finders
|
|
4
4
|
from django.core.exceptions import ValidationError
|
|
5
5
|
|
|
6
|
-
from django_ledger.io import IODigestContextManager
|
|
7
|
-
from django_ledger.models import LedgerModel
|
|
6
|
+
from django_ledger.io.io_digest import IODigestContextManager
|
|
7
|
+
from django_ledger.models.ledger import LedgerModel
|
|
8
|
+
from django_ledger.models.unit import EntityUnitModel
|
|
8
9
|
from django_ledger.settings import DJANGO_LEDGER_PDF_SUPPORT_ENABLED
|
|
9
10
|
from django_ledger.templatetags.django_ledger import currency_symbol, currency_format
|
|
10
11
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<div class="card-footer">
|
|
36
36
|
{% if journal_entry.can_lock %}
|
|
37
37
|
<a href="{{ journal_entry.get_lock_url }}" class="card-footer-item has-text-success has-text-weight-bold">{% trans 'Lock' %}</a>
|
|
38
|
-
{% endif %}
|
|
38
|
+
{% endif %}
|
|
39
39
|
{% if journal_entry.can_unlock %}
|
|
40
40
|
<a href="{{ journal_entry.get_unlock_url }}" class="card-footer-item has-text-warning has-text-weight-bold">{% trans 'UnLock' %}</a>
|
|
41
41
|
{% endif %}
|
|
@@ -19,7 +19,7 @@ from django_ledger import __version__
|
|
|
19
19
|
from django_ledger.forms.app_filters import EntityFilterForm, ActivityFilterForm
|
|
20
20
|
from django_ledger.forms.feedback import BugReportForm, RequestNewFeatureForm
|
|
21
21
|
from django_ledger.io import CREDIT, DEBIT, ROLES_ORDER_ALL
|
|
22
|
-
from django_ledger.io.
|
|
22
|
+
from django_ledger.io.io_core import validate_activity
|
|
23
23
|
from django_ledger.models import TransactionModel, BillModel, InvoiceModel, EntityUnitModel
|
|
24
24
|
from django_ledger.settings import (
|
|
25
25
|
DJANGO_LEDGER_FINANCIAL_ANALYSIS, DJANGO_LEDGER_CURRENCY_SYMBOL,
|
django_ledger/tests/base.py
CHANGED
|
@@ -12,7 +12,7 @@ from django.test import TestCase
|
|
|
12
12
|
from django.test.client import Client
|
|
13
13
|
from django.utils.timezone import get_default_timezone
|
|
14
14
|
|
|
15
|
-
from django_ledger.io.
|
|
15
|
+
from django_ledger.io.io_generator import EntityDataGenerator
|
|
16
16
|
from django_ledger.models.entity import EntityModel, EntityModelQuerySet
|
|
17
17
|
|
|
18
18
|
UserModel = get_user_model()
|