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

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.8.2.2'
9
+ __version__ = '0.8.2.3'
10
10
  __license__ = 'GPLv3 License'
11
11
 
12
12
  __author__ = 'Miguel Sanda'
@@ -1,3 +1,5 @@
1
+ from itertools import groupby
2
+
1
3
  from django import forms
2
4
  from django.forms import (
3
5
  ModelForm,
@@ -140,7 +142,7 @@ class StagedTransactionModelForm(ModelForm):
140
142
  self.fields['tx_split'].disabled = True
141
143
 
142
144
  if not staged_tx_model.can_unbundle():
143
- # self.fields['bundle_split'].widget = HiddenInput()
145
+ self.fields['bundle_split'].widget = HiddenInput()
144
146
  self.fields['bundle_split'].disabled = True
145
147
 
146
148
  def clean_account_model(self):
@@ -160,13 +162,42 @@ class StagedTransactionModelForm(ModelForm):
160
162
  def clean_tx_import(self):
161
163
  staged_txs_model: StagedTransactionModel = self.instance
162
164
  if staged_txs_model.is_children():
163
- return False
165
+ parent_form = self.BASE_FORMSET_INSTANCE.FORMS_BY_ID[staged_txs_model.parent_id]
166
+ if all(
167
+ [
168
+ any(
169
+ [
170
+ staged_txs_model.is_child_not_bundled_has_receipt(),
171
+ staged_txs_model.is_child_not_bundled_no_receipt(),
172
+ ]
173
+ ),
174
+ parent_form.cleaned_data['tx_import'] is True,
175
+ ]
176
+ ):
177
+ return True
164
178
  return self.cleaned_data['tx_import']
165
179
 
180
+ def clean_bundle_split(self):
181
+ staged_txs_model: StagedTransactionModel = self.instance
182
+ if staged_txs_model.is_single():
183
+ return True
184
+ if staged_txs_model.is_children():
185
+ parent_form = self.BASE_FORMSET_INSTANCE.FORMS_BY_ID[staged_txs_model.parent.uuid]
186
+ return parent_form.cleaned_data['bundle_split']
187
+ return self.cleaned_data['bundle_split']
188
+
166
189
  def clean(self):
167
190
  if self.cleaned_data['tx_import'] and self.cleaned_data['tx_split']:
168
191
  raise ValidationError(message=_('Cannot import and split at the same time'))
169
192
 
193
+ def has_changed(self):
194
+ has_changed = super().has_changed()
195
+ staged_txs_model: StagedTransactionModel = self.instance
196
+ if not has_changed and staged_txs_model.is_children():
197
+ parent_form = self.BASE_FORMSET_INSTANCE.FORMS_BY_ID[staged_txs_model.parent.uuid]
198
+ return parent_form.has_changed()
199
+ return has_changed
200
+
170
201
  class Meta:
171
202
  model = StagedTransactionModel
172
203
  fields = [
@@ -244,8 +275,8 @@ class BaseStagedTransactionModelFormSet(BaseModelFormSet):
244
275
  self.unit_model_qs = entity_model.entityunitmodel_set.all()
245
276
  self.UNIT_MODEL_CHOICES = [(None, '----')] + [(u.uuid, u) for i, u in enumerate(self.unit_model_qs)]
246
277
 
247
- self.VENDOR_MODEL_QS = entity_model.vendormodel_set.visible()
248
- self.CUSTOMER_MODEL_QS = entity_model.customermodel_set.visible()
278
+ self.VENDOR_MODEL_QS = entity_model.vendormodel_set.visible().order_by('vendor_name')
279
+ self.CUSTOMER_MODEL_QS = entity_model.customermodel_set.visible().order_by('customer_name')
249
280
 
250
281
  self.VENDOR_CHOICES = [(None, '-----')] + [(str(v.uuid), v) for v in self.VENDOR_MODEL_QS]
251
282
  self.CUSTOMER_CHOICES = [(None, '-----')] + [(str(c.uuid), c) for c in self.CUSTOMER_MODEL_QS]
@@ -253,6 +284,15 @@ class BaseStagedTransactionModelFormSet(BaseModelFormSet):
253
284
  self.VENDOR_MAP = dict(self.VENDOR_CHOICES)
254
285
  self.CUSTOMER_MAP = dict(self.CUSTOMER_CHOICES)
255
286
 
287
+ self.FORMS_BY_ID = {
288
+ f.instance.uuid: f for f in self.forms if getattr(f, 'instance', None) and getattr(f.instance, 'uuid', None)
289
+ }
290
+
291
+ form_children = [(f.instance.parent_id, f.instance.uuid) for f in self.forms if f.instance.parent_id]
292
+ form_children.sort(key=lambda f: f[0])
293
+
294
+ self.FORM_CHILDREN = {g: list(j[1] for j in p) for g, p in groupby(form_children, key=lambda i: i[0])}
295
+
256
296
  def get_form_kwargs(self, index):
257
297
  return {
258
298
  'base_formset_instance': self,
@@ -15,5 +15,6 @@ from django_ledger.models.unit import *
15
15
  from django_ledger.models.purchase_order import *
16
16
  from django_ledger.models.closing_entry import *
17
17
  from django_ledger.models.entity import *
18
+
18
19
  from django_ledger.models.data_import import *
19
20
  from django_ledger.models.receipt import *
@@ -16,7 +16,7 @@ from decimal import Decimal
16
16
  from typing import Dict, List, Optional, Set, Union
17
17
  from uuid import UUID, uuid4
18
18
 
19
- from django.core.exceptions import ObjectDoesNotExist, ValidationError
19
+ from django.core.exceptions import ValidationError
20
20
  from django.db import models, transaction
21
21
  from django.db.models import (
22
22
  BooleanField,
@@ -432,16 +432,31 @@ class StagedTransactionModelQuerySet(QuerySet):
432
432
 
433
433
  This method filters the objects in the queryset to determine whether there
434
434
  are any transactions that are pending (i.e., have a null transaction_model).
435
- Pending transactions are identified by checking if the `transaction_model` is
436
- null for any of the objects in the queryset.
435
+ Additionally, it includes parent transactions (not bundled) that have at least
436
+ one child transaction still pending import.
437
437
 
438
438
  Returns
439
439
  -------
440
440
  QuerySet
441
- A QuerySet containing objects with a null `transaction_model`.
441
+ A QuerySet containing objects with a null `transaction_model`, or parent
442
+ transactions (not bundled) with pending children.
442
443
 
443
444
  """
444
- return self.filter(transaction_model__isnull=True)
445
+ parents_with_pending_children = Q(
446
+ parent__isnull=True, bundle_split=False, children_mapping_done=False, children_import_pending_count__gt=0
447
+ )
448
+
449
+ return self.filter(
450
+ Q(
451
+ transaction_model__isnull=True,
452
+ )
453
+ | parents_with_pending_children
454
+ ).exclude(
455
+ bundle_split=False,
456
+ transaction_model__isnull=True,
457
+ children_count__gt=0,
458
+ children_import_pending_count=0
459
+ )
445
460
 
446
461
  def is_imported(self):
447
462
  """
@@ -449,13 +464,19 @@ class StagedTransactionModelQuerySet(QuerySet):
449
464
  related transaction model. This function checks whether the `transaction_model`
450
465
  field in the related objects is non-null.
451
466
 
467
+ Additionally, it includes non-bundled parent transactions only if they have at
468
+ least one imported child (i.e., a child with a non-null `transaction_model`).
469
+ This ensures a parent is not considered imported until at least one child is
470
+ imported.
471
+
452
472
  Returns
453
473
  -------
454
474
  QuerySet
455
- A filtered queryset containing only objects where the `transaction_model`
456
- is not null.
475
+ A filtered queryset containing objects where the `transaction_model` is not
476
+ null, plus non-bundled parents that have at least one imported child.
457
477
  """
458
- return self.filter(transaction_model__isnull=False)
478
+ parents_with_imported_children = Q(parent__isnull=True, bundle_split=False, imported_count__gt=0)
479
+ return self.filter(Q(transaction_model__isnull=False) | parents_with_imported_children)
459
480
 
460
481
  def is_parent(self):
461
482
  """
@@ -548,6 +569,7 @@ class StagedTransactionModelManager(Manager):
548
569
  entity_unit=F('transaction_model__journal_entry__entity_unit__name'),
549
570
  children_count=Count('split_transaction_set'),
550
571
  children_mapped_count=Count('split_transaction_set__account_model__uuid'),
572
+ imported_count=Count('split_transaction_set__transaction_model_id'),
551
573
  total_amount_split=Coalesce(
552
574
  Sum('split_transaction_set__amount_split'),
553
575
  Value(value=0.00, output_field=DecimalField()),
@@ -560,6 +582,7 @@ class StagedTransactionModelManager(Manager):
560
582
  )
561
583
  .annotate(
562
584
  children_mapping_pending_count=F('children_count') - F('children_mapped_count'),
585
+ children_import_pending_count=F('imported_count') - F('children_count'),
563
586
  )
564
587
  .annotate(
565
588
  children_mapping_done=Case(
@@ -602,7 +625,7 @@ class StagedTransactionModelManager(Manager):
602
625
  ),
603
626
  then=True,
604
627
  ),
605
- # is children, mapped and all parent amount is split...
628
+ # is parent, mapped and all parent amount is split...
606
629
  When(
607
630
  condition=(
608
631
  # no receipt type selected...
@@ -611,12 +634,12 @@ class StagedTransactionModelManager(Manager):
611
634
  Q(children_count__gt=0)
612
635
  & Q(bundle_split=True)
613
636
  & Q(receipt_type__isnull=True)
614
- & Q(children_count=F('children_mapped_count'))
637
+ & Q(children_mapping_done=True)
615
638
  & Q(total_amount_split__exact=F('amount'))
616
639
  & Q(parent__isnull=True)
617
640
  & Q(transaction_model__isnull=True)
618
641
  & Q(customer_model__isnull=True)
619
- & Q(vendor_model__isnull=False)
642
+ & Q(vendor_model__isnull=True)
620
643
  )
621
644
  # BUNDLED...
622
645
  # a receipt type is assigned... at least a customer or vendor is selected...
@@ -643,10 +666,19 @@ class StagedTransactionModelManager(Manager):
643
666
  & Q(receipt_type__isnull=True)
644
667
  & Q(vendor_model__isnull=True)
645
668
  & Q(customer_model__isnull=True)
646
- & Q(children_count=F('children_mapped_count'))
669
+ & Q(children_mapping_done=True)
647
670
  & Q(total_amount_split__exact=F('amount'))
648
671
  & Q(transaction_model__isnull=True)
649
672
  )
673
+ | (
674
+ Q(children_count__exact=0)
675
+ & Q(parent__isnull=False)
676
+ & Q(bundle_split=False)
677
+ & Q(receipt_type__isnull=False)
678
+ & (Q(vendor_model__isnull=False) | Q(customer_model__isnull=False))
679
+ & Q(children_mapping_done=True)
680
+ & Q(transaction_model__isnull=True)
681
+ )
650
682
  ),
651
683
  then=True,
652
684
  ),
@@ -905,10 +937,12 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
905
937
  return [
906
938
  {
907
939
  'account': self.account_model,
908
- 'amount': abs(self.amount),
909
- 'amount_staged': self.amount,
940
+ 'amount': abs(self.amount if not self.is_children() else self.amount_split),
941
+ 'amount_staged': self.amount if not self.is_children() else self.amount_split,
910
942
  'unit_model': self.unit_model,
911
- 'tx_type': CREDIT if not self.amount < 0.00 else DEBIT,
943
+ 'tx_type': CREDIT
944
+ if not (self.amount if not self.is_children() else self.amount_split) < 0.00
945
+ else DEBIT,
912
946
  'description': self.name,
913
947
  'staged_tx_model': self,
914
948
  }
@@ -996,34 +1030,57 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
996
1030
  properly. This method checks whether both `account_model_id` and
997
1031
  `transaction_model_id` are set.
998
1032
 
1033
+ Additionally, a parent transaction that is not bundled will be considered
1034
+ imported for listing purposes if it has at least one child transaction that is
1035
+ still pending import. This allows a parent to appear in both imported and
1036
+ pending states when its children are not fully imported.
1037
+
999
1038
  Returns
1000
1039
  -------
1001
1040
  bool
1002
1041
  True if both `account_model_id` and `transaction_model_id` are not None,
1003
- indicating that the models have been successfully imported. False otherwise.
1042
+ indicating that the models have been successfully imported; or if this is a
1043
+ non-bundled parent with at least one pending child. False otherwise.
1004
1044
  """
1005
- return all(
1045
+ own_imported = all(
1006
1046
  [
1007
1047
  self.account_model_id is not None,
1008
1048
  self.transaction_model_id is not None,
1009
1049
  ]
1010
1050
  )
1051
+ parent_with_imported_child = all(
1052
+ [
1053
+ self.is_parent(),
1054
+ not self.is_bundled(),
1055
+ self.split_transaction_set.filter(transaction_model__isnull=False).exists(),
1056
+ ]
1057
+ )
1058
+ return own_imported or parent_with_imported_child
1011
1059
 
1012
1060
  def is_pending(self) -> bool:
1013
1061
  """
1014
1062
  Determine if the transaction is pending.
1015
1063
 
1016
1064
  A transaction is considered pending if it has not been assigned a
1017
- `transaction_model_id`. This function checks the attribute and returns
1018
- a boolean indicating the status.
1065
+ `transaction_model_id`. Additionally, a parent transaction that is not
1066
+ bundled is also considered pending if any of its children are still
1067
+ pending import. This allows a parent to be both imported and pending
1068
+ while its children are not fully imported.
1019
1069
 
1020
1070
  Returns
1021
1071
  -------
1022
1072
  bool
1023
1073
  True if the transaction is pending (i.e., `transaction_model_id`
1024
- is None), False otherwise.
1074
+ is None), or this is a non-bundled parent with at least one pending
1075
+ child. False otherwise.
1025
1076
  """
1026
- return self.transaction_model_id is None
1077
+ return self.transaction_model_id is None or all(
1078
+ [
1079
+ self.is_parent(),
1080
+ not self.is_bundled(),
1081
+ self.split_transaction_set.filter(transaction_model__isnull=True).exists(),
1082
+ ]
1083
+ )
1027
1084
 
1028
1085
  def is_mapped(self) -> bool:
1029
1086
  """
@@ -1288,8 +1345,6 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1288
1345
  return False
1289
1346
 
1290
1347
  def has_receipt(self) -> bool:
1291
- # if self.is_children() and self.is_bundled():
1292
- # return self.parent.receipt_type is not None
1293
1348
  return self.receipt_type is not None
1294
1349
 
1295
1350
  def has_mapped_receipt(self) -> bool:
@@ -1318,9 +1373,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1318
1373
  return False
1319
1374
 
1320
1375
  def can_unbundle(self) -> bool:
1321
- if any([
1322
- not self.is_single()
1323
- ]):
1376
+ if any([not self.is_single()]):
1324
1377
  return True
1325
1378
  return False
1326
1379
 
@@ -1341,7 +1394,8 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1341
1394
  [
1342
1395
  self.is_single(),
1343
1396
  self.is_parent_is_bundled_has_receipt(),
1344
- self.is_parent_is_bundled_no_receipt()
1397
+ self.is_parent_is_bundled_no_receipt(),
1398
+ self.is_parent_not_bundled_no_receipt(),
1345
1399
  ]
1346
1400
  ):
1347
1401
  return True
@@ -1454,12 +1508,20 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1454
1508
 
1455
1509
  if not ready_to_import:
1456
1510
  return False
1511
+ is_role_valid = self.is_role_mapping_valid(raise_exception=False)
1512
+ if not is_role_valid:
1513
+ return False
1457
1514
 
1458
- if ready_to_import:
1459
- is_role_valid = self.is_role_mapping_valid(raise_exception=False)
1460
- if is_role_valid:
1515
+ if ready_to_import and is_role_valid:
1516
+ if self.is_bundled():
1461
1517
  return True
1462
1518
 
1519
+ # not bundled....
1520
+ else:
1521
+ if any([self.is_child_not_bundled_no_receipt(), self.is_child_not_bundled_has_receipt()]):
1522
+ return True
1523
+ return False
1524
+
1463
1525
  can_split_into_je = getattr(self, 'can_split_into_je')
1464
1526
  if can_split_into_je and as_split:
1465
1527
  return True
@@ -1472,10 +1534,13 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1472
1534
  if ready_to_import:
1473
1535
  if self.is_transfer():
1474
1536
  return True
1475
- if any([
1476
- self.is_single_has_receipt(),
1477
- self.is_parent_is_bundled_has_receipt()
1478
- ]):
1537
+ if any(
1538
+ [
1539
+ self.is_single_has_receipt(),
1540
+ self.is_parent_is_bundled_has_receipt(),
1541
+ self.is_child_not_bundled_has_receipt(),
1542
+ ]
1543
+ ):
1479
1544
  return True
1480
1545
  return False
1481
1546
 
@@ -1619,10 +1684,15 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1619
1684
  The journal entry activity if successfully retrieved or updated; otherwise,
1620
1685
  returns the existing activity or None if no activity is present.
1621
1686
  """
1622
- if (
1623
- force_update
1624
- or all([self.is_children() and self.parent.has_activity(), not self.is_bundled(), not self.has_activity()])
1625
- or all([not self.has_activity(), getattr(self, 'ready_to_import')])
1687
+ if any(
1688
+ [
1689
+ force_update,
1690
+ self.is_single(),
1691
+ self.is_parent_is_bundled_no_receipt(),
1692
+ self.is_parent_is_bundled_has_receipt(),
1693
+ self.is_child_not_bundled_has_receipt(),
1694
+ self.is_child_not_bundled_no_receipt(),
1695
+ ]
1626
1696
  ):
1627
1697
  role_set = self.get_import_role_set()
1628
1698
  if role_set is not None:
@@ -1761,14 +1831,11 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1761
1831
  force_je_retrieval=False,
1762
1832
  )
1763
1833
  staged_to_save += [i['staged_tx_model'] for i in je_data]
1764
- # staged_to_save = set(i['staged_tx_model'] for i in je_data)
1765
- # for i in staged_to_save:
1766
- # i.save(update_fields=['transaction_model', 'updated'])
1767
1834
  staged_to_save = set(staged_to_save)
1768
1835
  for i in staged_to_save:
1769
1836
  i.save(update_fields=['transaction_model', 'updated'])
1770
1837
 
1771
- def migrate_receipt(self, receipt_date: Optional[date | datetime] = None):
1838
+ def migrate_receipt(self, receipt_date: Optional[date | datetime] = None, split_amount: bool = False):
1772
1839
  if not self.can_migrate_receipt():
1773
1840
  raise StagedTransactionModelValidationError(
1774
1841
  'Migrate receipts can only be performed on receipt transactions. Use migrate_transactions() instead.'
@@ -1778,7 +1845,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1778
1845
 
1779
1846
  with transaction.atomic():
1780
1847
  receipt_model: ReceiptModel = self.generate_receipt_model(receipt_date=receipt_date, commit=True)
1781
- receipt_model.migrate_receipt()
1848
+ receipt_model.migrate_receipt(split_amount=split_amount)
1782
1849
 
1783
1850
  def generate_receipt_model(self, receipt_date: Optional[date] = None, commit: bool = False) -> ReceiptModel:
1784
1851
  if receipt_date:
@@ -1791,7 +1858,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1791
1858
  receipt_model.configure(
1792
1859
  receipt_date=receipt_date,
1793
1860
  entity_model=self.entity_slug,
1794
- amount=abs(self.amount),
1861
+ amount=abs(self.amount_split if self.is_children() else self.amount),
1795
1862
  unit_model=self.unit_model,
1796
1863
  receipt_type=self.receipt_type,
1797
1864
  vendor_model=self.vendor_model if self.is_expense() or self.is_debt_payment() else None,
@@ -1805,12 +1872,12 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1805
1872
  return receipt_model
1806
1873
 
1807
1874
  def can_undo_import(self):
1875
+ if self.transaction_model_id is None:
1876
+ return False
1808
1877
  if all([self.is_children(), self.is_bundled()]):
1809
1878
  return False
1810
-
1811
1879
  return True
1812
1880
 
1813
- # UNDO
1814
1881
  def undo_import(self, raise_exception: bool = True):
1815
1882
  """
1816
1883
  Undo import operation for a staged transaction. This method handles the deletion
@@ -1832,10 +1899,7 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1832
1899
 
1833
1900
  with transaction.atomic():
1834
1901
  # Receipt import case...
1835
- try:
1836
- receipt_model = self.receiptmodel
1837
- except ObjectDoesNotExist:
1838
- receipt_model = None
1902
+ receipt_model = getattr(self, 'receiptmodel', None)
1839
1903
 
1840
1904
  if receipt_model is not None:
1841
1905
  receipt_model.delete()
@@ -1850,10 +1914,10 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1850
1914
  tx_model = self.transaction_model
1851
1915
  journal_entry_model = tx_model.journal_entry
1852
1916
 
1853
- if journal_entry_model.can_unlock() and journal_entry_model.can_unpost():
1854
- journal_entry_model.unlock(raise_exception=False)
1855
- journal_entry_model.unpost(raise_exception=False)
1856
- journal_entry_model.delete()
1917
+ journal_entry_model.unpost(raise_exception=False)
1918
+ journal_entry_model.unlock(raise_exception=False)
1919
+
1920
+ journal_entry_model.delete()
1857
1921
 
1858
1922
  self.transaction_model = None
1859
1923
  self.save(update_fields=['transaction_model', 'updated'])
@@ -1862,6 +1926,11 @@ class StagedTransactionModelAbstract(CreateUpdateMixIn):
1862
1926
  if raise_exception:
1863
1927
  raise StagedTransactionModelValidationError(message=_('Nothing to undo for this staged transaction.'))
1864
1928
 
1929
+ def can_delete(self) -> bool:
1930
+ if self.is_children():
1931
+ return True
1932
+ return False
1933
+
1865
1934
  def clean(self, verify: bool = False):
1866
1935
  if self.has_children():
1867
1936
  self.amount_split = None
@@ -2001,3 +2070,10 @@ def stagedtransactionmodel_presave(instance: StagedTransactionModel, **kwargs):
2001
2070
 
2002
2071
 
2003
2072
  pre_save.connect(stagedtransactionmodel_presave, sender=StagedTransactionModel)
2073
+
2074
+
2075
+ def stagedtransactionmodel_predelete(instance: StagedTransactionModel, **kwargs):
2076
+ if not instance.can_delete():
2077
+ raise StagedTransactionModelValidationError(
2078
+ message=_('Cannot delete parent Staged Transactions.'),
2079
+ )
@@ -825,7 +825,7 @@ class ReceiptModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn, IOMixIn):
825
825
  ]
