django-ledger 0.7.11__py3-none-any.whl → 0.8.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/context.py +12 -0
- django_ledger/forms/bill.py +0 -4
- django_ledger/forms/closing_entry.py +13 -1
- django_ledger/forms/data_import.py +1 -1
- django_ledger/forms/estimate.py +3 -6
- django_ledger/forms/invoice.py +3 -7
- django_ledger/forms/item.py +10 -18
- django_ledger/forms/purchase_order.py +2 -4
- django_ledger/io/io_core.py +8 -26
- django_ledger/io/io_generator.py +7 -6
- django_ledger/io/io_library.py +1 -2
- django_ledger/migrations/0025_alter_billmodel_cash_account_and_more.py +70 -0
- django_ledger/models/accounts.py +109 -69
- django_ledger/models/bank_account.py +40 -23
- django_ledger/models/bill.py +79 -63
- django_ledger/models/chart_of_accounts.py +173 -105
- django_ledger/models/closing_entry.py +99 -48
- django_ledger/models/customer.py +60 -39
- django_ledger/models/data_import.py +55 -41
- django_ledger/models/deprecations.py +61 -0
- django_ledger/models/entity.py +18 -16
- django_ledger/models/estimate.py +57 -28
- django_ledger/models/invoice.py +46 -26
- django_ledger/models/items.py +503 -142
- django_ledger/models/journal_entry.py +61 -47
- django_ledger/models/ledger.py +106 -42
- django_ledger/models/mixins.py +5 -3
- django_ledger/models/purchase_order.py +39 -17
- django_ledger/models/transactions.py +152 -113
- django_ledger/models/unit.py +57 -30
- django_ledger/models/vendor.py +75 -43
- django_ledger/report/core.py +2 -14
- django_ledger/settings.py +56 -71
- django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
- django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +25 -0
- django_ledger/static/django_ledger/bundle/styles.bundle.js +1 -1
- django_ledger/static/django_ledger/css/djl_styles.css +273 -0
- django_ledger/templates/django_ledger/bills/includes/card_bill.html +2 -2
- django_ledger/templates/django_ledger/components/menu.html +41 -26
- django_ledger/templates/django_ledger/customer/tags/customer_table.html +5 -5
- django_ledger/templates/django_ledger/entity/includes/card_entity.html +12 -6
- django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +1 -1
- django_ledger/templates/django_ledger/financial_statements/cash_flow.html +4 -1
- django_ledger/templates/django_ledger/financial_statements/income_statement.html +4 -1
- django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +27 -3
- django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +16 -4
- django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +73 -18
- django_ledger/templates/django_ledger/includes/widget_ratios.html +18 -24
- django_ledger/templates/django_ledger/invoice/includes/card_invoice.html +3 -3
- django_ledger/templates/django_ledger/layouts/base.html +6 -1
- django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +9 -5
- django_ledger/tests/test_accounts.py +1 -2
- django_ledger/tests/test_io.py +17 -0
- django_ledger/tests/test_purchase_order.py +3 -3
- django_ledger/tests/test_transactions.py +1 -2
- django_ledger/urls/__init__.py +0 -4
- django_ledger/views/bill.py +8 -11
- django_ledger/views/chart_of_accounts.py +6 -4
- django_ledger/views/closing_entry.py +11 -7
- django_ledger/views/customer.py +13 -17
- django_ledger/views/data_import.py +7 -6
- django_ledger/views/djl_api.py +3 -5
- django_ledger/views/entity.py +2 -4
- django_ledger/views/estimate.py +3 -7
- django_ledger/views/inventory.py +3 -5
- django_ledger/views/invoice.py +4 -6
- django_ledger/views/item.py +7 -11
- django_ledger/views/journal_entry.py +1 -2
- django_ledger/views/mixins.py +25 -19
- django_ledger/views/purchase_order.py +24 -35
- django_ledger/views/unit.py +1 -2
- django_ledger/views/vendor.py +1 -2
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.0.dist-info}/METADATA +43 -75
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.0.dist-info}/RECORD +79 -108
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.0.dist-info}/WHEEL +1 -1
- django_ledger-0.8.0.dist-info/top_level.txt +1 -0
- django_ledger/contrib/django_ledger_graphene/__init__.py +0 -0
- django_ledger/contrib/django_ledger_graphene/accounts/schema.py +0 -33
- django_ledger/contrib/django_ledger_graphene/api.py +0 -42
- django_ledger/contrib/django_ledger_graphene/apps.py +0 -6
- django_ledger/contrib/django_ledger_graphene/auth/mutations.py +0 -49
- django_ledger/contrib/django_ledger_graphene/auth/schema.py +0 -6
- django_ledger/contrib/django_ledger_graphene/bank_account/mutations.py +0 -61
- django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +0 -34
- django_ledger/contrib/django_ledger_graphene/bill/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/bill/schema.py +0 -34
- django_ledger/contrib/django_ledger_graphene/coa/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/coa/schema.py +0 -30
- django_ledger/contrib/django_ledger_graphene/customers/__init__.py +0 -0
- django_ledger/contrib/django_ledger_graphene/customers/mutations.py +0 -71
- django_ledger/contrib/django_ledger_graphene/customers/schema.py +0 -43
- django_ledger/contrib/django_ledger_graphene/data_import/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/data_import/schema.py +0 -0
- django_ledger/contrib/django_ledger_graphene/entity/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/entity/schema.py +0 -94
- django_ledger/contrib/django_ledger_graphene/item/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/item/schema.py +0 -31
- django_ledger/contrib/django_ledger_graphene/journal_entry/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +0 -35
- django_ledger/contrib/django_ledger_graphene/ledger/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/ledger/schema.py +0 -32
- django_ledger/contrib/django_ledger_graphene/purchase_order/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/purchase_order/schema.py +0 -31
- django_ledger/contrib/django_ledger_graphene/transaction/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/transaction/schema.py +0 -36
- django_ledger/contrib/django_ledger_graphene/unit/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/unit/schema.py +0 -27
- django_ledger/contrib/django_ledger_graphene/vendor/mutations.py +0 -0
- django_ledger/contrib/django_ledger_graphene/vendor/schema.py +0 -37
- django_ledger/contrib/django_ledger_graphene/views.py +0 -12
- django_ledger-0.7.11.dist-info/top_level.txt +0 -4
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.0.dist-info/licenses}/AUTHORS.md +0 -0
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -25,6 +25,7 @@ The JournalEntryModel is also responsible for validating the Financial Activity
|
|
|
25
25
|
business. Whenever an account with ASSET_CA_CASH role is involved in a Journal Entry (see roles for more details), the
|
|
26
26
|
JE is responsible for programmatically determine the kind of operation for the JE (Operating, Financing, Investing).
|
|
27
27
|
"""
|
|
28
|
+
import warnings
|
|
28
29
|
from datetime import date, datetime
|
|
29
30
|
from decimal import Decimal
|
|
30
31
|
from enum import Enum
|
|
@@ -41,6 +42,7 @@ from django.urls import reverse
|
|
|
41
42
|
from django.utils.timezone import localtime
|
|
42
43
|
from django.utils.translation import gettext_lazy as _
|
|
43
44
|
|
|
45
|
+
from django_ledger.io import roles
|
|
44
46
|
from django_ledger.io.io_core import get_localtime
|
|
45
47
|
from django_ledger.io.roles import (
|
|
46
48
|
ASSET_CA_CASH, GROUP_CFS_FIN_DIVIDENDS, GROUP_CFS_FIN_ISSUING_EQUITY,
|
|
@@ -50,6 +52,7 @@ from django_ledger.io.roles import (
|
|
|
50
52
|
validate_roles
|
|
51
53
|
)
|
|
52
54
|
from django_ledger.models.accounts import CREDIT, DEBIT
|
|
55
|
+
from django_ledger.models.deprecations import deprecated_entity_slug_behavior
|
|
53
56
|
from django_ledger.models.entity import EntityStateModel, EntityModel
|
|
54
57
|
from django_ledger.models.ledger import LedgerModel
|
|
55
58
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
@@ -59,13 +62,12 @@ from django_ledger.models.signals import (
|
|
|
59
62
|
journal_entry_posted,
|
|
60
63
|
journal_entry_unposted
|
|
61
64
|
)
|
|
62
|
-
from django_ledger.models.transactions import TransactionModelQuerySet
|
|
65
|
+
from django_ledger.models.transactions import TransactionModelQuerySet
|
|
63
66
|
from django_ledger.settings import (
|
|
64
67
|
DJANGO_LEDGER_JE_NUMBER_PREFIX,
|
|
65
68
|
DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
|
|
66
|
-
DJANGO_LEDGER_JE_NUMBER_NO_UNIT_PREFIX
|
|
69
|
+
DJANGO_LEDGER_JE_NUMBER_NO_UNIT_PREFIX, DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
|
|
67
70
|
)
|
|
68
|
-
from django_ledger.io import roles
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
class JournalEntryValidationError(ValidationError):
|
|
@@ -82,7 +84,33 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
82
84
|
locked entries, and querying entries associated with specific ledgers.
|
|
83
85
|
"""
|
|
84
86
|
|
|
85
|
-
def
|
|
87
|
+
def for_user(self, user_model) -> 'JournalEntryModelQuerySet':
|
|
88
|
+
"""
|
|
89
|
+
Filters the JournalEntryModel queryset for the given user.
|
|
90
|
+
|
|
91
|
+
- Superusers will have access to all journal entries.
|
|
92
|
+
- Other authenticated users will only see entries for entities where
|
|
93
|
+
they are admins or managers.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
user_model : UserModel
|
|
98
|
+
An authenticated Django user object.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
JournalEntryModelQuerySet
|
|
103
|
+
A filtered queryset restricted by the user's entity relationships.
|
|
104
|
+
"""
|
|
105
|
+
if user_model.is_superuser:
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
return self.filter(
|
|
109
|
+
Q(ledger__entity__admin=user_model) | # Entries for entities where the user is admin
|
|
110
|
+
Q(ledger__entity__managers__in=[user_model]) # Entries for entities where the user is a manager
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def create(self, verify_on_save: bool = False, force_create: bool = False, **kwargs) -> 'JournalEntryModelQuerySet':
|
|
86
114
|
"""
|
|
87
115
|
Creates a new Journal Entry while enforcing business logic validations.
|
|
88
116
|
|
|
@@ -124,7 +152,7 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
124
152
|
obj.save(force_insert=True, using=self.db, verify=verify_on_save)
|
|
125
153
|
return obj
|
|
126
154
|
|
|
127
|
-
def posted(self):
|
|
155
|
+
def posted(self) -> 'JournalEntryModelQuerySet':
|
|
128
156
|
"""
|
|
129
157
|
Filters the QuerySet to include only "posted" Journal Entries.
|
|
130
158
|
|
|
@@ -135,7 +163,7 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
135
163
|
"""
|
|
136
164
|
return self.filter(posted=True)
|
|
137
165
|
|
|
138
|
-
def unposted(self):
|
|
166
|
+
def unposted(self) -> 'JournalEntryModelQuerySet':
|
|
139
167
|
"""
|
|
140
168
|
Filters the QuerySet to include only "unposted" Journal Entries.
|
|
141
169
|
|
|
@@ -146,7 +174,7 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
146
174
|
"""
|
|
147
175
|
return self.filter(posted=False)
|
|
148
176
|
|
|
149
|
-
def locked(self):
|
|
177
|
+
def locked(self) -> 'JournalEntryModelQuerySet':
|
|
150
178
|
"""
|
|
151
179
|
Filters the QuerySet to include only "locked" Journal Entries.
|
|
152
180
|
|
|
@@ -157,7 +185,7 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
157
185
|
"""
|
|
158
186
|
return self.filter(locked=True)
|
|
159
187
|
|
|
160
|
-
def unlocked(self):
|
|
188
|
+
def unlocked(self) -> 'JournalEntryModelQuerySet':
|
|
161
189
|
"""
|
|
162
190
|
Filters the QuerySet to include only "unlocked" Journal Entries.
|
|
163
191
|
|
|
@@ -168,7 +196,7 @@ class JournalEntryModelQuerySet(QuerySet):
|
|
|
168
196
|
"""
|
|
169
197
|
return self.filter(locked=False)
|
|
170
198
|
|
|
171
|
-
def for_ledger(self, ledger_pk: Union[str, UUID, LedgerModel]):
|
|
199
|
+
def for_ledger(self, ledger_pk: Union[str, UUID, LedgerModel]) -> 'JournalEntryModelQuerySet':
|
|
172
200
|
"""
|
|
173
201
|
Filters the QuerySet to include Journal Entries associated with a specific Ledger.
|
|
174
202
|
|
|
@@ -223,34 +251,8 @@ class JournalEntryModelManager(Manager):
|
|
|
223
251
|
txs_count=Count('transactionmodel') # Annotates the count of transactions
|
|
224
252
|
)
|
|
225
253
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Filters the JournalEntryModel queryset for the given user.
|
|
229
|
-
|
|
230
|
-
- Superusers will have access to all journal entries.
|
|
231
|
-
- Other authenticated users will only see entries for entities where
|
|
232
|
-
they are admins or managers.
|
|
233
|
-
|
|
234
|
-
Parameters
|
|
235
|
-
----------
|
|
236
|
-
user_model : UserModel
|
|
237
|
-
An authenticated Django user object.
|
|
238
|
-
|
|
239
|
-
Returns
|
|
240
|
-
-------
|
|
241
|
-
JournalEntryModelQuerySet
|
|
242
|
-
A filtered queryset restricted by the user's entity relationships.
|
|
243
|
-
"""
|
|
244
|
-
qs = self.get_queryset()
|
|
245
|
-
if user_model.is_superuser:
|
|
246
|
-
return qs
|
|
247
|
-
|
|
248
|
-
return qs.filter(
|
|
249
|
-
Q(ledger__entity__admin=user_model) | # Entries for entities where the user is admin
|
|
250
|
-
Q(ledger__entity__managers__in=[user_model]) # Entries for entities where the user is a manager
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
def for_entity(self, entity_slug: Union[str, EntityModel], user_model) -> JournalEntryModelQuerySet:
|
|
254
|
+
@deprecated_entity_slug_behavior
|
|
255
|
+
def for_entity(self, entity_model: EntityModel | str | UUID = None, **kwargs) -> JournalEntryModelQuerySet:
|
|
254
256
|
"""
|
|
255
257
|
Filters the JournalEntryModel queryset for a specific entity and user.
|
|
256
258
|
|
|
@@ -260,24 +262,36 @@ class JournalEntryModelManager(Manager):
|
|
|
260
262
|
|
|
261
263
|
Parameters
|
|
262
264
|
----------
|
|
263
|
-
|
|
265
|
+
entity_model : str or EntityModel
|
|
264
266
|
The slug of the entity (or an instance of `EntityModel`) used for filtering.
|
|
265
|
-
user_model : UserModel
|
|
266
|
-
An authenticated Django user object.
|
|
267
|
-
|
|
268
267
|
Returns
|
|
269
268
|
-------
|
|
270
269
|
JournalEntryModelQuerySet
|
|
271
270
|
A customized queryset containing journal entries associated with the
|
|
272
271
|
given entity and restricted by the user's access permissions.
|
|
273
272
|
"""
|
|
274
|
-
qs = self.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
qs = self.get_queryset()
|
|
274
|
+
if 'user_model' in kwargs:
|
|
275
|
+
warnings.warn(
|
|
276
|
+
'user_model parameter is deprecated and will be removed in a future release. '
|
|
277
|
+
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
278
|
+
DeprecationWarning,
|
|
279
|
+
stacklevel=2
|
|
280
|
+
)
|
|
281
|
+
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
282
|
+
qs = qs.for_user(kwargs['user_model'])
|
|
279
283
|
|
|
280
|
-
|
|
284
|
+
if isinstance(entity_model, EntityModel):
|
|
285
|
+
qs = qs.filter(ledger__entity=entity_model)
|
|
286
|
+
elif isinstance(entity_model, str):
|
|
287
|
+
qs = qs.filter(ledger__entity__slug__exact=entity_model)
|
|
288
|
+
elif isinstance(entity_model, UUID):
|
|
289
|
+
qs = qs.filter(ledger__entity_id=entity_model)
|
|
290
|
+
else:
|
|
291
|
+
raise JournalEntryValidationError(
|
|
292
|
+
message='Must provide EntityModel, slug or UUID',
|
|
293
|
+
)
|
|
294
|
+
return qs
|
|
281
295
|
|
|
282
296
|
|
|
283
297
|
class ActivityEnum(Enum):
|
django_ledger/models/ledger.py
CHANGED
|
@@ -23,20 +23,22 @@ layer as possible in order to minimize the amount of data being pulled for analy
|
|
|
23
23
|
The Django Ledger core model follows the following structure:
|
|
24
24
|
EntityModel -< LedgerModel -< JournalEntryModel -< TransactionModel
|
|
25
25
|
"""
|
|
26
|
+
import warnings
|
|
26
27
|
from datetime import date
|
|
27
28
|
from string import ascii_lowercase, digits
|
|
28
29
|
from typing import Optional
|
|
29
|
-
from uuid import uuid4
|
|
30
|
+
from uuid import uuid4, UUID
|
|
30
31
|
|
|
31
32
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
32
33
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
33
34
|
from django.db import models
|
|
34
|
-
from django.db.models import Q, Min, F, Count
|
|
35
|
+
from django.db.models import Q, Min, F, Count, Manager, QuerySet
|
|
35
36
|
from django.urls import reverse
|
|
36
37
|
from django.utils.translation import gettext_lazy as _
|
|
37
38
|
|
|
38
39
|
from django_ledger.io.io_core import IOMixIn
|
|
39
40
|
from django_ledger.models import lazy_loader
|
|
41
|
+
from django_ledger.models.deprecations import deprecated_entity_slug_behavior
|
|
40
42
|
from django_ledger.models.mixins import CreateUpdateMixIn
|
|
41
43
|
from django_ledger.models.signals import (
|
|
42
44
|
ledger_posted,
|
|
@@ -46,6 +48,7 @@ from django_ledger.models.signals import (
|
|
|
46
48
|
ledger_hidden,
|
|
47
49
|
ledger_unhidden
|
|
48
50
|
)
|
|
51
|
+
from django_ledger.settings import DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
|
|
49
52
|
|
|
50
53
|
LEDGER_ID_CHARS = ascii_lowercase + digits
|
|
51
54
|
|
|
@@ -54,75 +57,130 @@ class LedgerModelValidationError(ValidationError):
|
|
|
54
57
|
pass
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
class LedgerModelQuerySet(
|
|
60
|
+
class LedgerModelQuerySet(QuerySet):
|
|
58
61
|
"""
|
|
59
|
-
Custom
|
|
62
|
+
Custom QuerySet for filtering LedgerModel instances based on specific fields.
|
|
63
|
+
|
|
64
|
+
Provides predefined filtering methods to simplify working with ledger data.
|
|
65
|
+
These filters allow querying for entries that are locked, unlocked, posted,
|
|
66
|
+
unposted, hidden, visible, or current.
|
|
67
|
+
|
|
68
|
+
Methods
|
|
69
|
+
-------
|
|
70
|
+
locked()
|
|
71
|
+
Filters instances based on the 'locked' attribute set to `True`.
|
|
72
|
+
unlocked()
|
|
73
|
+
Filters instances based on the 'locked' attribute set to `False`.
|
|
74
|
+
posted()
|
|
75
|
+
Filters a queryset to include only posted entries with 'posted' set to `True`.
|
|
76
|
+
unposted()
|
|
77
|
+
Filters a queryset to include only unposted entries with 'posted' set to `False`.
|
|
78
|
+
hidden()
|
|
79
|
+
Filters a queryset to include only items marked as hidden.
|
|
80
|
+
visible()
|
|
81
|
+
Filters out hidden items from the queryset.
|
|
82
|
+
current()
|
|
83
|
+
Filters the queryset to include items where the earliest timestamp
|
|
84
|
+
comes after the entity's last closing date or is null.
|
|
60
85
|
"""
|
|
61
86
|
|
|
62
|
-
def
|
|
87
|
+
def for_user(self, user_model) -> 'LedgerModelQuerySet':
|
|
88
|
+
if user_model.is_superuser:
|
|
89
|
+
return self
|
|
90
|
+
return self.filter(
|
|
91
|
+
Q(entity__admin=user_model) |
|
|
92
|
+
Q(entity__managers__in=[user_model])
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def locked(self) -> 'LedgerModelQuerySet':
|
|
63
96
|
"""
|
|
64
|
-
Filters
|
|
97
|
+
Filters instances based on the 'locked' attribute.
|
|
98
|
+
|
|
99
|
+
This method returns a new instance where the 'locked' attribute is set to `True`.
|
|
65
100
|
|
|
66
101
|
Returns
|
|
67
102
|
-------
|
|
68
103
|
LedgerModelQuerySet
|
|
69
|
-
A
|
|
104
|
+
A new instance filtered with the attribute 'locked' set to `True`.
|
|
70
105
|
"""
|
|
71
106
|
return self.filter(locked=True)
|
|
72
107
|
|
|
73
|
-
def unlocked(self):
|
|
108
|
+
def unlocked(self) -> 'LedgerModelQuerySet':
|
|
74
109
|
"""
|
|
75
|
-
Filters
|
|
110
|
+
Filters and returns an instance where the `locked` attribute is set to `False`.
|
|
76
111
|
|
|
77
112
|
Returns
|
|
78
113
|
-------
|
|
79
114
|
LedgerModelQuerySet
|
|
80
|
-
A
|
|
115
|
+
A new or modified instance of the object with the filter applied.
|
|
81
116
|
"""
|
|
82
117
|
return self.filter(locked=False)
|
|
83
118
|
|
|
84
|
-
def posted(self):
|
|
119
|
+
def posted(self) -> 'LedgerModelQuerySet':
|
|
85
120
|
"""
|
|
86
|
-
Filters
|
|
121
|
+
Filters a queryset to include only posted entries.
|
|
87
122
|
|
|
88
123
|
Returns
|
|
89
124
|
-------
|
|
90
125
|
LedgerModelQuerySet
|
|
91
|
-
A
|
|
126
|
+
A new queryset instance containing only objects with
|
|
127
|
+
the `posted` field set to True.
|
|
92
128
|
"""
|
|
93
129
|
return self.filter(posted=True)
|
|
94
130
|
|
|
95
|
-
def unposted(self):
|
|
131
|
+
def unposted(self) -> 'LedgerModelQuerySet':
|
|
96
132
|
"""
|
|
97
|
-
Filters
|
|
133
|
+
Filters a queryset or a similar iterable-like object to include only items that
|
|
134
|
+
have the attribute `posted` set to `True`. This method returns a new instance
|
|
135
|
+
containing the filtered results.
|
|
98
136
|
|
|
99
137
|
Returns
|
|
100
138
|
-------
|
|
101
139
|
LedgerModelQuerySet
|
|
102
|
-
A
|
|
140
|
+
A new instance containing only the filtered results with `posted` set to True.
|
|
103
141
|
"""
|
|
104
142
|
return self.filter(posted=True)
|
|
105
143
|
|
|
106
|
-
def hidden(self):
|
|
144
|
+
def hidden(self) -> 'LedgerModelQuerySet':
|
|
145
|
+
"""
|
|
146
|
+
Filters the queryset to include only objects marked as hidden.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
LedgerModelQuerySet
|
|
151
|
+
A filtered queryset containing only objects with the `hidden` attribute
|
|
152
|
+
set to True.
|
|
153
|
+
"""
|
|
107
154
|
return self.filter(hidden=True)
|
|
108
155
|
|
|
109
|
-
def visible(self):
|
|
156
|
+
def visible(self) -> 'LedgerModelQuerySet':
|
|
157
|
+
"""
|
|
158
|
+
Filters out hidden items from the queryset.
|
|
159
|
+
|
|
160
|
+
Ensures that only items with the `hidden` attribute set to `False`
|
|
161
|
+
are included in the returned queryset.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
LedgerModelQuerySet
|
|
166
|
+
A queryset containing only visible items.
|
|
167
|
+
"""
|
|
110
168
|
return self.filter(hidden=False)
|
|
111
169
|
|
|
112
|
-
def current(self):
|
|
170
|
+
def current(self) -> 'LedgerModelQuerySet':
|
|
113
171
|
return self.filter(
|
|
114
172
|
Q(earliest_timestamp__date__gt=F('entity__last_closing_date'))
|
|
115
173
|
| Q(earliest_timestamp__isnull=True)
|
|
116
174
|
)
|
|
117
175
|
|
|
118
176
|
|
|
119
|
-
class LedgerModelManager(
|
|
177
|
+
class LedgerModelManager(Manager):
|
|
120
178
|
"""
|
|
121
179
|
A custom-defined LedgerModelManager that implements custom QuerySet methods related to the LedgerModel.
|
|
122
180
|
"""
|
|
123
181
|
|
|
124
|
-
def get_queryset(self):
|
|
125
|
-
qs =
|
|
182
|
+
def get_queryset(self) -> LedgerModelQuerySet:
|
|
183
|
+
qs = LedgerModelQuerySet(self.model, using=self._db)
|
|
126
184
|
return qs.select_related('entity').annotate(
|
|
127
185
|
Count('journal_entries'),
|
|
128
186
|
_entity_slug=F('entity__slug'),
|
|
@@ -130,40 +188,46 @@ class LedgerModelManager(models.Manager):
|
|
|
130
188
|
filter=Q(journal_entries__posted=True)),
|
|
131
189
|
)
|
|
132
190
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if user_model.is_superuser:
|
|
136
|
-
return qs
|
|
137
|
-
return qs.filter(
|
|
138
|
-
Q(entity__admin=user_model) |
|
|
139
|
-
Q(entity__managers__in=[user_model])
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
def for_entity(self, entity_slug, user_model):
|
|
191
|
+
@deprecated_entity_slug_behavior
|
|
192
|
+
def for_entity(self, entity_model: 'EntityModel | str | UUID' = None, **kwargs) -> LedgerModelQuerySet:
|
|
143
193
|
"""
|
|
144
194
|
Returns a QuerySet of LedgerModels associated with a specific EntityModel & UserModel.
|
|
145
195
|
May pass an instance of EntityModel or a String representing the EntityModel slug.
|
|
146
196
|
|
|
147
197
|
Parameters
|
|
148
198
|
----------
|
|
149
|
-
|
|
199
|
+
entity_model: str or EntityModel
|
|
150
200
|
The entity slug or EntityModel used for filtering the QuerySet.
|
|
151
|
-
user_model
|
|
152
|
-
The request UserModel to check for privileges.
|
|
153
201
|
|
|
154
202
|
Returns
|
|
155
203
|
-------
|
|
156
204
|
LedgerModelQuerySet
|
|
157
205
|
A Filtered LedgerModelQuerySet.
|
|
158
206
|
"""
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
207
|
+
EntityModel = lazy_loader.get_entity_model()
|
|
208
|
+
|
|
209
|
+
qs = self.get_queryset()
|
|
210
|
+
if 'user_model' in kwargs:
|
|
211
|
+
warnings.warn(
|
|
212
|
+
'user_model parameter is deprecated and will be removed in a future release. '
|
|
213
|
+
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
214
|
+
DeprecationWarning,
|
|
215
|
+
stacklevel=2
|
|
163
216
|
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
217
|
+
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
218
|
+
qs = qs.for_user(kwargs['user_model'])
|
|
219
|
+
|
|
220
|
+
if isinstance(entity_model, EntityModel):
|
|
221
|
+
qs = qs.filter(entity=entity_model)
|
|
222
|
+
elif isinstance(entity_model, str):
|
|
223
|
+
qs = qs.filter(entity__slug__exact=entity_model)
|
|
224
|
+
elif isinstance(entity_model, UUID):
|
|
225
|
+
qs = qs.filter(entity_id=entity_model)
|
|
226
|
+
else:
|
|
227
|
+
raise LedgerModelValidationError(
|
|
228
|
+
message='Must provide entity slug, EntityModel, or UUID parameter'
|
|
229
|
+
)
|
|
230
|
+
return qs
|
|
167
231
|
|
|
168
232
|
|
|
169
233
|
class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
django_ledger/models/mixins.py
CHANGED
|
@@ -106,7 +106,7 @@ class ContactInfoMixIn(models.Model):
|
|
|
106
106
|
phone: str
|
|
107
107
|
A string used to document the contact phone.
|
|
108
108
|
"""
|
|
109
|
-
address_1 = models.CharField(max_length=70, verbose_name=_('Address Line 1'))
|
|
109
|
+
address_1 = models.CharField(max_length=70, verbose_name=_('Address Line 1'), null=True, blank=True)
|
|
110
110
|
address_2 = models.CharField(null=True, blank=True, max_length=70, verbose_name=_('Address Line 2'))
|
|
111
111
|
city = models.CharField(null=True, blank=True, max_length=70, verbose_name=_('City'))
|
|
112
112
|
state = models.CharField(null=True, blank=True, max_length=70, verbose_name=_('State/Province'))
|
|
@@ -129,6 +129,10 @@ class ContactInfoMixIn(models.Model):
|
|
|
129
129
|
return f'{self.city}, {self.state}. {self.zip_code}. {self.country}'
|
|
130
130
|
|
|
131
131
|
def clean(self):
|
|
132
|
+
if self.address_2 and not self.address_1:
|
|
133
|
+
raise ValidationError(
|
|
134
|
+
{'address_1': _('Address line 1 is required if address_2 is provided.')},
|
|
135
|
+
)
|
|
132
136
|
super().clean()
|
|
133
137
|
|
|
134
138
|
|
|
@@ -231,8 +235,6 @@ class AccrualMixIn(models.Model):
|
|
|
231
235
|
null=True,
|
|
232
236
|
verbose_name=_('Prepaid Account'),
|
|
233
237
|
related_name=f'{REL_NAME_PREFIX}_prepaid_account')
|
|
234
|
-
|
|
235
|
-
# todo: rename to payable account...
|
|
236
238
|
unearned_account = models.ForeignKey('django_ledger.AccountModel',
|
|
237
239
|
on_delete=models.RESTRICT,
|
|
238
240
|
blank=True,
|
|
@@ -12,10 +12,11 @@ starts in draft model by default and goes through different states including InR
|
|
|
12
12
|
Void. The PurchaseOrderModel also keeps track of when these states take place.
|
|
13
13
|
|
|
14
14
|
"""
|
|
15
|
+
import warnings
|
|
15
16
|
from datetime import date
|
|
16
17
|
from string import ascii_uppercase, digits
|
|
17
18
|
from typing import Tuple, List, Union, Optional, Dict
|
|
18
|
-
from uuid import uuid4
|
|
19
|
+
from uuid import uuid4, UUID
|
|
19
20
|
|
|
20
21
|
from django.contrib.auth import get_user_model
|
|
21
22
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
@@ -30,6 +31,7 @@ from django.utils.translation import gettext_lazy as _
|
|
|
30
31
|
|
|
31
32
|
from django_ledger.io.io_core import get_localdate
|
|
32
33
|
from django_ledger.models.bill import BillModel, BillModelQuerySet
|
|
34
|
+
from django_ledger.models.deprecations import deprecated_entity_slug_behavior
|
|
33
35
|
from django_ledger.models.entity import EntityModel
|
|
34
36
|
from django_ledger.models.items import ItemTransactionModel, ItemTransactionModelQuerySet, ItemModelQuerySet, ItemModel
|
|
35
37
|
from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn, ItemizeMixIn
|
|
@@ -42,7 +44,8 @@ from django_ledger.models.signals import (
|
|
|
42
44
|
po_status_in_review
|
|
43
45
|
)
|
|
44
46
|
from django_ledger.models.utils import lazy_loader
|
|
45
|
-
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX
|
|
47
|
+
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX, \
|
|
48
|
+
DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
|
|
46
49
|
|
|
47
50
|
PO_NUMBER_CHARS = ascii_uppercase + digits
|
|
48
51
|
|
|
@@ -55,9 +58,17 @@ class PurchaseOrderModelValidationError(ValidationError):
|
|
|
55
58
|
|
|
56
59
|
class PurchaseOrderModelQuerySet(QuerySet):
|
|
57
60
|
"""
|
|
58
|
-
A custom
|
|
61
|
+
A custom-defined PurchaseOrderModel QuerySet.
|
|
59
62
|
"""
|
|
60
63
|
|
|
64
|
+
def for_user(self, user_model):
|
|
65
|
+
if user_model.is_superuser:
|
|
66
|
+
return self
|
|
67
|
+
return self.filter(
|
|
68
|
+
Q(entity__admin=user_model) |
|
|
69
|
+
Q(entity__managers__in=[user_model])
|
|
70
|
+
)
|
|
71
|
+
|
|
61
72
|
def approved(self):
|
|
62
73
|
"""
|
|
63
74
|
Filters the QuerySet to include Approved PurchaseOrderModels only.
|
|
@@ -105,16 +116,8 @@ class PurchaseOrderModelManager(Manager):
|
|
|
105
116
|
A custom defined PurchaseOrderModel Manager.
|
|
106
117
|
"""
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if user_model.is_superuser:
|
|
111
|
-
return qs
|
|
112
|
-
return qs.filter(
|
|
113
|
-
Q(entity__admin=user_model) |
|
|
114
|
-
Q(entity__managers__in=[user_model])
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
def for_entity(self, entity_slug, user_model) -> PurchaseOrderModelQuerySet:
|
|
119
|
+
@deprecated_entity_slug_behavior
|
|
120
|
+
def for_entity(self, entity_model: EntityModel | str | UUID = None, **kwargs) -> PurchaseOrderModelQuerySet:
|
|
118
121
|
"""
|
|
119
122
|
Fetches a QuerySet of PurchaseOrderModel associated with a specific EntityModel & UserModel.
|
|
120
123
|
May pass an instance of EntityModel or a String representing the EntityModel slug.
|
|
@@ -124,10 +127,29 @@ class PurchaseOrderModelManager(Manager):
|
|
|
124
127
|
PurchaseOrderModelQuerySet
|
|
125
128
|
A PurchaseOrderModelQuerySet with applied filters.
|
|
126
129
|
"""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
|
|
131
|
+
qs = self.get_queryset()
|
|
132
|
+
if 'user_model' in kwargs:
|
|
133
|
+
warnings.warn(
|
|
134
|
+
'user_model parameter is deprecated and will be removed in a future release. '
|
|
135
|
+
'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
|
|
136
|
+
DeprecationWarning,
|
|
137
|
+
stacklevel=2
|
|
138
|
+
)
|
|
139
|
+
if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
|
|
140
|
+
qs = qs.for_user(kwargs['user_model'])
|
|
141
|
+
|
|
142
|
+
if isinstance(entity_model, EntityModel):
|
|
143
|
+
qs = qs.filter(entity=entity_model)
|
|
144
|
+
elif isinstance(entity_model, str):
|
|
145
|
+
qs = qs.filter(entity__slug__exact=entity_model)
|
|
146
|
+
elif isinstance(entity_model, UUID):
|
|
147
|
+
qs = qs.filter(entity_id=entity_model)
|
|
148
|
+
else:
|
|
149
|
+
raise PurchaseOrderModelValidationError(
|
|
150
|
+
message='Entity slug must be either an EntityModel or a String representing the EntityModel slug',
|
|
151
|
+
)
|
|
152
|
+
return qs
|
|
131
153
|
|
|
132
154
|
|
|
133
155
|
class PurchaseOrderModelAbstract(CreateUpdateMixIn,
|