django-ledger 0.6.2__py3-none-any.whl → 0.6.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
@@ -9,7 +9,7 @@ Contributions to this module:
9
9
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
10
10
 
11
11
  """Django Ledger"""
12
- __version__ = '0.6.2'
12
+ __version__ = '0.6.3'
13
13
  __license__ = 'GPLv3 License'
14
14
 
15
15
  __author__ = 'Miguel Sanda'
@@ -111,6 +111,7 @@ class LedgerModelAdmin(ModelAdmin):
111
111
  ]
112
112
  list_display = [
113
113
  'name',
114
+ 'ledger_xid',
114
115
  'is_posted',
115
116
  'is_locked',
116
117
  'is_extended',
@@ -13,7 +13,8 @@ class ImportJobModelCreateForm(ModelForm):
13
13
  label='Select File...',
14
14
  widget=forms.FileInput(
15
15
  attrs={
16
- 'class': 'file-input'
16
+ 'class': 'file-input',
17
+ 'accept': '.ofx,.qfx'
17
18
  })
18
19
  )
19
20
 
django_ledger/io/ofx.py CHANGED
@@ -34,8 +34,9 @@ class OFXFileManager:
34
34
  def get_accounts(self):
35
35
  return [
36
36
  {
37
- 'bank': self.ofx_data.org,
38
- 'fid': self.ofx_data.fid,
37
+ # conditionally return the bank and fid if they are provided by the vendor
38
+ 'bank': self.ofx_data.fi.org if hasattr(self.ofx_data.fi, 'org') else None,
39
+ 'fid': self.ofx_data.fi.fid if hasattr(self.ofx_data.fi, 'fid') else None,
39
40
  'account_type': acc.accttype,
40
41
  'account_number': acc.acctid,
41
42
  'routing_number': acc.bankid,
@@ -4,7 +4,6 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
5
  Contributions to this module:
6
6
  * Miguel Sanda <msanda@arrobalytics.com>
7
- * Pranav P Tulshyan <ptulshyan77@gmail.com>
8
7
 
9
8
  This module implements the BillModel, which represents an Invoice received from a Supplier/Vendor, on which
10
9
  the Vendor states the amount owed by the recipient for the purposes of supplying goods and/or services.
@@ -39,6 +38,14 @@ from django_ledger.models.entity import EntityModel
39
38
  from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel, ItemModel, ItemModelQuerySet
40
39
  from django_ledger.models.mixins import (CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn,
41
40
  PaymentTermsMixIn, ItemizeMixIn)
41
+ from django_ledger.models.signals import (
42
+ bill_status_draft,
43
+ bill_status_in_review,
44
+ bill_status_approved,
45
+ bill_status_paid,
46
+ bill_status_canceled,
47
+ bill_status_void,
48
+ )
42
49
  from django_ledger.models.utils import lazy_loader
43
50
  from django_ledger.settings import (DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_BILL_NUMBER_PREFIX)
44
51
 
@@ -612,9 +619,8 @@ class BillModelAbstract(
612
619
  account_unit_total=Sum('total_amount')
613
620
  )
614
621
 
615
- def update_amount_due(self,
616
- itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None
617
- ) -> ItemTransactionModelQuerySet:
622
+ def update_amount_due(self, itemtxs_qs: Optional[
623
+ Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None) -> ItemTransactionModelQuerySet:
618
624
  """
619
625
  Updates the BillModel amount due.
620
626
 
@@ -1071,6 +1077,9 @@ class BillModelAbstract(
1071
1077
  'updated'
1072
1078
  ]
1073
1079
  )
1080
+ bill_status_draft.send_robust(sender=self.__class__,
1081
+ instance=self,
1082
+ commited=commit, **kwargs)
1074
1083
 
1075
1084
  def get_mark_as_draft_html_id(self) -> str:
1076
1085
  """
@@ -1176,6 +1185,9 @@ class BillModelAbstract(
1176
1185
  'updated'
1177
1186
  ]
1178
1187
  )
1188
+ bill_status_in_review.send_robust(sender=self.__class__,
1189
+ instance=self,
1190
+ commited=commit, **kwargs)
1179
1191
 
1180
1192
  def get_mark_as_review_html_id(self) -> str:
1181
1193
  """
@@ -1283,6 +1295,9 @@ class BillModelAbstract(
1283
1295
  force_migrate=self.accrue
1284
1296
  )
1285
1297
  self.ledger.post(commit=commit, raise_exception=raise_exception)
1298
+ bill_status_approved.send_robust(sender=self.__class__,
1299
+ instance=self,
1300
+ commited=commit, **kwargs)
1286
1301
 
1287
1302
  def get_mark_as_approved_html_id(self) -> str:
1288
1303
  """
@@ -1406,6 +1421,9 @@ class BillModelAbstract(
1406
1421
  force_migrate=True
1407
1422
  )
1408
1423
  self.lock_ledger(commit=True)
1424
+ bill_status_paid.send_robust(sender=self.__class__,
1425
+ instance=self,
1426
+ commited=commit, **kwargs)
1409
1427
 
1410
1428
  def get_mark_as_paid_html_id(self) -> str:
1411
1429
  """
@@ -1507,6 +1525,9 @@ class BillModelAbstract(
1507
1525
  force_migrate=True)
1508
1526
  self.save()
1509
1527
  self.lock_ledger(commit=False, raise_exception=False)
1528
+ bill_status_void.send_robust(sender=self.__class__,
1529
+ instance=self,
1530
+ commited=commit, **kwargs)
1510
1531
 
1511
1532
  def get_mark_as_void_html_id(self) -> str:
1512
1533
  """
@@ -1574,6 +1595,9 @@ class BillModelAbstract(
1574
1595
  self.clean()
1575
1596
  if commit:
1576
1597
  self.save()
1598
+ bill_status_canceled.send_robust(sender=self.__class__,
1599
+ instance=self,
1600
+ commited=commit, **kwargs)
1577
1601
 
1578
1602
  def get_mark_as_canceled_html_id(self) -> str:
1579
1603
  """
@@ -1890,13 +1914,3 @@ def billmodel_presave(instance: BillModel, **kwargs):
1890
1914
 
1891
1915
 
1892
1916
  pre_save.connect(receiver=billmodel_presave, sender=BillModel)
1893
-
1894
- # def billmodel_predelete(instance: BillModel, **kwargs):
1895
- # ledger_model = instance.ledger
1896
- # ledger_model.unpost(commit=False)
1897
- # ledger_model.remove_wrapped_model_info()
1898
- # ledger_model.itemtransactonmodel_set.all().delete()
1899
- # instance.ledger.delete()
1900
- #
1901
- #
1902
- # pre_delete.connect(receiver=billmodel_predelete, sender=BillModel)
@@ -1,3 +1,11 @@
1
+ """
2
+ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
+ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
+
5
+ Contributions to this module:
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
+ """
8
+
1
9
  from datetime import datetime, time
2
10
  from decimal import Decimal
3
11
  from itertools import groupby, chain
@@ -36,6 +36,14 @@ from django_ledger.models.entity import EntityModel, EntityStateModel
36
36
  from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel, ItemModelQuerySet, ItemModel
37
37
  from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn, ItemizeMixIn
38
38
  from django_ledger.models.purchase_order import PurchaseOrderModelQuerySet
39
+ from django_ledger.models.signals import (
40
+ estimate_status_void,
41
+ estimate_status_draft,
42
+ estimate_status_approved,
43
+ estimate_status_canceled,
44
+ estimate_status_completed,
45
+ estimate_status_in_review
46
+ )
39
47
  from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_ESTIMATE_NUMBER_PREFIX
40
48
 
41
49
  ESTIMATE_NUMBER_CHARS = ascii_uppercase + digits
@@ -610,7 +618,7 @@ class EstimateModelAbstract(CreateUpdateMixIn,
610
618
 
611
619
  # Actions...
612
620
  # DRAFT...
613
- def mark_as_draft(self, commit: bool = False):
621
+ def mark_as_draft(self, commit: bool = False, raise_exception: bool = True, **kwargs):
614
622
  """
615
623
  Marks the current EstimateModel instance as Draft.
616
624
 
@@ -620,7 +628,9 @@ class EstimateModelAbstract(CreateUpdateMixIn,
620
628
  Commits transaction into current EstimateModel instance.
621
629
  """
622
630
  if not self.can_draft():
623
- raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be marked as draft...')
631
+ if raise_exception:
632
+ raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be marked as draft...')
633
+ return
624
634
  self.status = self.CONTRACT_STATUS_DRAFT
625
635
  self.clean()
626
636
  if commit:
@@ -628,6 +638,10 @@ class EstimateModelAbstract(CreateUpdateMixIn,
628
638
  'status',
629
639
  'updated'
630
640
  ])
641
+ estimate_status_draft.send_robust(sender=self.__class__,
642
+ instance=self,
643
+ commited=commit,
644
+ **kwargs)
631
645
 
632
646
  def get_mark_as_draft_html_id(self):
633
647
  """
@@ -670,7 +684,10 @@ class EstimateModelAbstract(CreateUpdateMixIn,
670
684
  def mark_as_review(self,
671
685
  itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None,
672
686
  date_in_review: Optional[date] = None,
673
- commit: bool = True):
687
+ raise_exception: bool = True,
688
+ commit: bool = True,
689
+ **kwargs):
690
+
674
691
  """
675
692
  Marks the current EstimateModel instance as In Review.
676
693
 
@@ -685,7 +702,9 @@ class EstimateModelAbstract(CreateUpdateMixIn,
685
702
  Optional date when EstimateModel instance is In Review. Defaults to localdate().
686
703
  """
687
704
  if not self.can_review():
688
- raise ValidationError(f'Estimate {self.estimate_number} cannot be marked as In Review...')
705
+ if raise_exception:
706
+ raise ValidationError(f'Estimate {self.estimate_number} cannot be marked as In Review...')
707
+ return
689
708
 
690
709
  if not itemtxs_qs:
691
710
  itemtxs_qs = self.itemtransactionmodel_set.all()
@@ -710,6 +729,10 @@ class EstimateModelAbstract(CreateUpdateMixIn,
710
729
  'status',
711
730
  'updated'
712
731
  ])
732
+ estimate_status_in_review.send_robust(sender=self.__class__,
733
+ instance=self,
734
+ commited=commit,
735
+ **kwargs)
713
736
 
714
737
  def get_mark_as_review_html_id(self):
715
738
  """
@@ -749,7 +772,11 @@ class EstimateModelAbstract(CreateUpdateMixIn,
749
772
  return _('Do you want to mark Estimate %s as In Review?') % self.estimate_number
750
773
 
751
774
  # APPROVED
752
- def mark_as_approved(self, commit=False, date_approved: Optional[date] = None):
775
+ def mark_as_approved(self,
776
+ commit=False,
777
+ date_approved: Optional[date] = None,
778
+ raise_exception: bool = True,
779
+ **kwargs):
753
780
  """
754
781
  Marks the current EstimateModel instance as Approved.
755
782
 
@@ -761,9 +788,12 @@ class EstimateModelAbstract(CreateUpdateMixIn,
761
788
  Optional date when EstimateModel instance is Approved. Defaults to localdate().
762
789
  """
763
790
  if not self.can_approve():
764
- raise EstimateModelValidationError(
765
- f'Estimate {self.estimate_number} cannot be marked as approved.'
766
- )
791
+ if raise_exception:
792
+ raise EstimateModelValidationError(
793
+ f'Estimate {self.estimate_number} cannot be marked as approved.'
794
+ )
795
+ return
796
+
767
797
  if not date_approved:
768
798
  date_approved = get_localdate()
769
799
  self.date_approved = date_approved
@@ -775,6 +805,10 @@ class EstimateModelAbstract(CreateUpdateMixIn,
775
805
  'date_approved',
776
806
  'updated'
777
807
  ])
808
+ estimate_status_approved.send_robust(sender=self.__class__,
809
+ instance=self,
810
+ commited=commit,
811
+ **kwargs)
778
812
 
779
813
  def get_mark_as_approved_html_id(self):
780
814
  """
@@ -814,7 +848,11 @@ class EstimateModelAbstract(CreateUpdateMixIn,
814
848
  return _('Do you want to mark Estimate %s as Approved?') % self.estimate_number
815
849
 
816
850
  # COMPLETED
817
- def mark_as_completed(self, commit=False, date_completed: Optional[date] = None):
851
+ def mark_as_completed(self,
852
+ commit=False,
853
+ date_completed: Optional[date] = None,
854
+ raise_exception: bool = True,
855
+ **kwargs):
818
856
  """
819
857
  Marks the current EstimateModel instance as Completed.
820
858
 
@@ -826,7 +864,9 @@ class EstimateModelAbstract(CreateUpdateMixIn,
826
864
  Optional date when EstimateModel instance is completed. Defaults to localdate().
827
865
  """
828
866
  if not self.can_complete():
829
- raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be marked as completed.')
867
+ if raise_exception:
868
+ raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be marked as completed.')
869
+ return
830
870
  if not date_completed:
831
871
  date_completed = get_localdate()
832
872
  self.date_completed = date_completed
@@ -834,11 +874,18 @@ class EstimateModelAbstract(CreateUpdateMixIn,
834
874
  self.clean()
835
875
  if commit:
836
876
  self.clean()
837
- self.save(update_fields=[
838
- 'status',
839
- 'date_completed',
840
- 'updated'
841
- ])
877
+ self.save(
878
+ update_fields=[
879
+ 'status',
880
+ 'date_completed',
881
+ 'updated'
882
+ ])
883
+ estimate_status_completed.send_robust(
884
+ sender=self.__class__,
885
+ instance=self,
886
+ commited=commit,
887
+ **kwargs
888
+ )
842
889
 
843
890
  def get_mark_as_completed_html_id(self):
844
891
  """
@@ -878,7 +925,11 @@ class EstimateModelAbstract(CreateUpdateMixIn,
878
925
  return _('Do you want to mark Estimate %s as Completed?') % self.estimate_number
879
926
 
880
927
  # CANCEL
881
- def mark_as_canceled(self, commit: bool = False, date_canceled: Optional[date] = None):
928
+ def mark_as_canceled(self,
929
+ commit: bool = False,
930
+ date_canceled: Optional[date] = None,
931
+ raise_exception: bool = True,
932
+ **kwargs):
882
933
  """
883
934
  Marks the current EstimateModel instance as Canceled.
884
935
 
@@ -890,7 +941,9 @@ class EstimateModelAbstract(CreateUpdateMixIn,
890
941
  Optional date when EstimateModel instance is canceled. Defaults to localdate().
891
942
  """
892
943
  if not self.can_cancel():
893
- raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be canceled...')
944
+ if raise_exception:
945
+ raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be canceled...')
946
+ return
894
947
  if not date_canceled:
895
948
  date_canceled = get_localdate()
896
949
  self.date_canceled = date_canceled
@@ -902,6 +955,12 @@ class EstimateModelAbstract(CreateUpdateMixIn,
902
955
  'date_canceled',
903
956
  'updated'
904
957
  ])
958
+ estimate_status_canceled.send_robust(
959
+ sender=self.__class__,
960
+ instance=self,
961
+ commited=commit,
962
+ **kwargs
963
+ )
905
964
 
906
965
  def get_mark_as_canceled_html_id(self):
907
966
  """
@@ -941,7 +1000,12 @@ class EstimateModelAbstract(CreateUpdateMixIn,
941
1000
  return _('Do you want to mark Estimate %s as Canceled?') % self.estimate_number
942
1001
 
943
1002
  # VOID
944
- def mark_as_void(self, commit: bool = False, date_void: Optional[date] = None):
1003
+ def mark_as_void(self,
1004
+ commit: bool = False,
1005
+ date_void: Optional[date] = None,
1006
+ raise_exception: bool = True,
1007
+ **kwargs):
1008
+
945
1009
  """
946
1010
  Marks the current EstimateModel instance as Void.
947
1011
 
@@ -953,7 +1017,10 @@ class EstimateModelAbstract(CreateUpdateMixIn,
953
1017
  Optional date when EstimateModel instance is void. Defaults to localdate().
954
1018
  """
955
1019
  if not self.can_void():
956
- raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be void...')
1020
+ if raise_exception:
1021
+ raise EstimateModelValidationError(f'Estimate {self.estimate_number} cannot be void...')
1022
+ return
1023
+
957
1024
  if not date_void:
958
1025
  date_void = get_localdate()
959
1026
  self.date_void = date_void
@@ -965,6 +1032,12 @@ class EstimateModelAbstract(CreateUpdateMixIn,
965
1032
  'date_void',
966
1033
  'updated'
967
1034
  ])
1035
+ estimate_status_void.send_robust(
1036
+ sender=self.__class__,
1037
+ instance=self,
1038
+ commited=commit,
1039
+ **kwargs
1040
+ )
968
1041
 
969
1042
  def get_mark_as_void_html_id(self):
970
1043
  """
@@ -1167,8 +1240,7 @@ class EstimateModelAbstract(CreateUpdateMixIn,
1167
1240
  'updated'
1168
1241
  ])
1169
1242
 
1170
- def update_state(self,
1171
- itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None):
1243
+ def update_state(self, itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None):
1172
1244
  itemtxs_qs, _ = self.get_itemtxs_data(queryset=itemtxs_qs)
1173
1245
  self.update_cost_estimate(itemtxs_qs)
1174
1246
  self.update_revenue_estimate(itemtxs_qs)
@@ -38,8 +38,21 @@ from django_ledger.io import ASSET_CA_CASH, ASSET_CA_RECEIVABLES, LIABILITY_CL_D
38
38
  from django_ledger.io.io_core import get_localtime, get_localdate
39
39
  from django_ledger.models import lazy_loader, ItemTransactionModelQuerySet, ItemModelQuerySet, ItemModel
40
40
  from django_ledger.models.entity import EntityModel
41
- from django_ledger.models.mixins import CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn, PaymentTermsMixIn, \
41
+ from django_ledger.models.mixins import (
42
+ CreateUpdateMixIn, AccrualMixIn,
43
+ MarkdownNotesMixIn, PaymentTermsMixIn,
42
44
  ItemizeMixIn
45
+ )
46
+
47
+ from django_ledger.models.signals import (
48
+ invoice_status_draft,
49
+ invoice_status_in_review,
50
+ invoice_status_approved,
51
+ invoice_status_paid,
52
+ invoice_status_canceled,
53
+ invoice_status_void
54
+ )
55
+
43
56
  from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_INVOICE_NUMBER_PREFIX
44
57
 
45
58
  UserModel = get_user_model()
@@ -446,7 +459,7 @@ class InvoiceModelAbstract(
446
459
 
447
460
  if self.can_generate_invoice_number():
448
461
  self.generate_invoice_number(commit=commit)
449
- ledger_model.ledger_xid=f'invoice-{self.invoice_number.lower()}-{str(ledger_model.entity_id)[-5:]}'
462
+ ledger_model.ledger_xid = f'invoice-{self.invoice_number.lower()}-{str(ledger_model.entity_id)[-5:]}'
450
463
  ledger_model.save(update_fields=['ledger_xid'])
451
464
 
452
465
  self.clean()
@@ -1002,6 +1015,9 @@ class InvoiceModelAbstract(
1002
1015
  'invoice_status',
1003
1016
  'updated'
1004
1017
  ])
1018
+ invoice_status_draft.send_robust(sender=self.__class__,
1019
+ instance=self,
1020
+ commited=commit, **kwargs)
1005
1021
 
1006
1022
  def get_mark_as_draft_html_id(self):
1007
1023
  """
@@ -1050,7 +1066,8 @@ class InvoiceModelAbstract(
1050
1066
  def mark_as_review(self,
1051
1067
  date_in_review: date = None,
1052
1068
  itemtxs_qs=None,
1053
- commit: bool = False, **kwargs):
1069
+ commit: bool = False,
1070
+ **kwargs):
1054
1071
  """
1055
1072
  Marks InvoiceModel as In Review.
1056
1073
 
@@ -1088,6 +1105,10 @@ class InvoiceModelAbstract(
1088
1105
  'date_in_review',
1089
1106
  'updated'
1090
1107
  ])
1108
+ invoice_status_in_review.send_robust(sender=self.__class__,
1109
+ instance=self,
1110
+ commited=commit,
1111
+ **kwargs)
1091
1112
 
1092
1113
  def get_mark_as_review_html_id(self):
1093
1114
  """
@@ -1189,6 +1210,10 @@ class InvoiceModelAbstract(
1189
1210
  force_migrate=self.accrue
1190
1211
  )
1191
1212
  self.ledger.post(commit=commit, raise_exception=raise_exception)
1213
+ invoice_status_approved.send_robust(sender=self.__class__,
1214
+ instance=self,
1215
+ commited=commit,
1216
+ **kwargs)
1192
1217
 
1193
1218
  def get_mark_as_approved_html_id(self):
1194
1219
  """
@@ -1292,6 +1317,10 @@ class InvoiceModelAbstract(
1292
1317
  je_timestamp=date_paid
1293
1318
  )
1294
1319
  self.lock_ledger(commit=True)
1320
+ invoice_status_paid.send_robust(sender=self.__class__,
1321
+ instance=self,
1322
+ commited=commit,
1323
+ **kwargs)
1295
1324
 
1296
1325
  def get_mark_as_paid_html_id(self):
1297
1326
  """
@@ -1398,6 +1427,10 @@ class InvoiceModelAbstract(
1398
1427
  )
1399
1428
  self.save()
1400
1429
  self.lock_ledger(commit=True, raise_exception=False)
1430
+ invoice_status_void.send_robust(sender=self.__class__,
1431
+ instance=self,
1432
+ commited=commit,
1433
+ **kwargs)
1401
1434
 
1402
1435
  def get_mark_as_void_html_id(self):
1403
1436
  """
@@ -1442,7 +1475,10 @@ class InvoiceModelAbstract(
1442
1475
  return _('Do you want to mark Invoice %s as Void?') % self.invoice_number
1443
1476
 
1444
1477
  # CANCEL
1445
- def mark_as_canceled(self, date_canceled: date = None, commit: bool = False, **kwargs):
1478
+ def mark_as_canceled(self,
1479
+ date_canceled: date = None,
1480
+ commit: bool = False,
1481
+ **kwargs):
1446
1482
  """
1447
1483
  Mark InvoiceModel as Canceled.
1448
1484
 
@@ -1465,6 +1501,10 @@ class InvoiceModelAbstract(
1465
1501
  self.unlock_ledger(commit=True, raise_exception=False)
1466
1502
  self.unpost_ledger(commit=True, raise_exception=False)
1467
1503
  self.save()
1504
+ invoice_status_canceled.send_robust(sender=self.__class__,
1505
+ instance=self,
1506
+ commited=commit,
1507
+ **kwargs)
1468
1508
 
1469
1509
  def get_mark_as_canceled_html_id(self):
1470
1510
  """
@@ -1787,13 +1827,3 @@ def invoicemodel_presave(instance: InvoiceModel, **kwargs):
1787
1827
 
1788
1828
 
1789
1829
  pre_save.connect(receiver=invoicemodel_presave, sender=InvoiceModel)
1790
-
1791
- # def invoicemodel_predelete(instance: InvoiceModel, **kwargs):
1792
- # ledger_model = instance.ledger
1793
- # ledger_model.unpost(commit=False)
1794
- # ledger_model.remove_wrapped_model_info()
1795
- # ledger_model.itemtransactonmodel_set.all().delete()
1796
- # instance.ledger.delete()
1797
- #
1798
- #
1799
- # pre_delete.connect(receiver=invoicemodel_predelete, sender=InvoiceModel)
@@ -45,17 +45,28 @@ from django.utils.timezone import localtime
45
45
  from django.utils.translation import gettext_lazy as _
46
46
 
47
47
  from django_ledger.io.io_core import get_localtime
48
- from django_ledger.io.roles import (ASSET_CA_CASH, GROUP_CFS_FIN_DIVIDENDS, GROUP_CFS_FIN_ISSUING_EQUITY,
49
- GROUP_CFS_FIN_LT_DEBT_PAYMENTS, GROUP_CFS_FIN_ST_DEBT_PAYMENTS,
50
- GROUP_CFS_INVESTING_AND_FINANCING, GROUP_CFS_INVESTING_PPE,
51
- GROUP_CFS_INVESTING_SECURITIES, validate_roles)
48
+ from django_ledger.io.roles import (
49
+ ASSET_CA_CASH, GROUP_CFS_FIN_DIVIDENDS, GROUP_CFS_FIN_ISSUING_EQUITY,
50
+ GROUP_CFS_FIN_LT_DEBT_PAYMENTS, GROUP_CFS_FIN_ST_DEBT_PAYMENTS,
51
+ GROUP_CFS_INVESTING_AND_FINANCING, GROUP_CFS_INVESTING_PPE,
52
+ GROUP_CFS_INVESTING_SECURITIES, validate_roles
53
+ )
52
54
  from django_ledger.models.accounts import CREDIT, DEBIT
53
55
  from django_ledger.models.entity import EntityStateModel, EntityModel
54
56
  from django_ledger.models.mixins import CreateUpdateMixIn
57
+ from django_ledger.models.signals import (
58
+ journal_entry_unlocked,
59
+ journal_entry_locked,
60
+ journal_entry_posted,
61
+ journal_entry_unposted
62
+ )
55
63
  from django_ledger.models.transactions import TransactionModelQuerySet, TransactionModel
56
64
  from django_ledger.models.utils import lazy_loader
57
- from django_ledger.settings import (DJANGO_LEDGER_JE_NUMBER_PREFIX, DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
58
- DJANGO_LEDGER_JE_NUMBER_NO_UNIT_PREFIX)
65
+ from django_ledger.settings import (
66
+ DJANGO_LEDGER_JE_NUMBER_PREFIX,
67
+ DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING,
68
+ DJANGO_LEDGER_JE_NUMBER_NO_UNIT_PREFIX
69
+ )
59
70
 
60
71
 
61
72
  class JournalEntryValidationError(ValidationError):
@@ -588,31 +599,48 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
588
599
  """
589
600
  if verify and not self.is_verified():
590
601
  txs_qs, verified = self.verify()
591
-
592
602
  if not len(txs_qs):
593
- raise JournalEntryValidationError(
594
- message=_('Cannot post an empty Journal Entry.')
595
- )
603
+ if raise_exception:
604
+ raise JournalEntryValidationError(
605
+ message=_('Cannot post an empty Journal Entry.')
606
+ )
607
+ return
596
608
 
597
609
  if force_lock and not self.is_locked():
598
- self.mark_as_locked(commit=False, raise_exception=True)
610
+ try:
611
+ self.mark_as_locked(commit=False, raise_exception=True)
612
+ except JournalEntryValidationError as e:
613
+ if raise_exception:
614
+ raise e
615
+ return
599
616
 
600
617
  if not self.can_post(ignore_verify=False):
601
618
  if raise_exception:
602
619
  raise JournalEntryValidationError(f'Journal Entry {self.uuid} cannot post.'
603
620
  f' Is verified: {self.is_verified()}')
604
- else:
605
- if not self.is_posted():
606
- self.posted = True
607
- if self.is_posted():
608
- if commit:
609
- self.save(verify=False,
610
- update_fields=[
611
- 'posted',
612
- 'locked',
613
- 'activity',
614
- 'updated'
615
- ])
621
+ return
622
+
623
+ if not self.is_posted():
624
+ self.posted = True
625
+ if self.is_posted():
626
+ if commit:
627
+ self.save(verify=False,
628
+ update_fields=[
629
+ 'posted',
630
+ 'locked',
631
+ 'activity',
632
+ 'updated'
633
+ ])
634
+ journal_entry_posted.send_robust(sender=self.__class__,
635
+ instance=self,
636
+ commited=commit,
637
+ **kwargs)
638
+
639
+ def post(self, **kwargs):
640
+ """
641
+ Proxy function for `mark_as_posted` method.
642
+ """
643
+ return self.mark_as_posted(**kwargs)
616
644
 
617
645
  def mark_as_unposted(self, commit: bool = False, raise_exception: bool = False, **kwargs):
618
646
  """
@@ -632,18 +660,30 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
632
660
  if not self.can_unpost():
633
661
  if raise_exception:
634
662
  raise JournalEntryValidationError(f'Journal Entry {self.uuid} cannot unpost.')
635
- else:
636
- if self.is_posted():
637
- self.posted = False
638
- self.activity = None
639
- if not self.is_posted():
640
- if commit:
641
- self.save(verify=False,
642
- update_fields=[
643
- 'posted',
644
- 'activity',
645
- 'updated'
646
- ])
663
+ return
664
+ if self.is_posted():
665
+ self.posted = False
666
+ self.activity = None
667
+ if not self.is_posted():
668
+ if commit:
669
+ self.save(
670
+ verify=False,
671
+ update_fields=[
672
+ 'posted',
673
+ 'activity',
674
+ 'updated'
675
+ ]
676
+ )
677
+ journal_entry_unposted.send_robust(sender=self.__class__,
678
+ instance=self,
679
+ commited=commit,
680
+ **kwargs)
681
+
682
+ def unpost(self, **kwargs):
683
+ """
684
+ Proxy function for `mark_as_unposted` method.
685
+ """
686
+ return self.mark_as_unposted(**kwargs)
647
687
 
648
688
  def mark_as_locked(self, commit: bool = False, raise_exception: bool = False, **kwargs):
649
689
  """
@@ -662,14 +702,26 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
662
702
  """
663
703
  if not self.can_lock():
664
704
  if raise_exception:
665
- raise JournalEntryValidationError(f'Journal Entry {self.uuid} is already locked.')
666
- else:
667
- if not self.is_locked():
668
- self.generate_activity(force_update=True)
669
- self.locked = True
670
- if self.is_locked():
671
- if commit:
672
- self.save(verify=False)
705
+ if raise_exception:
706
+ raise JournalEntryValidationError(f'Journal Entry {self.uuid} is already locked.')
707
+ return
708
+
709
+ if not self.is_locked():
710
+ self.generate_activity(force_update=True)
711
+ self.locked = True
712
+ if self.is_locked():
713
+ if commit:
714
+ self.save(verify=False)
715
+ journal_entry_locked.send_robust(sender=self.__class__,
716
+ instance=self,
717
+ commited=commit,
718
+ **kwargs)
719
+
720
+ def lock(self, **kwargs):
721
+ """
722
+ Proxy function for `mark_as_locked` method.
723
+ """
724
+ return self.mark_as_locked(**kwargs)
673
725
 
674
726
  def mark_as_unlocked(self, commit: bool = False, raise_exception: bool = False, **kwargs):
675
727
  """
@@ -687,12 +739,23 @@ class JournalEntryModelAbstract(CreateUpdateMixIn):
687
739
  if not self.can_unlock():
688
740
  if raise_exception:
689
741
  raise JournalEntryValidationError(f'Journal Entry {self.uuid} is already unlocked.')
690
- else:
691
- if self.is_locked():
692
- self.locked = False
693
- if not self.is_locked():
694
- if commit:
695
- self.save(verify=False)
742
+ return
743
+
744
+ if self.is_locked():
745
+ self.locked = False
746
+ if not self.is_locked():
747
+ if commit:
748
+ self.save(verify=False)
749
+ journal_entry_unlocked.send_robust(sender=self.__class__,
750
+ instance=self,
751
+ commited=commit,
752
+ **kwargs)
753
+
754
+ def unlock(self, **kwargs):
755
+ """
756
+ Proxy function for `mark_as_unlocked` method.
757
+ """
758
+ return self.mark_as_unlocked(**kwargs)
696
759
 
697
760
  def get_transaction_queryset(self, select_accounts: bool = True) -> TransactionModelQuerySet:
698
761
  """
@@ -37,6 +37,14 @@ from django.db import models
37
37
  from django.db.models import Q, Min, F, Count
38
38
  from django.urls import reverse
39
39
  from django.utils.translation import gettext_lazy as _
40
+ from django_ledger.models.signals import (
41
+ ledger_posted,
42
+ ledger_unposted,
43
+ ledger_locked,
44
+ ledger_unlocked,
45
+ ledger_hidden,
46
+ ledger_unhidden
47
+ )
40
48
 
41
49
  from django_ledger.io.io_core import IOMixIn
42
50
  from django_ledger.models import lazy_loader
@@ -456,6 +464,10 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
456
464
  'posted',
457
465
  'updated'
458
466
  ])
467
+ ledger_posted.send_robust(sender=self.__class__,
468
+ instance=self,
469
+ commited=commit,
470
+ **kwargs)
459
471
 
460
472
  def post_journal_entries(self, commit: bool = True, **kwargs):
461
473
  je_model_qs = self.journal_entries.unposted()
@@ -488,6 +500,10 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
488
500
  'posted',
489
501
  'updated'
490
502
  ])
503
+ ledger_unposted.send_robust(sender=self.__class__,
504
+ instance=self,
505
+ commited=commit,
506
+ **kwargs)
491
507
 
492
508
  def lock(self, commit: bool = False, raise_exception: bool = True, **kwargs):
493
509
  """
@@ -513,6 +529,10 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
513
529
  'locked',
514
530
  'updated'
515
531
  ])
532
+ ledger_locked.send_robust(sender=self.__class__,
533
+ instance=self,
534
+ commited=commit,
535
+ **kwargs)
516
536
 
517
537
  def lock_journal_entries(self, commit: bool = True, **kwargs):
518
538
  je_model_qs = self.journal_entries.unlocked()
@@ -544,6 +564,10 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
544
564
  'locked',
545
565
  'updated'
546
566
  ])
567
+ ledger_unlocked.send_robust(sender=self.__class__,
568
+ instance=self,
569
+ commited=commit,
570
+ **kwargs)
547
571
 
548
572
  def hide(self, commit: bool = False, raise_exception: bool = True, **kwargs):
549
573
  if not self.can_hide():
@@ -558,6 +582,10 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
558
582
  'hidden',
559
583
  'updated'
560
584
  ])
585
+ ledger_hidden.send_robust(sender=self.__class__,
586
+ instance=self,
587
+ commited=commit,
588
+ **kwargs)
561
589
 
562
590
  def unhide(self, commit: bool = False, raise_exception: bool = True, **kwargs):
563
591
  if not self.can_unhide():
@@ -572,6 +600,10 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
572
600
  'hidden',
573
601
  'updated'
574
602
  ])
