django-ledger 0.5.5.4__py3-none-any.whl → 0.5.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -1
- django_ledger/admin/__init__.py +10 -0
- django_ledger/admin/coa.py +135 -0
- django_ledger/admin/entity.py +199 -0
- django_ledger/admin/ledger.py +283 -0
- django_ledger/forms/entity.py +4 -12
- django_ledger/forms/ledger.py +19 -0
- django_ledger/forms/transactions.py +1 -1
- django_ledger/io/__init__.py +4 -1
- django_ledger/io/{io_mixin.py → io_core.py} +95 -35
- django_ledger/io/io_digest.py +15 -0
- django_ledger/io/{data_generator.py → io_generator.py} +51 -8
- django_ledger/io/io_library.py +317 -0
- django_ledger/io/{io_context.py → io_middleware.py} +16 -9
- django_ledger/migrations/0001_initial.py +413 -132
- django_ledger/migrations/0014_ledgermodel_ledger_xid_and_more.py +22 -0
- django_ledger/models/accounts.py +68 -7
- django_ledger/models/bank_account.py +12 -11
- django_ledger/models/bill.py +5 -9
- django_ledger/models/closing_entry.py +14 -14
- django_ledger/models/coa.py +60 -18
- django_ledger/models/customer.py +5 -11
- django_ledger/models/data_import.py +12 -6
- django_ledger/models/entity.py +90 -12
- django_ledger/models/estimate.py +12 -9
- django_ledger/models/invoice.py +10 -4
- django_ledger/models/items.py +11 -6
- django_ledger/models/journal_entry.py +61 -18
- django_ledger/models/ledger.py +90 -24
- django_ledger/models/mixins.py +2 -3
- django_ledger/models/purchase_order.py +11 -7
- django_ledger/models/transactions.py +3 -1
- django_ledger/models/unit.py +22 -13
- django_ledger/models/vendor.py +12 -11
- django_ledger/report/cash_flow_statement.py +1 -1
- django_ledger/report/core.py +3 -2
- django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +1 -1
- django_ledger/templates/django_ledger/journal_entry/je_list.html +3 -0
- django_ledger/templatetags/django_ledger.py +1 -1
- django_ledger/tests/base.py +1 -1
- django_ledger/tests/test_entity.py +1 -1
- django_ledger/urls/ledger.py +3 -0
- django_ledger/views/entity.py +9 -3
- django_ledger/views/ledger.py +14 -7
- django_ledger/views/mixins.py +9 -1
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/METADATA +9 -9
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/RECORD +51 -46
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/top_level.txt +0 -1
- django_ledger/admin.py +0 -160
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/WHEEL +0 -0
|
@@ -10,7 +10,7 @@ Michael Noel <noel.michael87@gmail.com>
|
|
|
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
|
-
from django_ledger.io import check_tx_balance
|
|
13
|
+
from django_ledger.io.io_core import check_tx_balance
|
|
14
14
|
from django_ledger.models.accounts import AccountModel
|
|
15
15
|
from django_ledger.models.journal_entry import JournalEntryModel
|
|
16
16
|
from django_ledger.models.transactions import TransactionModel
|
django_ledger/io/__init__.py
CHANGED
|
@@ -6,6 +6,9 @@ Contributions to this module:
|
|
|
6
6
|
Miguel Sanda <msanda@arrobalytics.com>
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from django_ledger.io.
|
|
9
|
+
from django_ledger.io.io_digest import *
|
|
10
|
+
from django_ledger.io.io_middleware import *
|
|
10
11
|
from django_ledger.io.ratios import *
|
|
11
12
|
from django_ledger.io.roles import *
|
|
13
|
+
# due to circular import
|
|
14
|
+
# from django_ledger.io.io_library import IOLibrary
|
|
@@ -4,6 +4,15 @@ 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
|
+
|
|
8
|
+
This module provides the building block interface for Django Ledger. The classes and functions contained in this module
|
|
9
|
+
provide an interface to Django Ledger to create and manage Transactions into the Database. It also provides an
|
|
10
|
+
optimized interface to push as much work as possible to the database without having to pull transactions from the
|
|
11
|
+
database into the Python memory.
|
|
12
|
+
|
|
13
|
+
The database records the individual transactions associated with each Journal Entry. However, this interface aggregates
|
|
14
|
+
transactions during the digest method based on a specific request. The Python interpreter is responsible for applying
|
|
15
|
+
accounting rules to the transactions associated with each Journal Entry so the appropriate account balances are computed.
|
|
7
16
|
"""
|
|
8
17
|
from collections import namedtuple
|
|
9
18
|
from dataclasses import dataclass
|
|
@@ -14,6 +23,7 @@ from random import choice
|
|
|
14
23
|
from typing import List, Set, Union, Tuple, Optional, Dict
|
|
15
24
|
from zoneinfo import ZoneInfo
|
|
16
25
|
|
|
26
|
+
from django.conf import settings as global_settings
|
|
17
27
|
from django.contrib.auth import get_user_model
|
|
18
28
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
19
29
|
from django.db.models import Sum, QuerySet
|
|
@@ -26,14 +36,15 @@ from django.utils.translation import gettext_lazy as _
|
|
|
26
36
|
from django_ledger import settings
|
|
27
37
|
from django_ledger.exceptions import InvalidDateInputError, TransactionNotInBalanceError
|
|
28
38
|
from django_ledger.io import roles as roles_module
|
|
29
|
-
from django_ledger.io.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
from django_ledger.io.io_middleware import (
|
|
40
|
+
AccountRoleIOMiddleware, AccountGroupIOMiddleware, JEActivityIOMiddleware,
|
|
41
|
+
BalanceSheetIOMiddleware, IncomeStatementIOMiddleware,
|
|
42
|
+
CashFlowStatementIOMiddleware
|
|
33
43
|
)
|
|
34
44
|
from django_ledger.io.io_digest import IODigestContextManager
|
|
35
45
|
from django_ledger.io.ratios import FinancialRatioManager
|
|
36
46
|
from django_ledger.models.utils import lazy_loader
|
|
47
|
+
from django_ledger.settings import DJANGO_LEDGER_PDF_SUPPORT_ENABLED
|
|
37
48
|
|
|
38
49
|
UserModel = get_user_model()
|
|
39
50
|
|
|
@@ -107,13 +118,6 @@ def validate_io_date(
|
|
|
107
118
|
return
|
|
108
119
|
|
|
109
120
|
if isinstance(dt, date):
|
|
110
|
-
# dt = make_aware(
|
|
111
|
-
# value=datetime.combine(
|
|
112
|
-
# dt,
|
|
113
|
-
# datetime.min.time()
|
|
114
|
-
# ),
|
|
115
|
-
# timezone=ZoneInfo('UTC')
|
|
116
|
-
# )
|
|
117
121
|
return dt
|
|
118
122
|
|
|
119
123
|
elif isinstance(dt, datetime):
|
|
@@ -635,7 +639,7 @@ class IODatabaseMixIn:
|
|
|
635
639
|
io_state['by_activity'] = by_activity
|
|
636
640
|
io_state['by_tx_type'] = by_tx_type
|
|
637
641
|
|
|
638
|
-
io_result = self.python_digest(
|
|
642
|
+
io_result: IOResult = self.python_digest(
|
|
639
643
|
user_model=user_model,
|
|
640
644
|
accounts=accounts,
|
|
641
645
|
role=role,
|
|
@@ -656,8 +660,10 @@ class IODatabaseMixIn:
|
|
|
656
660
|
io_state['io_result'] = io_result
|
|
657
661
|
io_state['accounts'] = io_result.accounts_digest
|
|
658
662
|
|
|
663
|
+
# IO Middleware...
|
|
664
|
+
|
|
659
665
|
if process_roles:
|
|
660
|
-
roles_mgr =
|
|
666
|
+
roles_mgr = AccountRoleIOMiddleware(
|
|
661
667
|
io_data=io_state,
|
|
662
668
|
by_period=by_period,
|
|
663
669
|
by_unit=by_unit
|
|
@@ -672,7 +678,7 @@ class IODatabaseMixIn:
|
|
|
672
678
|
income_statement,
|
|
673
679
|
cash_flow_statement
|
|
674
680
|
]):
|
|
675
|
-
group_mgr =
|
|
681
|
+
group_mgr = AccountGroupIOMiddleware(
|
|
676
682
|
io_data=io_state,
|
|
677
683
|
by_period=by_period,
|
|
678
684
|
by_unit=by_unit
|
|
@@ -692,19 +698,19 @@ class IODatabaseMixIn:
|
|
|
692
698
|
io_state = ratio_gen.digest()
|
|
693
699
|
|
|
694
700
|
if process_activity:
|
|
695
|
-
activity_manager =
|
|
701
|
+
activity_manager = JEActivityIOMiddleware(io_data=io_state, by_unit=by_unit, by_period=by_period)
|
|
696
702
|
activity_manager.digest()
|
|
697
703
|
|
|
698
704
|
if balance_sheet_statement:
|
|
699
|
-
balance_sheet_mgr =
|
|
705
|
+
balance_sheet_mgr = BalanceSheetIOMiddleware(io_data=io_state)
|
|
700
706
|
io_state = balance_sheet_mgr.digest()
|
|
701
707
|
|
|
702
708
|
if income_statement:
|
|
703
|
-
income_statement_mgr =
|
|
709
|
+
income_statement_mgr = IncomeStatementIOMiddleware(io_data=io_state)
|
|
704
710
|
io_state = income_statement_mgr.digest()
|
|
705
711
|
|
|
706
712
|
if cash_flow_statement:
|
|
707
|
-
cfs =
|
|
713
|
+
cfs = CashFlowStatementIOMiddleware(io_data=io_state)
|
|
708
714
|
io_state = cfs.digest()
|
|
709
715
|
|
|
710
716
|
return IODigestContextManager(io_state=io_state)
|
|
@@ -722,7 +728,7 @@ class IODatabaseMixIn:
|
|
|
722
728
|
Creates JE from TXS list using provided account_id.
|
|
723
729
|
|
|
724
730
|
TXS = List[{
|
|
725
|
-
'
|
|
731
|
+
'account': Account Database UUID
|
|
726
732
|
'tx_type': credit/debit,
|
|
727
733
|
'amount': Decimal/Float/Integer,
|
|
728
734
|
'description': string,
|
|
@@ -743,12 +749,32 @@ class IODatabaseMixIn:
|
|
|
743
749
|
JournalEntryModel = lazy_loader.get_journal_entry_model()
|
|
744
750
|
TransactionModel = lazy_loader.get_txs_model()
|
|
745
751
|
|
|
746
|
-
# if isinstance(self, lazy_loader.get_entity_model()):
|
|
747
|
-
|
|
748
752
|
# Validates that credits/debits balance.
|
|
749
753
|
check_tx_balance(je_txs, perform_correction=False)
|
|
750
754
|
je_timestamp = validate_io_date(dt=je_timestamp)
|
|
751
755
|
|
|
756
|
+
entity_model = self.get_entity_model_from_io()
|
|
757
|
+
|
|
758
|
+
if entity_model.last_closing_date:
|
|
759
|
+
if isinstance(je_timestamp, datetime):
|
|
760
|
+
if entity_model.last_closing_date >= je_timestamp.date():
|
|
761
|
+
raise IOValidationError(
|
|
762
|
+
message=_(
|
|
763
|
+
f'Cannot commit transactions. The journal entry date {je_timestamp} is on a closed period.')
|
|
764
|
+
)
|
|
765
|
+
elif isinstance(je_timestamp, date):
|
|
766
|
+
if entity_model.last_closing_date >= je_timestamp:
|
|
767
|
+
raise IOValidationError(
|
|
768
|
+
message=_(
|
|
769
|
+
f'Cannot commit transactions. The journal entry date {je_timestamp} is on a closed period.')
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
if self.is_ledger_model():
|
|
773
|
+
if self.is_locked():
|
|
774
|
+
raise IOValidationError(
|
|
775
|
+
message=_('Cannot commit on locked ledger')
|
|
776
|
+
)
|
|
777
|
+
|
|
752
778
|
# if calling from EntityModel must pass an instance of LedgerModel...
|
|
753
779
|
if all([
|
|
754
780
|
isinstance(self, lazy_loader.get_entity_model()),
|
|
@@ -836,7 +862,7 @@ class IOReportMixIn:
|
|
|
836
862
|
to_date: Union[date, datetime],
|
|
837
863
|
user_model: Optional[UserModel] = None,
|
|
838
864
|
txs_queryset: Optional[QuerySet] = None,
|
|
839
|
-
**kwargs: Dict) ->
|
|
865
|
+
**kwargs: Dict) -> IODigestContextManager:
|
|
840
866
|
return self.digest(
|
|
841
867
|
user_model=user_model,
|
|
842
868
|
to_date=to_date,
|
|
@@ -854,7 +880,13 @@ class IOReportMixIn:
|
|
|
854
880
|
user_model: Optional[UserModel] = None,
|
|
855
881
|
save_pdf: bool = False,
|
|
856
882
|
**kwargs
|
|
857
|
-
):
|
|
883
|
+
) -> IODigestContextManager:
|
|
884
|
+
|
|
885
|
+
if not DJANGO_LEDGER_PDF_SUPPORT_ENABLED:
|
|
886
|
+
raise IOValidationError(
|
|
887
|
+
message=_('PDF support not enabled. Install PDF support from Pipfile.')
|
|
888
|
+
)
|
|
889
|
+
|
|
858
890
|
io_digest = self.digest_balance_sheet(
|
|
859
891
|
to_date=to_date,
|
|
860
892
|
user_model=user_model,
|
|
@@ -870,7 +902,7 @@ class IOReportMixIn:
|
|
|
870
902
|
report_subtitle=subtitle
|
|
871
903
|
)
|
|
872
904
|
if save_pdf:
|
|
873
|
-
base_dir = Path(
|
|
905
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
874
906
|
filename = report.get_pdf_filename() if not filename else filename
|
|
875
907
|
filepath = base_dir.joinpath(filename)
|
|
876
908
|
report.create_pdf_report()
|
|
@@ -882,7 +914,7 @@ class IOReportMixIn:
|
|
|
882
914
|
to_date: Union[date, datetime],
|
|
883
915
|
user_model: Optional[UserModel] = None,
|
|
884
916
|
txs_queryset: Optional[QuerySet] = None,
|
|
885
|
-
**kwargs) ->
|
|
917
|
+
**kwargs) -> IODigestContextManager:
|
|
886
918
|
return self.digest(
|
|
887
919
|
user_model=user_model,
|
|
888
920
|
from_date=from_date,
|
|
@@ -904,6 +936,11 @@ class IOReportMixIn:
|
|
|
904
936
|
save_pdf: bool = False,
|
|
905
937
|
**kwargs
|
|
906
938
|
):
|
|
939
|
+
if not DJANGO_LEDGER_PDF_SUPPORT_ENABLED:
|
|
940
|
+
raise IOValidationError(
|
|
941
|
+
message=_('PDF support not enabled. Install PDF support from Pipfile.')
|
|
942
|
+
)
|
|
943
|
+
|
|
907
944
|
io_digest = self.digest_income_statement(
|
|
908
945
|
from_date=from_date,
|
|
909
946
|
to_date=to_date,
|
|
@@ -920,7 +957,7 @@ class IOReportMixIn:
|
|
|
920
957
|
report_subtitle=subtitle
|
|
921
958
|
)
|
|
922
959
|
if save_pdf:
|
|
923
|
-
base_dir = Path(
|
|
960
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
924
961
|
filename = report.get_pdf_filename() if not filename else filename
|
|
925
962
|
filepath = base_dir.joinpath(filename)
|
|
926
963
|
report.create_pdf_report()
|
|
@@ -930,9 +967,9 @@ class IOReportMixIn:
|
|
|
930
967
|
def digest_cash_flow_statement(self,
|
|
931
968
|
from_date: Union[date, datetime],
|
|
932
969
|
to_date: Union[date, datetime],
|
|
933
|
-
user_model: UserModel,
|
|
970
|
+
user_model: Optional[UserModel] = None,
|
|
934
971
|
txs_queryset: Optional[QuerySet] = None,
|
|
935
|
-
**kwargs) ->
|
|
972
|
+
**kwargs) -> IODigestContextManager:
|
|
936
973
|
return self.digest(
|
|
937
974
|
user_model=user_model,
|
|
938
975
|
from_date=from_date,
|
|
@@ -953,6 +990,11 @@ class IOReportMixIn:
|
|
|
953
990
|
save_pdf: bool = False,
|
|
954
991
|
**kwargs):
|
|
955
992
|
|
|
993
|
+
if not DJANGO_LEDGER_PDF_SUPPORT_ENABLED:
|
|
994
|
+
raise IOValidationError(
|
|
995
|
+
message=_('PDF support not enabled. Install PDF support from Pipfile.')
|
|
996
|
+
)
|
|
997
|
+
|
|
956
998
|
io_digest = self.digest_cash_flow_statement(
|
|
957
999
|
from_date=from_date,
|
|
958
1000
|
to_date=to_date,
|
|
@@ -969,13 +1011,30 @@ class IOReportMixIn:
|
|
|
969
1011
|
report_subtitle=subtitle
|
|
970
1012
|
)
|
|
971
1013
|
if save_pdf:
|
|
972
|
-
base_dir = Path(
|
|
1014
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
973
1015
|
filename = report.get_pdf_filename() if not filename else filename
|
|
974
1016
|
filepath = base_dir.joinpath(filename)
|
|
975
1017
|
report.create_pdf_report()
|
|
976
1018
|
report.output(filepath)
|
|
977
1019
|
return report
|
|
978
1020
|
|
|
1021
|
+
def digest_financial_statements(self,
|
|
1022
|
+
from_date: Union[date, datetime],
|
|
1023
|
+
to_date: Union[date, datetime],
|
|
1024
|
+
user_model: Optional[UserModel] = None,
|
|
1025
|
+
**kwargs) -> IODigestContextManager:
|
|
1026
|
+
|
|
1027
|
+
return self.digest(
|
|
1028
|
+
from_date=from_date,
|
|
1029
|
+
to_date=to_date,
|
|
1030
|
+
user_model=user_model,
|
|
1031
|
+
balance_sheet_statement=True,
|
|
1032
|
+
income_statement=True,
|
|
1033
|
+
cash_flow_statement=True,
|
|
1034
|
+
as_io_digest=True,
|
|
1035
|
+
**kwargs
|
|
1036
|
+
)
|
|
1037
|
+
|
|
979
1038
|
def get_financial_statements(self,
|
|
980
1039
|
from_date: Union[date, datetime],
|
|
981
1040
|
to_date: Union[date, datetime],
|
|
@@ -985,14 +1044,15 @@ class IOReportMixIn:
|
|
|
985
1044
|
filepath: Optional[Path] = None,
|
|
986
1045
|
**kwargs) -> ReportTuple:
|
|
987
1046
|
|
|
988
|
-
|
|
1047
|
+
if not DJANGO_LEDGER_PDF_SUPPORT_ENABLED:
|
|
1048
|
+
raise IOValidationError(
|
|
1049
|
+
message=_('PDF support not enabled. Install PDF support from Pipfile.')
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
io_digest = self.digest_financial_statements(
|
|
989
1053
|
from_date=from_date,
|
|
990
1054
|
to_date=to_date,
|
|
991
1055
|
user_model=user_model,
|
|
992
|
-
balance_sheet_statement=True,
|
|
993
|
-
income_statement=True,
|
|
994
|
-
cash_flow_statement=True,
|
|
995
|
-
as_io_digest=True,
|
|
996
1056
|
**kwargs
|
|
997
1057
|
)
|
|
998
1058
|
|
|
@@ -1019,7 +1079,7 @@ class IOReportMixIn:
|
|
|
1019
1079
|
)
|
|
1020
1080
|
|
|
1021
1081
|
if save_pdf:
|
|
1022
|
-
base_dir = Path(
|
|
1082
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
1023
1083
|
bs_report.create_pdf_report()
|
|
1024
1084
|
bs_report.output(base_dir.joinpath(bs_report.get_pdf_filename(dt_strfmt=dt_strfmt)))
|
|
1025
1085
|
|
django_ledger/io/io_digest.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
"""
|
|
1
8
|
from collections import defaultdict
|
|
2
9
|
from datetime import date
|
|
3
10
|
from typing import Dict, Optional
|
|
@@ -114,6 +121,14 @@ class IODigestContextManager:
|
|
|
114
121
|
'IO Digest does not have cash flow statement information available.'
|
|
115
122
|
)
|
|
116
123
|
|
|
124
|
+
# All Available Statements
|
|
125
|
+
def get_financial_statements_data(self) -> Dict:
|
|
126
|
+
return {
|
|
127
|
+
'balance_sheet': self.get_balance_sheet_data() if self.has_balance_sheet() else None,
|
|
128
|
+
'income_statement': self.get_income_statement_data() if self.has_income_statement() else None,
|
|
129
|
+
'cash_flow_statement': self.get_cash_flow_statement_data() if self.has_cash_flow_statement() else None,
|
|
130
|
+
}
|
|
131
|
+
|
|
117
132
|
# CLOSING ENTRIES...
|
|
118
133
|
|
|
119
134
|
def get_closing_entry_data(self):
|
|
@@ -4,8 +4,18 @@ 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
|
-
"""
|
|
8
7
|
|
|
8
|
+
This is a random data generator module used during the testing of the API and for Educational purposes.
|
|
9
|
+
|
|
10
|
+
The class EntityDataGenerator will only work on new entities that contain no Transactions. This is with the intention
|
|
11
|
+
of avoiding unintentional commingling with an actual EntityModel with production data and the data generated randomly.
|
|
12
|
+
|
|
13
|
+
This class will conveniently create a Chart of Accounts and populate the database will Bills, Invoices and various
|
|
14
|
+
other Transactions. The user will be able to immediately browse the Entity data by clicking on the newly created entity's
|
|
15
|
+
details page.
|
|
16
|
+
|
|
17
|
+
All data generated is random and fake, not related to any other entity data.
|
|
18
|
+
"""
|
|
9
19
|
from datetime import date, timedelta, datetime
|
|
10
20
|
from decimal import Decimal
|
|
11
21
|
from itertools import groupby
|
|
@@ -15,12 +25,13 @@ from typing import Union, Optional
|
|
|
15
25
|
|
|
16
26
|
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
|
17
27
|
from django.utils.timezone import localtime, localdate
|
|
28
|
+
from django.utils.translation import gettext_lazy as _
|
|
18
29
|
|
|
19
30
|
from django_ledger.io.roles import (INCOME_OPERATIONAL, ASSET_CA_INVENTORY, COGS, ASSET_CA_CASH, ASSET_CA_PREPAID,
|
|
20
31
|
LIABILITY_CL_DEFERRED_REVENUE, EXPENSE_OPERATIONAL, EQUITY_CAPITAL,
|
|
21
32
|
ASSET_CA_RECEIVABLES, LIABILITY_CL_ACC_PAYABLE)
|
|
22
33
|
from django_ledger.models import (EntityModel, TransactionModel, VendorModel, CustomerModel,
|
|
23
|
-
EntityUnitModel, BankAccountModel,
|
|
34
|
+
EntityUnitModel, BankAccountModel, UnitOfMeasureModel, ItemModel,
|
|
24
35
|
BillModel, ItemTransactionModel, InvoiceModel,
|
|
25
36
|
EstimateModel, LoggingMixIn, InvoiceModelValidationError, ChartOfAccountModel)
|
|
26
37
|
from django_ledger.utils import (generate_random_sku, generate_random_upc, generate_random_item_id)
|
|
@@ -34,7 +45,28 @@ except ImportError:
|
|
|
34
45
|
FAKER_IMPORTED = False
|
|
35
46
|
|
|
36
47
|
|
|
48
|
+
class EntityModelValidationError(ValidationError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
37
52
|
class EntityDataGenerator(LoggingMixIn):
|
|
53
|
+
"""
|
|
54
|
+
A random data generator for Entity Models. Requires a user to me the entity model administrator.
|
|
55
|
+
|
|
56
|
+
Attributes
|
|
57
|
+
----------
|
|
58
|
+
user_model : UserModel
|
|
59
|
+
The Django user model that administers the entity.
|
|
60
|
+
entity_model : EntityModel
|
|
61
|
+
The Entity model to populate.
|
|
62
|
+
start_dttm: datetime
|
|
63
|
+
The start datetime for new transactions. All transactions will be posted no earlier than this date.
|
|
64
|
+
capital_contribution: Decimal
|
|
65
|
+
The initial capital contribution amount for the Entity Model. This will help fund the entity.
|
|
66
|
+
days_forward: int
|
|
67
|
+
The number of days to span from the start_dttm for new transactions.
|
|
68
|
+
|
|
69
|
+
"""
|
|
38
70
|
|
|
39
71
|
def __init__(self,
|
|
40
72
|
user_model,
|
|
@@ -50,6 +82,11 @@ class EntityDataGenerator(LoggingMixIn):
|
|
|
50
82
|
if not FAKER_IMPORTED:
|
|
51
83
|
raise ImproperlyConfigured('Must install Faker library to generate random data.')
|
|
52
84
|
|
|
85
|
+
if entity_model.admin != user_model:
|
|
86
|
+
raise EntityModelValidationError(
|
|
87
|
+
message=_(f'User {user_model} must have admin privileges for entity model {entity_model}.')
|
|
88
|
+
)
|
|
89
|
+
|
|
53
90
|
self.fk = Faker(['en_US'])
|
|
54
91
|
self.fk.add_provider(company)
|
|
55
92
|
self.fk.add_provider(address)
|
|
@@ -97,7 +134,7 @@ class EntityDataGenerator(LoggingMixIn):
|
|
|
97
134
|
def get_logger_name(self):
|
|
98
135
|
return self.entity_model.slug
|
|
99
136
|
|
|
100
|
-
def populate_entity(self):
|
|
137
|
+
def populate_entity(self, force_populate: bool = False):
|
|
101
138
|
|
|
102
139
|
self.logger.info('Checking for existing transactions...')
|
|
103
140
|
txs_qs = TransactionModel.objects.for_entity(
|
|
@@ -105,9 +142,10 @@ class EntityDataGenerator(LoggingMixIn):
|
|
|
105
142
|
user_model=self.user_model
|
|
106
143
|
)
|
|
107
144
|
|
|
108
|
-
if txs_qs.count() > 0:
|
|
109
|
-
raise
|
|
110
|
-
f'Cannot populate random data on {self.entity_model.name} because it already has existing Transactions'
|
|
145
|
+
if txs_qs.count() > 0 and not force_populate:
|
|
146
|
+
raise EntityModelValidationError(
|
|
147
|
+
f'Cannot populate random data on {self.entity_model.name} because it already has existing Transactions'
|
|
148
|
+
)
|
|
111
149
|
|
|
112
150
|
self.create_coa()
|
|
113
151
|
self.logger.info(f'Pulling Entity {self.entity_model} accounts...')
|
|
@@ -156,9 +194,14 @@ class EntityDataGenerator(LoggingMixIn):
|
|
|
156
194
|
|
|
157
195
|
def create_coa(self):
|
|
158
196
|
entity_model = self.entity_model
|
|
159
|
-
|
|
197
|
+
|
|
198
|
+
if not self.entity_model.has_default_coa():
|
|
199
|
+
coa_model = entity_model.create_chart_of_accounts(assign_as_default=True, commit=True)
|
|
200
|
+
else:
|
|
201
|
+
coa_model = entity_model.get_default_coa()
|
|
202
|
+
|
|
160
203
|
entity_model.populate_default_coa(coa_model=coa_model, activate_accounts=True)
|
|
161
|
-
self.default_coa =
|
|
204
|
+
self.default_coa = coa_model
|
|
162
205
|
|
|
163
206
|
def create_entity_units(self, nb_units: int = None):
|
|
164
207
|
self.logger.info(f'Creating entity units...')
|