django-ledger 0.7.3__py3-none-any.whl → 0.7.4__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 (42) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +2 -3
  3. django_ledger/contrib/django_ledger_graphene/transaction/schema.py +9 -7
  4. django_ledger/forms/journal_entry.py +19 -12
  5. django_ledger/forms/transactions.py +8 -12
  6. django_ledger/io/io_core.py +14 -11
  7. django_ledger/io/io_library.py +3 -3
  8. django_ledger/migrations/0001_initial.py +1 -1
  9. django_ledger/migrations/0019_alter_transactionmodel_amount_and_more.py +33 -0
  10. django_ledger/models/bill.py +17 -2
  11. django_ledger/models/chart_of_accounts.py +4 -0
  12. django_ledger/models/closing_entry.py +8 -6
  13. django_ledger/models/invoice.py +12 -4
  14. django_ledger/models/journal_entry.py +843 -481
  15. django_ledger/models/ledger.py +45 -4
  16. django_ledger/models/transactions.py +303 -305
  17. django_ledger/models/unit.py +42 -22
  18. django_ledger/templates/django_ledger/account/tags/accounts_table.html +1 -1
  19. django_ledger/templates/django_ledger/bills/bill_detail.html +1 -1
  20. django_ledger/templates/django_ledger/invoice/invoice_detail.html +1 -1
  21. django_ledger/templates/django_ledger/journal_entry/je_create.html +2 -3
  22. django_ledger/templates/django_ledger/journal_entry/je_delete.html +2 -3
  23. django_ledger/templates/django_ledger/journal_entry/je_detail.html +1 -1
  24. django_ledger/templates/django_ledger/journal_entry/je_detail_txs.html +8 -8
  25. django_ledger/templates/django_ledger/journal_entry/je_list.html +16 -13
  26. django_ledger/templates/django_ledger/journal_entry/je_update.html +2 -3
  27. django_ledger/templates/django_ledger/journal_entry/tags/je_table.html +24 -24
  28. django_ledger/templates/django_ledger/journal_entry/tags/je_txs_table.html +17 -14
  29. django_ledger/templates/django_ledger/ledger/tags/ledgers_table.html +38 -37
  30. django_ledger/templates/django_ledger/transactions/tags/txs_table.html +69 -0
  31. django_ledger/templatetags/django_ledger.py +24 -45
  32. django_ledger/urls/account.py +4 -4
  33. django_ledger/views/account.py +7 -7
  34. django_ledger/views/journal_entry.py +84 -101
  35. django_ledger/views/ledger.py +16 -21
  36. django_ledger/views/mixins.py +11 -10
  37. {django_ledger-0.7.3.dist-info → django_ledger-0.7.4.dist-info}/METADATA +8 -3
  38. {django_ledger-0.7.3.dist-info → django_ledger-0.7.4.dist-info}/RECORD +42 -40
  39. {django_ledger-0.7.3.dist-info → django_ledger-0.7.4.dist-info}/AUTHORS.md +0 -0
  40. {django_ledger-0.7.3.dist-info → django_ledger-0.7.4.dist-info}/LICENSE +0 -0
  41. {django_ledger-0.7.3.dist-info → django_ledger-0.7.4.dist-info}/WHEEL +0 -0
  42. {django_ledger-0.7.3.dist-info → django_ledger-0.7.4.dist-info}/top_level.txt +0 -0
django_ledger/__init__.py CHANGED
@@ -6,7 +6,7 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
6
6
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
7
7
 
8
8
  """Django Ledger"""
9
- __version__ = '0.7.3'
9
+ __version__ = '0.7.4'
10
10
  __license__ = 'GPLv3 License'
11
11
 
12
12
  __author__ = 'Miguel Sanda'
@@ -27,10 +27,9 @@ class JournalEntryQuery(graphene.ObjectType):
27
27
  sort = info.context.GET.get('sort')
28
28
  if not sort:
29
29
  sort = '-updated'
30
- return JournalEntryModel.objects.for_ledger(
31
- ledger_pk=pk_ledger,
30
+ return JournalEntryModel.objects.for_entity(
32
31
  entity_slug=slug_name,
33
32
  user_model=info.context.user
34
- ).order_by(sort)
33
+ ).for_ledger(ledger_pk=pk_ledger).order_by(sort)
35
34
  else:
36
35
  return JournalEntryModel.objects.none()
@@ -17,18 +17,20 @@ class TransactionNode(DjangoObjectType):
17
17
  'description': ['exact', 'icontains', 'istartswith'],
18
18
  }
19
19
  interfaces = (relay.Node,)
20
+
21
+
20
22
  class TransactionsQuery(graphene.ObjectType):
21
- all_transactions = DjangoFilterConnectionField(TransactionNode, slug_name=graphene.String(required=True),
22
- pk_je=graphene.UUID(), pk_ledger=graphene.UUID())
23
+ all_transactions = DjangoFilterConnectionField(
24
+ TransactionNode,
25
+ slug_name=graphene.String(required=True),
26
+ pk_je=graphene.UUID(),
27
+ pk_ledger=graphene.UUID())
23
28
 
24
29
  def resolve_all_transactions(self, info, slug_name, pk_je, pk_ledger, **kwargs):
25
30
  if info.context.user.is_authenticated:
26
- return TransactionModel.objects.for_journal_entry(
31
+ return TransactionModel.objects.for_entity(
27
32
  entity_slug=slug_name,
28
33
  user_model=info.context.user,
29
- je_model=pk_je,
30
- ledger_model=pk_ledger
31
- ).order_by('account__code')
34
+ ).for_journal_entry(je_model=pk_je).order_by('account__code')
32
35
  else:
33
36
  return TransactionModel.objects.none()
34
-
@@ -1,32 +1,39 @@
1
+ from typing import Union
2
+ from uuid import UUID
3
+
1
4
  from django.forms import ModelForm, Textarea, Select, DateTimeInput, ValidationError
2
5
  from django.utils.translation import gettext_lazy as _
3
6
 
7
+ from django_ledger.models import EntityModel
4
8
  from django_ledger.models.journal_entry import JournalEntryModel
5
9
  from django_ledger.models.ledger import LedgerModel
6
- from django_ledger.models.unit import EntityUnitModel
7
10
  from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
8
11
 
9
12
 
10
13
  class JournalEntryModelCreateForm(ModelForm):
11
14
  def __init__(self,
12
- entity_slug: str,
13
- ledger_model: LedgerModel,
14
- user_model, *args, **kwargs):
15
+ entity_model: EntityModel,
16
+ ledger_model: Union[str, UUID, LedgerModel],
17
+ *args, **kwargs
18
+ ):
15
19
  super().__init__(*args, **kwargs)
16
- self.USER_MODEL = user_model
17
- self.ENTITY_SLUG = entity_slug
20
+ self.ENTITY_MODEL: EntityModel = entity_model
21
+
22
+ # processes the provided ledger model UUID is valid....
23
+ if isinstance(ledger_model, (str, UUID)):
24
+ ledger_model = self.ENTITY_MODEL.get_ledgers().get(uuid__exact=ledger_model)
25
+ elif isinstance(ledger_model, LedgerModel):
26
+ self.ENTITY_MODEL.validate_ledger_model_for_entity(ledger_model)
27
+
18
28
  self.LEDGER_MODEL: LedgerModel = ledger_model
19
29
 
20
30
  if 'timestamp' in self.fields:
21
31
  self.fields['timestamp'].required = False
22
32
  if 'entity_unit' in self.fields:
23
- self.fields['entity_unit'].queryset = EntityUnitModel.objects.for_entity(
24
- entity_slug=self.ENTITY_SLUG,
25
- user_model=self.USER_MODEL
26
- )
33
+ self.fields['entity_unit'].queryset = self.ENTITY_MODEL.entityunitmodel_set.all()
27
34
 
28
35
  def clean(self):
29
- if not self.LEDGER_MODEL.can_edit_journal_entries():
36
+ if self.LEDGER_MODEL.is_locked():
30
37
  raise ValidationError(message=_('Cannot create new Journal Entries on a locked Ledger.'))
31
38
  self.instance.ledger = self.LEDGER_MODEL
32
39
  return super().clean()
@@ -57,7 +64,7 @@ class JournalEntryModelCreateForm(ModelForm):
57
64
  }
58
65
 
59
66
 
60
- class JournalEntryModelUpdateForm(JournalEntryModelCreateForm):
67
+ class JournalEntryModelUpdateForm(ModelForm):
61
68
 
