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.

Files changed (139) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/context.py +12 -0
  3. django_ledger/forms/account.py +45 -46
  4. django_ledger/forms/bill.py +0 -4
  5. django_ledger/forms/closing_entry.py +13 -1
  6. django_ledger/forms/data_import.py +182 -63
  7. django_ledger/forms/estimate.py +3 -6
  8. django_ledger/forms/invoice.py +3 -7
  9. django_ledger/forms/item.py +10 -18
  10. django_ledger/forms/purchase_order.py +2 -4
  11. django_ledger/io/io_core.py +515 -400
  12. django_ledger/io/io_generator.py +7 -6
  13. django_ledger/io/io_library.py +1 -2
  14. django_ledger/migrations/0025_alter_billmodel_cash_account_and_more.py +70 -0
  15. django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py +56 -0
  16. django_ledger/models/__init__.py +2 -1
  17. django_ledger/models/accounts.py +109 -69
  18. django_ledger/models/bank_account.py +40 -23
  19. django_ledger/models/bill.py +386 -333
  20. django_ledger/models/chart_of_accounts.py +173 -105
  21. django_ledger/models/closing_entry.py +99 -48
  22. django_ledger/models/customer.py +100 -66
  23. django_ledger/models/data_import.py +818 -323
  24. django_ledger/models/deprecations.py +61 -0
  25. django_ledger/models/entity.py +891 -644
  26. django_ledger/models/estimate.py +57 -28
  27. django_ledger/models/invoice.py +46 -26
  28. django_ledger/models/items.py +503 -142
  29. django_ledger/models/journal_entry.py +61 -47
  30. django_ledger/models/ledger.py +106 -42
  31. django_ledger/models/mixins.py +424 -281
  32. django_ledger/models/purchase_order.py +39 -17
  33. django_ledger/models/receipt.py +1083 -0
  34. django_ledger/models/transactions.py +242 -139
  35. django_ledger/models/unit.py +93 -54
  36. django_ledger/models/utils.py +12 -2
  37. django_ledger/models/vendor.py +121 -70
  38. django_ledger/report/core.py +2 -14
  39. django_ledger/settings.py +57 -71
  40. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  41. django_ledger/static/django_ledger/bundle/djetler.bundle.js.LICENSE.txt +25 -0
  42. django_ledger/static/django_ledger/bundle/styles.bundle.js +1 -1
  43. django_ledger/static/django_ledger/css/djl_styles.css +273 -0
  44. django_ledger/templates/django_ledger/bills/includes/card_bill.html +2 -2
  45. django_ledger/templates/django_ledger/components/menu.html +41 -26
  46. django_ledger/templates/django_ledger/components/period_navigator.html +5 -3
  47. django_ledger/templates/django_ledger/customer/customer_detail.html +87 -0
  48. django_ledger/templates/django_ledger/customer/customer_list.html +0 -1
  49. django_ledger/templates/django_ledger/customer/tags/customer_table.html +8 -6
  50. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +24 -3
  51. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +26 -10
  52. django_ledger/templates/django_ledger/entity/entity_dashboard.html +2 -2
  53. django_ledger/templates/django_ledger/entity/includes/card_entity.html +12 -6
  54. django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +1 -1
  55. django_ledger/templates/django_ledger/financial_statements/cash_flow.html +4 -1
  56. django_ledger/templates/django_ledger/financial_statements/income_statement.html +4 -1
  57. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +27 -3
  58. django_ledger/templates/django_ledger/financial_statements/tags/cash_flow_statement.html +16 -4
  59. django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +73 -18
  60. django_ledger/templates/django_ledger/includes/widget_ratios.html +18 -24
  61. django_ledger/templates/django_ledger/invoice/includes/card_invoice.html +3 -3
  62. django_ledger/templates/django_ledger/layouts/base.html +7 -2
  63. django_ledger/templates/django_ledger/layouts/content_layout_1.html +1 -1
  64. django_ledger/templates/django_ledger/receipt/customer_receipt_report.html +115 -0
  65. django_ledger/templates/django_ledger/receipt/receipt_delete.html +30 -0
  66. django_ledger/templates/django_ledger/receipt/receipt_detail.html +89 -0
  67. django_ledger/templates/django_ledger/receipt/receipt_list.html +134 -0
  68. django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html +115 -0
  69. django_ledger/templates/django_ledger/vendor/tags/vendor_table.html +12 -7
  70. django_ledger/templates/django_ledger/vendor/vendor_detail.html +86 -0
  71. django_ledger/templatetags/django_ledger.py +338 -191
  72. django_ledger/tests/test_accounts.py +1 -2
  73. django_ledger/tests/test_io.py +17 -0
  74. django_ledger/tests/test_purchase_order.py +3 -3
  75. django_ledger/tests/test_transactions.py +1 -2
  76. django_ledger/urls/__init__.py +1 -4
  77. django_ledger/urls/customer.py +3 -0
  78. django_ledger/urls/data_import.py +3 -0
  79. django_ledger/urls/receipt.py +102 -0
  80. django_ledger/urls/vendor.py +1 -0
  81. django_ledger/views/__init__.py +1 -0
  82. django_ledger/views/bill.py +8 -11
  83. django_ledger/views/chart_of_accounts.py +6 -4
  84. django_ledger/views/closing_entry.py +11 -7
  85. django_ledger/views/customer.py +68 -30
  86. django_ledger/views/data_import.py +120 -66
  87. django_ledger/views/djl_api.py +3 -5
  88. django_ledger/views/entity.py +2 -4
  89. django_ledger/views/estimate.py +3 -7
  90. django_ledger/views/inventory.py +3 -5
  91. django_ledger/views/invoice.py +4 -6
  92. django_ledger/views/item.py +7 -11
  93. django_ledger/views/journal_entry.py +1 -2
  94. django_ledger/views/mixins.py +125 -93
  95. django_ledger/views/purchase_order.py +24 -35
  96. django_ledger/views/receipt.py +294 -0
  97. django_ledger/views/unit.py +1 -2
  98. django_ledger/views/vendor.py +54 -16
  99. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/METADATA +43 -75
  100. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/RECORD +104 -122
  101. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info}/WHEEL +1 -1
  102. django_ledger-0.8.1.dist-info/top_level.txt +1 -0
  103. django_ledger/contrib/django_ledger_graphene/__init__.py +0 -0
  104. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +0 -33
  105. django_ledger/contrib/django_ledger_graphene/api.py +0 -42
  106. django_ledger/contrib/django_ledger_graphene/apps.py +0 -6
  107. django_ledger/contrib/django_ledger_graphene/auth/mutations.py +0 -49
  108. django_ledger/contrib/django_ledger_graphene/auth/schema.py +0 -6
  109. django_ledger/contrib/django_ledger_graphene/bank_account/mutations.py +0 -61
  110. django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +0 -34
  111. django_ledger/contrib/django_ledger_graphene/bill/mutations.py +0 -0
  112. django_ledger/contrib/django_ledger_graphene/bill/schema.py +0 -34
  113. django_ledger/contrib/django_ledger_graphene/coa/mutations.py +0 -0
  114. django_ledger/contrib/django_ledger_graphene/coa/schema.py +0 -30
  115. django_ledger/contrib/django_ledger_graphene/customers/__init__.py +0 -0
  116. django_ledger/contrib/django_ledger_graphene/customers/mutations.py +0 -71
  117. django_ledger/contrib/django_ledger_graphene/customers/schema.py +0 -43
  118. django_ledger/contrib/django_ledger_graphene/data_import/mutations.py +0 -0
  119. django_ledger/contrib/django_ledger_graphene/data_import/schema.py +0 -0
  120. django_ledger/contrib/django_ledger_graphene/entity/mutations.py +0 -0
  121. django_ledger/contrib/django_ledger_graphene/entity/schema.py +0 -94
  122. django_ledger/contrib/django_ledger_graphene/item/mutations.py +0 -0
  123. django_ledger/contrib/django_ledger_graphene/item/schema.py +0 -31
  124. django_ledger/contrib/django_ledger_graphene/journal_entry/mutations.py +0 -0
  125. django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +0 -35
  126. django_ledger/contrib/django_ledger_graphene/ledger/mutations.py +0 -0
  127. django_ledger/contrib/django_ledger_graphene/ledger/schema.py +0 -32
  128. django_ledger/contrib/django_ledger_graphene/purchase_order/mutations.py +0 -0
  129. django_ledger/contrib/django_ledger_graphene/purchase_order/schema.py +0 -31
  130. django_ledger/contrib/django_ledger_graphene/transaction/mutations.py +0 -0
  131. django_ledger/contrib/django_ledger_graphene/transaction/schema.py +0 -36
  132. django_ledger/contrib/django_ledger_graphene/unit/mutations.py +0 -0
  133. django_ledger/contrib/django_ledger_graphene/unit/schema.py +0 -27
  134. django_ledger/contrib/django_ledger_graphene/vendor/mutations.py +0 -0
  135. django_ledger/contrib/django_ledger_graphene/vendor/schema.py +0 -37
  136. django_ledger/contrib/django_ledger_graphene/views.py +0 -12
  137. django_ledger-0.7.11.dist-info/top_level.txt +0 -4
  138. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info/licenses}/AUTHORS.md +0 -0
  139. {django_ledger-0.7.11.dist-info → django_ledger-0.8.1.dist-info/licenses}/LICENSE +0 -0