603
+ ledger_unhidden.send_robust(sender=self.__class__,
604
+ instance=self,
605
+ commited=commit,
606
+ **kwargs)
575
607
 
576
608
  def delete(self, **kwargs):
577
609
  if not self.can_delete():
@@ -39,6 +39,14 @@ from django_ledger.models.items import ItemTransactionModel, ItemTransactionMode
39
39
  from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn, ItemizeMixIn
40
40
  from django_ledger.models.utils import lazy_loader
41
41
  from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_PO_NUMBER_PREFIX
42
+ from django_ledger.models.signals import (
43
+ po_status_draft,
44
+ po_status_void,
45
+ po_status_fulfilled,
46
+ po_status_approved,
47
+ po_status_canceled,
48
+ po_status_in_review
49
+ )
42
50
 
43
51
  PO_NUMBER_CHARS = ascii_uppercase + digits
44
52
 
@@ -403,9 +411,8 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
403
411
  } if not lazy_agg else None
404
412
 
405
413
  # ### ItemizeMixIn implementation END...
406
- def update_state(self,
407
- itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None
408
- ) -> Tuple:
414
+ def update_state(self, itemtxs_qs: Optional[
415
+ Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None) -> Tuple:
409
416
 
410
417
  """
411
418
  Updates the state of the PurchaseOrderModel.
@@ -696,6 +703,10 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
696
703
  'po_status',
697
704
  'updated'
698
705
  ])
706
+ po_status_draft.send_robust(sender=self.__class__,
707
+ instance=self,
708
+ commited=commit,
709
+ **kwargs)
699
710
 
700
711
  def get_mark_as_draft_html_id(self):
701
712
  """
@@ -765,6 +776,10 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
765
776
  'date_in_review',
766
777
  'updated'
767
778
  ])
