django-ledger 0.5.6.4__py3-none-any.whl → 0.6.0__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.

@@ -5,15 +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
- The TransactionModel is the lowest accounting level where the financial information is recorded on the books.
9
- Every transaction which has an financial implication must be recorded as part of a JournalEntryModel, which in turn
10
- encapsulates a collection of TransactionModels. Transaction models cannot exist without being part of a validated
11
- JournalEntryModel. Orphan TransactionModels are not allowed, and this is enforced by the database.
12
-
13
- A transaction by definition must perform a CREDIT or a DEBIT to the underlying AccountModel. The IOMixIn plays a crucial
14
- role in the production of financial statements and sets its foundation in the TransactionModel API to effective query
15
- amd aggregate transactions at the Database layer without the need of pulling all TransactionModels into memory for the
16
- production of financial statements.
8
+ The TransactionModel is the lowest accounting level where financial information is recorded. Every transaction with a
9
+ financial implication must be part of a JournalEntryModel, which encapsulates a collection of TransactionModels.
10
+ Transaction models cannot exist without being part of a validated JournalEntryModel. Orphan TransactionModels are not
11
+ allowed, and this is enforced by the database.
12
+
13
+ A transaction must perform a CREDIT or a DEBIT to the underlying AccountModel. The IOMixIn is crucial for the
14
+ production of financial statements and sets its foundation in the TransactionModel API. It allows for effective
15
+ querying and aggregating transactions at the Database layer without pulling all TransactionModels into memory.
16
+ This approach streamlines the production of financial statements. The IOMixIn in the TransactionModel API is essential
17
+ for efficient and effective financial statement generation.
17
18
  """
18
19
  from datetime import datetime, date
19
20
  from typing import List, Union, Optional
@@ -46,7 +47,37 @@ class TransactionModelValidationError(ValidationError):
46
47
 
47
48
  class TransactionModelQuerySet(QuerySet):
48
49
  """
49
- A custom defined EntityUnitModel Queryset.
50
+ A custom QuerySet class for TransactionModels implementing methods to effectively and safely read
51
+ TransactionModels from the database.
52
+
53
+ Methods
54
+ -------
55
+ posted() -> TransactionModelQuerySet:
56
+ Fetches a QuerySet of posted transactions only.
57
+
58
+ for_accounts(account_list: List[str or AccountModel]) -> TransactionModelQuerySet:
59
+ Fetches a QuerySet of TransactionModels which AccountModel has a specific role.
60
+
61
+ for_roles(role_list: Union[str, List[str]]) -> TransactionModelQuerySet:
62
+ Fetches a QuerySet of TransactionModels which AccountModel has a specific role.
63
+
64
+ for_unit(unit_slug: Union[str, EntityUnitModel]) -> TransactionModelQuerySet:
65
+ Fetches a QuerySet of TransactionModels associated with a specific EntityUnitModel.
66
+
67
+ for_activity(activity_list: Union[str, List[str]]) -> TransactionModelQuerySet:
68
+ Fetches a QuerySet of TransactionModels associated with a specific activity or list of activities.
69
+
70
+ to_date(to_date: Union[str, date, datetime]) -> TransactionModelQuerySet:
71
+ Fetches a QuerySet of TransactionModels associated with a maximum date or timestamp filter.
72
+
73
+ from_date(from_date: Union[str, date, datetime]) -> TransactionModelQuerySet:
74
+ Fetches a QuerySet of TransactionModels associated with a minimum date or timestamp filter.
75
+
76
+ not_closing_entry() -> TransactionModelQuerySet:
77
+ Fetches a QuerySet of TransactionModels that are not part of a closing entry.
78
+
79
+ is_closing_entry() -> TransactionModelQuerySet:
80
+ Fetches a QuerySet of TransactionModels that are part of a closing entry.
50
81
  """
51
82
 
52
83
  def posted(self) -> QuerySet:
@@ -189,38 +220,62 @@ class TransactionModelQuerySet(QuerySet):
189
220
  return self.filter(journal_entry__timestamp__gte=from_date)
190
221
 
191
222
  def not_closing_entry(self):
223
+ """
224
+ Filter the Transactions based on whether they are closing entries or not.
225
+
226
+ Returns:
227
+ QuerySet: A filtered QuerySet of entries where the journal_entry__is_closing_entry field is False.
228
+ """
192
229
  return self.filter(journal_entry__is_closing_entry=False)
193
230
 
194
231
  def is_closing_entry(self):
232
+ """
233
+ Filter the Transactions based on whether they are closing entries or not.
234
+
235
+ Returns:
236
+ QuerySet: A filtered QuerySet of entries where the journal_entry__is_closing_entry field is True.
237
+ """
195
238
  return self.filter(journal_entry__is_closing_entry=True)
196
239
 
197
240
 
198
241
  class TransactionModelAdmin(models.Manager):
242
+ """
243
+ A manager class for the TransactionModel.
244
+ """
245
+
246
+ def get_queryset(self) -> TransactionModelQuerySet:
247
+ qs = TransactionModelQuerySet(self.model, using=self._db)
248
+ return qs.select_related(
249
+ 'journal_entry',
250
+ 'account',
251
+ 'account__coa_model',
252
+ )
199
253
 
200
254
  def for_user(self, user_model) -> TransactionModelQuerySet:
201
255
  """
