django-ledger 0.5.5.5__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/entity.py +16 -2
- django_ledger/admin/ledger.py +2 -1
- 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} +49 -28
- django_ledger/io/io_digest.py +7 -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 +8 -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 +1 -1
- django_ledger/models/customer.py +5 -11
- django_ledger/models/data_import.py +12 -6
- django_ledger/models/entity.py +88 -10
- 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 +6 -13
- django_ledger/models/ledger.py +65 -15
- 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 +13 -14
- django_ledger/models/vendor.py +12 -11
- 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/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.5.dist-info → django_ledger-0.5.6.0.dist-info}/METADATA +8 -8
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/RECORD +45 -43
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/WHEEL +0 -0
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/top_level.txt +0 -0
django_ledger/__init__.py
CHANGED
django_ledger/admin/entity.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
1
2
|
from uuid import uuid4
|
|
2
3
|
|
|
3
4
|
from django.contrib.admin import TabularInline, ModelAdmin
|
|
@@ -8,7 +9,7 @@ from django.utils.html import format_html
|
|
|
8
9
|
from django.utils.timezone import localtime
|
|
9
10
|
|
|
10
11
|
from django_ledger.admin.coa import ChartOfAccountsInLine
|
|
11
|
-
from django_ledger.models import EntityUnitModel
|
|
12
|
+
from django_ledger.models import EntityUnitModel, ItemTransactionModel, TransactionModel
|
|
12
13
|
from django_ledger.models.entity import EntityModel, EntityManagementModel
|
|
13
14
|
|
|
14
15
|
|
|
@@ -50,6 +51,7 @@ class EntityUnitModelInLine(TabularInline):
|
|
|
50
51
|
|
|
51
52
|
class EntityModelAdmin(ModelAdmin):
|
|
52
53
|
list_display = [
|
|
54
|
+
'slug',
|
|
53
55
|
'name',
|
|
54
56
|
'accrual_method',
|
|
55
57
|
'last_closing_date',
|
|
@@ -108,7 +110,8 @@ class EntityModelAdmin(ModelAdmin):
|
|
|
108
110
|
EntityManagementInLine
|
|
109
111
|
]
|
|
110
112
|
actions = [
|
|
111
|
-
'add_code_of_accounts'
|
|
113
|
+
'add_code_of_accounts',
|
|
114
|
+
'populate_random_data'
|
|
112
115
|
]
|
|
113
116
|
|
|
114
117
|
class Meta:
|
|
@@ -171,6 +174,14 @@ class EntityModelAdmin(ModelAdmin):
|
|
|
171
174
|
assign_as_default=False
|
|
172
175
|
)
|
|
173
176
|
|
|
177
|
+
def populate_random_data(self, request, queryset):
|
|
178
|
+
for entity_model in queryset:
|
|
179
|
+
start_date = localtime() - timedelta(days=180)
|
|
180
|
+
entity_model.populate_random_data(
|
|
181
|
+
start_date=start_date,
|
|
182
|
+
days_forward=180,
|
|
183
|
+
tx_quantity=25)
|
|
184
|
+
|
|
174
185
|
def get_coa_count(self, obj):
|
|
175
186
|
return obj.chartofaccountmodel__count
|
|
176
187
|
|
|
@@ -183,3 +194,6 @@ class EntityModelAdmin(ModelAdmin):
|
|
|
183
194
|
EntityModel.add_root(instance=obj)
|
|
184
195
|
return
|
|
185
196
|
super().save_model(request, obj, form, change)
|
|
197
|
+
|
|
198
|
+
def has_delete_permission(self, request, obj=None):
|
|
199
|
+
return False
|
django_ledger/admin/ledger.py
CHANGED
django_ledger/forms/entity.py
CHANGED
|
@@ -20,15 +20,6 @@ class EntityModelCreateForm(ModelForm):
|
|
|
20
20
|
default_coa = BooleanField(required=False, initial=False, label=_('Populate Default CoA'))
|
|
21
21
|
activate_all_accounts = BooleanField(required=False, initial=False, label=_('Activate All Accounts'))
|
|
22
22
|
generate_sample_data = BooleanField(required=False, initial=False, label=_('Fill With Sample Data?'))
|
|
23
|
-
tx_quantity = IntegerField(required=True, initial=50, label=_('Number of Transaction Loops'),
|
|
24
|
-
validators=[
|
|
25
|
-
MinValueValidator(limit_value=0),
|
|
26
|
-
MaxValueValidator(limit_value=100)
|
|
27
|
-
],
|
|
28
|
-
widget=TextInput(attrs={
|
|
29
|
-
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
30
|
-
}))
|
|
31
|
-
|
|
32
23
|
|
|
33
24
|
def clean_name(self):
|
|
34
25
|
name = self.cleaned_data.get('name')
|
|
@@ -200,7 +191,8 @@ class EntityModelUpdateForm(ModelForm):
|
|
|
200
191
|
'placeholder': _('Website...')
|
|
201
192
|
}
|
|
202
193
|
),
|
|
203
|
-
'fy_start_month': Select(
|
|
204
|
-
|
|
205
|
-
|
|
194
|
+
'fy_start_month': Select(
|
|
195
|
+
attrs={
|
|
196
|
+
'class': 'input'
|
|
197
|
+
})
|
|
206
198
|
}
|
django_ledger/forms/ledger.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
from django.core.exceptions import ValidationError
|
|
1
2
|
from django.forms import ModelForm, TextInput, Select
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
4
|
|
|
3
5
|
from django_ledger.models.ledger import LedgerModel
|
|
4
6
|
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
@@ -11,10 +13,19 @@ class LedgerModelCreateForm(ModelForm):
|
|
|
11
13
|
self.ENTITY_SLUG: str = entity_slug
|
|
12
14
|
self.USER_MODEL = user_model
|
|
13
15
|
|
|
16
|
+
def validate_unique(self):
|
|
17
|
+
exclude = self._get_validation_exclusions()
|
|
18
|
+
exclude.remove('entity')
|
|
19
|
+
try:
|
|
20
|
+
self.instance.validate_unique(exclude=exclude)
|
|
21
|
+
except ValidationError as e:
|
|
22
|
+
self._update_errors(e)
|
|
23
|
+
|
|
14
24
|
class Meta:
|
|
15
25
|
model = LedgerModel
|
|
16
26
|
fields = [
|
|
17
27
|
'name',
|
|
28
|
+
'ledger_xid'
|
|
18
29
|
]
|
|
19
30
|
widgets = {
|
|
20
31
|
'name': TextInput(
|
|
@@ -22,6 +33,14 @@ class LedgerModelCreateForm(ModelForm):
|
|
|
22
33
|
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
23
34
|
}
|
|
24
35
|
),
|
|
36
|
+
'ledger_xid': TextInput(
|
|
37
|
+
attrs={
|
|
38
|
+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
labels = {
|
|
43
|
+
'ledger_xid': _('Ledger External ID')
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
|
|
@@ -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,10 +36,10 @@ 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
|
|
@@ -108,13 +118,6 @@ def validate_io_date(
|
|
|
108
118
|
return
|
|
109
119
|
|
|
110
120
|
if isinstance(dt, date):
|
|
111
|
-
# dt = make_aware(
|
|
112
|
-
# value=datetime.combine(
|
|
113
|
-
# dt,
|
|
114
|
-
# datetime.min.time()
|
|
115
|
-
# ),
|
|
116
|
-
# timezone=ZoneInfo('UTC')
|
|
117
|
-
# )
|
|
118
121
|
return dt
|
|
119
122
|
|
|
120
123
|
elif isinstance(dt, datetime):
|
|
@@ -636,7 +639,7 @@ class IODatabaseMixIn:
|
|
|
636
639
|
io_state['by_activity'] = by_activity
|
|
637
640
|
io_state['by_tx_type'] = by_tx_type
|
|
638
641
|
|
|
639
|
-
io_result = self.python_digest(
|
|
642
|
+
io_result: IOResult = self.python_digest(
|
|
640
643
|
user_model=user_model,
|
|
641
644
|
accounts=accounts,
|
|
642
645
|
role=role,
|
|
@@ -657,8 +660,10 @@ class IODatabaseMixIn:
|
|
|
657
660
|
io_state['io_result'] = io_result
|
|
658
661
|
io_state['accounts'] = io_result.accounts_digest
|
|
659
662
|
|
|
663
|
+
# IO Middleware...
|
|
664
|
+
|
|
660
665
|
if process_roles:
|
|
661
|
-
roles_mgr =
|
|
666
|
+
roles_mgr = AccountRoleIOMiddleware(
|
|
662
667
|
io_data=io_state,
|
|
663
668
|
by_period=by_period,
|
|
664
669
|
by_unit=by_unit
|
|
@@ -673,7 +678,7 @@ class IODatabaseMixIn:
|
|
|
673
678
|
income_statement,
|
|
674
679
|
cash_flow_statement
|
|
675
680
|
]):
|
|
676
|
-
group_mgr =
|
|
681
|
+
group_mgr = AccountGroupIOMiddleware(
|
|
677
682
|
io_data=io_state,
|
|
678
683
|
by_period=by_period,
|
|
679
684
|
by_unit=by_unit
|
|
@@ -693,19 +698,19 @@ class IODatabaseMixIn:
|
|
|
693
698
|
io_state = ratio_gen.digest()
|
|
694
699
|
|
|
695
700
|
if process_activity:
|
|
696
|
-
activity_manager =
|
|
701
|
+
activity_manager = JEActivityIOMiddleware(io_data=io_state, by_unit=by_unit, by_period=by_period)
|
|
697
702
|
activity_manager.digest()
|
|
698
703
|
|
|
699
704
|
if balance_sheet_statement:
|
|
700
|
-
balance_sheet_mgr =
|
|
705
|
+
balance_sheet_mgr = BalanceSheetIOMiddleware(io_data=io_state)
|
|
701
706
|
io_state = balance_sheet_mgr.digest()
|
|
702
707
|
|
|
703
708
|
if income_statement:
|
|
704
|
-
income_statement_mgr =
|
|
709
|
+
income_statement_mgr = IncomeStatementIOMiddleware(io_data=io_state)
|
|
705
710
|
io_state = income_statement_mgr.digest()
|
|
706
711
|
|
|
707
712
|
if cash_flow_statement:
|
|
708
|
-
cfs =
|
|
713
|
+
cfs = CashFlowStatementIOMiddleware(io_data=io_state)
|
|
709
714
|
io_state = cfs.digest()
|
|
710
715
|
|
|
711
716
|
return IODigestContextManager(io_state=io_state)
|
|
@@ -744,12 +749,32 @@ class IODatabaseMixIn:
|
|
|
744
749
|
JournalEntryModel = lazy_loader.get_journal_entry_model()
|
|
745
750
|
TransactionModel = lazy_loader.get_txs_model()
|
|
746
751
|
|
|
747
|
-
# if isinstance(self, lazy_loader.get_entity_model()):
|
|
748
|
-
|
|
749
752
|
# Validates that credits/debits balance.
|
|
750
753
|
check_tx_balance(je_txs, perform_correction=False)
|
|
751
754
|
je_timestamp = validate_io_date(dt=je_timestamp)
|
|
752
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
|
+
|
|
753
778
|
# if calling from EntityModel must pass an instance of LedgerModel...
|
|
754
779
|
if all([
|
|
755
780
|
isinstance(self, lazy_loader.get_entity_model()),
|
|
@@ -877,7 +902,7 @@ class IOReportMixIn:
|
|
|
877
902
|
report_subtitle=subtitle
|
|
878
903
|
)
|
|
879
904
|
if save_pdf:
|
|
880
|
-
base_dir = Path(
|
|
905
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
881
906
|
filename = report.get_pdf_filename() if not filename else filename
|
|
882
907
|
filepath = base_dir.joinpath(filename)
|
|
883
908
|
report.create_pdf_report()
|
|
@@ -932,7 +957,7 @@ class IOReportMixIn:
|
|
|
932
957
|
report_subtitle=subtitle
|
|
933
958
|
)
|
|
934
959
|
if save_pdf:
|
|
935
|
-
base_dir = Path(
|
|
960
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
936
961
|
filename = report.get_pdf_filename() if not filename else filename
|
|
937
962
|
filepath = base_dir.joinpath(filename)
|
|
938
963
|
report.create_pdf_report()
|
|
@@ -986,7 +1011,7 @@ class IOReportMixIn:
|
|
|
986
1011
|
report_subtitle=subtitle
|
|
987
1012
|
)
|
|
988
1013
|
if save_pdf:
|
|
989
|
-
base_dir = Path(
|
|
1014
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
990
1015
|
filename = report.get_pdf_filename() if not filename else filename
|
|
991
1016
|
filepath = base_dir.joinpath(filename)
|
|
992
1017
|
report.create_pdf_report()
|
|
@@ -1028,10 +1053,6 @@ class IOReportMixIn:
|
|
|
1028
1053
|
from_date=from_date,
|
|
1029
1054
|
to_date=to_date,
|
|
1030
1055
|
user_model=user_model,
|
|
1031
|
-
balance_sheet_statement=True,
|
|
1032
|
-
income_statement=True,
|
|
1033
|
-
cash_flow_statement=True,
|
|
1034
|
-
as_io_digest=True,
|
|
1035
1056
|
**kwargs
|
|
1036
1057
|
)
|
|
1037
1058
|
|
|
@@ -1058,7 +1079,7 @@ class IOReportMixIn:
|
|
|
1058
1079
|
)
|
|
1059
1080
|
|
|
1060
1081
|
if save_pdf:
|
|
1061
|
-
base_dir = Path(
|
|
1082
|
+
base_dir = Path(global_settings.BASE_DIR) if not filepath else Path(filepath)
|
|
1062
1083
|
bs_report.create_pdf_report()
|
|
1063
1084
|
bs_report.output(base_dir.joinpath(bs_report.get_pdf_filename(dt_strfmt=dt_strfmt)))
|
|
1064
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
|
|
@@ -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...')
|