779
+ po_status_in_review.send_robust(sender=self.__class__,
780
+ instance=self,
781
+ commited=commit,
782
+ **kwargs)
768
783
 
769
784
  def get_mark_as_review_html_id(self):
770
785
  """
@@ -828,6 +843,10 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
828
843
  'po_status',
829
844
  'updated'
830
845
  ])
846
+ po_status_approved.send_robust(sender=self.__class__,
847
+ instance=self,
848
+ commited=commit,
849
+ **kwargs)
831
850
 
832
851
  def get_mark_as_approved_html_id(self):
833
852
  """
@@ -890,6 +909,10 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
890
909
  'date_canceled',
891
910
  'updated'
892
911
  ])
912
+ po_status_canceled.send_robust(sender=self.__class__,
913
+ instance=self,
914
+ commited=commit,
915
+ **kwargs)
893
916
 
894
917
  def get_mark_as_canceled_html_id(self):
895
918
  """
@@ -983,6 +1006,10 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
983
1006
  'po_status',
984
1007
  'updated'
985
1008
  ])
1009
+ po_status_fulfilled.send_robust(sender=self.__class__,
1010
+ instance=self,
1011
+ commited=commit,
1012
+ **kwargs)
986
1013
 
987
1014
  def get_mark_as_fulfilled_html_id(self):
988
1015
  """
