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
@@ -8,21 +8,25 @@ created before it can be assigned to the InvoiceModel. Only customers who are ac
8
8
 
9
9
  import os
10
10
  import warnings
11
- from uuid import uuid4, UUID
11
+ from uuid import UUID, uuid4
12
12
 
13
13
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
14
- from django.db import models, transaction, IntegrityError
15
- from django.db.models import Q, F, QuerySet, Manager
14
+ from django.db import IntegrityError, models, transaction
15
+ from django.db.models import F, Manager, Q, QuerySet
16
16
  from django.utils.text import slugify
17
17
  from django.utils.translation import gettext_lazy as _
18
18
 
19
19
  from django_ledger.models.deprecations import deprecated_entity_slug_behavior
20
- from django_ledger.models.mixins import ContactInfoMixIn, CreateUpdateMixIn, TaxCollectionMixIn
20
+ from django_ledger.models.mixins import (
21
+ ContactInfoMixIn,
22
+ CreateUpdateMixIn,
23
+ TaxCollectionMixIn,
24
+ )
21
25
  from django_ledger.models.utils import lazy_loader
22
26
  from django_ledger.settings import (
23
- DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
24
27
  DJANGO_LEDGER_CUSTOMER_NUMBER_PREFIX,
25
- DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR
28
+ DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
29
+ DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR,
26
30
  )
27
31
 
28
32
 
@@ -67,8 +71,8 @@ class CustomerModelQueryset(QuerySet):
67
71
  if user_model.is_superuser:
68
72
  return self
69
73
  return self.filter(
70
- Q(entity_model__admin=user_model) |
71
- Q(entity_model__managers__in=[user_model])
74
+ Q(entity_model__admin=user_model)
75
+ | Q(entity_model__managers__in=[user_model])
72
76
  )
73
77
 
74
78
  def active(self) -> QuerySet:
@@ -117,9 +121,7 @@ class CustomerModelQueryset(QuerySet):
117
121
  CustomerModelQueryset
118
122
  A QuerySet of visible Customers.
119
123
  """
120
- return self.filter(
121
- Q(hidden=False) & Q(active=True)
122
- )
124
+ return self.filter(Q(hidden=False) & Q(active=True))
123
125
 
124
126
 
125
127
  class CustomerModelManager(Manager):
@@ -129,7 +131,9 @@ class CustomerModelManager(Manager):
129
131
  """
130
132
 
131
133
  @deprecated_entity_slug_behavior
132
- def for_entity(self, entity_model: 'EntityModel | str | UUID' = None, **kwargs) -> CustomerModelQueryset:
134
+ def for_entity(
135
+ self, entity_model: 'EntityModel | str | UUID' = None, **kwargs
136
+ ) -> CustomerModelQueryset:
133
137
  """
134
138
  Fetches a QuerySet of CustomerModel associated with a specific EntityModel & UserModel.
135
139
  May pass an instance of EntityModel or a String representing the EntityModel slug.
@@ -151,7 +155,7 @@ class CustomerModelManager(Manager):
151
155
  'user_model parameter is deprecated and will be removed in a future release. '
152
156
  'Use for_user(user_model).for_entity(entity_model) instead to keep current behavior.',
153
157
  DeprecationWarning,
154
- stacklevel=2
158
+ stacklevel=2,
155
159
  )
156
160
  if DJANGO_LEDGER_USE_DEPRECATED_BEHAVIOR:
157
161
  qs = qs.for_user(kwargs['user_model'])
@@ -176,7 +180,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
176
180
 
177
181
  1. :func:`ContactInfoMixIn <django_ledger.models.mixins.ContactInfoMixIn>`
178
182
  2. :func:`CreateUpdateMixIn <django_ledger.models.mixins.CreateUpdateMixIn>`
179
-
183
+
180
184
  Attributes
181
185
  __________
182
186
  uuid : UUID
@@ -210,19 +214,27 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
210
214
  unique=True,
211
215
  null=True,
212
216
  blank=True,
213
- verbose_name='User defined customer code'
217
+ verbose_name='User defined customer code',
214
218
  )
215
219
  customer_name = models.CharField(max_length=100)
216
- customer_number = models.CharField(max_length=30, editable=False, verbose_name=_('Customer Number'),
217
- help_text='System generated customer number.')
218
- entity_model = models.ForeignKey('django_ledger.EntityModel',
219
- editable=False,
220
- on_delete=models.CASCADE,
221
- 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
+ )
222
232
  description = models.TextField()
223
233
  active = models.BooleanField(default=True)
224
234
  hidden = models.BooleanField(default=False)
225
- 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
+ )
226
238
 
227
239
  additional_info = models.JSONField(null=True, blank=True, default=dict)
228
240
 
@@ -238,9 +250,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
238
250
  models.Index(fields=['active']),
239
251
  models.Index(fields=['hidden']),
240
252
  ]
241
- unique_together = [
242
- ('entity_model', 'customer_number')
243
- ]
253
+ unique_together = [('entity_model', 'customer_number')]
244
254
 
245
255
  def __str__(self):
246
256
  if not self.customer_number:
@@ -257,10 +267,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
257
267
  bool
258
268
  True if customer model can be generated, else False.
259
269
  """
260
- return all([
261
- self.entity_model_id,
262
- not self.customer_number
263
- ])
270
+ return all([self.entity_model_id, not self.customer_number])
264
271
 
265
272
  def _get_next_state_model(self, raise_exception: bool = True):
266
273
  """
@@ -282,10 +289,12 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
282
289
  try:
283
290
  LOOKUP = {
284
291
  'entity_model_id__exact': self.entity_model_id,
285
- 'key__exact': EntityStateModel.KEY_CUSTOMER
292
+ 'key__exact': EntityStateModel.KEY_CUSTOMER,
286
293
  }
287
294
 
288
- state_model_qs = EntityStateModel.objects.filter(**LOOKUP).select_for_update()
295
+ state_model_qs = EntityStateModel.objects.filter(
296
+ **LOOKUP
297
+ ).select_for_update()
289
298
  state_model = state_model_qs.get()
290
299
  state_model.sequence = F('sequence') + 1
291
300
  state_model.save()
@@ -293,13 +302,12 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
293
302
 
294
303
  return state_model
295
304
  except ObjectDoesNotExist:
296
-
297
305
  LOOKUP = {
298
306
  'entity_model_id': self.entity_model_id,
299
307
  'entity_unit_id': None,
300
308
  'fiscal_year': None,
301
309
  'key': EntityStateModel.KEY_CUSTOMER,
302
- 'sequence': 1
310
+ 'sequence': 1,
303
311
  }
304
312
  state_model = EntityStateModel.objects.create(**LOOKUP)
305
313
  return state_model
@@ -324,7 +332,6 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
324
332
  """
325
333
  if self.can_generate_customer_number():
326
334
  with transaction.atomic(durable=True):
327
-
328
335
  state_model = None
329
336
  while not state_model:
330
337
  state_model = self._get_next_state_model(raise_exception=False)
@@ -337,6 +344,12 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
337
344
 
338
345
  return self.customer_number
339
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
+
340
353
  def clean(self):
341
354
  """
342
355
  Custom defined clean method that fetches the next customer number if not yet fetched.