@@ -95,8 +95,7 @@ class AccountModelTests(DjangoLedgerBaseTest):
95
95
  self.assertEqual(response_create.status_code, 302)
96
96
  self.assertTrue(AccountModel.objects.for_entity(
97
97
  entity_model=entity_model,
98
- user_model=self.user_model,
99
- coa_slug=entity_model.default_coa_slug,
98
+ coa_model=entity_model.default_coa_slug,
100
99
  ).with_codes(codes=NEW_ACCOUNT_CODE).exists())
101
100
 
102
101
  # cannot create an account with same code again...
@@ -195,3 +195,20 @@ class IOTest(DjangoLedgerBaseTest):
195
195
  # )
196
196
  #
197
197
  # self.assertEqual(io_digest.get_io_txs_queryset().count(), 0)
198
+
199
+ def test_io_transactions_belong_to_entity(self):
200
+ entity_model = self.get_random_entity_model()
201
+ from_datetime = self.START_DATE
202
+ to_datetime = self.START_DATE + timedelta(days=randint(10, 60))
203
+
204
+ io_digest = entity_model.digest(
205
+ entity_slug=entity_model.slug,
206
+ from_date=from_datetime,
207
+ to_date=to_datetime,
208
+ for_test=True
209
+ )
210
+
211
+ tx_qs = io_digest.get_io_txs_queryset()
212
+ # Every transaction returned by the IO for an entity digest must belong to that entity.
213
+ for tx in tx_qs:
214
+ self.assertEqual(tx['journal_entry__ledger__entity_id'], entity_model.uuid)
@@ -1,6 +1,6 @@
1
1
  from datetime import date, datetime
