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
@@ -7,17 +7,27 @@ created before it can be assigned to the InvoiceModel. Only customers who are ac
7
7
  """
8
8
 
9
9
  import os
10
- from uuid import uuid4
10
+ import warnings
11
+ from uuid import UUID, uuid4
11
12
 
12
- from django.core.exceptions import ObjectDoesNotExist
13
- from django.db import models, transaction, IntegrityError
14
- from django.db.models import Q, F, QuerySet, Manager
13
+ from django.core.exceptions import ObjectDoesNotExist, ValidationError
14
+ from django.db import IntegrityError, models, transaction
15
+ from django.db.models import F, Manager, Q, QuerySet
15
16
  from django.utils.text import slugify
16
17
  from django.utils.translation import gettext_lazy as _
17
18
 
18
- from django_ledger.models.mixins import ContactInfoMixIn, CreateUpdateMixIn, TaxCollectionMixIn
19
+ from django_ledger.models.deprecations import deprecated_entity_slug_behavior
20
+ from django_ledger.models.mixins import (
21
+ ContactInfoMixIn,
22
+ CreateUpdateMixIn,
23
+ TaxCollectionMixIn,
24
+ )
19
25
  from django_ledger.models.utils import lazy_loader
20
- from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_CUSTOMER_NUMBER_PREFIX
26
+ from django_ledger.settings import (
27
+ DJANGO_LEDGER_CUSTOMER_NUMBER_PREFIX,
28
+ DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
29
+ DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR,
30
+ )
21
31
 
22
32
 
23
33
  def customer_picture_upload_to(instance, filename):
@@ -32,6 +42,10 @@ def customer_picture_upload_to(instance, filename):
32
42
  return f'customer_pictures/{customer_number}/{safe_name}{ext.lower()}'
33
43
 
34
44
 
45
+ class CustomerModelValidationError(ValidationError):
46
+ pass
47
+
48
+
35
49
  class CustomerModelQueryset(QuerySet):
36
50
  """
37
51
  A custom defined QuerySet for the CustomerModel. This implements multiple methods or queries needed to get a
@@ -40,6 +54,27 @@ class CustomerModelQueryset(QuerySet):
40
54
  reports.
41
55
  """
42
56
 
57
+ def for_user(self, user_model):
58
+ """
59
+ Fetches a QuerySet of BillModels that the UserModel as access to.
60
+ May include BillModels from multiple Entities.
61
+
62
+ The user has access to bills if:
63
+ 1. Is listed as Manager of Entity.
64
+ 2. Is the Admin of the Entity.
65
+
66
+ Parameters
67
+ __________
68
+ user_model
69
+ Logged in and authenticated django UserModel instance.
70
+ """
71
+ if user_model.is_superuser:
72
+ return self
73
+ return self.filter(
74
+ Q(entity_model__admin=user_model)
75
+ | Q(entity_model__managers__in=[user_model])
76
+ )
77
+
43
78
  def active(self) -> QuerySet:
44
79
  """
45
80
  Active customers can be assigned to new Invoices and show on dropdown menus and views.
@@ -86,66 +121,56 @@ class CustomerModelQueryset(QuerySet):
86
121
  CustomerModelQueryset
87
122
  A QuerySet of visible Customers.
88
123
  """
89
- return self.filter(
90
- Q(hidden=False) & Q(active=True)
91
- )
124
+ return self.filter(Q(hidden=False) & Q(active=True))
92
125
 
93
126
 
94
127
  class CustomerModelManager(Manager):
95
128
  """
96
- A custom defined CustomerModelManager that will act as an interface to handling the DB queries to the
129
+ A custom-defined CustomerModelManager that will act as an interface to handling the DB queries to the
97
130
  CustomerModel.
98
131
  """
99
132
 
100
- def for_user(self, user_model):
101
- """
102
- Fetches a QuerySet of BillModels that the UserModel as access to.
103
- May include BillModels from multiple Entities.
104
-
105
- The user has access to bills if:
106
- 1. Is listed as Manager of Entity.
107
- 2. Is the Admin of the Entity.
108
-
109
- Parameters
110
- __________
111
- user_model
112
- Logged in and authenticated django UserModel instance.
113
- """
114
- qs = self.get_queryset()
115
- if user_model.is_superuser:
116
- return qs
117
- return qs.filter(
118
- Q(entity_model__admin=user_model) |
119
- Q(entity_model__managers__in=[user_model])
120
- )
121
-
122
- def for_entity(self, entity_slug, user_model) -> CustomerModelQueryset:
133
+ @deprecated_entity_slug_behavior
134
+ def for_entity(
135
+ self, entity_model: 'EntityModel | str | UUID' = None, **kwargs
136
+ ) -> CustomerModelQueryset:
123
137
  """
124
138
  Fetches a QuerySet of CustomerModel associated with a specific EntityModel & UserModel.
125
139
  May pass an instance of EntityModel or a String representing the EntityModel slug.
126
140
 
127
141
  Parameters
128
142
  __________
129
- entity_slug: str or EntityModel
143
+ entity_model: str or EntityModel
130
144
  The entity slug or EntityModel used for filtering the QuerySet.
131
- user_model
132
- Logged in and authenticated django UserModel instance.
133
-
134
145
 
135
146
  Returns
136
147
  -------
137
148
  CustomerModelQueryset
138
149
  A filtered CustomerModel QuerySet.