202
- Fetches a QuerySet of TransactionModels that the UserModel as access to. For convenience, the AccountModel
203
- information is selected, since much of the operations associated with transactions will involve information
204
- from the AccountModel. For example, the AccountModel balance type plays a crucial role in the production of
205
- financial statements.
206
-
207
- May include TransactionModels from multiple Entities.
208
-
209
- The user has access to transactions if:
210
- 1. Is listed as Manager of Entity.
211
- 2. Is the Admin of the Entity.
212
-
213
256
  Parameters
214
257
  ----------
215
- user_model
216
- Logged in and authenticated django UserModel instance.
258
+ user_model : User model object
259
+ The user model object representing the user for whom to filter the transactions.
217
260
 
218
261
  Returns
219
262
  -------
220
263
  TransactionModelQuerySet
221
- Returns a TransactionModelQuerySet with applied filters.
264
+ A queryset of transaction models filtered based on the user's permissions.
265
+
266
+ Raises
267
+ ------
268
+ None
269
+
270
+ Description
271
+ -----------
272
+ This method filters the transactions based on the user's permissions.
273
+ If the user is a superuser, all transactions are returned. Otherwise, the transactions are filtered based on
274
+ the user's relationship to the entities associated with the transactions. Specifically, the transactions are
275
+ filtered to include only those where either the user is an admin of the entity associated with the transaction's
276
+ ledger or the user is one of the managers of the entity associated with the transaction's ledger.
222
277
  """
223
- qs = self.get_queryset().select_related('journal_entry')
278
+ qs = self.get_queryset()
224
279
  if user_model.is_superuser:
225
280
  return qs
226
281
  return qs.filter(
@@ -233,22 +288,20 @@ class TransactionModelAdmin(models.Manager):
233
288
  user_model: Optional[UserModel] = None,
234
289
  ) -> TransactionModelQuerySet:
235
290
  """
236
- Fetches a QuerySet of TransactionModels associated with the specified
237
- EntityModel. For security if UserModel is provided, will make sure the user_model provided is either the admin
238
- or the manager of the entity.
239
-
240
291
  Parameters
241
292
  ----------
242
- entity_slug: str or EntityModel
243
- The entity slug or EntityModel used for filtering the QuerySet.
244
- user_model: Optional Django UserModel
245
- Optional logged in and authenticated Django UserModel instance to match against Entity Admin or Managers.
246
- Will make sure the authenticated user has access to the EntityModel transactions.
293
+ entity_slug : Union[EntityModel, str, UUID]
294
+ The entity slug or ID for which to retrieve transactions.
295
+ Can be an instance of EntityModel, a string representing the slug, or a UUID.
296
+ user_model : Optional[UserModel], optional
297
+ The user model for which to filter transactions.
298
+ If provided, only transactions associated with the specified user will be returned.
299
+ Defaults to None.
247
300
 
248
301
  Returns
249
302
  -------
250
303
  TransactionModelQuerySet
251
- Returns a TransactionModelQuerySet with applied filters.
304
+ A QuerySet of TransactionModel instances filtered by the provided parameters.
252
305
  """
253
306
 
254
307
  if user_model:
@@ -267,22 +320,19 @@ class TransactionModelAdmin(models.Manager):
267
320
  ledger_model: Union[LedgerModel, UUID],
268
321
  user_model: Optional[UserModel] = None):
269
322
  """