2
2
  from random import choice, randint
3
- from typing import Union, Optional
3
+ from typing import Union, Optional, List
4
4
  from urllib.parse import urlparse
5
5
  from uuid import uuid4
6
6
 
@@ -31,8 +31,8 @@ class PurchaseOrderModelTests(DjangoLedgerBaseTest):
31
31
  commit=True)
32
32
  return po_model
33
33
 
34
- def get_purchase_orders(self, entity_model: EntityModel) -> list[PurchaseOrderModel]:
35
- return PurchaseOrderModel.objects.for_entity(entity_model, self.user_model)
34
+ def get_purchase_orders(self, entity_model: EntityModel) -> List[PurchaseOrderModel]:
35
+ return PurchaseOrderModel.objects.for_entity(entity_model=entity_model)
36
36
 
37
37
  def test_protected_views(self):
38
38
  """
@@ -96,8 +96,7 @@ class TransactionModelFormSetTest(DjangoLedgerBaseTest):
96
96
 
97
97
  # get a journal entry that has transactions...
98
98
  je_model = JournalEntryModel.objects.for_entity(
99
- entity_slug=entity_model,
100
- user_model=self.user_model
99
+ entity_model=entity_model
101
100
  ).annotate(
102
101
  txs_count=Count('transactionmodel')).filter(
103
102
  txs_count__gt=0).order_by('-timestamp').first()
@@ -5,11 +5,9 @@ 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
-
9
8
  from django.urls import path, include
10
9
 
11
10
  from django_ledger import views
12
- from django_ledger.settings import DJANGO_LEDGER_GRAPHQL_SUPPORT_ENABLED
13
11
 
14
12
  app_name = 'django_ledger'
15
13
 
@@ -37,9 +35,8 @@ urlpatterns = [
37
35
  path('feedback/', include('django_ledger.urls.feedback')),
38
36
  path('inventory/', include('django_ledger.urls.inventory')),
39
37
  path('home/', include('django_ledger.urls.home')),
38
+ path('receipt/', include('django_ledger.urls.receipt')),
40
39
  path('api/', include('django_ledger.urls.djl_api')),
41
40
  path('', views.RootUrlView.as_view(), name='root'),
42
41
  ]
43
42
 
44
- if DJANGO_LEDGER_GRAPHQL_SUPPORT_ENABLED:
45
- pass
@@ -7,4 +7,7 @@ urlpatterns = [
7
7
  path('<slug:entity_slug>/update/<uuid:customer_pk>/',
8
8
  views.CustomerModelUpdateView.as_view(),
9
9
  name='customer-update'),
10
+ path('<slug:entity_slug>/detail/<uuid:customer_pk>/',
11
+ views.CustomerModelDetailView.as_view(),
12
+ name='customer-detail'),
10
13
  ]
@@ -18,4 +18,7 @@ urlpatterns = [
18
18
  path('<slug:entity_slug>/jobs/<uuid:job_pk>/txs/',
19
19
  views.DataImportJobDetailView.as_view(),
20
20
  name='data-import-job-txs'),
21
+ path('<slug:entity_slug>/jobs/<uuid:job_pk>/txs/<uuid:staged_tx_pk>/undo/',
22
+ views.StagedTransactionUndoView.as_view(),
23
+ name='data-import-staged-tx-undo'),
21
24
  ]
@@ -0,0 +1,102 @@
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 django.urls import path
10
+
11
+ from django_ledger import views
12
+
13
+ urlpatterns = [
14
+ path(
15
+ '<slug:entity_slug>/latest/',
16
+ views.ReceiptModelListView.as_view(),
17
+ name='receipt-list',
18
+ ),
19
+ path(
20
+ '<slug:entity_slug>/year/<int:year>/',
21
+ views.ReceiptModelYearListView.as_view(),
22
+ name='receipt-list-year',
23
+ ),
24
+ path(
25
+ '<slug:entity_slug>/quarter/<int:year>/q<int:quarter>/',
26
+ views.ReceiptModelQuarterListView.as_view(),
27
+ name='receipt-list-quarter',
28
+ ),
29
+ path(
30
+ '<slug:entity_slug>/month/<int:year>/<int:month>/',
31
+ views.ReceiptModelMonthListView.as_view(),
32
+ name='receipt-list-month',
33
+ ),
34
+ path(
35
+ '<slug:entity_slug>/detail/<uuid:receipt_pk>/',
36
+ views.ReceiptModelDetailView.as_view(),
37
+ name='receipt-detail',
38
+ ),
39
+ path(
40
+ '<slug:entity_slug>/delete/<uuid:receipt_pk>/',
41
+ views.ReceiptModelDeleteView.as_view(),
42
+ name='receipt-delete',
43
+ ),
44
+ # Filtered lists
45
+ path(
46
+ '<slug:entity_slug>/type/<str:receipt_type>/',
47
+ views.ReceiptModelListView.as_view(),
48
+ name='receipt-list-type',
49
+ ),
50
+ path(
51
+ '<slug:entity_slug>/vendor/<uuid:vendor_pk>/',
52
+ views.ReceiptModelListView.as_view(),
53
+ name='receipt-list-vendor',
54
+ ),
55
+ path(
56
+ '<slug:entity_slug>/customer/<uuid:customer_pk>/',
57
+ views.ReceiptModelListView.as_view(),
58
+ name='receipt-list-customer',
59
+ ),
60
+ # Reports: Vendor
61
+ path(
62
+ '<slug:entity_slug>/report/vendor/<uuid:vendor_pk>/latest/',
63
+ views.VendorReceiptReportListView.as_view(),
64
+ name='receipt-report-vendor',
65
+ ),
66
+ path(
67
+ '<slug:entity_slug>/report/vendor/<uuid:vendor_pk>/year/<int:year>/',
68
+ views.VendorReceiptReportYearListView.as_view(),
69
+ name='receipt-report-vendor-year',
70
+ ),
71
+ path(
72
+ '<slug:entity_slug>/report/vendor/<uuid:vendor_pk>/quarter/<int:year>/q<int:quarter>/',
73
+ views.VendorReceiptReportQuarterListView.as_view(),
74
+ name='receipt-report-vendor-quarter',
75
+ ),
76
+ path(
77
+ '<slug:entity_slug>/report/vendor/<uuid:vendor_pk>/month/<int:year>/<int:month>/',
78
+ views.VendorReceiptReportMonthListView.as_view(),
79
+ name='receipt-report-vendor-month',
80
+ ),
81
+ # Reports: Customer
82
+ path(
83
+ '<slug:entity_slug>/report/customer/<uuid:customer_pk>/latest/',
84
+ views.CustomerReceiptReportListView.as_view(),
85
+ name='receipt-report-customer',
86
+ ),
87
+ path(
88
+ '<slug:entity_slug>/report/customer/<uuid:customer_pk>/year/<int:year>/',
89
+ views.CustomerReceiptReportYearListView.as_view(),
90
+ name='receipt-report-customer-year',
91
+ ),
92
+ path(
93
+ '<slug:entity_slug>/report/customer/<uuid:customer_pk>/quarter/<int:year>/q<int:quarter>/',
94
+ views.CustomerReceiptReportQuarterListView.as_view(),
95
+ name='receipt-report-customer-quarter',
96
+ ),
97
+ path(
98
+ '<slug:entity_slug>/report/customer/<uuid:customer_pk>/month/<int:year>/<int:month>/',
99
+ views.CustomerReceiptReportMonthListView.as_view(),
100
+ name='receipt-report-customer-month',
101
+ ),
102
+ ]
@@ -5,4 +5,5 @@ urlpatterns = [
5
5
  path('<slug:entity_slug>/list/', views.VendorModelListView.as_view(), name='vendor-list'),
6
6
  path('<slug:entity_slug>/create/', views.VendorModelCreateView.as_view(), name='vendor-create'),
7
7
  path('<slug:entity_slug>/update/<uuid:vendor_pk>/', views.VendorModelUpdateView.as_view(), name='vendor-update'),
8
+ path('<slug:entity_slug>/detail/<uuid:vendor_pk>/', views.VendorModelDetailView.as_view(), name='vendor-detail'),
8
9
  ]
@@ -28,3 +28,4 @@ from django_ledger.views.transactions import *
28
28
  from django_ledger.views.unit import *
29
29
  from django_ledger.views.vendor import *
30
30
  from django_ledger.views.closing_entry import *
31
+ from django_ledger.views.receipt import *
@@ -64,8 +64,7 @@ class BillModelCreateView(BillModelModelBaseView, CreateView):
64
64
 
65
65
  if self.for_estimate and 'ce_pk' in self.kwargs:
66
66
  estimate_qs = EstimateModel.objects.for_entity(
67
- entity_slug=self.kwargs['entity_slug'],
68
- user_model=self.request.user
67
+ entity_model=self.AUTHORIZED_ENTITY_MODEL,
69
68
  )
70
69
  estimate_model: EstimateModel = get_object_or_404(estimate_qs, uuid__exact=self.kwargs['ce_pk'])
71
70
  if not estimate_model.can_bind():
@@ -87,8 +86,7 @@ class BillModelCreateView(BillModelModelBaseView, CreateView):
87
86
  return HttpResponseBadRequest()
88
87
 
89
88
  po_qs = PurchaseOrderModel.objects.for_entity(
90
- entity_slug=self.kwargs['entity_slug'],
91
- user_model=self.request.user
89
+ entity_model=self.kwargs['entity_slug'],
92
90
  ).prefetch_related('itemtransactionmodel_set')
93
91
  po_model: PurchaseOrderModel = get_object_or_404(po_qs, uuid__exact=po_pk)
94
92
  po_itemtxs_qs = po_model.itemtransactionmodel_set.filter(
@@ -104,8 +102,7 @@ class BillModelCreateView(BillModelModelBaseView, CreateView):
104
102
  }) + f'?item_uuids={po_item_uuids_qry_param}'
105
103
  elif self.for_estimate:
106
104
  estimate_qs = EstimateModel.objects.for_entity(
107
- entity_slug=self.kwargs['entity_slug'],
108
- user_model=self.request.user
105
+ entity_model=self.AUTHORIZED_ENTITY_MODEL
109
106
  )
110
107
  estimate_uuid = self.kwargs['ce_pk']
111
108
  estimate_model: EstimateModel = get_object_or_404(estimate_qs, uuid__exact=estimate_uuid)
@@ -137,15 +134,16 @@ class BillModelCreateView(BillModelModelBaseView, CreateView):
137
134
  def form_valid(self, form):
138
135
  bill_model: BillModel = form.save(commit=False)
139
136
  ledger_model, bill_model = bill_model.configure(
140
- entity_slug=self.AUTHORIZED_ENTITY_MODEL,
137
+ entity_slug=self.AUTHORIZED_ENTITY_MODEL.slug,
138
+ user_model=self.request.user,
141
139
  commit_ledger=True
142
140
  )
143
141
 
144
142
  if self.for_estimate:
145
143
  ce_pk = self.kwargs['ce_pk']
146
144
  estimate_model_qs = EstimateModel.objects.for_entity(
147
- entity_slug=self.kwargs['entity_slug'],
148
- user_model=self.request.user)
145
+ entity_model=self.kwargs['entity_slug']
146
+ )
149
147
 
150
148
  estimate_model = get_object_or_404(estimate_model_qs, uuid__exact=ce_pk)
151
149
  bill_model.bind_estimate(estimate_model=estimate_model, commit=False)
@@ -157,8 +155,7 @@ class BillModelCreateView(BillModelModelBaseView, CreateView):
157
155
  return HttpResponseBadRequest()
158
156
  item_uuids = item_uuids.split(',')
159
157
  po_qs = PurchaseOrderModel.objects.for_entity(
160
- entity_slug=self.kwargs['entity_slug'],
161
- user_model=self.request.user
158
+ entity_model=self.kwargs['entity_slug'],
162
159
  )
163
160
  po_model: PurchaseOrderModel = get_object_or_404(po_qs, uuid__exact=po_pk)
164
161
 
@@ -2,6 +2,7 @@
2
2
  Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
  """