62
69
  def clean_timestamp(self):
63
70
  if 'timestamp' in self.changed_data:
@@ -3,15 +3,15 @@ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
5
  Contributions to this module:
6
- Miguel Sanda <msanda@arrobalytics.com>
7
- Michael Noel <noel.michael87@gmail.com>
6
+ - Miguel Sanda <msanda@arrobalytics.com>
7
+ - Michael Noel <noel.michael87@gmail.com>
8
8
  """
9
9
 
10
10
  from django.forms import ModelForm, modelformset_factory, BaseModelFormSet, TextInput, Select, ValidationError
11
11
  from django.utils.translation import gettext_lazy as _
12
12
 
13
13
  from django_ledger.io.io_core import check_tx_balance
14
- from django_ledger.models.accounts import AccountModel
14
+ from django_ledger.models import EntityModel
15
15
  from django_ledger.models.journal_entry import JournalEntryModel
16
16
  from django_ledger.models.transactions import TransactionModel
17
17
  from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
@@ -44,17 +44,13 @@ class TransactionModelForm(ModelForm):
44
44
 
45
45
  class TransactionModelFormSet(BaseModelFormSet):
46
46
 
47
- def __init__(self, *args, entity_slug, user_model, ledger_pk, je_model=None, **kwargs):
47
+ def __init__(self, *args, entity_model: EntityModel, je_model: JournalEntryModel, **kwargs):
48
48
  super().__init__(*args, **kwargs)
49
- self.USER_MODEL = user_model
49
+ je_model.validate_for_entity(entity_model)
50
50
  self.JE_MODEL: JournalEntryModel = je_model
51
- self.LEDGER_PK = ledger_pk
52
- self.ENTITY_SLUG = entity_slug
51
+ self.ENTITY_MODEL = entity_model
53
52
 
54
- account_qs = AccountModel.objects.for_entity(
55
- user_model=self.USER_MODEL,
56
- entity_model=self.ENTITY_SLUG
57
- ).available().order_by('code')
53
+ account_qs = self.ENTITY_MODEL.get_coa_accounts().active().order_by('code')
58
54
 
59
55
  for form in self.forms:
60
56
  form.fields['account'].queryset = account_qs
@@ -64,7 +60,7 @@ class TransactionModelFormSet(BaseModelFormSet):
64
60
  form.fields['amount'].disabled = True
65
61
 
66
62
  def get_queryset(self):
67
- return self.JE_MODEL.transactionmodel_set.all().order_by('account__code')
63
+ return self.JE_MODEL.transactionmodel_set.all()
68
64
 
69
65
  def clean(self):
70
66
  if any(self.errors):
@@ -359,11 +359,12 @@ class IODatabaseMixIn:
359
359
  raise IOValidationError('Inconsistent entity_slug. '
360
360
  f'Provided {entity_slug} does not match actual {self.slug}')
361
361
  if unit_slug:
362
- txs_queryset_init = TransactionModel.objects.for_unit(
362
+
363
+ txs_queryset_init = TransactionModel.objects.for_entity(
363
364
  user_model=user_model,
364
- entity_slug=entity_slug or self.slug,
365
- unit_slug=unit_slug
366
- )
365
+ entity_slug=entity_slug or self.slug
366
+ ).for_unit(unit_slug=unit_slug)
367
+
367
368
  else:
368
369
  txs_queryset_init = TransactionModel.objects.for_entity(
369
370
  user_model=user_model,
@@ -373,20 +374,22 @@ class IODatabaseMixIn:
373
374
  if not entity_slug:
374
375
  raise IOValidationError(
375
376
  'Calling digest from Entity Unit requires entity_slug explicitly for safety')
376
- txs_queryset_init = TransactionModel.objects.for_unit(
377
+
378
+ txs_queryset_init = TransactionModel.objects.for_entity(
377
379
  user_model=user_model,
378
380
  entity_slug=entity_slug,
379
- unit_slug=unit_slug or self
380
- )
381
+ ).for_unit(unit_slug=unit_slug or self)
382
+
381
383
  elif self.is_ledger_model():
382
384
  if not entity_slug:
383
385
  raise IOValidationError(
384
386
  'Calling digest from Ledger Model requires entity_slug explicitly for safety')
385
- txs_queryset_init = TransactionModel.objects.for_ledger(
386
- user_model=user_model,
387
+
388
+ txs_queryset_init = TransactionModel.objects.for_entity(
387
389
  entity_slug=entity_slug,
388
- ledger_model=self
389
- )
390
+ user_model=user_model,
391
+ ).for_ledger(ledger_model=self)
392
+
390
393
  else:
391
394
  raise IOValidationError(
392
395
  message=f'Cannot call digest from {self.__class__.__name__}'
@@ -433,9 +433,9 @@ class IOBluePrint:
433
433
  return round(amount, self.precision_decimals)
434
434
 
435
435
  def _amount(self, amount: Union[float, Decimal, int]) -> Decimal:
436
- if amount <= 0:
436
+ if amount < 0:
437
437
  raise IOBluePrintValidationError(
438
- message='Amounts must be greater than 0'
438
+ message='Amounts cannot be negative.'
439
439
  )
440
440
 
441
441
  if isinstance(amount, float):
@@ -448,7 +448,7 @@ class IOBluePrint:
448
448
  return Decimal(str(amount))
449
449
 
450
450
  raise IOBluePrintValidationError(
451
- message='Amounts must be float or Decimal'
451
+ message='Amounts must be float, Decimal or int.'
452
452
  )
453
453
 
454
454
  def credit(self, account_code: str, amount: Union[float, Decimal], description: str = None):
@@ -46,7 +46,7 @@ class Migration(migrations.Migration):
46
46
  ('lia_cl_acc_payable', 'Accounts Payable'), ('lia_cl_wages_payable', 'Wages Payable'),
47
47
  ('lia_cl_int_payable', 'Interest Payable'), ('lia_cl_taxes_payable', 'Taxes Payable'),
48
48
  ('lia_cl_st_notes_payable', 'Notes Payable'),
49
- ('lia_cl_ltd_mat', 'Current Maturities of Long Tern Debt'), ('lia_cl_def_rev', 'Deferred Revenue'),
49
+ ('lia_cl_ltd_mat', 'Current Maturities of Long Term Debt'), ('lia_cl_def_rev', 'Deferred Revenue'),
50
50
  ('lia_cl_other', 'Other Liabilities'), ('lia_ltl_notes', 'Notes Payable'),
51
51
  ('lia_ltl_bonds', 'Bonds Payable'), ('lia_ltl_mortgage', 'Mortgage Payable'))), ('Equity', (
52
52
  ('eq_capital', 'Capital'), ('eq_stock_common', 'Common Stock'),
@@ -0,0 +1,33 @@
1
+ # Generated by Django 5.1.5 on 2025-01-17 00:11
2
+
3
+ import django.core.validators
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ dependencies = [
9
+ ('django_ledger', '0018_transactionmodel_cleared_transactionmodel_reconciled_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='transactionmodel',
15
+ name='amount',
16
+ field=models.DecimalField(decimal_places=2, default=0.0, help_text='Amount of the transaction.',
17
+ max_digits=20, validators=[django.core.validators.MinValueValidator(0)],
18
+ verbose_name='Amount'),
19
+ ),
20
+ migrations.AlterField(
21
+ model_name='transactionmodel',
22
+ name='description',
23
+ field=models.CharField(blank=True,
24
+ help_text='A description to be included with this individual transaction.',
25
+ max_length=100, null=True, verbose_name='Transaction Description'),
26
+ ),
27
+ migrations.AlterField(
28
+ model_name='transactionmodel',
29
+ name='tx_type',
30
+ field=models.CharField(choices=[('credit', 'Credit'), ('debit', 'Debit')], max_length=10,
31
+ verbose_name='Transaction Type'),
32
+ ),
33
+ ]
@@ -33,8 +33,13 @@ from django_ledger.io import ASSET_CA_CASH, ASSET_CA_PREPAID, LIABILITY_CL_ACC_P
33
33
  from django_ledger.io.io_core import get_localtime, get_localdate
34
34
  from django_ledger.models.entity import EntityModel
35
35
  from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel, ItemModel, ItemModelQuerySet
36
- from django_ledger.models.mixins import (CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn,
37
- PaymentTermsMixIn, ItemizeMixIn)
36
+ from django_ledger.models.mixins import (
37
+ CreateUpdateMixIn,
38
+ AccrualMixIn,
39
+ MarkdownNotesMixIn,
40
+ PaymentTermsMixIn,
41
+ ItemizeMixIn
42
+ )
38
43
  from django_ledger.models.signals import (
39
44
  bill_status_draft,
40
45
  bill_status_in_review,
@@ -575,6 +580,16 @@ class BillModelAbstract(
575
580
 
576
581
  # ### ItemizeMixIn implementation END...
577
582
 
583
+ def get_transaction_queryset(self, annotated: bool = False):
584
+ """
585
+ Fetches the TransactionModelQuerySet associated with the BillModel instance.
586
+ """
587
+ TransactionModel = lazy_loader.get_txs_model()
588
+ transaction_model_qs = TransactionModel.objects.all().for_ledger(ledger_model=self.ledger_id)
589
+ if annotated:
590
+ return transaction_model_qs.with_annotated_details()
591
+ return transaction_model_qs
592
+
578
593
  # State..
579
594
  def get_migrate_state_desc(self) -> str:
580
595
  """