270
- Fetches a QuerySet of TransactionModels that the UserModel as access to and are associated with a specific
271
- LedgerModel instance.
272
-
273
323
  Parameters
274
324
  ----------
275
- user_model
276
- Optional logged in and authenticated django UserModel instance to validate against managers and admin.
277
- entity_slug: str or EntityModel
278
- The entity slug or EntityModel used for filtering the QuerySet.
279
- ledger_model: LedgerModel or UUID
280
- The LedgerModel or LedgerModel UUID associated with the TransactionModel to be queried.
325
+ entity_slug : Union[EntityModel, str]
326
+ The slug or instance of the entity for which to filter the ledger.
327
+ ledger_model : Union[LedgerModel, UUID]
328
+ The ledger model or UUID of the ledger for which to filter the journal entries.
329
+ user_model : Optional[UserModel], optional
330
+ The user model associated with the entity. Default is None.
281
331
 
282
332
  Returns
283
333
  -------
284
- TransactionModelQuerySet
285
- Returns a TransactionModelQuerySet with applied filters.
334
+ QuerySet
335
+ The filtered QuerySet containing the journal entries for the specified entity and ledger.
286
336
  """
287
337
  qs = self.for_entity(user_model=user_model, entity_slug=entity_slug)
288
338
  if isinstance(ledger_model, UUID):
@@ -294,22 +344,26 @@ class TransactionModelAdmin(models.Manager):
294
344
  unit_slug: str = Union[EntityUnitModel, str],
295
345
  user_model: Optional[UserModel] = None):
296
346
  """
297
- Fetches a QuerySet of TransactionModels that the UserModel as access to and are associated with a specific
298
- EntityUnitModel instance.
347
+ Returns the queryset filtered for the specified entity unit.
299
348
 
300
349
  Parameters
301
350
  ----------
302
- user_model
303
- Optional logged in and authenticated django UserModel instance to validate against managers and admin.
304
- entity_slug: str or EntityModel
305
- The entity slug or EntityModel used for filtering the QuerySet.
306
- unit_slug: EntityUnitModel or EntityUnitModel UUID.
307
- The EntityUnitModel or EntityUnitModel UUID associated with the TransactionModel to be queried.
351
+ entity_slug : Union[EntityModel, str]
352
+ The entity model or slug used to filter the queryset.
353
+ unit_slug : Union[EntityUnitModel, str]
354
+ The entity unit model or slug used to filter the queryset.
355
+ user_model : Optional[UserModel], optional
356
+ The user model to consider for filtering the queryset, by default None.
308
357
 
309
358
  Returns
310
359
  -------
311
- TransactionModelQuerySet
312
- Returns a TransactionModelQuerySet with applied filters.
360
+ QuerySet
361
+ The filtered queryset based on the specified entity unit.
362
+
363
+ Notes
364
+ -----
365
+ - If `unit_slug` is an instance of `EntityUnitModel`, the queryset is filtered using `journal_entry__entity_unit=unit_slug`.
366
+ - If `unit_slug` is a string, the queryset is filtered using `journal_entry__entity_unit__slug__exact=unit_slug`.
313
367
  """
314
368
  qs = self.for_entity(user_model=user_model, entity_slug=entity_slug)
315
369
  if isinstance(unit_slug, EntityUnitModel):
@@ -322,24 +376,23 @@ class TransactionModelAdmin(models.Manager):
322
376
  je_model,
323
377
  user_model: Optional[UserModel] = None):
324
378
  """
325
- Fetches a QuerySet of TransactionModels that the UserModel as access to and are associated with a specific
326
- LedgerModel AND JournalEntryModel instance.
327
-
328
379
  Parameters
329
380
  ----------