5
+ from typing import Optional
5
6
 
6
7
  from django.contrib import messages
7
8
  from django.core.exceptions import ImproperlyConfigured, ValidationError
@@ -10,18 +11,19 @@ from django.views.generic import UpdateView, ListView, RedirectView, CreateView
10
11
  from django.views.generic.detail import SingleObjectMixin
11
12
 
12
13
  from django_ledger.forms.chart_of_accounts import ChartOfAccountsModelUpdateForm, ChartOfAccountsModelCreateForm
14
+ from django_ledger.models import EntityModel, ChartOfAccountModelQuerySet
13
15
  from django_ledger.models.chart_of_accounts import ChartOfAccountModel
14
16
  from django_ledger.views.mixins import DjangoLedgerSecurityMixIn
15
17
 
16
18
 
17
19
  class ChartOfAccountModelModelBaseViewMixIn(DjangoLedgerSecurityMixIn):
18
- queryset = None
20
+ queryset: Optional[ChartOfAccountModelQuerySet] = None
19
21
 
20
22
  def get_queryset(self):
21
23
  if self.queryset is None:
22
- entity_model = self.get_authorized_entity_instance()
23
- self.queryset = entity_model.chartofaccountmodel_set.all().order_by('-updated')
24
- return super().get_queryset()
24
+ entity_model: EntityModel = self.get_authorized_entity_instance()
25
+ self.queryset: ChartOfAccountModelQuerySet = entity_model.chartofaccountmodel_set.all().order_by('-updated')
26
+ return self.queryset
25
27
 