826
826
  )
827
827
 
828
- def migrate_receipt(self):
828
+ def migrate_receipt(self, split_amount: bool = False):
829
829
  """Post staged transactions into the ledger as journal entries.
830
830
 
831
831
  This method commits staged transactions linked to the receipt into the
@@ -846,7 +846,7 @@ class ReceiptModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn, IOMixIn):
846
846
  message='Must have VendorModel or CustomerModel, not both.',
847
847
  )
848
848
 
849
- commit_dict = self.staged_transaction_model.commit_dict(split_txs=False)
849
+ commit_dict = self.staged_transaction_model.commit_dict(split_txs=split_amount)
850
850
  ledger_model = self.ledger_model
851
851
  staged_to_save = list()
852
852
  for je_data in commit_dict:
@@ -50,8 +50,8 @@
50
50
  {{ imported_tx.get_activity_display }}
51
51
  {% endif %}</td>
52
52
  <td>{% if imported_tx.entity_unit %}{{ imported_tx.entity_unit }}{% endif %}</td>
53
- <td>{{ imported_tx.account_model }}</td>
54
- <td>{{ imported_tx.transaction_model }}</td>
53
+ <td>{{ imported_tx.account_model | default:'' }}</td>
54
+ <td>{{ imported_tx.transaction_model | default:'' }}</td>
55
55
  <td class="has-text-centered">
56
56
  <div class="dropdown is-hoverable is-right">
57
57
  <div class="dropdown-trigger">
@@ -63,9 +63,11 @@
63
63
  </div>
64
64
  <div class="dropdown-menu" id="actions-{{ imported_tx.uuid }}" role="menu">
65
65
  <div class="dropdown-content">
66
- <a href="{% url 'django_ledger:je-detail' entity_slug=import_job_model.entity_slug ledger_pk=imported_tx.transaction_model.journal_entry.ledger_id je_pk=imported_tx.transaction_model.journal_entry_id %}"
67
- class="dropdown-item">{% trans 'View JE' %}</a>
68
- {% if imported_tx.has_receipt %}
66
+ {% if not imported_tx.is_parent and not imported_tx.is_bundled %}
67
+ <a href="{% url 'django_ledger:je-detail' entity_slug=import_job_model.entity_slug ledger_pk=imported_tx.transaction_model.journal_entry.ledger_id je_pk=imported_tx.transaction_model.journal_entry_id %}"
68
+ class="dropdown-item">{% trans 'View JE' %}</a>
69
+ {% endif %}
70
+ {% if imported_tx.receipt_uuid %}
69
71
  <a href="{% url 'django_ledger:receipt-detail' entity_slug=import_job_model.entity_slug receipt_pk=imported_tx.receiptmodel.uuid %}"
70
72
  class="dropdown-item">{% trans 'View Receipt' %}</a>
71
73
  {% endif %}
@@ -77,8 +79,8 @@
77
79
  <button type="submit"
78
80
  class="dropdown-item has-text-danger">{% trans 'Undo Import' %}</button>
79
81
  </form>
82
+ </div>
80
83
  {% endif %}
81
- </div>
82
84
  </div>
83
85
  </div>
84
86
  </td>
@@ -78,8 +78,6 @@
78
78
  <td class="has-text-centered">
79
79
  {{ txf.customer_model }}
80
80
  {{ txf.vendor_model }}
81
- {# {{ txf.instance.can_migrate }}#}
82
- {# {{ txf.instance.ready_to_import }}#}
83
81
  </td>
84
82
  <td class="has-text-centered">{{ txf.tx_import }}</td>
85
83
  <td class="has-text-centered">{{ txf.bundle_split }}</td>
@@ -58,8 +58,10 @@
58
58
  <a href="{{ tx.get_journal_entry_detail_url }}">{{ tx.journal_entry.description|default:'—' }}</a>
59
59
  </td>
60
60
  <td>{{ tx.account.code }} - {{ tx.account.name }}</td>
61
- <td class="has-text-right">{% if tx.is_debit %}{{ tx.amount }}{% endif %}</td>
62
- <td class="has-text-right">{% if tx.is_credit %}{{ tx.amount }}{% endif %}</td>
61
+ <td class="has-text-right">{% currency_symbol %}{% if tx.is_debit %}
62
+ {{ tx.amount | currency_format }}{% endif %}</td>
63
+ <td class="has-text-right">{% currency_symbol %}{% if tx.is_credit %}
64
+ {{ tx.amount | currency_format }}{% endif %}</td>
63
65
  <td>{{ tx.journal_entry.entity_unit.name|default:'—' }}</td>
64
66
  </tr>
65
67
  {% endfor %}
@@ -210,17 +210,18 @@ class DataImportJobDetailView(ImportJobModelViewBaseView, DetailView):
210
210
  for tx_form in txs_formset:
211
211
  if tx_form.has_changed():
212
212
  staged_transaction_model: StagedTransactionModel = tx_form.instance
213
+
213
214
  is_split = tx_form.cleaned_data['tx_split'] is True
215
+ is_import = tx_form.cleaned_data['tx_import'] is True
216
+ is_bundled = tx_form.cleaned_data['bundle_split'] is True
217
+
214
218
  if is_split:
215
219
  staged_transaction_model.add_split()
216
- # import entry was selected for import...
217
- is_import = tx_form.cleaned_data['tx_import']
218
- if is_import:
219
- # all entries in split will be going so the same journal entry... (same unit...)
220
- is_bundled = tx_form.cleaned_data['bundle_split']
220
+ elif is_import:
221
221
  if staged_transaction_model.can_migrate_receipt():
222
222
  staged_transaction_model.migrate_receipt(
223
- receipt_date=staged_transaction_model.date_posted
223
+ receipt_date=staged_transaction_model.date_posted,
224
+ split_amount=not is_bundled,
224
225
  )
225
226
  else:
226
227
  staged_transaction_model.migrate_transactions(split_txs=not is_bundled)
@@ -276,9 +277,12 @@ class ImportJobModelResetView(ImportJobModelViewBaseView, DetailView):
276
277
 
277
278
  def post(self, request, **kwargs):
278
279
  import_job_model: ImportJobModel = self.get_object()
279
- imported_staged_txs = import_job_model.stagedtransactionmodel_set.is_imported()
280
+ imported_staged_txs = import_job_model.stagedtransactionmodel_set.all()
280
281
  for staged_tx in imported_staged_txs:
281
282
  staged_tx.undo_import(raise_exception=False)
283
+ if staged_tx.is_children():
284
+ staged_tx.delete()
285
+
282
286
 
283
287
  return redirect(
284
288
  to=import_job_model.get_data_import_url(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ledger
3
- Version: 0.8.2.2
3
+ Version: 0.8.2.3
4
4
  Summary: Double entry accounting system built on the Django Web Framework.
5
5
  Author-email: Miguel Sanda <msanda@arrobalytics.com>
6
6
  Maintainer-email: Miguel Sanda <msanda@arrobalytics.com>
@@ -1,4 +1,4 @@
1
- django_ledger/__init__.py,sha256=DQY-BUhQriGeWBMqAUYBynkiPI0FhUS9ecHusI9R23k,382
1
+ django_ledger/__init__.py,sha256=ZL-fuSwWwUj6jcETC_xzBKG2xCheCQcy4KfU6QD3v-U,382
2
2
  django_ledger/apps.py,sha256=H-zEWUjKGakgSDSZmLIoXChZ2h6e0dth0ZO5SpoT-8U,163
3
3
  django_ledger/context.py,sha256=OBNHHm6qlt3fBc1_EiOcbzwafsqJBXtdZY5ubW_gga0,301
4
4
  django_ledger/exceptions.py,sha256=rML8sQQ0Hq-DYMLZ76dfw2RYSAsXWUoyHuyC_yP9o1o,491
@@ -18,7 +18,7 @@ django_ledger/forms/bill.py,sha256=u5zJWWq4huEsfqukHCCnlGEcUsArOQmrKyBcRtsSWCU,1
18
18
  django_ledger/forms/chart_of_accounts.py,sha256=wymLQ8iLNPU_LgS79BOdMUapuLqoTvgqVdAyL1Pw0Ro,2414
19
19
  django_ledger/forms/closing_entry.py,sha256=kJQtVqira0rpYvlmMlGAaV5L3Wz3XNXSgrcVfZ2VyLM,1948
20
20
  django_ledger/forms/customer.py,sha256=xK1tlA1yYvZM2TWeSumcHOfmw0DiiIzgypOoXDK5fF4,2707
21
- django_ledger/forms/data_import.py,sha256=EePipfO_4J7Vq4dD3ISPGA3hhhOU6fsdAs2TSv90aHg,10606
21
+ django_ledger/forms/data_import.py,sha256=4phvkT09Gl9xT6FAj3zdW8w1yvxgjbS3Ua6Eqzqrx0E,12390
22
22
  django_ledger/forms/entity.py,sha256=b0QirmsFSnaM8YWDO4V6GQXfFgR_MLmdq27I2q2sGQ0,6880
23
23
  django_ledger/forms/estimate.py,sha256=oGx4NbIkySA3a9r4bTNCz7t2swsTQgxuOSOdXIrXcZo,5064
24
24
  django_ledger/forms/feedback.py,sha256=WUT-kI4uT6q5aqEYaDYwyIFfhXpmtwMv6qf9BFSYsDo,1850
@@ -71,7 +71,7 @@ django_ledger/migrations/0025_alter_billmodel_cash_account_and_more.py,sha256=8p
71
71
  django_ledger/migrations/0026_stagedtransactionmodel_customer_model_and_more.py,sha256=qv2PFWhXgAHWGptb8Xgoq0O4TsGqJelnI0-BRSJqZBE,4470
72
72
  django_ledger/migrations/0027_alter_accountmodel_role_alter_receiptmodel_amount_and_more.py,sha256=2lOHxOP6IvizjTwukN4GB0GWrROmDv_mdRM3wf6IXZo,7391
73
73
  django_ledger/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
- django_ledger/models/__init__.py,sha256=kS8Qr1pBIeg2j1jQvDTEGzGMsK4YxKtpyUj8CWpm9yw,851
74
+ django_ledger/models/__init__.py,sha256=AJTkUumUJzwg5RnbtuHikD7dqX4KaIYj7B8dZYtRz7E,852
75
75
  django_ledger/models/accounts.py,sha256=Y0EuVy1xCELOLhIMXnku6VlWFC33JctK7NsnJ-L62QM,38924
76
76
  django_ledger/models/bank_account.py,sha256=P-62wjQ94EneCseewDV2tyRt0ghlaEh1jFIowWJdHCU,8942
77
77
  django_ledger/models/bill.py,sha256=a15MHAl5HSu536BsWwbcYnGXL8PGo14ToncVNktME2s,64469
@@ -79,7 +79,7 @@ django_ledger/models/chart_of_accounts.py,sha256=g7NWRCSoj4YFuOPBGUqgBVekwCU7xPO
79
79
  django_ledger/models/closing_entry.py,sha256=zxoZOC2ymbrwyPOGPsKXXBwHDGegAtTnqDye0Y-mVAo,20625
80
80
  django_ledger/models/coa_default.py,sha256=L3fHc-HvYo_gTvCWv5oZl-rWnH9ZPuloK-bw3_cICo0,27614
81
81
  django_ledger/models/customer.py,sha256=JDlCII2r4Ebj08tYc2DfRVBWRb68Nw61ZRDGIEAUjvk,13375
82
- django_ledger/models/data_import.py,sha256=hJPHjpk8Jfgl9W7wS1MPSJsXKkIZDvEyNHeSPswl61M,75849
82
+ django_ledger/models/data_import.py,sha256=0vB8n9JwwlGCJ5lTsf14LGCru0RDdxFqQX4MPSmxXtY,79393
83
83
  django_ledger/models/deprecations.py,sha256=49sLBeVhHMYSf3F3VPCE6_5iNZH1xmElWZsgGIGUErs,2275
84
84
  django_ledger/models/entity.py,sha256=SN73ceuj3kNcfWCrLRZVoMUePGBl6j4SABkwr9OZg_Y,122092
85
85
  django_ledger/models/estimate.py,sha256=0DmDs7U5WqaQM9xciuXKR29pGHaLT7z_QmlNvvzUy_0,59406
@@ -89,7 +89,7 @@ django_ledger/models/journal_entry.py,sha256=X3wRhYqOmDGcRZj4WYGCNDQhBBZ1hp5fnCc
89
89
  django_ledger/models/ledger.py,sha256=hT5u9IHJy5vun9_bzcC5FKN8ocP0OGdR4RDDe3icgzo,29400
90
90
  django_ledger/models/mixins.py,sha256=v5m7MwUcsndLctbTrrof-RSlMyc5tcQqRhcmPjJbOk4,54287
91
91
  django_ledger/models/purchase_order.py,sha256=X5ZIfwTAMWUbqL79nQCWWltlOiKSEFGocfaWHK3i154,45128
92
- django_ledger/models/receipt.py,sha256=cBAPrhW_sGs_2uXgo2U1kv6xLnfLz_x_j6YKpb-yb3o,37317
92
+ django_ledger/models/receipt.py,sha256=q5yT5v2c55sPyvIpweHoR6LY5hm7PXlybkAilSobWUQ,37352
93
93
  django_ledger/models/signals.py,sha256=3cm_8--Jz-Jb0fPgrVmm5xx_jKFARV6_A29VDjqHeIw,1563
94
94
  django_ledger/models/transactions.py,sha256=TmHqTVaPx3d0Flaab_cUbKfa7a1hEpkgbsdV6YZlyVc,26431
95
95
  django_ledger/models/unit.py,sha256=iP7gMLCPV82uxsDIWa-BhdgLtm8FwGbQ_kOCSC-tJJo,10227
@@ -217,8 +217,8 @@ django_ledger/templates/django_ledger/data_import/import_job_create.html,sha256=
217
217
  django_ledger/templates/django_ledger/data_import/import_job_delete.html,sha256=qgobtrI-WNYFqhn8de05mcD-FQ0UGSLT6ZoaBlblXAU,944
218
218
  django_ledger/templates/django_ledger/data_import/import_job_update.html,sha256=ZDy391RSOXUq3keUs48QrVrI_ZicdNvDeLT_JacH17M,887
219
219
  django_ledger/templates/django_ledger/data_import/tags/data_import_job_list_table.html,sha256=y_U1xixMC9YPR4aq6F_cNpyb-dZ5qcVh9D6HSN9Xpps,2919
220
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html,sha256=uufMvCsyaa-oyePFl5t7V1I86MCaE7twzrbl7rsExwo,5223
221
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html,sha256=OtrMoTE8B7hHaHcH4u4bRPqBEadqOHhjPjajnpI0bxU,4840
220
+ django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html,sha256=NYck3UKL5-eLab_PV3wVnZKf0oaPisgha-2YghVrWd0,5416
221
+ django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html,sha256=93Pqi8bvLv_x4-cavBtAa6p7gKDn1_UnUjAvKwaatgY,4718
222
222
  django_ledger/templates/django_ledger/entity/entitiy_list.html,sha256=onM9uaWTm2oQ00OPIH5ki2FEfgjx7_EIOT8akuAhQM4,1507
223
223
  django_ledger/templates/django_ledger/entity/entity_create.html,sha256=TDayIv2qno7cT3xTCapKV6EZKcTTNfIMxXJFQmHHyz0,1387
224
224
  django_ledger/templates/django_ledger/entity/entity_dashboard.html,sha256=7iQYLYsP_Q1S2wGcYcZUn3FWJBNJPxZ4OvGQjfctoiw,5435
@@ -301,7 +301,7 @@ django_ledger/templates/django_ledger/purchase_order/includes/po_table.html,sha2
301
301
  django_ledger/templates/django_ledger/purchase_order/tags/po_item_table.html,sha256=2DK0aaqG4R-GsOr_I4mHVEI8O614FuggpEBN6r-jcu4,1998
302
302
  django_ledger/templates/django_ledger/receipt/customer_receipt_report.html,sha256=Z2M4JnelzRRscVgA7jA3gWvDWMv9wiz5IiVVubqn98E,5197
303
303
  django_ledger/templates/django_ledger/receipt/receipt_delete.html,sha256=yB5jHmFUOc1ZMulSU5uq8oc1jomRmIZA0aK91iF0Oxc,1226
304
- django_ledger/templates/django_ledger/receipt/receipt_detail.html,sha256=thlMFiqfZFKuXqX0mbdO4OmRlvAR9CrPuchK8eCMVPw,4757
304
+ django_ledger/templates/django_ledger/receipt/receipt_detail.html,sha256=QITtI2VFa7E1Ub9KAR9ParTIPCxUvQOERuyORMykywo,4917
305
305
  django_ledger/templates/django_ledger/receipt/receipt_list.html,sha256=KZSxhgA_uZj015Arx_CM8ghbxTlSLUew6v3NP49AXzM,6810
306
306
  django_ledger/templates/django_ledger/receipt/vendor_receipt_report.html,sha256=ySVb7twSChUXEuPSujZZaD8PtNBBsEDLdTKEE8zq4qQ,5149
307
307
  django_ledger/templates/django_ledger/service/service_create.html,sha256=kGlqz3-txJojmBEnwXat8V6p4mZ0yozKBYDSlMgGcB8,1431
@@ -375,7 +375,7 @@ django_ledger/views/bill.py,sha256=dtp8KqyqvDGPy99lB1bz4jHEhHJ_4b87O6jpRFS-oOI,2
375
375
  django_ledger/views/chart_of_accounts.py,sha256=AlRdX_g-Hjqetv1pVdQRMIYj4D4gnCURUgbAnPt_kpA,5659
376
376
  django_ledger/views/closing_entry.py,sha256=JG2uDrqi90S1_Edb6-dsfNgx5xXLQRm6awjf_c8Y8fk,8433
377
377
  django_ledger/views/customer.py,sha256=UPdEZ8DJj6FHR-eO2EgqdrYzU6QoAp1QAvPgHK0SXSg,4867
378
- django_ledger/views/data_import.py,sha256=LNdzBaU1_7wWD9_aKaAPkAx45ouDfusMM9TzLOEubI0,10959
378
+ django_ledger/views/data_import.py,sha256=mDQqHj5Z8lAxWwKCSmSkMPHOINm_INPlCy281HkcxlU,10934
379
379
  django_ledger/views/djl_api.py,sha256=W4DooOXtTDvcZ6AyBgcwv86CUytQx_KHxxycU6s3e3I,4359
380
380
  django_ledger/views/entity.py,sha256=eY7dDY45Mv3M5pdegXtdkbC0TAzJh7LuG0NrZx56lM8,9394
381
381
  django_ledger/views/estimate.py,sha256=z5rlNiiXDXR24yPdzCNXVM_yJRDE4CG4ZrItijaXtmY,12648
@@ -393,9 +393,9 @@ django_ledger/views/receipt.py,sha256=mtiHfk529oVVSIrAR28LgQc696HUVFnV4rYd9HTqoV
393
393
  django_ledger/views/transactions.py,sha256=3ijtJzdLPFkqG7OYpe-7N4QVjCyR2yl5ht_9RyfquBA,212
394
394
  django_ledger/views/unit.py,sha256=IK0KWf_NkTCzJILtA4SDU9tD4OL3kbAktLdvL6fxRhU,10035
395
395
  django_ledger/views/vendor.py,sha256=-tUs6-flA_SiXuDWs173Sho4JwQr0zMAhqD_-xM0eeA,4746
396
- django_ledger-0.8.2.2.dist-info/licenses/AUTHORS.md,sha256=ShPwf-qniJkbjRzX5_lqhmgoLMEYMSHSwKPXHZtWmyk,824
397
- django_ledger-0.8.2.2.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
398
- django_ledger-0.8.2.2.dist-info/METADATA,sha256=p37l1i-Rpg7RHpm4Bx_bFhrOpT8jCGUQtlEfaV2DguQ,8460
399
- django_ledger-0.8.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
400
- django_ledger-0.8.2.2.dist-info/top_level.txt,sha256=57KG3JBsF9fqPnPGyFfcpKtfFNS9TQONjtfKhUlwiy4,14
401
- django_ledger-0.8.2.2.dist-info/RECORD,,
396
+ django_ledger-0.8.2.3.dist-info/licenses/AUTHORS.md,sha256=ShPwf-qniJkbjRzX5_lqhmgoLMEYMSHSwKPXHZtWmyk,824
397
+ django_ledger-0.8.2.3.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
398
+ django_ledger-0.8.2.3.dist-info/METADATA,sha256=xvPML8oA6ACobgjGWNeEUXjwDgpvNm2pdf1ToEL2Q3Q,8460
399
+ django_ledger-0.8.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
400
+ django_ledger-0.8.2.3.dist-info/top_level.txt,sha256=57KG3JBsF9fqPnPGyFfcpKtfFNS9TQONjtfKhUlwiy4,14
401
+ django_ledger-0.8.2.3.dist-info/RECORD,,