330
- user_model: UserModel
331
- Optional logged in and authenticated django UserModel instance to validate against managers and admin.
332
- entity_slug: str or EntityModel
333
- The entity slug or EntityModel used for filtering the QuerySet.
334
- ledger_model: LedgerModel or str or UUID
335
- The LedgerModel or LedgerModel UUID associated with the TransactionModel to be queried.
336
- je_model: JournalEntryModel or str or UUID
337
- The JournalEntryModel or JournalEntryModel UUID associated with the TransactionModel to be queried.
381
+ entity_slug : Union[EntityModel, str]
382
+ The entity slug or instance of EntityModel representing the entity for which the journal entry is requested.
383
+ ledger_model : Union[LedgerModel, str, UUID]
384
+ The ledger model or its identifier (str or UUID) representing the ledger for which the journal entry
385
+ is requested.
386
+ je_model : Type[JournalEntryModel]
387
+ The journal entry model or its identifier (str or UUID) representing the journal entry to filter by.
388
+ user_model : Optional[UserModel], default=None
389
+ An optional user model instance representing the user for whom the journal entry is requested.
338
390
 
339
391
  Returns
340
392
  -------
341
- TransactionModelQuerySet
342
- Returns a TransactionModelQuerySet with applied filters.
393
+ QuerySet
394
+ The filtered queryset of journal entries.
395
+
343
396
  """
344
397
  qs = self.for_ledger(user_model=user_model,
345
398
  entity_slug=entity_slug,
@@ -354,22 +407,20 @@ class TransactionModelAdmin(models.Manager):
354
407
  entity_slug: str,
355
408
  bill_model: Union[BillModel, str, UUID]):
356
409
  """
357
- Fetches a QuerySet of TransactionModels that the UserModel as access to and are associated with a specific
358
- BillModel instance.
359
-
360
410
  Parameters
361
411
  ----------
362
- user_model
363
- Logged in and authenticated django UserModel instance.
364
- entity_slug: str or EntityModel
365
- The entity slug or EntityModel used for filtering the QuerySet.
366
- bill_model: BillModel or str or UUID
367
- The BillModel or BillModel UUID associated with the TransactionModel to be queried.
412
+ user_model : Type
413
+ An instance of user model.
414
+ entity_slug : str
415
+ The slug of the entity.
416
+ bill_model : Union[BillModel, str, UUID]
417
+ An instance of bill model or a string/UUID representing the UUID of the bill model.
368
418
 
369
419
  Returns
370
420
  -------
371
- TransactionModelQuerySet
372
- Returns a TransactionModelQuerySet with applied filters.
421
+ FilterQuerySet
422
+ A filtered queryset based on the user model, entity slug, and bill model.
423
+
373
424
  """
374
425
  qs = self.for_entity(
375
426
  user_model=user_model,
@@ -383,22 +434,19 @@ class TransactionModelAdmin(models.Manager):
383
434
  entity_slug: str,
384
435
  invoice_model: Union[InvoiceModel, str, UUID]):
385
436
  """
386
- Fetches a QuerySet of TransactionModels that the UserModel as access to and are associated with a specific
387
- InvoiceModel instance.
388
-
389
437
  Parameters
390
438
  ----------
391
- user_model
392
- Logged in and authenticated django UserModel instance.
393
- entity_slug: str or EntityModel
394
- The entity slug or EntityModel used for filtering the QuerySet.
395
- invoice_model: InvoiceModel or str or UUID
396
- The InvoiceModel or InvoiceModel UUID associated with the TransactionModel to be queried.
439
+ user_model : [type]
440
+ The user model used for filtering entities.
441
+ entity_slug : str
442
+ The slug of the entity used for filtering.
443
+ invoice_model : Union[InvoiceModel, str, UUID]
444
+ The invoice model or its identifier used for filtering.
397
445
 
398
446
  Returns
399
447
  -------
400
- TransactionModelQuerySet
401
- Returns a TransactionModelQuerySet with applied filters.
448
+ QuerySet
449
+ The filtered queryset based on the specified parameters.
402
450
  """
403
451
  qs = self.for_entity(
404
452
  user_model=user_model,
@@ -410,27 +458,46 @@ class TransactionModelAdmin(models.Manager):
410
458
 
411
459
  class TransactionModelAbstract(CreateUpdateMixIn):
412
460
  """
413
- This is the main abstract class which the TransactionModel database will inherit from.
414
- The TransactionModel inherits functionality from the following MixIns:
415
461
 
416
- 1. :func:`CreateUpdateMixIn <django_ledger.models.mixins.CreateUpdateMixIn>`
462
+ TransactionModelAbstract
463
+
464
+ An abstract class that represents a transaction in the ledger system.
417
465
 