26
28
 
27
29
  class ChartOfAccountModelListView(ChartOfAccountModelModelBaseViewMixIn, ListView):
@@ -5,6 +5,7 @@ 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
+ from typing import Optional
8
9
 
9
10
  from django.contrib import messages
10
11
  from django.core.exceptions import ValidationError, ImproperlyConfigured
@@ -21,13 +22,14 @@ from django.views.generic.detail import SingleObjectMixin
21
22
 
22
23
  from django_ledger.forms.closing_entry import ClosingEntryCreateForm, ClosingEntryUpdateForm
23
24
  from django_ledger.io.io_core import get_localdate
25
+ from django_ledger.models import EstimateModel, ClosingEntryModelQuerySet
24
26
  from django_ledger.models.closing_entry import ClosingEntryModel
25
27
  from django_ledger.models.entity import EntityModel
26
28
  from django_ledger.views import DjangoLedgerSecurityMixIn
27
29
 
28
30
 
29
31
  class ClosingEntryModelBaseView(DjangoLedgerSecurityMixIn):
30
- queryset = None
32
+ queryset: Optional[ClosingEntryModelQuerySet] = None
31
33
 
32
34
  def get_queryset(self):
33
35
  if self.queryset is None:
@@ -75,16 +77,18 @@ class ClosingEntryModelCreateView(ClosingEntryModelBaseView, CreateView):
75
77
  'header_title': PAGE_TITLE,
