django-ledger 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-ledger might be problematic. Click here for more details.

Files changed (56) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/forms/account.py +45 -46
  3. django_ledger/forms/data_import.py +182 -64
  4. django_ledger/io/io_core.py +507 -374
  5. django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
  6. django_ledger/models/__init__.py +2 -1
  7. django_ledger/models/bill.py +337 -300
  8. django_ledger/models/customer.py +47 -34
  9. django_ledger/models/data_import.py +770 -289
  10. django_ledger/models/entity.py +882 -637
  11. django_ledger/models/mixins.py +421 -282
  12. django_ledger/models/receipt.py +1083 -0
  13. django_ledger/models/transactions.py +105 -41
  14. django_ledger/models/unit.py +42 -30
  15. django_ledger/models/utils.py +12 -2
  16. django_ledger/models/vendor.py +85 -66
  17. django_ledger/settings.py +1 -0
  18. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  19. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +1 -13
  20. django_ledger/templates/django_ledger/bills/bill_update.html +1 -1
  21. django_ledger/templates/django_ledger/components/period_navigator.html +5 -3
  22. django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
  23. django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
  24. django_ledger/templates/django_ledger/customer/tags/customer_table.html +3 -1
  25. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
  26. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
  27. django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
  28. django_ledger/templates/django_ledger/invoice/invoice_update.html +1 -1
  29. django_ledger/templates/django_ledger/layouts/base.html +3 -1
  30. django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
  31. django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
  32. django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
  33. django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
  34. django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
  35. django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
  36. django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +3 -2
  37. django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
  38. django_ledger/templatetags/django_ledger.py +338 -191
  39. django_ledger/urls/__init__.py +1 -0
  40. django_ledger/urls/customer.py +3 -0
  41. django_ledger/urls/data_import.py +3 -0
  42. django_ledger/urls/receipt.py +102 -0
  43. django_ledger/urls/vendor.py +1 -0
  44. django_ledger/views/__init__.py +1 -0
  45. django_ledger/views/customer.py +56 -14
  46. django_ledger/views/data_import.py +119 -66
  47. django_ledger/views/mixins.py +112 -86
  48. django_ledger/views/receipt.py +294 -0
  49. django_ledger/views/vendor.py +53 -14
  50. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/METADATA +1 -1
  51. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/RECORD +55 -45
  52. django_ledger/static/django_ledger/bundle/styles.bundle.js +0 -1
  53. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/WHEEL +0 -0
  54. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/licenses/AUTHORS.md +0 -0
  55. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/licenses/LICENSE +0 -0
  56. {django_ledger-0.8.0.dist-info → django_ledger-0.8.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,294 @@
1
+ """
2
+ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
+ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
+
5
+ Contributions to this module:
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
+ """
8
+
9
+ from typing import Optional
10
+ from calendar import month_name
11
+
12
+ from django.http import HttpResponseForbidden
13
+ from django.shortcuts import get_object_or_404
14
+ from django.utils.translation import gettext_lazy as _
15
+ from django.views.generic import (
16
+ ArchiveIndexView,
17
+ DeleteView,
18
+ DetailView,
19
+ MonthArchiveView,
20
+ YearArchiveView,
21
+ )
22
+
23
+ from django_ledger.models import CustomerModel, EntityModel, LedgerModel, VendorModel
24
+ from django_ledger.models.receipt import ReceiptModel, ReceiptModelQuerySet
25
+ from django_ledger.models.transactions import TransactionModel
26
+ from django_ledger.views.mixins import (
27
+ DjangoLedgerSecurityMixIn,
28
+ QuarterlyReportMixIn,
29
+ )
30
+
31
+
32
+ class BaseReceiptModelViewMixIn(DjangoLedgerSecurityMixIn):
33
+ queryset: Optional[ReceiptModelQuerySet] = None
34
+
35
+ def get_queryset(self):
36
+ if self.queryset is None:
37
+ entity_model: EntityModel = self.AUTHORIZED_ENTITY_MODEL
38
+ qs = entity_model.get_receipts()
39
+ qs = qs.select_related(
40
+ 'ledger_model', 'customer_model', 'vendor_model'
41
+ ).order_by('-receipt_date', '-created')
42
+
43
+ receipt_type = self.kwargs.get('receipt_type')
44
+ if receipt_type:
45
+ qs = qs.filter(receipt_type__exact=receipt_type)
46
+ if receipt_type in [
47
+ ReceiptModel.SALES_RECEIPT,
48
+ ReceiptModel.SALES_REFUND,
49
+ ]:
50
+ qs = qs.filter(
51
+ customer_model__isnull=False, vendor_model__isnull=True
52
+ )
53
+
54
+ elif receipt_type in [
55
+ ReceiptModel.EXPENSE_RECEIPT,
56
+ ReceiptModel.EXPENSE_REFUND,
57
+ ]:
58
+ qs = qs.filter(
59
+ vendor_model__isnull=False, customer_model__isnull=True
60
+ )
61
+
62
+ vendor_pk = self.kwargs.get('vendor_pk')
63
+ if vendor_pk:
64
+ qs = qs.for_vendor(vendor_model=vendor_pk)
65
+
66
+ customer_pk = self.kwargs.get('customer_pk')
67
+ if customer_pk:
68
+ qs = qs.for_customer(customer_model=customer_pk)
69
+
70
+ self.queryset = qs
71
+
72
+ return self.queryset
73
+
74
+
75
+ class ReceiptModelListView(BaseReceiptModelViewMixIn, ArchiveIndexView):
76
+ template_name = 'django_ledger/receipt/receipt_list.html'
77
+ context_object_name = 'receipt_list'
78
+ PAGE_TITLE = _('Receipts List')
79
+ date_field = 'receipt_date'
80
+ paginate_by = 20
81
+ paginate_orphans = 2
82
+ allow_empty = True
83
+ extra_context = {
84
+ 'title': PAGE_TITLE,
85
+ 'page_title': PAGE_TITLE,
86
+ 'header_title': PAGE_TITLE,
87
+ 'header_subtitle_icon': 'mdi:receipt',
88
+ }
89
+
90
+ def get_context_data(self, **kwargs):
91
+ context = super().get_context_data(**kwargs)
92
+ subtitle = None
93
+
94
+ receipt_type = self.kwargs.get('receipt_type')
95
+
96
+ if receipt_type:
97
+ context['title'] = ReceiptModel.RECEIPT_TYPES_MAP[receipt_type]
98
+
99
+ vendor_pk = self.kwargs.get('vendor_pk')
100
+ if vendor_pk:
101
+ vendor = VendorModel.objects.for_entity(
102
+ entity_model=self.AUTHORIZED_ENTITY_MODEL
103
+ ).get(uuid__exact=vendor_pk)
104
+ subtitle = vendor.vendor_name
105
+ customer_pk = self.kwargs.get('customer_pk')
106
+
107
+ if customer_pk:
108
+ customer = CustomerModel.objects.for_entity(
109
+ entity_model=self.AUTHORIZED_ENTITY_MODEL
110
+ ).get(uuid__exact=customer_pk)
111
+ subtitle = customer.customer_name
112
+
113
+ if subtitle:
114
+ context['header_subtitle'] = subtitle
115
+ return context
116
+
117
+
118
+ class ReceiptModelYearListView(ReceiptModelListView, YearArchiveView):
119
+ def get_context_data(self, **kwargs):
120
+ context = super().get_context_data(**kwargs)
121
+ context['year'] = self.get_year()
122
+ context['page_title'] = _(f'Receipts List {self.get_year()}')
123
+ context['header_title'] = _(f'Receipts List {self.get_year()}')
124
+ context['header_subtitle'] = self.AUTHORIZED_ENTITY_MODEL.name
125
+ return context
126
+
127
+
128
+ class ReceiptModelQuarterListView(ReceiptModelYearListView, QuarterlyReportMixIn):
129
+ def get_queryset(self):
130
+ qs = super().get_queryset()
131
+ return qs.for_dates(from_date=self.get_from_date(), to_date=self.get_to_date())
132
+
133
+ def get_context_data(self, **kwargs):
134
+ context = super().get_context_data(**kwargs)
135
+ context['page_title'] = _(f'Receipts List Q{self.get_quarter()}')
136
+ context['header_title'] = _(f'Receipts List Q{self.get_quarter()}')
137
+ context['header_subtitle'] = self.AUTHORIZED_ENTITY_MODEL.name
138
+ return context
139
+
140
+
141
+ class ReceiptModelMonthListView(ReceiptModelYearListView, MonthArchiveView):
142
+ def get_context_data(self, **kwargs):
143
+ context = super().get_context_data(**kwargs)
144
+ year = self.get_year()
145
+ month_num = int(self.get_month())
146
+ month_label = month_name[month_num]
147
+ context['page_title'] = _(f'Receipts List {month_label} {year}')
148
+ context['header_title'] = _(f'Receipts List {month_label}, {year}')
149
+ context['header_subtitle'] = self.AUTHORIZED_ENTITY_MODEL.name
150
+ return context
151
+
152
+
153
+ class ReceiptModelDetailView(BaseReceiptModelViewMixIn, DetailView):
154
+ template_name = 'django_ledger/receipt/receipt_detail.html'
155
+ context_object_name = 'receipt'
156
+ slug_field = 'uuid'
157
+ slug_url_kwarg = 'receipt_pk'
158
+
159
+ def get_context_data(self, **kwargs):
160
+ context = super().get_context_data(**kwargs)
161
+ receipt_model: ReceiptModel = self.object
162
+ ledger_model: LedgerModel = receipt_model.ledger_model
163
+ title = _(f'Receipt {receipt_model.receipt_number}')
164
+ context['page_title'] = title
165
+ context['header_title'] = title
166
+ context['header_subtitle'] = receipt_model.receipt_date
167
+ context['header_subtitle_icon'] = 'mdi:receipt'
168
+
169
+ tx_list = (
170
+ TransactionModel.objects.for_entity(
171
+ entity_model=self.AUTHORIZED_ENTITY_MODEL
172
+ )
173
+ .for_ledger(ledger_model=ledger_model)
174
+ .posted()
175
+ .not_closing_entry()
176
+ .select_related(
177
+ 'account',
178
+ 'journal_entry',
179
+ 'journal_entry__entity_unit',
180
+ )
181
+ .order_by('journal_entry__timestamp', 'account__code')
182
+ )
183
+
184
+ context['tx_list'] = tx_list
185
+ context['staged_tx'] = receipt_model.staged_transaction_model
186
+ if receipt_model.staged_transaction_model_id:
187
+ context['import_job'] = receipt_model.staged_transaction_model.import_job
188
+ return context
189
+
190
+
191
+ # VENDOR VIEWS......
192
+ class VendorReceiptReportListView(ReceiptModelListView):
193
+ template_name = 'django_ledger/receipt/vendor_receipt_report.html'
194
+
195
+ def get_context_data(self, **kwargs):
196
+ context = super().get_context_data(**kwargs)
197
+ vendor_pk = self.kwargs['vendor_pk']
198
+ vendor_model_qs = VendorModel.objects.for_entity(
199
+ entity_model=self.AUTHORIZED_ENTITY_MODEL
200
+ )
201
+ vendor_model: VendorModel = get_object_or_404(
202
+ vendor_model_qs, uuid__exact=vendor_pk
203
+ )
204
+ context['vendor_model'] = vendor_model
205
+ context['page_title'] = _(f'Vendor Receipts {vendor_model.vendor_name}')
206
+ context['header_title'] = _('Vendor Receipts')
207
+ context['header_subtitle'] = vendor_model.vendor_name
208
+ context['header_subtitle_icon'] = 'mdi:receipt'
209
+ return context
210
+
211
+
212
+ class VendorReceiptReportYearListView(ReceiptModelYearListView):
213
+ template_name = 'django_ledger/receipt/vendor_receipt_report.html'
214
+
215
+
216
+ class VendorReceiptReportQuarterListView(ReceiptModelQuarterListView):
217
+ template_name = 'django_ledger/receipt/vendor_receipt_report.html'
218
+
219
+
220
+ class VendorReceiptReportMonthListView(ReceiptModelMonthListView):
221
+ template_name = 'django_ledger/receipt/vendor_receipt_report.html'
222
+
223
+
224
+ # CUSTOMERS VIEWS......
225
+ class CustomerReceiptReportListView(ReceiptModelListView):
226
+ template_name = 'django_ledger/receipt/customer_receipt_report.html'
227
+ allow_empty = True
228
+
229
+ def get_context_data(self, **kwargs):
230
+ context = super().get_context_data(**kwargs)
231
+ customer_model_qs = CustomerModel.objects.for_entity(
232
+ entity_model=self.AUTHORIZED_ENTITY_MODEL
233
+ )
234
+ customer_pk = self.kwargs['customer_pk']
235
+ customer_model = get_object_or_404(
236
+ customer_model_qs,
237
+ uuid__exact=customer_pk,
238
+ )
239
+ context['vendor_model'] = customer_model
240
+ context['page_title'] = _(f'Customer Receipts {customer_model.name}')
241
+ context['header_title'] = _('Customer Receipts')
242
+ context['header_subtitle'] = customer_model.vendor_name
243
+ context['header_subtitle_icon'] = 'mdi:receipt'
244
+ return context
245
+
246
+
247
+ class CustomerReceiptReportYearListView(CustomerReceiptReportListView):
248
+ template_name = 'django_ledger/receipt/customer_receipt_report.html'
249
+ make_object_list = True
250
+
251
+
252
+ class CustomerReceiptReportQuarterListView(ReceiptModelQuarterListView):
253
+ template_name = 'django_ledger/receipt/customer_receipt_report.html'
254
+ make_object_list = True
255
+
256
+
257
+ class CustomerReceiptReportMonthListView(ReceiptModelMonthListView):
258
+ template_name = 'django_ledger/receipt/customer_receipt_report.html'
259
+ month_format = '%m'
260
+
261
+
262
+ class ReceiptModelDeleteView(BaseReceiptModelViewMixIn, DeleteView):
263
+ template_name = 'django_ledger/receipt/receipt_delete.html'
264
+ context_object_name = 'receipt'
265
+ slug_field = 'uuid'
266
+ slug_url_kwarg = 'receipt_pk'
267
+
268
+ def get_context_data(self, **kwargs):
269
+ context = super().get_context_data(**kwargs)
270
+ receipt: ReceiptModel = self.object
271
+ title = _(f'Delete Receipt {receipt.receipt_number}')
272
+ context['page_title'] = title
273
+ context['header_title'] = title
274
+ context['header_subtitle_icon'] = 'mdi:receipt'
275
+ return context
276
+
277
+ def can_delete(self, receipt_model: ReceiptModel) -> bool:
278
+ entity_model: EntityModel = self.AUTHORIZED_ENTITY_MODEL
279
+ ce_date = entity_model.get_closing_entry_for_date(
280
+ io_date=receipt_model.receipt_date, inclusive=True
281
+ )
282
+ return ce_date is None
283
+
284
+ def delete(self, request, *args, **kwargs):
285
+ receipt_model: ReceiptModel = self.object
286
+ if not receipt_model.can_delete():
287
+ return HttpResponseForbidden(
288
+ 'Receipt cannot be deleted because it falls within a closed period.'
289
+ )
290
+ return super().delete(request, *args, **kwargs)
291
+
292
+ def get_success_url(self):
293
+ receipt_model: ReceiptModel = self.object
294
+ return receipt_model.get_list_url()
@@ -5,13 +5,16 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
5
5
  Contributions to this module:
6
6
  * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
+
8
9
  from django.shortcuts import get_object_or_404
9
10
  from django.urls import reverse
10
11
  from django.utils.translation import gettext_lazy as _
11
- from django.views.generic import ListView, CreateView, UpdateView
12
+ from django.views.generic import CreateView, DetailView, ListView, UpdateView
12
13
 
13
14
  from django_ledger.forms.vendor import VendorModelForm
15
+ from django_ledger.models.bill import BillModel
14
16
  from django_ledger.models.entity import EntityModel
17
+ from django_ledger.models.receipt import ReceiptModel
15
18
  from django_ledger.models.vendor import VendorModel
16
19
  from django_ledger.views.mixins import DjangoLedgerSecurityMixIn
17
20
 
@@ -34,7 +37,7 @@ class VendorModelListView(VendorModelModelBaseView, ListView):
34
37
  extra_context = {
35
38
  'page_title': PAGE_TITLE,
36
39
  'header_title': PAGE_TITLE,
37
- 'header_subtitle_icon': 'bi:person-lines-fill'
40
+ 'header_subtitle_icon': 'bi:person-lines-fill',
38
41
  }
39
42
 
40
43
  def get_context_data(self, **kwargs):
@@ -52,21 +55,21 @@ class VendorModelCreateView(VendorModelModelBaseView, CreateView):
52
55
  extra_context = {
53
56
  'page_title': PAGE_TITLE,
54
57
  'header_title': PAGE_TITLE,
55
- 'header_subtitle_icon': 'bi:person-lines-fill'
58
+ 'header_subtitle_icon': 'bi:person-lines-fill',
56
59
  }
57
60
 
58
61
  def get_success_url(self):
59
- return reverse('django_ledger:vendor-list',
60
- kwargs={
61
- 'entity_slug': self.kwargs['entity_slug']
62
- })
62
+ return reverse(
63
+ 'django_ledger:vendor-list',
64
+ kwargs={'entity_slug': self.kwargs['entity_slug']},
65
+ )
63
66
 
64
67
  def form_valid(self, form):
65
68
  vendor_model: VendorModel = form.save(commit=False)
66
- entity_model_qs = EntityModel.objects.for_user(
67
- user_model=self.request.user
69
+ entity_model_qs = EntityModel.objects.for_user(user_model=self.request.user)
70
+ entity_model = get_object_or_404(
71
+ klass=entity_model_qs, slug__exact=self.kwargs['entity_slug']
68
72
  )
69
- entity_model = get_object_or_404(klass=entity_model_qs, slug__exact=self.kwargs['entity_slug'])
70
73
  vendor_model.entity_model = entity_model
71
74
  return super().form_valid(form)
72
75
 
@@ -90,7 +93,43 @@ class VendorModelUpdateView(VendorModelModelBaseView, UpdateView):
90
93
  return context
91
94
 
92
95
  def get_success_url(self):
93
- return reverse('django_ledger:vendor-list',
94
- kwargs={
95
- 'entity_slug': self.kwargs['entity_slug']
96
- })
96
+ return reverse(
97
+ 'django_ledger:vendor-list',
98
+ kwargs={'entity_slug': self.kwargs['entity_slug']},
99
+ )
100
+
101
+
102
+ class VendorModelDetailView(VendorModelModelBaseView, DetailView):
103
+ template_name = 'django_ledger/vendor/vendor_detail.html'
104
+ context_object_name = 'vendor'
105
+ PAGE_TITLE = _('Vendor Details')
106
+ slug_url_kwarg = 'vendor_pk'
107
+ slug_field = 'uuid'
108
+
109
+ def get_context_data(self, **kwargs):
110
+ context = super().get_context_data(**kwargs)
111
+
112
+ vendor_model: VendorModel = self.object
113
+ receipts_qs = (
114
+ ReceiptModel.objects.for_entity(entity_model=self.AUTHORIZED_ENTITY_MODEL)
115
+ .for_vendor(vendor_model=vendor_model)
116
+ .order_by('-updated')
117
+ )
118
+
119
+ bills_qs = (
120
+ BillModel.objects.for_entity(entity_model=self.AUTHORIZED_ENTITY_MODEL)
121
+ .filter(vendor=vendor_model)
122
+ .order_by('-updated')
123
+ )
124
+
125
+ context.update(
126
+ {
127
+ 'page_title': self.PAGE_TITLE,
128
+ 'header_title': self.PAGE_TITLE,
129
+ 'header_subtitle': f'{vendor_model.vendor_name} · {vendor_model.vendor_number}',
130
+ 'header_subtitle_icon': 'bi:person-lines-fill',
131
+ 'receipts': receipts_qs,
132
+ 'bills': bills_qs,
133
+ }
134
+ )
135
+ return context
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ledger
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Double entry accounting system built on the Django Web Framework.
5
5
  Author-email: Miguel Sanda <msanda@arrobalytics.com>
6
6
  Maintainer-email: Miguel Sanda <msanda@arrobalytics.com>