@@ -286,6 +286,10 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
286
286
  return qs
287
287
  return qs.get()
288
288
 
289
+ raise ChartOfAccountsModelValidationError(
290
+ message='Adding Root account to Chart of Accounts is not allowed.'
291
+ )
292
+
289
293
  def get_non_root_coa_accounts_qs(self) -> AccountModelQuerySet:
290
294
  """
291
295
  Returns a query set of non-root accounts in the chart of accounts.
@@ -232,10 +232,11 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
232
232
  message=_(f'Closing Entry {self.closing_date} is not posted.')
233
233
  )
234
234
  self.posted = False
235
- TransactionModel.objects.for_ledger(
236
- ledger_model=self.ledger_model,
235
+
236
+ TransactionModel.objects.for_entity(
237
237
  entity_slug=self.entity_model_id
238
- ).delete()
238
+ ).for_ledger(ledger_model=self.ledger_model).delete()
239
+
239
240
  self.ledger_model.journal_entries.all().delete()
240
241
  if commit:
241
242
  self.save(
@@ -303,10 +304,11 @@ class ClosingEntryModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
303
304
  raise ClosingEntryValidationError(
304
305
  message=_('Cannot delete a posted Closing Entry')
305
306
  )
306
- TransactionModel.objects.for_ledger(
307
- ledger_model=self.ledger_model,
307
+
308
+ TransactionModel.objects.for_entity(
308
309
  entity_slug=self.entity_model_id
309
- ).delete()
310
+ ).for_ledger(ledger_model=self.ledger_model).delete()
311
+
310
312
  return self.ledger_model.delete()
311
313
 
312
314
  def get_delete_html_id(self) -> str:
@@ -546,6 +546,16 @@ class InvoiceModelAbstract(
546
546
 
547
547
  # ### ItemizeMixIn implementation END...
548
548
 
549
+ def get_transaction_queryset(self, annotated: bool = False):
550
+ """
551
+ Fetches the TransactionModelQuerySet associated with the InvoiceModel instance.
552
+ """
553
+ TransactionModel = lazy_loader.get_txs_model()
554
+ transaction_model_qs = TransactionModel.objects.all().for_ledger(ledger_model=self.ledger_id)
555
+ if annotated:
556
+ return transaction_model_qs.with_annotated_details()
557
+ return transaction_model_qs
558
+
549
559
  def get_migrate_state_desc(self):
550
560
  """
551
561
  Description used when migrating transactions into the LedgerModel.
@@ -557,8 +567,7 @@ class InvoiceModelAbstract(
557
567
  """
558
568
  return f'Invoice {self.invoice_number} account adjustment.'
559
569
 
560
- def get_migration_data(self,
561
- queryset: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet:
570
+ def get_migration_data(self, queryset: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet:
562
571
 
563
572
  """
564
573
  Fetches necessary item transaction data to perform a migration into the LedgerModel.
@@ -591,8 +600,7 @@ class InvoiceModelAbstract(
591
600
  'total_amount').annotate(
592
601
  account_unit_total=Sum('total_amount'))
593
602
 
594
- def update_amount_due(self,
595
- itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet:
603
+ def update_amount_due(self, itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet:
596
604
  """
597
605
  Updates the InvoiceModel amount due.
598
606