76
78
  'header_subtitle_icon': 'file-icons:finaldraft'
77
79
  }
80
+ form_class = ClosingEntryCreateForm
78
81
 
79
82
  def get_initial(self):
80
83
  return {
81
- 'closing_date': get_localdate()
84
+ 'closing_date': get_localdate(),
85
+ 'entity_model': self.AUTHORIZED_ENTITY_MODEL
82
86
  }
83
87
 
84
- def get_form(self, form_class=None, **kwargs):
85
- return ClosingEntryCreateForm(
86
- **self.get_form_kwargs()
87
- )
88
+ def get_form_kwargs(self, **kwargs):
89
+ return super().get_form_kwargs(**kwargs) | {
90
+ 'entity_model': self.AUTHORIZED_ENTITY_MODEL
91
+ }
88
92
 
89
93
  def get_context_data(self, **kwargs):
90
94
  ctx = super().get_context_data(**kwargs)
@@ -100,7 +104,7 @@ class ClosingEntryModelCreateView(ClosingEntryModelBaseView, CreateView):
100
104
  force_update=True,
101
105
  post_closing_entry=False
102
106
  )
103
- self.ce_model = ce_model
107
+ self.ce_model: EstimateModel = ce_model
104
108
  return HttpResponseRedirect(self.get_success_url())