@@ -1057,6 +1084,10 @@ class PurchaseOrderModelAbstract(CreateUpdateMixIn,
1057
1084
  'po_status',
1058
1085
  'updated'
1059
1086
  ])
1087
+ po_status_void.send_robust(sender=self.__class__,
1088
+ instance=self,
1089
+ commited=commit,
1090
+ **kwargs)
1060
1091
 
1061
1092
  def get_mark_as_void_html_id(self):
1062
1093
  """
@@ -0,0 +1,58 @@
1
+ """
2
+ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
+ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
+
5
+ Contributions to this module:
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
+
8
+ The signals module provide the means to notify listeners about important events or states in the models,
9
+ such as a ledger model being posted or a bill status changing.
10
+ """
11
+
12
+ from django.dispatch import Signal
13
+
14
+ # Ledger Model Signals...
15
+ ledger_posted = Signal()
16
+ ledger_unposted = Signal()
17
+ ledger_locked = Signal()
18
+ ledger_unlocked = Signal()
19
+ ledger_hidden = Signal()
20
+ ledger_unhidden = Signal()
21
+
22
+ # Journal Entry Model Signals...
23
+ journal_entry_posted = Signal()
24
+ journal_entry_unposted = Signal()
25
+ journal_entry_locked = Signal()
26
+ journal_entry_unlocked = Signal()
27
+
28
+ # Bill Model Signals...
29
+ bill_status_draft = Signal()
30
+ bill_status_in_review = Signal()
31
+ bill_status_approved = Signal()
32
+ bill_status_paid = Signal()
33
+ bill_status_canceled = Signal()
34
+ bill_status_void = Signal()
35
+
36
+ # Invoice Model Signals...
37
+ invoice_status_draft = Signal()
38
+ invoice_status_in_review = Signal()
39
+ invoice_status_approved = Signal()
40
+ invoice_status_paid = Signal()
41
+ invoice_status_canceled = Signal()
42
+ invoice_status_void = Signal()
43
+
44
+ # PO Model Signals...
45
+ po_status_draft = Signal()
46
+ po_status_in_review = Signal()
47
+ po_status_approved = Signal()
48
+ po_status_fulfilled = Signal()
49
+ po_status_canceled = Signal()
50
+ po_status_void = Signal()
51
+
52
+ # Estimate Model Signals...
53
+ estimate_status_draft = Signal()
54
+ estimate_status_in_review = Signal()
55
+ estimate_status_approved = Signal()
56
+ estimate_status_completed = Signal()
57
+ estimate_status_canceled = Signal()
58
+ estimate_status_void = Signal()
@@ -3,7 +3,7 @@ from typing import Optional, Dict
3
3
  from django.contrib.staticfiles import finders
4
4
  from django.core.exceptions import ValidationError
5
5
 
6
- from django_ledger.io.io_digest import IODigestContextManager
6
+ from django_ledger.io.io_context import IODigestContextManager
7
7
  from django_ledger.models.ledger import LedgerModel
8
8
  from django_ledger.models.unit import EntityUnitModel
9
9
  from django_ledger.settings import DJANGO_LEDGER_PDF_SUPPORT_ENABLED
@@ -44,10 +44,10 @@
44
44
  {% balance_sheet_statement io_model=object %}
45
45
  {% if entity %}
46
46
  <a class="button is-fullwidth is-dark mb-1"
47
- href="{% url 'django_ledger:ledger-list' entity_slug=view.kwargs.entity_slug %}">{% trans 'Go Back' %}</a>
47
+ href="{% url 'django_ledger:entity-dashboard' entity_slug=view.kwargs.entity_slug %}">{% trans 'Go Back' %}</a>
48
48
  {% elif ledger %}
49
49
  <a class="button is-fullwidth is-dark my-2"
50
- href="{% url 'django_ledger:entity-dashboard' entity_slug=view.kwargs.entity_slug %}">{% trans 'Go Back' %}</a>
50
+ href="{% url 'django_ledger:ledger-list' entity_slug=view.kwargs.entity_slug %}">{% trans 'Go Back' %}</a>
51
51
  {% endif %}
52
52
  <a class="button is-fullwidth is-light my-2"
53
53
  href="?by_unit=1">{% trans 'By Unit' %}</a>
File without changes
@@ -0,0 +1,52 @@
1
+ import os
2
+ from decimal import Decimal
3
+
4
+ from django_ledger.io.ofx import OFXFileManager
5
+ from django_ledger.tests.base import DjangoLedgerBaseTest
6
+
7
+
8
+ class SimpleOFXTest(DjangoLedgerBaseTest):
9
+ BASE_PATH = "django_ledger/tests/test_io_ofx/samples/"
10
+
11
+ def get_sample_ofx(self, ofx_sample_name: str):
12
+ ofx = OFXFileManager(ofx_file_or_path=os.path.join(self.BASE_PATH, ofx_sample_name))
13
+
14
+ return ofx
15
+
16
+ def test_ofx_v1_with_intu_bid_field(self):
17
+ """
18
+ OFX v1 with <INTU.BID> field. These are ofx files that are exported for Quickbooks.
19
+ This field can be used to identify the bank in the absence of the <FI.ORG> fields.
20
+ """
21
+ ofx = self.get_sample_ofx("v1_with_intu_bid.ofx")
22
+ accounts = ofx.get_accounts()
23
+
24
+ # The bank and fid fields are not provided in this ofx file.
25
+ self.assertIsNone(accounts[0]["fid"])
26
+ self.assertIsNone(accounts[0]["bank"])
27
+ # balance observed from the ofx file
28
+ self.assertEqual(ofx.ofx_data.statements[0].balance.balamt, Decimal("123456.49"))
29
+
30
+ def test_ofx_v1_with_open_tags(self):
31
+ """
32
+ OFX v1 with open tags like `<DTSERVER>20211015063225[-5:EST]` instead of `<DTSERVER>20230510120000</DTSERVER>`
33
+ """
34
+ ofx = self.get_sample_ofx("v1_with_open_tags.ofx")
35
+ accounts = ofx.get_accounts()
36
+ account = accounts[0]
37
+
38
+ self.assertIsNone(account["fid"])
39
+ self.assertIsNone(account["bank"])
40
+ self.assertEqual(ofx.ofx_data.statements[0].balance.balamt, Decimal("1868.27"))
41
+
42
+ def test_ofx_v2_good(self):
43
+ """
44
+ ofx v2 uses XML rather than SGML. This is a good ofx v2 file.
45
+ """
46
+ ofx = self.get_sample_ofx("v2_good.ofx")
47
+ accounts = ofx.get_accounts()
48
+ account = accounts[0]
49
+
50
+ self.assertEqual(account["fid"], "123456789")
51
+ self.assertEqual(account["bank"], "BANK NAME")
52
+ self.assertEqual(ofx.ofx_data.statements[0].balance.balamt, Decimal("5000.00"))
@@ -210,7 +210,7 @@ class AccountModelYearDetailView(DjangoLedgerSecurityMixIn,
210
210
  context['header_title'] = f'Account {account.code} - {account.name}'
211
211
  context['page_title'] = f'Account {account.code} - {account.name}'
212
212
  account_model: AccountModel = self.object
213
- txs_qs = account_model.transactionmodel_set.posted().order_by(
213
+ txs_qs = account_model.transactionmodel_set.all().posted().order_by(
214
214
  'journal_entry__timestamp').select_related(
215
215
  'journal_entry', 'journal_entry__entity_unit')
216
216
  txs_qs = txs_qs.from_date(self.get_from_date())
@@ -3,7 +3,7 @@ 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>
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
 
9
9
  from django.contrib import messages
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-ledger
3
- Version: 0.6.2
3
+ Version: 0.6.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>
@@ -48,7 +48,7 @@ assets/node_modules/node-gyp/gyp/tools/pretty_gyp.py,sha256=2ZCRPW-MZfK7gdnCIaqh
48
48
  assets/node_modules/node-gyp/gyp/tools/pretty_sln.py,sha256=b_Fxm-SXUCPL3Tix4EyNwZNmQ-zkeRIFFmuL0R5wFhw,5482
49
49
  assets/node_modules/node-gyp/gyp/tools/pretty_vcproj.py,sha256=AwQrxK1F-jhjsbbT35XQjrvWNbc3IBFaKXoJogqMh_o,10633
50
50
  assets/node_modules/node-gyp/test/fixtures/test-charmap.py,sha256=5raXzaQnO2eJnrlFtlDtWftryhZX7Fj0amFW3hdSnhE,547
51
- django_ledger/__init__.py,sha256=XWsm2GPCPw5ayJIKYpT2rO83Yxp4iYr5a0vNyC4ao3Y,456
51
+ django_ledger/__init__.py,sha256=YIkeEEOhmF8tF2yAy5LrIeaNAzkX8i-oaFluYzjuH9I,456
52
52
  django_ledger/apps.py,sha256=H-zEWUjKGakgSDSZmLIoXChZ2h6e0dth0ZO5SpoT-8U,163
53
53
  django_ledger/exceptions.py,sha256=rML8sQQ0Hq-DYMLZ76dfw2RYSAsXWUoyHuyC_yP9o1o,491
54
54
  django_ledger/settings.py,sha256=bZyPKgjmRcO_Rj7hDi4gGlW0VFr_LP2yKeUVIkmWgQM,6321
@@ -56,7 +56,7 @@ django_ledger/utils.py,sha256=l8xq-uSvUdJNpyDjC_0UrsSfjeEpwf7B-tavbnt40a8,4305
56
56
  django_ledger/admin/__init__.py,sha256=MipzxmBhXswpx63uf3Ai2amyBMAP5fZL7mKXKxjNRIY,458
57
57
  django_ledger/admin/coa.py,sha256=BcBsvNs4Z1hOyZy4YqCtIfk1aw8DejrI1bAEH93Tkjc,3542
58
58
  django_ledger/admin/entity.py,sha256=DhH-6o3kjUdkhVPHzwOSF3crtvf5MCzcc1vPCk9O2Bk,6287
59
- django_ledger/admin/ledger.py,sha256=z33FYDT50ahrK4AGs-bZhnrvdIt-imG0QJpZ_KRGUWw,7914
59
+ django_ledger/admin/ledger.py,sha256=WKJCKDT54B_OWtAGlPKKAOBRZAqJ-SPNYiuvV-Wa9y8,7936
60
60
  django_ledger/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  django_ledger/contrib/django_ledger_graphene/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  django_ledger/contrib/django_ledger_graphene/api.py,sha256=exrsmMcX21-Vhpe2_9X0eRLcdlnoE2ut0KUxBLu-TM8,871
@@ -101,7 +101,7 @@ django_ledger/forms/bill.py,sha256=aTAlMWtVA3eI-w0e9Vuxk2GSVey7pmAropPzrQ4OzcI,1
101
101
  django_ledger/forms/closing_entry.py,sha256=AwEEhphjQ-D4pQ6lRk2zGSmMSMkoamIVICnUY8rgqKU,1494
102
102
  django_ledger/forms/coa.py,sha256=-w0GbVde1ve0lEgWCc7kAIfDDVP_nBYgrxykfC301Gw,1312
103
103
  django_ledger/forms/customer.py,sha256=4Ce0J2d3hH5vfT1kdJbspUveIPEsRX_JktzIIu4rNks,2502
104
- django_ledger/forms/data_import.py,sha256=6fAS-_kDm3wMgTyQx0SlrU8Cw_hLTsFrdJ76_thlUxY,5635
104
+ django_ledger/forms/data_import.py,sha256=VF4oJNcWNVW3r0VEGT6MFAC5jN5Aqff5qU35NdO9Wj0,5674
105
105
  django_ledger/forms/entity.py,sha256=b0QirmsFSnaM8YWDO4V6GQXfFgR_MLmdq27I2q2sGQ0,6880
106
106
  django_ledger/forms/estimate.py,sha256=AotWILz-XYiDHEIpey-KQTFfqcLr_CubtzCoaDm3SL4,5171
107
107
  django_ledger/forms/feedback.py,sha256=WUT-kI4uT6q5aqEYaDYwyIFfhXpmtwMv6qf9BFSYsDo,1850
@@ -120,7 +120,7 @@ django_ledger/io/io_core.py,sha256=b_-je0NNPkMOglkJwObxfKA6eWa0YvEod9LODm5RRIg,4
120
120
  django_ledger/io/io_generator.py,sha256=JF4plsABUkCIrtI2X-YD7o5eNghRIgLUseNcBIGOj3U,34613
121
121
  django_ledger/io/io_library.py,sha256=vvQm3IQRLFdH7HS_DYX46Xe-U9IvgZ6MQnHjy0-fyjk,22480
122
122
  django_ledger/io/io_middleware.py,sha256=c-vwpcjg2HbYbb4O36fdf6011dFOnoNsDHOAQXmJgB8,20052
123
- django_ledger/io/ofx.py,sha256=JnmDjhIpLySoixK1WVe6DivRuu02rYsBjqI8yi5Opzs,1488
123
+ django_ledger/io/ofx.py,sha256=tsggMXfAz9rslCTUcxlandPapcHXbGqLO9Diel5z_jE,1677
124
124
  django_ledger/io/ratios.py,sha256=dsuCv9-r73SMLv3OrxeoT5JebfRmrDsRKG_YzHggWFw,3542
125
125
  django_ledger/io/roles.py,sha256=J9Z8WtunOQShKORCY97HpFtlAHG4N4hPfBkpUtRQDIY,20223
126
126
  django_ledger/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -146,20 +146,21 @@ django_ledger/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
146
146
  django_ledger/models/__init__.py,sha256=8mn-OGhAVgLs8YASEBwo8dpX6tHyGtMxRHVPGDGECVU,793
147
147
  django_ledger/models/accounts.py,sha256=0OWMrv89fUdec7RF1EiWE6xZdJMOdEpgYPWechAJYrM,28881
148
148
  django_ledger/models/bank_account.py,sha256=0-eTBxxRyvUKOVVNcGqWV1kiOKcXA2KPQIdiVHDUDCY,7678
149
- django_ledger/models/bill.py,sha256=ZC6PmPYeSFMUBSUqTqebQOZrWEpo59PH6Y4evTO5uy8,63717
150
- django_ledger/models/closing_entry.py,sha256=557vVKhrRZOdzqmmvtVlU48VbzJk8tTV018b0dTfpek,17746
149
+ django_ledger/models/bill.py,sha256=0JaSDu6pA07VVMRjPRiZnMldn50QkPFDbj5MbzDNuXk,64569
150
+ django_ledger/models/closing_entry.py,sha256=s5DvvWnv5SogWtlUdtpdDgKce80FBSjJ6YWQnQxqwV0,17959
151
151
  django_ledger/models/coa.py,sha256=o-VM2XK64djM3px6pJlGrUVTXu5qNb4ENESS70I___0,27154
152
152
  django_ledger/models/coa_default.py,sha256=4Zj8OMhgBiYuREjM82cFfyGWd8uCAeqggVkeNhg4SLU,27338
153
153
  django_ledger/models/customer.py,sha256=JQOktcYKUlENJv4frek9rAW6sRerrQ0xXHlC5KPmhWk,11807
154
154
  django_ledger/models/data_import.py,sha256=2H-4oTVLa7qXq03m9fd7T5zSQLkZKOAn2OAeOQBzMPA,19477
155
155
  django_ledger/models/entity.py,sha256=VFknz-7FQZu_gVDb5RWqPoCb3eXVzIMgmr4hatUlzBI,121876
156
- django_ledger/models/estimate.py,sha256=-qB5t2cEdyYpFUq7tOUQnFqvE6EDUiVdTtzjEbESwEQ,55829
157
- django_ledger/models/invoice.py,sha256=h5Jh5KOfYr31Eu9gFW1mdoGoVzx7nW8qBdx7vyiXnZU,61568
156
+ django_ledger/models/estimate.py,sha256=i88GtPqJ4k_Nzgyj7uzbI3tWgALkCuHLaV_Cjg-_mE0,58304
157
+ django_ledger/models/invoice.py,sha256=bdyeyngKuaJ0nA9N0L25-m7QbB7AEK43v1TKJkFWsOY,62896
158
158
  django_ledger/models/items.py,sha256=Wh_zPBnYCdI393nHafT6xd4aSutKBQPwKSjDtXTTPNQ,55042
159
- django_ledger/models/journal_entry.py,sha256=VfXXvm3tUFuy2Z6j3PLlDk9ndHqsZgn_PuhrxTNqaiY,50918
160
- django_ledger/models/ledger.py,sha256=kPxyKo5u0-2viifCY87Ms3xglmgrfiDAg0oJgsOrDwc,23603
159
+ django_ledger/models/journal_entry.py,sha256=2MwSAYjlSn8YVNM0riVBQWALD2lukGyf_gEHAIrn5UU,52788
160
+ django_ledger/models/ledger.py,sha256=CjUVi_KufNsSYKo_ZC5T3j8rioRQXhEl16zf_ipzcLg,24997
161
161
  django_ledger/models/mixins.py,sha256=s8ZjEjYQfmU88cLyFNKoiFi79_g1rTe1knEccV2WUXw,52122
162
- django_ledger/models/purchase_order.py,sha256=CDibi90e7Yhpv_UiyP32mMcsQ0EUElXJ2r8pLzuS7yE,42729
162
+ django_ledger/models/purchase_order.py,sha256=Se62XFyrbDbQzFH_almroIaSZ3y27QcsI2xkYXvPIGc,44184
163
+ django_ledger/models/signals.py,sha256=YfGwq-DUC0dtJEru2Q7MJH5zvPkhmPEb3vQoRJiYc34,1639
163
164
  django_ledger/models/transactions.py,sha256=kOL7s-hiRc6iqS7J62bVJY6ikja9Q8WdkRq0FT0zO2U,22722
164
165
  django_ledger/models/unit.py,sha256=x5FFJXgOi1OdajQejIakW6wGY4DjrJhL3S0Pm5OimMk,8074
165
166
  django_ledger/models/utils.py,sha256=3gkdCrfJp9qwN3Sf8R96AliilzwcKBm31UEao4WJO9o,8436
@@ -172,7 +173,7 @@ django_ledger/models/schemas/pnl.py,sha256=_M5qZtgXca4LQLsjS0weUAzLX98M4-5HCCGM0
172
173
  django_ledger/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
174
  django_ledger/report/balance_sheet.py,sha256=iw8VOcJfJJHcKwFmdR9p2uYj5iKyX6avUjVSBrT0nZc,8436
174
175
  django_ledger/report/cash_flow_statement.py,sha256=GosnBzMZWlqNGitOlxvDiHxkT4JrNm0FH3uXfjmXPG8,8717
175
- django_ledger/report/core.py,sha256=tpVdAOXTx4DGEcTeG-SWRO9af7ZTXdmW0-vhwJDY8hQ,7435
176
+ django_ledger/report/core.py,sha256=MSWOHvYrAfAJJtm1_vXv7vZxgAja6ycKxknoaIvJT_g,7436
176
177
  django_ledger/report/income_statement.py,sha256=m4pG0Yjpl7SELx3-yYKT4dCluKt6nT2bkD9gay9tNKI,11156
177
178
  django_ledger/static/django_ledger/bundle/djetler.bundle.js,sha256=1UzHryjoKN43wUa5b6N2ia2yoE-TCrtfNMnuPIf9Im8,547613
178
179
  django_ledger/static/django_ledger/bundle/styles.bundle.js,sha256=myDLVMYHwKTxypheUretM6MZ4HhK8Yn0BqB_OGUW3KU,506543
@@ -272,7 +273,7 @@ django_ledger/templates/django_ledger/expense/expense_create.html,sha256=ozRliR0
272
273
  django_ledger/templates/django_ledger/expense/expense_list.html,sha256=d7IwuA2qrDPYKmg8a0b4T2CIzIfHB6ximk8Ak4VR4xY,867
273
274
  django_ledger/templates/django_ledger/expense/expense_update.html,sha256=7YTGqytGb1KFAiP7x3jZ-ljkXziqir6KnMiFrl_s8Kk,1439
274
275
  django_ledger/templates/django_ledger/expense/tags/expense_item_table.html,sha256=uKUB-1eIvA5KWKZ7kwu0hGf-64ly_onjWgKokQ0S9dI,1962
275
- django_ledger/templates/django_ledger/financial_statements/balance_sheet.html,sha256=rFzmK2-AjV_11fp7b6ou-KtMSwm7jN3vfpd4Ki_9-s0,2793
276
+ django_ledger/templates/django_ledger/financial_statements/balance_sheet.html,sha256=_trd5e282eMsC5VfzP9tL7zPJ_kTGMxlrh77RWwLlY4,2793
276
277
  django_ledger/templates/django_ledger/financial_statements/cash_flow.html,sha256=zeu7OcXl2T_vDoFyJvb0htFIjQz0IfyJBiOcck2skus,3031
277
278
  django_ledger/templates/django_ledger/financial_statements/income_statement.html,sha256=pfrv12Bu_PmU-MrL7JXYX7Wv4PZ06fKvOdydzzgeEnw,2731
278
279
  django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html,sha256=u2o3krlo_I7w-erXI9DO4gNChbYn0KpdeLRZs7UckOQ,6430
@@ -367,6 +368,8 @@ django_ledger/tests/test_purchase_order.py,sha256=vPjPthS6nS8Q1KmZW5zUFogqQKR1MZ
367
368
  django_ledger/tests/test_transactions.py,sha256=qeIJr0q43IpI_9YrzjVcnltnk_7puGvdhmH-hDePgEc,11721
368
369
  django_ledger/tests/bdd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
369
370
  django_ledger/tests/bdd/features/steps/README.py,sha256=4HMdVjjflcKQBf0LeZbc5i3TXbe5qGxYBcGmHn4i3jU,599
371
+ django_ledger/tests/test_io_ofx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
372
+ django_ledger/tests/test_io_ofx/tests.py,sha256=v-2xY410a6Tr2TwG5oqc_UkFzXBIAFxWYasZkeiXAfE,1993
370
373
  django_ledger/urls/__init__.py,sha256=98Dk0iQPeK2chSTY8H-7yuk9OycoUoZJdbpuR0Wv2rw,1994
371
374
  django_ledger/urls/account.py,sha256=9G6m2mkki8sNtC_mlNIHcn2SCSoG7ienbQ5qg2kHWg0,2399
372
375
  django_ledger/urls/auth.py,sha256=8dY2h6PQkMayUL81guu9JCxm--Tj0rglvvxg9Qh03aY,230
@@ -392,10 +395,10 @@ django_ledger/urls/transactions.py,sha256=e_x_z5qbkR6i7o8OWWdXshDiY_WVmu9WVhR9A9
392
395
  django_ledger/urls/unit.py,sha256=QEVKrgcw2dqMaaXsUHfqYecTa5-iaPlS9smrYJ1QsgM,1506
393
396
  django_ledger/urls/vendor.py,sha256=ODHpAwe5lomluj8ZCqbMtugTeeRsv0Yo9SqkZEmfYaw,393
394
397
  django_ledger/views/__init__.py,sha256=l5Pm2_oAW6Q_jJbXf-BiHA3ilCbiGb6gkXCm73K5DGY,1158
395
- django_ledger/views/account.py,sha256=d2pzYXKPOF74hCD4ehsQ_WNFsgqyGXXekCh22gDawAM,10523
398
+ django_ledger/views/account.py,sha256=cz9bgEyTY05lN997Zln7M4xwBzh45CXo0OTgqnXtdEY,10529
396
399
  django_ledger/views/auth.py,sha256=-zTjMlLpyxHGPlY9EXFQyeVHMmyeJ2H9RptcW7PDeDg,771
397
400
  django_ledger/views/bank_account.py,sha256=bMgqrDydz6WuXina4L27uV-cmQicW0_JoPaXxlO7uN4,5176
398
- django_ledger/views/bill.py,sha256=-WWAPF6PKqfaApAJBFwNMmIveDajXD-5a12d-LbcqFA,23087
401
+ django_ledger/views/bill.py,sha256=AaDdb1RLJDbHhuRdWXaYAIkCOVMtW2U3KcNgDKJKm8Y,23093
399
402
  django_ledger/views/closing_entry.py,sha256=y78azZesVgdUoQmaSEYiP7MBaPRE45qAAHPDlcThlUs,8103
400
403
  django_ledger/views/coa.py,sha256=WnWQVz-4Ik9v28KHzD_WiKcgix7l6bBj1A60p4k-eos,4934
401
404
  django_ledger/views/customer.py,sha256=RoBsXBxZC9b79DSNNHaoSZtQ2AoXf7DJAGmZEO3xdxs,3672
@@ -416,9 +419,9 @@ django_ledger/views/purchase_order.py,sha256=1J3u4QnCkM7z1Y6DePijVdM67x4CQgfmQJc
416
419
  django_ledger/views/transactions.py,sha256=5taQRGLSMkM_N8paQJ07HMspI_Nl7PawF8OohCiRmao,206
417
420
  django_ledger/views/unit.py,sha256=_RgPJO9mR6v5ohBXlnL3T8nTWgS1lwlCvERQcHk0wHE,10232
418
421
  django_ledger/views/vendor.py,sha256=gUdBPTFLeSwlNfdHSA1KFdE_y3QpwpkFhEB0r3-UYdI,3461
419
- django_ledger-0.6.2.dist-info/AUTHORS.md,sha256=SRM2cynD89ZfEsL09zrbUVeO17r9zE2ZM7y6ReMqVRo,713
420
- django_ledger-0.6.2.dist-info/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
421
- django_ledger-0.6.2.dist-info/METADATA,sha256=hGmUnvHJ9byl1Du4AKDEwNxw5aWk1ibCATLKSjM2sDc,9641
422
- django_ledger-0.6.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
423
- django_ledger-0.6.2.dist-info/top_level.txt,sha256=0U3SjF63ND36grQNWDONVe-T9-T07lFl5e6QkG7bR2E,44
424
- django_ledger-0.6.2.dist-info/RECORD,,
422
+ django_ledger-0.6.3.dist-info/AUTHORS.md,sha256=SRM2cynD89ZfEsL09zrbUVeO17r9zE2ZM7y6ReMqVRo,713
423
+ django_ledger-0.6.3.dist-info/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
424
+ django_ledger-0.6.3.dist-info/METADATA,sha256=hSjmos-2S46tN0421i8xYk_7M6zhPmQBMlYyCTfTL1s,9641
425
+ django_ledger-0.6.3.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
426
+ django_ledger-0.6.3.dist-info/top_level.txt,sha256=0U3SjF63ND36grQNWDONVe-T9-T07lFl5e6QkG7bR2E,44
427
+ django_ledger-0.6.3.dist-info/RECORD,,