418
466
  Attributes
419
467
  ----------
420
- uuid : UUID
421
- This is a unique primary key generated for the table. The default value of this field is uuid4().
422
- tx_type: str
423
- Transaction type as a String, representing a CREDIT or a DEBIT to the AccountModel associated with the
424
- TransactionModel instance.
425
- journal_entry: JournalEntryModel
426
- The JournalEntryModel associated with the TransactionModel instance.
427
- account: AccountModel
428
- The AccountModel associated with the TransactionModel instance.
429
- amount: Decimal
430
- The monetary amount of the transaction instance, represented by the python Decimal field.
431
- Must be greater than zero.
432
- description: str
433
- A TransactionModel description. Maximum length is 100.
468
+
469
+ - CREDIT: A constant representing a credit transaction.
470
+ - DEBIT: A constant representing a debit transaction.
471
+
472
+ - TX_TYPE: A list of tuples representing the transaction type choices.
473
+
474
+ - uuid: A UUIDField representing the unique identifier of the transaction.
475
+ This field is automatically generated and is not editable.
476
+
477
+ - tx_type: A CharField representing the type of the transaction.
478
+ It has a maximum length of 10 characters and accepts choices from the TX_TYPE list.
479
+
480
+ - journal_entry: A ForeignKey representing the journal entry associated with the transaction.
481
+ It references the 'django_ledger.JournalEntryModel' model.
482
+
483
+ - account: A ForeignKey representing the account associated with the transaction.
484
+ It references the 'django_ledger.AccountModel' model.
485
+
486
+ - amount: A DecimalField representing the amount of the transaction.
487
+ It has a maximum of 2 decimal places and a maximum of 20 digits.
488
+ It defaults to 0.00 and accepts a minimum value of 0.
489
+
490
+ - description: A CharField representing the description of the transaction.
491
+ It has a maximum length of 100 characters and is optional.
492
+
493
+ - objects: An instance of the TransactionModelAdmin class.
494
+
495
+ Methods
496
+ -------
497
+
498
+ - clean(): Performs validation on the transaction instance.
499
+ Raises a TransactionModelValidationError if the account is a root account.
500
+
434
501
  """
435
502
 
436
503
  CREDIT = 'credit'
@@ -463,7 +530,8 @@ class TransactionModelAbstract(CreateUpdateMixIn):
463
530
  blank=True,
464
531
  verbose_name=_('Tx Description'),
465
532
  help_text=_('A description to be included with this individual transaction'))
466
- objects = TransactionModelAdmin.from_queryset(queryset_class=TransactionModelQuerySet)()
533
+
534
+ objects = TransactionModelAdmin()
467
535
 
468
536
  class Meta:
469
537
  abstract = True
@@ -499,9 +567,39 @@ class TransactionModel(TransactionModelAbstract):
499
567
 
500
568
 
501
569
  def transactionmodel_presave(instance: TransactionModel, **kwargs):
502
- if instance.journal_entry_id and instance.journal_entry.is_locked():
570
+ """
571
+ Parameters
572
+ ----------
573
+ instance : TransactionModel
574
+ The transaction model instance that is being saved.
575
+ kwargs : dict
576
+ Additional keyword arguments.
577
+
578
+ Notes
579
+ -----
580
+ This method is called before saving a transaction model instance.
581
+ It performs some checks before allowing the save operation.
582
+
583
+ Raises
584
+ ------
585
+ TransactionModelValidationError
586
+ If one of the following conditions is met:
587
+ - `bypass_account_state` is False and the `can_transact` method of the associated account model returns False.
588
+ - The journal entry associated with the transaction is locked.
589
+
590
+ """
591
+ bypass_account_state = kwargs.get('bypass_account_state', False)
592
+ if all([
593
+ not bypass_account_state,
594
+ not instance.account.can_transact()
595
+ ]):
596
+ raise TransactionModelValidationError(
597
+ message=_(f'Cannot create or modify transactions on account model {instance.account}.')
598
+ )
599
+
600
+ if instance.journal_entry.is_locked():
503
601
  raise TransactionModelValidationError(
504
- message=_('Cannot modify transactions on locked journal entries')
602
+ message=_('Cannot modify transactions on locked journal entries.')
505
603
  )
506
604
 
507
605