105
109
 
106
110
  def get_success_url(self):
@@ -6,44 +6,49 @@ Contributions to this module:
6
6
  * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
 
9
+ from typing import Optional
10
+
9
11
  from django.urls import reverse
10
12
  from django.utils.translation import gettext_lazy as _
11
- from django.views.generic import ListView, CreateView, UpdateView
13
+ from django.views.generic import (
14
+ CreateView,
15
+ DeleteView,
16
+ DetailView,
17
+ ListView,
18
+ UpdateView,
19
+ )
12
20
 
13
21
  from django_ledger.forms.customer import CustomerModelForm
14
- from django_ledger.models.customer import CustomerModel
22
+ from django_ledger.models.customer import CustomerModel, CustomerModelQueryset
15
23
  from django_ledger.models.entity import EntityModel
24
+ from django_ledger.models.invoice import InvoiceModel
25
+ from django_ledger.models.receipt import ReceiptModel
16
26
  from django_ledger.views.mixins import DjangoLedgerSecurityMixIn
17
27
 
18
28
 
19
- class CustomerModelModelViewQuerySetMixIn:
20
- queryset = None
29
+ class CustomerModelModelViewQuerySetMixIn(DjangoLedgerSecurityMixIn):
30
+ queryset: Optional[CustomerModelQueryset] = None
21
31
 
22
32
  def get_queryset(self):
23
33
  if self.queryset is None:
24
34
  self.queryset = CustomerModel.objects.for_entity(
25
- entity_slug=self.kwargs['entity_slug'],
26
- user_model=self.request.user
35
+ entity_model=self.kwargs['entity_slug'],
27
36
  ).order_by('-updated')
28
- return super().get_queryset()
37
+ return self.queryset
29
38
 
30
39
 
31
- class CustomerModelListView(DjangoLedgerSecurityMixIn,
32
- CustomerModelModelViewQuerySetMixIn,
33
- ListView):
40
+ class CustomerModelListView(CustomerModelModelViewQuerySetMixIn, ListView):
34
41
  template_name = 'django_ledger/customer/customer_list.html'
35
42
  PAGE_TITLE = _('Customer List')
36
43
  extra_context = {
37
44
  'page_title': PAGE_TITLE,
38
45
  'header_title': PAGE_TITLE,
39
- 'header_subtitle_icon': 'dashicons:businesswoman'
46
+ 'header_subtitle_icon': 'dashicons:businesswoman',
40
47
  }
41
48
  context_object_name = 'customers'
42
49
 
43
50
 
44
- class CustomerModelCreateView(DjangoLedgerSecurityMixIn,
45
- CustomerModelModelViewQuerySetMixIn,
46
- CreateView):
51
+ class CustomerModelCreateView(CustomerModelModelViewQuerySetMixIn, CreateView):
47
52
  template_name = 'django_ledger/customer/customer_create.html'