139
150
  """
140
- qs = self.for_user(user_model)
141
-
142
- if isinstance(entity_slug, lazy_loader.get_entity_model()):
143
- return qs.filter(
144
- Q(entity_model=entity_slug)
151
+ EntityModel = lazy_loader.get_entity_model()
152
+ qs = self.get_queryset()
153
+ if 'user_model' in kwargs:
154
+ warnings.warn(
155
+ 'user_model parameter is deprecated and will be removed in a future release. '
156
+ 'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
157
+ DeprecationWarning,
158
+ stacklevel=2,
145
159
  )
146
- return qs.filter(
147
- Q(entity_model__slug__exact=entity_slug)
148
- )
160
+ if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
161
+ qs = qs.for_user(kwargs['user_model'])
162
+
163
+ if isinstance(entity_model, EntityModel):
164
+ qs = qs.filter(entity_model=entity_model)
165
+ elif isinstance(entity_model, str):
166
+ qs = qs.filter(entity_model__slug__exact=entity_model)
167
+ elif isinstance(entity_model, UUID):
168
+ qs = qs.filter(entity_model_id=entity_model)
169
+ else:
170
+ raise CustomerModelValidationError(
171
+ message='Must pass EntityModel, slug or EntityModel UUID',
172
+ )
173
+ return qs
149
174
 
150
175
 
151
176
  class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMixIn):
@@ -155,7 +180,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
155
180
 
156
181
  1. :func:`ContactInfoMixIn <django_ledger.models.mixins.ContactInfoMixIn>`
157
182
  2. :func:`CreateUpdateMixIn <django_ledger.models.mixins.CreateUpdateMixIn>`
158
-
183
+
159
184
  Attributes
160
185
  __________
161
186
  uuid : UUID
@@ -189,19 +214,27 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
189
214
  unique=True,
190
215
  null=True,
191
216
  blank=True,
192
- verbose_name='User defined customer code'
217
+ verbose_name='User defined customer code',
193
218
  )
194
219
  customer_name = models.CharField(max_length=100)
195
- customer_number = models.CharField(max_length=30, editable=False, verbose_name=_('Customer Number'),
196
- help_text='System generated customer number.')
197
- entity_model = models.ForeignKey('django_ledger.EntityModel',
198
- editable=False,
199
- on_delete=models.CASCADE,
200
- verbose_name=_('Customer Entity'))
220
+ customer_number = models.CharField(
221
+ max_length=30,
222
+ editable=False,
223
+ verbose_name=_('Customer Number'),
224
+ help_text='System generated customer number.',
225
+ )
226
+ entity_model = models.ForeignKey(
227
+ 'django_ledger.EntityModel',
228
+ editable=False,
229
+ on_delete=models.CASCADE,
230
+ verbose_name=_('Customer Entity'),
231
+ )
201
232
  description = models.TextField()
202
233
  active = models.BooleanField(default=True)
203
234
  hidden = models.BooleanField(default=False)
204
- picture = models.ImageField(upload_to=customer_picture_upload_to, null=True, blank=True)
235
+ picture = models.ImageField(
236
+ upload_to=customer_picture_upload_to, null=True, blank=True
237
+ )
205
238
 
206
239
  additional_info = models.JSONField(null=True, blank=True, default=dict)
207
240
 
@@ -217,9 +250,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
217
250
  models.Index(fields=['active']),
218
251
  models.Index(fields=['hidden']),
219
252
  ]
220
- unique_together = [
221
- ('entity_model', 'customer_number')
222
- ]
253
+ unique_together = [('entity_model', 'customer_number')]
223
254
 
224
255
  def __str__(self):
225
256
  if not self.customer_number:
@@ -236,10 +267,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
236
267
  bool
237
268
  True if customer model can be generated, else False.
238
269
  """
239
- return all([
240
- self.entity_model_id,
241
- not self.customer_number
242
- ])
270
+ return all([self.entity_model_id, not self.customer_number])
243
271
 
244
272
  def _get_next_state_model(self, raise_exception: bool = True):
245
273
  """
@@ -261,10 +289,12 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
261
289
  try:
262
290
  LOOKUP = {
263
291
  'entity_model_id__exact': self.entity_model_id,
264
- 'key__exact': EntityStateModel.KEY_CUSTOMER
292
+ 'key__exact': EntityStateModel.KEY_CUSTOMER,
265
293
  }
266
294
 
267
- state_model_qs = EntityStateModel.objects.filter(**LOOKUP).select_for_update()
295
+ state_model_qs = EntityStateModel.objects.filter(
296
+ **LOOKUP
297
+ ).select_for_update()
268
298
  state_model = state_model_qs.get()
269
299
  state_model.sequence = F('sequence') + 1
270
300
  state_model.save()
@@ -272,13 +302,12 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
272
302
 
273
303
  return state_model
274
304
  except ObjectDoesNotExist:
275
-
276
305
  LOOKUP = {
277
306
  'entity_model_id': self.entity_model_id,
278
307
  'entity_unit_id': None,
279
308
  'fiscal_year': None,
280
309
  'key': EntityStateModel.KEY_CUSTOMER,
281
- 'sequence': 1
310
+ 'sequence': 1,
282
311
  }
283
312
  state_model = EntityStateModel.objects.create(**LOOKUP)
284
313
  return state_model
@@ -303,7 +332,6 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
303
332
  """
304
333
  if self.can_generate_customer_number():
305
334
  with transaction.atomic(durable=True):
306
-
307
335
  state_model = None
308
336
  while not state_model:
309
337
  state_model = self._get_next_state_model(raise_exception=False)
@@ -316,6 +344,12 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
316
344
 
317
345
  return self.customer_number
318
346
 
347
+ def validate_for_entity(self, entity_model: 'EntityModel'):
348
+ if entity_model.uuid != self.entity_model_id:
349
+ raise CustomerModelValidationError(
350
+ 'EntityModel does not belong to this Vendor'
351
+ )
352
+
319
353
  def clean(self):
320
354
  """
321
355
  Custom defined clean method that fetches the next customer number if not yet fetched.