django-ledger 0.7.11__py3-none-any.whl → 0.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -1
- django_ledger/context.py +12 -0
- django_ledger/forms/account.py +45 -46
- django_ledger/forms/bill.py +0 -4
- django_ledger/forms/closing_entry.py +13 -1
- django_ledger/forms/data_import.py +182 -63
- 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 +515 -400
- 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/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
- django_ledger/models/__init__.py +2 -1
- django_ledger/models/accounts.py +109 -69
- django_ledger/models/bank_account.py +40 -23
- django_ledger/models/bill.py +386 -333
- django_ledger/models/chart_of_accounts.py +173 -105
- django_ledger/models/closing_entry.py +99 -48
- django_ledger/models/customer.py +100 -66
- django_ledger/models/data_import.py +818 -323
- django_ledger/models/deprecations.py +61 -0
- django_ledger/models/entity.py +891 -644
- 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 +424 -281
- django_ledger/models/purchase_order.py +39 -17
- django_ledger/models/receipt.py +1083 -0
- django_ledger/models/transactions.py +242 -139
- django_ledger/models/unit.py +93 -54
- django_ledger/models/utils.py +12 -2
- django_ledger/models/vendor.py +121 -70
- django_ledger/report/core.py +2 -14
- django_ledger/settings.py +57 -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/components/period_navigator.html +5 -3
- django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
- django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
- django_ledger/templates/django_ledger/customer/tags/customer_table.html +8 -6
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
- django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
- 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 +7 -2
- django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
- django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
- django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
- django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
- django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
- django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
- django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +12 -7
- django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
- django_ledger/templatetags/django_ledger.py +338 -191
- 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 +1 -4
- django_ledger/urls/customer.py +3 -0
- django_ledger/urls/data_import.py +3 -0
- django_ledger/urls/receipt.py +102 -0
- django_ledger/urls/vendor.py +1 -0
- django_ledger/views/__init__.py +1 -0
- 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 +68 -30
- django_ledger/views/data_import.py +120 -66
- 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 +125 -93
- django_ledger/views/purchase_order.py +24 -35
- django_ledger/views/receipt.py +294 -0
- django_ledger/views/unit.py +1 -2
- django_ledger/views/vendor.py +54 -16
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/METADATA +43 -75
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/RECORD +104 -122
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/WHEEL +1 -1
- django_ledger-0.8.1.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.1.dist-info/licenses}/AUTHORS.md +0 -0
- {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info/licenses}/LICENSE +0 -0
django_ledger/views/mixins.py
CHANGED
|
@@ -11,17 +11,21 @@ from datetime import timedelta, date
|
|
|
11
11
|
from typing import Tuple, Optional
|
|
12
12
|
|
|
13
13
|
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
|
|
14
|
-
from django.core.exceptions import
|
|
14
|
+
from django.core.exceptions import (
|
|
15
|
+
ValidationError,
|
|
16
|
+
ObjectDoesNotExist,
|
|
17
|
+
ImproperlyConfigured,
|
|
18
|
+
)
|
|
15
19
|
from django.db.models import Q
|
|
16
|
-
from django.http import Http404, HttpResponse
|
|
20
|
+
from django.http import Http404, HttpResponse
|
|
17
21
|
from django.urls import reverse
|
|
18
22
|
from django.utils.dateparse import parse_date
|
|
19
23
|
from django.utils.translation import gettext_lazy as _
|
|
20
24
|
from django.views.generic.dates import YearMixin, MonthMixin, DayMixin
|
|
21
25
|
|
|
22
|
-
from django_ledger.models import EntityModel, InvoiceModel, BillModel
|
|
26
|
+
from django_ledger.models import EntityModel, InvoiceModel, BillModel, LedgerModel
|
|
23
27
|
from django_ledger.models.entity import EntityModelFiscalPeriodMixIn
|
|
24
|
-
from django_ledger.settings import
|
|
28
|
+
from django_ledger.settings import DJANGO_LEDGER_AUTHORIZED_SUPERUSER
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
class ContextFromToDateMixin:
|
|
@@ -35,15 +39,18 @@ class ContextFromToDateMixin:
|
|
|
35
39
|
return self.TO_DATE_CONTEXT_NAME
|
|
36
40
|
|
|
37
41
|
|
|
38
|
-
class YearlyReportMixIn(
|
|
39
|
-
|
|
42
|
+
class YearlyReportMixIn(
|
|
43
|
+
YearMixin, ContextFromToDateMixin, EntityModelFiscalPeriodMixIn
|
|
44
|
+
):
|
|
40
45
|
def get_from_date(self, year: int = None, fy_start: int = None, **kwargs) -> date:
|
|
41
46
|
return self.get_year_start_date(year, fy_start)
|
|
42
47
|
|
|
43
48
|
def get_to_date(self, year: int = None, fy_start: int = None, **kwargs) -> date:
|
|
44
49
|
return self.get_year_end_date(year, fy_start)
|
|
45
50
|
|
|
46
|
-
def get_from_to_dates(
|
|
51
|
+
def get_from_to_dates(
|
|
52
|
+
self, year: int = None, fy_start: int = None, **kwargs
|
|
53
|
+
) -> Tuple[date, date]:
|
|
47
54
|
from_date = self.get_from_date(year, fy_start, **kwargs)
|
|
48
55
|
to_date = self.get_to_date(year, fy_start, **kwargs)
|
|
49
56
|
return from_date, to_date
|
|
@@ -78,7 +85,9 @@ class YearlyReportMixIn(YearMixin, ContextFromToDateMixin, EntityModelFiscalPeri
|
|
|
78
85
|
return context
|
|
79
86
|
|
|
80
87
|
|
|
81
|
-
class QuarterlyReportMixIn(
|
|
88
|
+
class QuarterlyReportMixIn(
|
|
89
|
+
YearMixin, ContextFromToDateMixin, EntityModelFiscalPeriodMixIn
|
|
90
|
+
):
|
|
82
91
|
quarter = None
|
|
83
92
|
quarter_url_kwarg = 'quarter'
|
|
84
93
|
|
|
@@ -89,9 +98,11 @@ class QuarterlyReportMixIn(YearMixin, ContextFromToDateMixin, EntityModelFiscalP
|
|
|
89
98
|
try:
|
|
90
99
|
self.validate_quarter(quarter)
|
|
91
100
|
except ValidationError:
|
|
92
|
-
raise Http404(_(
|
|
101
|
+
raise Http404(_('Invalid quarter number'))
|
|
93
102
|
except ValueError:
|
|
94
|
-
raise Http404(
|
|
103
|
+
raise Http404(
|
|
104
|
+
_(f'Invalid quarter format. Cannot parse {quarter} into integer.')
|
|
105
|
+
)
|
|
95
106
|
return quarter
|
|
96
107
|
|
|
97
108
|
def get_quarter(self) -> int:
|
|
@@ -103,33 +114,43 @@ class QuarterlyReportMixIn(YearMixin, ContextFromToDateMixin, EntityModelFiscalP
|
|
|
103
114
|
try:
|
|
104
115
|
quarter = self.request.GET[self.quarter_url_kwarg]
|
|
105
116
|
except KeyError:
|
|
106
|
-
raise Http404(_(
|
|
117
|
+
raise Http404(_('No quarter specified'))
|
|
107
118
|
quarter = self.parse_quarter(quarter)
|
|
108
119
|
return quarter
|
|
109
120
|
|
|
110
|
-
def get_from_date(
|
|
121
|
+
def get_from_date(
|
|
122
|
+
self, quarter: int = None, year: int = None, fy_start: int = None, **kwargs
|
|
123
|
+
) -> date:
|
|
111
124
|
return self.get_quarter_start_date(quarter, year, fy_start)
|
|
112
125
|
|
|
113
|
-
def get_to_date(
|
|
126
|
+
def get_to_date(
|
|
127
|
+
self, quarter: int = None, year: int = None, fy_start: int = None, **kwargs
|
|
128
|
+
) -> date:
|
|
114
129
|
return self.get_quarter_end_date(quarter, year, fy_start)
|
|
115
130
|
|
|
116
|
-
def get_from_to_dates(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
to_date = self.get_to_date(
|
|
131
|
+
def get_from_to_dates(
|
|
132
|
+
self, quarter: int = None, year: int = None, fy_start: int = None, **kwargs
|
|
133
|
+
) -> Tuple[date, date]:
|
|
134
|
+
from_date = self.get_from_date(
|
|
135
|
+
quarter=quarter, year=year, fy_start=fy_start, **kwargs
|
|
136
|
+
)
|
|
137
|
+
to_date = self.get_to_date(
|
|
138
|
+
quarter=quarter, year=year, fy_start=fy_start, **kwargs
|
|
139
|
+
)
|
|
123
140
|
return from_date, to_date
|
|
124
141
|
|
|
125
|
-
def get_quarter_start_date(
|
|
142
|
+
def get_quarter_start_date(
|
|
143
|
+
self, quarter: int = None, year: int = None, fy_start: int = None
|
|
144
|
+
) -> date:
|
|
126
145
|
if not year:
|
|
127
146
|
year = self.get_year()
|
|
128
147
|
if not quarter:
|
|
129
148
|
quarter = self.get_quarter()
|
|
130
149
|
return self.get_quarter_start(year, quarter, fy_start)
|
|
131
150
|
|
|
132
|
-
def get_quarter_end_date(
|
|
151
|
+
def get_quarter_end_date(
|
|
152
|
+
self, quarter: int = None, year: int = None, fy_start: int = None
|
|
153
|
+
) -> date:
|
|
133
154
|
if not year:
|
|
134
155
|
year = self.get_year()
|
|
135
156
|
if not quarter:
|
|
@@ -162,17 +183,15 @@ class QuarterlyReportMixIn(YearMixin, ContextFromToDateMixin, EntityModelFiscalP
|
|
|
162
183
|
|
|
163
184
|
|
|
164
185
|
class MonthlyReportMixIn(YearlyReportMixIn, ContextFromToDateMixin, MonthMixin):
|
|
165
|
-
|
|
166
186
|
def get_from_date(self, month: int = None, year: int = None, **kwargs) -> date:
|
|
167
187
|
return self.get_month_start_date(month=month, year=year)
|
|
168
188
|
|
|
169
189
|
def get_to_date(self, month: int = None, year: int = None, **kwargs) -> date:
|
|
170
190
|
return self.get_month_end_date(month=month, year=year)
|
|
171
191
|
|
|
172
|
-
def get_from_to_dates(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
**kwargs) -> Tuple[date, date]:
|
|
192
|
+
def get_from_to_dates(
|
|
193
|
+
self, month: int = None, year: int = None, **kwargs
|
|
194
|
+
) -> Tuple[date, date]:
|
|
176
195
|
from_date = self.get_from_date(month=month, year=year, **kwargs)
|
|
177
196
|
to_date = self.get_to_date(month=month, year=year, **kwargs)
|
|
178
197
|
return from_date, to_date
|
|
@@ -220,7 +239,6 @@ class MonthlyReportMixIn(YearlyReportMixIn, ContextFromToDateMixin, MonthMixin):
|
|
|
220
239
|
|
|
221
240
|
|
|
222
241
|
class DateReportMixIn(MonthlyReportMixIn, ContextFromToDateMixin, DayMixin):
|
|
223
|
-
|
|
224
242
|
def get_context_data(self, **kwargs):
|
|
225
243
|
context = super(MonthlyReportMixIn, self).get_context_data(**kwargs)
|
|
226
244
|
view_date = self.get_date()
|
|
@@ -233,11 +251,7 @@ class DateReportMixIn(MonthlyReportMixIn, ContextFromToDateMixin, DayMixin):
|
|
|
233
251
|
return context
|
|
234
252
|
|
|
235
253
|
def get_date(self) -> date:
|
|
236
|
-
return date(
|
|
237
|
-
year=self.get_year(),
|
|
238
|
-
month=self.get_month(),
|
|
239
|
-
day=self.get_day()
|
|
240
|
-
)
|
|
254
|
+
return date(year=self.get_year(), month=self.get_month(), day=self.get_day())
|
|
241
255
|
|
|
242
256
|
def get_from_date(self, month: int = None, year: int = None, **kwargs) -> date:
|
|
243
257
|
return self.get_date()
|
|
@@ -245,7 +259,9 @@ class DateReportMixIn(MonthlyReportMixIn, ContextFromToDateMixin, DayMixin):
|
|
|
245
259
|
def get_to_date(self, month: int = None, year: int = None, **kwargs) -> date:
|
|
246
260
|
return self.get_date()
|
|
247
261
|
|
|
248
|
-
def get_from_to_dates(
|
|
262
|
+
def get_from_to_dates(
|
|
263
|
+
self, month: int = None, year: int = None, **kwargs
|
|
264
|
+
) -> Tuple[date, date]:
|
|
249
265
|
dt = self.get_from_date(month=month, year=year, **kwargs)
|
|
250
266
|
return dt, dt
|
|
251
267
|
|
|
@@ -289,7 +305,6 @@ class FromToDatesParseMixIn:
|
|
|
289
305
|
|
|
290
306
|
|
|
291
307
|
class SuccessUrlNextMixIn:
|
|
292
|
-
|
|
293
308
|
def has_next_url(self):
|
|
294
309
|
return self.request.GET.get('next') is not None
|
|
295
310
|
|
|
@@ -312,7 +327,9 @@ class DjangoLedgerSecurityMixIn(LoginRequiredMixin, PermissionRequiredMixin):
|
|
|
312
327
|
|
|
313
328
|
def get_context_data(self, **kwargs):
|
|
314
329
|
context = super().get_context_data(**kwargs)
|
|
315
|
-
context[self.ENTITY_MODEL_CONTEXT_NAME] = self.get_authorized_entity_instance(
|
|
330
|
+
context[self.ENTITY_MODEL_CONTEXT_NAME] = self.get_authorized_entity_instance(
|
|
331
|
+
raise_exception=False
|
|
332
|
+
)
|
|
316
333
|
return context
|
|
317
334
|
|
|
318
335
|
def get_login_url(self):
|
|
@@ -323,9 +340,7 @@ class DjangoLedgerSecurityMixIn(LoginRequiredMixin, PermissionRequiredMixin):
|
|
|
323
340
|
|
|
324
341
|
def get_entity_slug_kwarg(self):
|
|
325
342
|
if self.ENTITY_SLUG_URL_KWARG is None:
|
|
326
|
-
raise ImproperlyConfigured(
|
|
327
|
-
_('ENTITY_SLUG_URL_KWARG must be provided.')
|
|
328
|
-
)
|
|
343
|
+
raise ImproperlyConfigured(_('ENTITY_SLUG_URL_KWARG must be provided.'))
|
|
329
344
|
return self.ENTITY_SLUG_URL_KWARG
|
|
330
345
|
|
|
331
346
|
def get_superuser_authorization(self):
|
|
@@ -342,7 +357,9 @@ class DjangoLedgerSecurityMixIn(LoginRequiredMixin, PermissionRequiredMixin):
|
|
|
342
357
|
if self.request.user.is_authenticated:
|
|
343
358
|
if entity_slug_kwarg in self.kwargs:
|
|
344
359
|
try:
|
|
345
|
-
self.AUTHORIZED_ENTITY_MODEL = entity_model_qs.get(
|
|
360
|
+
self.AUTHORIZED_ENTITY_MODEL = entity_model_qs.get(
|
|
361
|
+
slug__exact=self.kwargs[entity_slug_kwarg]
|
|
362
|
+
)
|
|
346
363
|
except ObjectDoesNotExist:
|
|
347
364
|
return False
|
|
348
365
|
return True
|
|
@@ -354,7 +371,9 @@ class DjangoLedgerSecurityMixIn(LoginRequiredMixin, PermissionRequiredMixin):
|
|
|
354
371
|
authorized_superuser=self.get_superuser_authorization(),
|
|
355
372
|
)
|
|
356
373
|
|
|
357
|
-
def get_authorized_entity_instance(
|
|
374
|
+
def get_authorized_entity_instance(
|
|
375
|
+
self, raise_exception: bool = True
|
|
376
|
+
) -> Optional[EntityModel]:
|
|
358
377
|
if self.AUTHORIZED_ENTITY_MODEL is None:
|
|
359
378
|
if raise_exception:
|
|
360
379
|
raise Http404()
|
|
@@ -383,10 +402,9 @@ class EntityUnitMixIn:
|
|
|
383
402
|
unit_slug = self.get_unit_slug()
|
|
384
403
|
context['unit_slug'] = unit_slug
|
|
385
404
|
|
|
386
|
-
by_unit = any(
|
|
387
|
-
True if unit_slug else False,
|
|
388
|
-
|
|
389
|
-
])
|
|
405
|
+
by_unit = any(
|
|
406
|
+
[True if unit_slug else False, self.request.GET.get('by_unit') is not None]
|
|
407
|
+
)
|
|
390
408
|
|
|
391
409
|
context['by_unit'] = by_unit
|
|
392
410
|
return context
|
|
@@ -418,17 +436,10 @@ class DigestContextMixIn:
|
|
|
418
436
|
context = super(DigestContextMixIn, self).get_context_data(**kwargs)
|
|
419
437
|
return self.get_io_digest(context=context, **kwargs)
|
|
420
438
|
|
|
421
|
-
def get_io_digest(self,
|
|
422
|
-
|
|
423
|
-
from_date=None,
|
|
424
|
-
to_date=None,
|
|
425
|
-
**kwargs):
|
|
426
|
-
|
|
427
|
-
if any([self.IO_DIGEST_UNBOUNDED,
|
|
428
|
-
self.IO_DIGEST_BOUNDED]):
|
|
429
|
-
|
|
439
|
+
def get_io_digest(self, context, from_date=None, to_date=None, **kwargs):
|
|
440
|
+
if any([self.IO_DIGEST_UNBOUNDED, self.IO_DIGEST_BOUNDED]):
|
|
430
441
|
by_period = self.request.GET.get('by_period')
|
|
431
|
-
|
|
442
|
+
io_model: EntityModel | LedgerModel = self.object
|
|
432
443
|
if not to_date:
|
|
433
444
|
to_date = context['to_date']
|
|
434
445
|
if not from_date:
|
|
@@ -441,19 +452,26 @@ class DigestContextMixIn:
|
|
|
441
452
|
unit_slug = None
|
|
442
453
|
|
|
443
454
|
if self.IO_DIGEST_UNBOUNDED:
|
|
444
|
-
io_digest =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
455
|
+
io_digest = io_model.digest(
|
|
456
|
+
user_model=self.request.user,
|
|
457
|
+
entity_slug=io_model.entity_slug
|
|
458
|
+
if isinstance(io_model, LedgerModel)
|
|
459
|
+
else None,
|
|
460
|
+
to_date=to_date,
|
|
461
|
+
unit_slug=unit_slug,
|
|
462
|
+
by_period=True if by_period else False,
|
|
463
|
+
process_ratios=True,
|
|
464
|
+
process_roles=True,
|
|
465
|
+
process_groups=True,
|
|
466
|
+
)
|
|
451
467
|
|
|
452
468
|
context[self.get_io_manager_unbounded_context_name()] = io_digest
|
|
453
|
-
context[self.get_io_digest_unbounded_context_name()] =
|
|
469
|
+
context[self.get_io_digest_unbounded_context_name()] = (
|
|
470
|
+
io_digest.get_io_data()
|
|
471
|
+
)
|
|
454
472
|
|
|
455
473
|
if self.IO_DIGEST_BOUNDED:
|
|
456
|
-
io_digest_equity =
|
|
474
|
+
io_digest_equity = io_model.digest(
|
|
457
475
|
user_model=self.request.user,
|
|
458
476
|
equity_only=True,
|
|
459
477
|
to_date=to_date,
|
|
@@ -462,11 +480,13 @@ class DigestContextMixIn:
|
|
|
462
480
|
by_period=True if by_period else False,
|
|
463
481
|
process_ratios=True,
|
|
464
482
|
process_roles=True,
|
|
465
|
-
process_groups=True
|
|
483
|
+
process_groups=True,
|
|
466
484
|
)
|
|
467
485
|
|
|
468
486
|
context[self.get_io_manager_bounded_context_name()] = io_digest_equity
|
|
469
|
-
context[self.get_io_digest_bounded_context_name()] =
|
|
487
|
+
context[self.get_io_digest_bounded_context_name()] = (
|
|
488
|
+
io_digest_equity.get_io_data()
|
|
489
|
+
)
|
|
470
490
|
|
|
471
491
|
# todo: how is this used??....
|
|
472
492
|
context['date_filter'] = to_date
|
|
@@ -488,17 +508,20 @@ class UnpaidElementsMixIn:
|
|
|
488
508
|
from_date = context['from_date'] if not from_date else from_date
|
|
489
509
|
to_date = context['to_date'] if not to_date else to_date
|
|
490
510
|
|
|
491
|
-
qs =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
Q(date_approved__gte=from_date) &
|
|
496
|
-
|
|
497
|
-
|
|
511
|
+
qs = (
|
|
512
|
+
InvoiceModel.objects.for_entity(entity_model=self.kwargs['entity_slug'])
|
|
513
|
+
.for_user(user_model=self.request.user)
|
|
514
|
+
.approved()
|
|
515
|
+
.filter(Q(date_approved__gte=from_date) & Q(date_approved__lte=to_date))
|
|
516
|
+
.select_related('customer')
|
|
517
|
+
.order_by('date_due')
|
|
518
|
+
)
|
|
498
519
|
|
|
499
520
|
unit_slug = self.get_unit_slug()
|
|
500
521
|
if unit_slug:
|
|
501
|
-
qs = qs.filter(
|
|
522
|
+
qs = qs.filter(
|
|
523
|
+
ledger__journal_entries__entity_unit__slug__exact=unit_slug
|
|
524
|
+
)
|
|
502
525
|
|
|
503
526
|
return qs
|
|
504
527
|
|
|
@@ -507,17 +530,21 @@ class UnpaidElementsMixIn:
|
|
|
507
530
|
from_date = context['from_date'] if not from_date else from_date
|
|
508
531
|
to_date = context['to_date'] if not to_date else to_date
|
|
509
532
|
|
|
510
|
-
qs =
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
Q(date_approved__gte=from_date) &
|
|
515
|
-
|
|
516
|
-
|
|
533
|
+
qs = (
|
|
534
|
+
BillModel.objects.for_entity(entity_model=self.kwargs['entity_slug'])
|
|
535
|
+
.for_user(user_model=self.request.user)
|
|
536
|
+
.unpaid()
|
|
537
|
+
.filter(Q(date_approved__gte=from_date) & Q(date_approved__lte=to_date))
|
|
538
|
+
.select_related('vendor')
|
|
539
|
+
.order_by('date_due')
|
|
540
|
+
)
|
|
517
541
|
|
|
518
542
|
unit_slug = self.get_unit_slug()
|
|
543
|
+
|
|
519
544
|
if unit_slug:
|
|
520
|
-
qs = qs.filter(
|
|
545
|
+
qs = qs.filter(
|
|
546
|
+
ledger__journal_entries__entity_unit__slug__exact=unit_slug
|
|
547
|
+
)
|
|
521
548
|
|
|
522
549
|
return qs
|
|
523
550
|
|
|
@@ -528,7 +555,7 @@ class BaseDateNavigationUrlMixIn:
|
|
|
528
555
|
'unit_slug',
|
|
529
556
|
'ledger_pk',
|
|
530
557
|
'account_pk',
|
|
531
|
-
'coa_slug'
|
|
558
|
+
'coa_slug',
|
|
532
559
|
)
|
|
533
560
|
|
|
534
561
|
def get_context_data(self, **kwargs):
|
|
@@ -542,9 +569,9 @@ class BaseDateNavigationUrlMixIn:
|
|
|
542
569
|
context['date_navigation_url'] = reverse(
|
|
543
570
|
f'django_ledger:{view_name_base}',
|
|
544
571
|
kwargs={
|
|
545
|
-
k: v for k, v in self.kwargs.items() if
|
|
546
|
-
|
|
547
|
-
|
|
572
|
+
k: v for k, v in self.kwargs.items() if k in self.BASE_DATE_URL_KWARGS
|
|
573
|
+
},
|
|
574
|
+
)
|
|
548
575
|
|
|
549
576
|
|
|
550
577
|
class PDFReportMixIn:
|
|
@@ -566,7 +593,9 @@ class PDFReportMixIn:
|
|
|
566
593
|
|
|
567
594
|
def get_pdf_func_name(self):
|
|
568
595
|
if not self.pdf_report_type:
|
|
569
|
-
raise NotImplementedError(
|
|
596
|
+
raise NotImplementedError(
|
|
597
|
+
f'Must define pdf_report_type from {self.PDFReportEnum.__name__}'
|
|
598
|
+
)
|
|
570
599
|
return self.pdf_io_mixin_function_map[self.pdf_report_type]
|
|
571
600
|
|
|
572
601
|
def get_pdf(self):
|
|
@@ -578,7 +607,7 @@ class PDFReportMixIn:
|
|
|
578
607
|
from_date=self.get_pdf_from_date(),
|
|
579
608
|
to_date=self.get_pdf_to_date(),
|
|
580
609
|
user_model=self.request.user,
|
|
581
|
-
subtitle=self.get_pdf_subtitle()
|
|
610
|
+
subtitle=self.get_pdf_subtitle(),
|
|
582
611
|
)
|
|
583
612
|
pdf.create_pdf_report()
|
|
584
613
|
return pdf
|
|
@@ -595,17 +624,20 @@ class PDFReportMixIn:
|
|
|
595
624
|
return ctx['to_date']
|
|
596
625
|
|
|
597
626
|
def get_pdf_response(self) -> HttpResponse:
|
|
598
|
-
if not DJANGO_LEDGER_PDF_SUPPORT_ENABLED:
|
|
599
|
-
return HttpResponseNotFound(content='PDF format is not supported')
|
|
600
627
|
pdf = self.get_pdf()
|
|
601
628
|
response = HttpResponse(
|
|
602
629
|
bytes(pdf.output()),
|
|
603
|
-
content_type=
|
|
630
|
+
content_type='application/pdf',
|
|
631
|
+
)
|
|
632
|
+
response.headers['Content-Disposition'] = (
|
|
633
|
+
f'attachment; filename={pdf.get_pdf_filename()}'
|
|
604
634
|
)
|
|
605
|
-
response.headers['Content-Disposition'] = f'attachment; filename={pdf.get_pdf_filename()}'
|
|
606
635
|
return response
|
|
607
636
|
|
|
608
637
|
def get(self, request, **kwargs):
|
|
609
|
-
if
|
|
638
|
+
if (
|
|
639
|
+
request.GET.get(self.pdf_format_query_param)
|
|
640
|
+
== self.pdf_format_query_param_value
|
|
641
|
+
):
|
|
610
642
|
return self.get_pdf_response()
|
|
611
643
|
return super().get(request, **kwargs)
|
|
@@ -23,21 +23,18 @@ from django_ledger.models import PurchaseOrderModel, ItemTransactionModel, Estim
|
|
|
23
23
|
from django_ledger.views.mixins import DjangoLedgerSecurityMixIn
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class PurchaseOrderModelModelViewQuerySetMixIn:
|
|
26
|
+
class PurchaseOrderModelModelViewQuerySetMixIn(DjangoLedgerSecurityMixIn):
|
|
27
27
|
queryset = None
|
|
28
28
|
|
|
29
29
|
def get_queryset(self):
|
|
30
30
|
if self.queryset is None:
|
|
31
31
|
self.queryset = PurchaseOrderModel.objects.for_entity(
|
|
32
|
-
|
|
33
|
-
user_model=self.request.user
|
|
32
|
+
entity_model=self.AUTHORIZED_ENTITY_MODEL,
|
|
34
33
|
).select_related('entity', 'ce_model')
|
|
35
34
|
return super().get_queryset()
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
class PurchaseOrderModelListView(
|
|
39
|
-
PurchaseOrderModelModelViewQuerySetMixIn,
|
|
40
|
-
ArchiveIndexView):
|
|
37
|
+
class PurchaseOrderModelListView(PurchaseOrderModelModelViewQuerySetMixIn, ArchiveIndexView):
|
|
41
38
|
template_name = 'django_ledger/purchase_order/po_list.html'
|
|
42
39
|
context_object_name = 'po_list'
|
|
43
40
|
PAGE_TITLE = _('PO List')
|
|
@@ -76,9 +73,7 @@ class PurchaseOrderModelMonthListView(MonthArchiveView,
|
|
|
76
73
|
date_list_period = 'year'
|
|
77
74
|
|
|
78
75
|
|
|
79
|
-
class PurchaseOrderModelCreateView(
|
|
80
|
-
PurchaseOrderModelModelViewQuerySetMixIn,
|
|
81
|
-
CreateView):
|
|
76
|
+
class PurchaseOrderModelCreateView(PurchaseOrderModelModelViewQuerySetMixIn, CreateView):
|
|
82
77
|
template_name = 'django_ledger/purchase_order/po_create.html'
|
|
83
78
|
PAGE_TITLE = _('Create Purchase Order')
|
|
84
79
|
extra_context = {
|
|
@@ -92,8 +87,7 @@ class PurchaseOrderModelCreateView(DjangoLedgerSecurityMixIn,
|
|
|
92
87
|
response = super(PurchaseOrderModelCreateView, self).get(request, entity_slug, **kwargs)
|
|
93
88
|
if self.for_estimate and 'ce_pk' in self.kwargs:
|
|
94
89
|
estimate_qs = EstimateModel.objects.for_entity(
|
|
95
|
-
|
|
96
|
-
user_model=self.request.user
|
|
90
|
+
entity_model=self.AUTHORIZED_ENTITY_MODEL,
|
|
97
91
|
)
|
|
98
92
|
estimate_model: EstimateModel = get_object_or_404(estimate_qs, uuid__exact=self.kwargs['ce_pk'])
|
|
99
93
|
if not estimate_model.can_bind():
|
|
@@ -110,8 +104,7 @@ class PurchaseOrderModelCreateView(DjangoLedgerSecurityMixIn,
|
|
|
110
104
|
'ce_pk': self.kwargs['ce_pk']
|
|
111
105
|
})
|
|
112
106
|
estimate_qs = EstimateModel.objects.for_entity(
|
|
113
|
-
|
|
114
|
-
user_model=self.request.user
|
|
107
|
+
entity_model=self.AUTHORIZED_ENTITY_MODEL
|
|
115
108
|
).select_related('customer')
|
|
116
109
|
estimate_model = get_object_or_404(estimate_qs, uuid__exact=self.kwargs['ce_pk'])
|
|
117
110
|
context['estimate_model'] = estimate_model
|
|
@@ -125,23 +118,25 @@ class PurchaseOrderModelCreateView(DjangoLedgerSecurityMixIn,
|
|
|
125
118
|
|
|
126
119
|
def get_form(self, form_class=None):
|
|
127
120
|
entity_slug = self.kwargs['entity_slug']
|
|
128
|
-
form = PurchaseOrderModelCreateForm(
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
form = PurchaseOrderModelCreateForm(
|
|
122
|
+
entity_slug=entity_slug,
|
|
123
|
+
user_model=self.request.user,
|
|
124
|
+
**self.get_form_kwargs()
|
|
125
|
+
)
|
|
131
126
|
return form
|
|
132
127
|
|
|
133
128
|
def form_valid(self, form):
|
|
134
129
|
po_model: PurchaseOrderModel = form.save(commit=False)
|
|
135
130
|
po_model = po_model.configure(
|
|
136
131
|
entity_slug=self.kwargs['entity_slug'],
|
|
137
|
-
user_model=self.request.user
|
|
132
|
+
user_model=self.request.user
|
|
133
|
+
)
|
|
138
134
|
|
|
139
135
|
if self.for_estimate:
|
|
140
136
|
ce_pk = self.kwargs['ce_pk']
|
|
141
137
|
estimate_model_qs = EstimateModel.objects.for_entity(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
138
|
+
entity_model=self.AUTHORIZED_ENTITY_MODEL,
|
|
139
|
+
).for_user(user_model=self.request.user)
|
|
145
140
|
estimate_model = get_object_or_404(estimate_model_qs, uuid__exact=ce_pk)
|
|
146
141
|
po_model.action_bind_estimate(estimate_model=estimate_model, commit=False)
|
|
147
142
|
return super().form_valid(form=form)
|
|
@@ -162,9 +157,7 @@ class PurchaseOrderModelCreateView(DjangoLedgerSecurityMixIn,
|
|
|
162
157
|
})
|
|
163
158
|
|
|
164
159
|
|
|
165
|
-
class PurchaseOrderModelUpdateView(
|
|
166
|
-
PurchaseOrderModelModelViewQuerySetMixIn,
|
|
167
|
-
UpdateView):
|
|
160
|
+
class PurchaseOrderModelUpdateView(PurchaseOrderModelModelViewQuerySetMixIn, UpdateView):
|
|
168
161
|
slug_url_kwarg = 'po_pk'
|
|
169
162
|
slug_field = 'uuid'
|
|
170
163
|
context_object_name = 'po_model'
|
|
@@ -322,8 +315,7 @@ class PurchaseOrderModelUpdateView(DjangoLedgerSecurityMixIn,
|
|
|
322
315
|
|
|
323
316
|
if form.has_changed():
|
|
324
317
|
po_items_qs = ItemTransactionModel.objects.for_po(
|
|
325
|
-
|
|
326
|
-
user_model=self.request.user,
|
|
318
|
+
entity_model=self.kwargs['entity_slug'],
|
|
327
319
|
po_pk=po_model.uuid,
|
|
328
320
|
).select_related('bill_model')
|
|
329
321
|
|
|
@@ -360,9 +352,7 @@ class PurchaseOrderModelUpdateView(DjangoLedgerSecurityMixIn,
|
|
|
360
352
|
return super().form_valid(form)
|
|
361
353
|
|
|
362
354
|
|
|
363
|
-
class PurchaseOrderModelDetailView(
|
|
364
|
-
PurchaseOrderModelModelViewQuerySetMixIn,
|
|
365
|
-
DetailView):
|
|
355
|
+
class PurchaseOrderModelDetailView(PurchaseOrderModelModelViewQuerySetMixIn, DetailView):
|
|
366
356
|
slug_url_kwarg = 'po_pk'
|
|
367
357
|
slug_field = 'uuid'
|
|
368
358
|
context_object_name = 'po_model'
|
|
@@ -390,9 +380,7 @@ class PurchaseOrderModelDetailView(DjangoLedgerSecurityMixIn,
|
|
|
390
380
|
return context
|
|
391
381
|
|
|
392
382
|
|
|
393
|
-
class PurchaseOrderModelDeleteView(
|
|
394
|
-
PurchaseOrderModelModelViewQuerySetMixIn,
|
|
395
|
-
DeleteView):
|
|
383
|
+
class PurchaseOrderModelDeleteView(PurchaseOrderModelModelViewQuerySetMixIn, DeleteView):
|
|
396
384
|
slug_url_kwarg = 'po_pk'
|
|
397
385
|
slug_field = 'uuid'
|
|
398
386
|
context_object_name = 'po_model'
|
|
@@ -436,10 +424,11 @@ class PurchaseOrderModelDeleteView(DjangoLedgerSecurityMixIn,
|
|
|
436
424
|
|
|
437
425
|
|
|
438
426
|
# ACTIONS...
|
|
439
|
-
class BasePurchaseOrderActionActionView(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
427
|
+
class BasePurchaseOrderActionActionView(
|
|
428
|
+
PurchaseOrderModelModelViewQuerySetMixIn,
|
|
429
|
+
RedirectView,
|
|
430
|
+
SingleObjectMixin
|
|
431
|
+
):
|
|
443
432
|
http_method_names = ['get']
|
|
444
433
|
pk_url_kwarg = 'po_pk'
|
|
445
434
|
action_name = None
|