48
53
  PAGE_TITLE = _('Create New Customer')
49
54
  form_class = CustomerModelForm
@@ -51,28 +56,26 @@ class CustomerModelCreateView(DjangoLedgerSecurityMixIn,
51
56
  extra_context = {
52
57
  'page_title': PAGE_TITLE,
53
58
  'header_title': PAGE_TITLE,
54
- 'header_subtitle_icon': 'dashicons:businesswoman'
59
+ 'header_subtitle_icon': 'dashicons:businesswoman',
55
60
  }
56
61
 
57
62
  def get_success_url(self):
58
- return reverse('django_ledger:customer-list',
59
- kwargs={
60
- 'entity_slug': self.kwargs['entity_slug']
61
- })
63
+ return reverse(
64
+ 'django_ledger:customer-list',
65
+ kwargs={'entity_slug': self.kwargs['entity_slug']},
66
+ )
62
67
 
63
68
  def form_valid(self, form):
64
69
  customer_model: CustomerModel = form.save(commit=False)
65
- entity_model = EntityModel.objects.for_user(
66
- user_model=self.request.user
67
- ).get(slug__exact=self.kwargs['entity_slug'])
70
+ entity_model = EntityModel.objects.for_user(user_model=self.request.user).get(
71
+ slug__exact=self.kwargs['entity_slug']
72
+ )
68
73
  customer_model.entity_model = entity_model
69
74
  customer_model.save()
70
75
  return super().form_valid(form)
71
76
 
72
77
 
73
- class CustomerModelUpdateView(DjangoLedgerSecurityMixIn,
74
- CustomerModelModelViewQuerySetMixIn,
75
- UpdateView):
78
+ class CustomerModelUpdateView(CustomerModelModelViewQuerySetMixIn, UpdateView):
76
79
  template_name = 'django_ledger/customer/customer_update.html'
77
80
  PAGE_TITLE = _('Customer Update')
78
81
  form_class = CustomerModelForm
@@ -90,13 +93,48 @@ class CustomerModelUpdateView(DjangoLedgerSecurityMixIn,
90
93
  return context
91
94
 
92
95
  def get_success_url(self):
93
- return reverse('django_ledger:customer-list',
94
- kwargs={
95
- 'entity_slug': self.kwargs['entity_slug']
96
- })
96
+ return reverse(
97
+ 'django_ledger:customer-list',
98
+ kwargs={'entity_slug': self.kwargs['entity_slug']},
99
+ )
97
100
 
98
101
  def form_valid(self, form):
99
102
  form.save()
100
103
  return super().form_valid(form)
101
104
 
102
- # todo: add CustomerDeleteView
105
+
106
+ class CustomerModelDeleteView(CustomerModelModelViewQuerySetMixIn, DeleteView):
107
+ pass
108
+
109
+
110
+ class CustomerModelDetailView(CustomerModelModelViewQuerySetMixIn, DetailView):
111
+ template_name = 'django_ledger/customer/customer_detail.html'
112
+ context_object_name = 'customer'
113
+ PAGE_TITLE = _('Customer Details')
114
+ slug_url_kwarg = 'customer_pk'
115
+ slug_field = 'uuid'
116
+
117
+ def get_context_data(self, **kwargs):
118
+ context = super().get_context_data(**kwargs)
119
+ customer: CustomerModel = self.object
120
+ receipts_qs = (
121
+ ReceiptModel.objects.for_entity(entity_model=self.AUTHORIZED_ENTITY_MODEL)
122
+ .for_customer(customer_model=customer)
123
+ .order_by('-updated')
124
+ )
125
+ invoices_qs = (
126
+ InvoiceModel.objects.for_entity(entity_model=self.AUTHORIZED_ENTITY_MODEL)
127
+ .filter(customer=customer)
128
+ .order_by('-updated')
129
+ )
130
+ context.update(
131
+ {
132
+ 'page_title': self.PAGE_TITLE,
133
+ 'header_title': self.PAGE_TITLE,
134
+ 'header_subtitle': f'{customer.customer_name} · {customer.customer_number}',
135
+ 'header_subtitle_icon': 'dashicons:businesswoman',
136
+ 'receipts': receipts_qs,
137
+ 'invoices': invoices_qs,
138
+ }
139
+ )
140
+ return context