django-ledger 0.6.3__py3-none-any.whl → 0.7.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 -4
- django_ledger/admin/coa.py +1 -2
- django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
- django_ledger/forms/account.py +60 -44
- django_ledger/forms/bank_account.py +3 -2
- django_ledger/forms/bill.py +24 -36
- django_ledger/forms/customer.py +1 -1
- django_ledger/forms/data_import.py +3 -3
- django_ledger/forms/estimate.py +1 -1
- django_ledger/forms/invoice.py +5 -7
- django_ledger/forms/item.py +24 -15
- django_ledger/forms/transactions.py +3 -3
- django_ledger/io/io_core.py +4 -2
- django_ledger/io/io_middleware.py +5 -0
- django_ledger/io/roles.py +6 -0
- django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
- django_ledger/models/accounts.py +629 -342
- django_ledger/models/bank_account.py +0 -4
- django_ledger/models/bill.py +0 -3
- django_ledger/models/closing_entry.py +0 -3
- django_ledger/models/coa.py +59 -48
- django_ledger/models/coa_default.py +9 -8
- django_ledger/models/customer.py +0 -4
- django_ledger/models/data_import.py +0 -3
- django_ledger/models/entity.py +80 -40
- django_ledger/models/estimate.py +0 -9
- django_ledger/models/invoice.py +0 -3
- django_ledger/models/items.py +4 -6
- django_ledger/models/journal_entry.py +2 -5
- django_ledger/models/ledger.py +0 -3
- django_ledger/models/mixins.py +0 -3
- django_ledger/models/purchase_order.py +0 -4
- django_ledger/models/signals.py +0 -3
- django_ledger/models/transactions.py +2 -5
- django_ledger/models/unit.py +0 -3
- django_ledger/models/utils.py +0 -3
- django_ledger/models/vendor.py +0 -3
- django_ledger/report/cash_flow_statement.py +1 -1
- django_ledger/static/.DS_Store +0 -0
- django_ledger/static/django_ledger/.DS_Store +0 -0
- django_ledger/static/django_ledger/logo_2/.DS_Store +0 -0
- django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark.png +0 -0
- django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-full-vert.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-full-vert@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-full-vert@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-full-vert@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-vert.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-logo@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-vert.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@3x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-horiz.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-horiz@0.5x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-horiz@2x.png +0 -0
- django_ledger/static/django_ledger/logo_2/djl-txt-horiz@3x.png +0 -0
- django_ledger/templates/django_ledger/account/account_create.html +2 -2
- django_ledger/templates/django_ledger/account/account_update.html +1 -1
- django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +27 -18
- django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
- django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
- django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
- django_ledger/templatetags/django_ledger.py +7 -1
- django_ledger/tests/base.py +23 -7
- django_ledger/tests/test_accounts.py +145 -9
- django_ledger/urls/account.py +17 -24
- django_ledger/utils.py +8 -0
- django_ledger/views/__init__.py +1 -1
- django_ledger/views/account.py +80 -118
- django_ledger/views/auth.py +1 -1
- django_ledger/views/bank_account.py +9 -11
- django_ledger/views/bill.py +91 -80
- django_ledger/views/closing_entry.py +8 -0
- django_ledger/views/coa.py +2 -1
- django_ledger/views/customer.py +1 -1
- django_ledger/views/data_import.py +1 -1
- django_ledger/views/entity.py +1 -1
- django_ledger/views/estimate.py +13 -8
- django_ledger/views/feedback.py +1 -1
- django_ledger/views/financial_statement.py +1 -1
- django_ledger/views/home.py +1 -1
- django_ledger/views/inventory.py +9 -0
- django_ledger/views/invoice.py +5 -2
- django_ledger/views/item.py +58 -68
- django_ledger/views/journal_entry.py +1 -1
- django_ledger/views/ledger.py +3 -1
- django_ledger/views/mixins.py +9 -8
- django_ledger/views/purchase_order.py +1 -1
- django_ledger/views/transactions.py +1 -1
- django_ledger/views/unit.py +9 -0
- django_ledger/views/vendor.py +1 -1
- {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/AUTHORS.md +8 -2
- {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/METADATA +34 -43
- {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/RECORD +115 -79
- {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/WHEEL +1 -1
- {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/top_level.txt +0 -1
- {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/LICENSE +0 -0
django_ledger/__init__.py
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
|
-
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
4
|
"""
|
|
8
5
|
|
|
9
6
|
default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
|
|
10
7
|
|
|
11
8
|
"""Django Ledger"""
|
|
12
|
-
__version__ = '0.
|
|
9
|
+
__version__ = '0.7.0'
|
|
13
10
|
__license__ = 'GPLv3 License'
|
|
14
11
|
|
|
15
12
|
__author__ = 'Miguel Sanda'
|
django_ledger/admin/coa.py
CHANGED
|
@@ -108,7 +108,6 @@ class ChartOfAccountsModelAdmin(ModelAdmin):
|
|
|
108
108
|
list_display_links = ['name']
|
|
109
109
|
fields = [
|
|
110
110
|
'name',
|
|
111
|
-
'locked',
|
|
112
111
|
'description',
|
|
113
112
|
]
|
|
114
113
|
inlines = [
|
|
@@ -132,4 +131,4 @@ class ChartOfAccountsModelAdmin(ModelAdmin):
|
|
|
132
131
|
def account_model_count(self, obj):
|
|
133
132
|
return obj.accountmodel__count
|
|
134
133
|
|
|
135
|
-
account_model_count.short_description = 'Accounts'
|
|
134
|
+
account_model_count.short_description = 'Accounts'
|
|
@@ -25,7 +25,7 @@ class Accountlist_Query(graphene.ObjectType):
|
|
|
25
25
|
def resolve_all_accounts(self, info, slug_name, **kwargs):
|
|
26
26
|
if info.context.user.is_authenticated:
|
|
27
27
|
return AccountModel.objects.for_entity(
|
|
28
|
-
|
|
28
|
+
entity_model=slug_name,
|
|
29
29
|
user_model=info.context.user,
|
|
30
30
|
).select_related('parent').order_by('code')
|
|
31
31
|
else:
|
django_ledger/forms/account.py
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
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
|
+
from random import randint
|
|
1
9
|
from typing import Optional
|
|
2
10
|
|
|
3
|
-
from django.forms import TextInput, Select, ModelForm, ChoiceField, ValidationError, CheckboxInput
|
|
11
|
+
from django.forms import TextInput, Select, ModelForm, ChoiceField, ValidationError, CheckboxInput, HiddenInput
|
|
4
12
|
from django.utils.translation import gettext_lazy as _
|
|
5
13
|
from treebeard.forms import MoveNodeForm
|
|
6
14
|
|
|
7
15
|
from django_ledger.io import ACCOUNT_CHOICES_NO_ROOT
|
|
16
|
+
from django_ledger.models import ChartOfAccountModel, EntityModel
|
|
8
17
|
from django_ledger.models.accounts import AccountModel
|
|
9
18
|
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
10
19
|
|
|
11
|
-
"""
|
|
12
|
-
The account Model has the below forms: All these form have Account Model as their base.
|
|
13
|
-
|
|
14
|
-
CreateForm
|
|
15
|
-
CreateChildForm
|
|
16
|
-
Update Form
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
20
|
|
|
20
21
|
class AccountModelCreateForm(ModelForm):
|
|
21
22
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
role: The role needs to be selected rom list of the options available. Choices are given under ACCOUNT ROLES. Refer the account model documentation for more info
|
|
31
|
-
balance_type: Need to be selected from drop down as "Debit" or Credit"
|
|
23
|
+
A form for creating and managing account models within the system.
|
|
24
|
+
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
ENTITY_MODEL : Model
|
|
28
|
+
The entity model being used in the form.
|
|
29
|
+
COA_MODEL : Model
|
|
30
|
+
The Chart of Account Model being used in the form.
|
|
32
31
|
"""
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
self.
|
|
33
|
+
FORM_ID_SEP = '___'
|
|
34
|
+
|
|
35
|
+
def __init__(self, coa_model: ChartOfAccountModel, *args, **kwargs):
|
|
36
|
+
self.COA_MODEL: ChartOfAccountModel = coa_model
|
|
38
37
|
super().__init__(*args, **kwargs)
|
|
39
38
|
self.fields['role'].choices = ACCOUNT_CHOICES_NO_ROOT
|
|
40
39
|
self.fields['code'].required = False
|
|
40
|
+
self.fields['coa_model'].disabled = True
|
|
41
|
+
self.fields['coa_model'].required = False
|
|
42
|
+
|
|
43
|
+
self.form_id: str = self.get_form_id()
|
|
44
|
+
|
|
45
|
+
def get_form_id(self) -> str:
|
|
46
|
+
return f'account-model-create-form-{self.COA_MODEL.slug}{self.FORM_ID_SEP}{randint(100000, 999999)}'
|
|
41
47
|
|
|
42
48
|
def clean_role_default(self):
|
|
43
49
|
role_default = self.cleaned_data['role_default']
|
|
@@ -45,12 +51,8 @@ class AccountModelCreateForm(ModelForm):
|
|
|
45
51
|
return None
|
|
46
52
|
return role_default
|
|
47
53
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
is_code_valid = not self.COA_MODEL.accountmodel_set.filter(code=code).exists()
|
|
51
|
-
if not is_code_valid:
|
|
52
|
-
raise ValidationError(message=_('Code {} already exists for CoA {}').format(code, self.COA_MODEL.slug))
|
|
53
|
-
return code
|
|
54
|
+
def clean_coa_model(self):
|
|
55
|
+
return self.COA_MODEL
|
|
54
56
|
|
|
55
57
|
class Meta:
|
|
56
58
|
model = AccountModel
|
|
@@ -61,7 +63,8 @@ class AccountModelCreateForm(ModelForm):
|
|
|
61
63
|
'role_default',
|
|
62
64
|
'balance_type',
|
|
63
65
|
'active',
|
|
64
|
-
'active'
|
|
66
|
+
'active',
|
|
67
|
+
'coa_model'
|
|
65
68
|
]
|
|
66
69
|
widgets = {
|
|
67
70
|
'code': TextInput(attrs={
|
|
@@ -79,33 +82,41 @@ class AccountModelCreateForm(ModelForm):
|
|
|
79
82
|
'balance_type': Select(attrs={
|
|
80
83
|
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
81
84
|
}),
|
|
85
|
+
'coa_model': HiddenInput()
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
|
|
85
89
|
class AccountModelUpdateForm(MoveNodeForm):
|
|
86
90
|
"""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
AccountModelUpdateForm
|
|
92
|
+
|
|
93
|
+
A form for updating account model, inheriting from MoveNodeForm.
|
|
94
|
+
|
|
95
|
+
Attributes
|
|
96
|
+
----------
|
|
97
|
+
_position : ChoiceField
|
|
98
|
+
A choice field for selecting the position.
|
|
99
|
+
_ref_node_id : ChoiceField
|
|
100
|
+
An optional choice field for selecting the relative node.
|
|
90
101
|
"""
|
|
91
102
|
|
|
92
103
|
_position = ChoiceField(required=True,
|
|
93
|
-
label=_(
|
|
104
|
+
label=_('Position'),
|
|
94
105
|
widget=Select(attrs={
|
|
95
106
|
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
96
107
|
}))
|
|
97
108
|
_ref_node_id = ChoiceField(required=False,
|
|
98
|
-
label=_(
|
|
109
|
+
label=_('Relative to'),
|
|
99
110
|
widget=Select(attrs={
|
|
100
111
|
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
101
112
|
}))
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.COA_MODEL = coa_model
|
|
106
|
-
self.USER_MODEL = user_model
|
|
114
|
+
|
|
115
|
+
def __init__(self, *args, **kwargs):
|
|
107
116
|
super().__init__(*args, **kwargs)
|
|
108
|
-
|
|
117
|
+
self.fields['role'].disabled = True
|
|
118
|
+
self.fields['coa_model'].disabled = True
|
|
119
|
+
|
|
109
120
|
|
|
110
121
|
@classmethod
|
|
111
122
|
def mk_dropdown_tree(cls, model, for_node: Optional[AccountModel] = None):
|
|
@@ -114,15 +125,18 @@ class AccountModelUpdateForm(MoveNodeForm):
|
|
|
114
125
|
if not for_node:
|
|
115
126
|
raise ValidationError(message='Must provide for_node argument.')
|
|
116
127
|
|
|
117
|
-
options = list()
|
|
118
128
|
qs = for_node.get_account_move_choice_queryset()
|
|
119
129
|
|
|
120
|
-
# for node in qs:
|
|
121
|
-
# cls.add_subtree(for_node, node, options)
|
|
122
130
|
return [
|
|
123
131
|
(i.uuid, f'{"-" * (i.depth - 1)} {i}') for i in qs
|
|
124
132
|
]
|
|
125
133
|
|
|
134
|
+
def clean_role(self):
|
|
135
|
+
return self.instance.role
|
|
136
|
+
|
|
137
|
+
def coa_model(self):
|
|
138
|
+
return self.instance.coa_model
|
|
139
|
+
|
|
126
140
|
def clean_role_default(self):
|
|
127
141
|
role_default = self.cleaned_data['role_default']
|
|
128
142
|
if not role_default:
|
|
@@ -131,8 +145,10 @@ class AccountModelUpdateForm(MoveNodeForm):
|
|
|
131
145
|
|
|
132
146
|
class Meta:
|
|
133
147
|
model = AccountModel
|
|
134
|
-
exclude = ('depth', 'numchild', 'path', 'balance_type'
|
|
148
|
+
exclude = ('depth', 'numchild', 'path', 'balance_type')
|
|
135
149
|
widgets = {
|
|
150
|
+
'role': HiddenInput(),
|
|
151
|
+
'coa_model': HiddenInput(),
|
|
136
152
|
'parent': Select(attrs={
|
|
137
153
|
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
138
154
|
}),
|
|
@@ -13,9 +13,10 @@ class BankAccountCreateForm(ModelForm):
|
|
|
13
13
|
super().__init__(*args, **kwargs)
|
|
14
14
|
self.ENTITY_SLUG = entity_slug
|
|
15
15
|
self.USER_MODEL = user_model
|
|
16
|
-
account_qs = AccountModel.objects.
|
|
16
|
+
account_qs = AccountModel.objects.for_entity(
|
|
17
17
|
user_model=self.USER_MODEL,
|
|
18
|
-
|
|
18
|
+
entity_model=self.ENTITY_SLUG
|
|
19
|
+
).available().filter(
|
|
19
20
|
role__exact=ASSET_CA_CASH)
|
|
20
21
|
self.fields['cash_account'].queryset = account_qs
|
|
21
22
|
|
django_ledger/forms/bill.py
CHANGED
|
@@ -6,25 +6,20 @@ from django.utils.translation import gettext_lazy as _
|
|
|
6
6
|
|
|
7
7
|
from django_ledger.io.roles import ASSET_CA_CASH, ASSET_CA_PREPAID, LIABILITY_CL_ACC_PAYABLE
|
|
8
8
|
from django_ledger.models import (ItemModel, AccountModel, BillModel, ItemTransactionModel,
|
|
9
|
-
VendorModel, EntityUnitModel)
|
|
9
|
+
VendorModel, EntityUnitModel, EntityModel)
|
|
10
10
|
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class BillModelCreateForm(ModelForm):
|
|
14
|
-
def __init__(self, *args,
|
|
14
|
+
def __init__(self, *args, entity_model: EntityModel, **kwargs):
|
|
15
15
|
super().__init__(*args, **kwargs)
|
|
16
|
-
self.
|
|
17
|
-
self.USER_MODEL = user_model
|
|
18
|
-
self.BILL_MODEL: BillModel = self.instance
|
|
16
|
+
self.ENTITY_MODEL = entity_model
|
|
19
17
|
self.get_vendor_queryset()
|
|
20
18
|
self.get_accounts_queryset()
|
|
21
19
|
|
|
22
20
|
def get_vendor_queryset(self):
|
|
23
21
|
if 'vendor' in self.fields:
|
|
24
|
-
vendor_qs =
|
|
25
|
-
user_model=self.USER_MODEL,
|
|
26
|
-
entity_slug=self.ENTITY_SLUG
|
|
27
|
-
)
|
|
22
|
+
vendor_qs = self.ENTITY_MODEL.vendormodel_set.active()
|
|
28
23
|
self.fields['vendor'].queryset = vendor_qs
|
|
29
24
|
|
|
30
25
|
def get_accounts_queryset(self):
|
|
@@ -34,14 +29,7 @@ class BillModelCreateForm(ModelForm):
|
|
|
34
29
|
'prepaid_account' in self.fields,
|
|
35
30
|
'unearned_account' in self.fields,
|
|
36
31
|
]):
|
|
37
|
-
account_qs =
|
|
38
|
-
user_model=self.USER_MODEL,
|
|
39
|
-
entity_slug=self.ENTITY_SLUG
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# forcing evaluation of qs to cache results for fields... (avoids multiple database queries)
|
|
43
|
-
len(account_qs)
|
|
44
|
-
|
|
32
|
+
account_qs = self.ENTITY_MODEL.default_coa.accountmodel_set.all().for_bill()
|
|
45
33
|
self.fields['cash_account'].queryset = account_qs.filter(role__exact=ASSET_CA_CASH)
|
|
46
34
|
self.fields['prepaid_account'].queryset = account_qs.filter(role__exact=ASSET_CA_PREPAID)
|
|
47
35
|
self.fields['unearned_account'].queryset = account_qs.filter(role__exact=LIABILITY_CL_ACC_PAYABLE)
|
|
@@ -107,11 +95,11 @@ class BaseBillModelUpdateForm(BillModelCreateForm):
|
|
|
107
95
|
|
|
108
96
|
def __init__(self,
|
|
109
97
|
*args,
|
|
110
|
-
|
|
98
|
+
entity_model,
|
|
111
99
|
user_model,
|
|
112
100
|
**kwargs):
|
|
113
|
-
super().__init__(
|
|
114
|
-
self.
|
|
101
|
+
super().__init__(entity_model=entity_model, *args, **kwargs)
|
|
102
|
+
self.ENTITY_MODEL = entity_model
|
|
115
103
|
self.USER_MODEL = user_model
|
|
116
104
|
self.BILL_MODEL: BillModel = self.instance
|
|
117
105
|
|
|
@@ -120,7 +108,7 @@ class BaseBillModelUpdateForm(BillModelCreateForm):
|
|
|
120
108
|
self.BILL_MODEL.update_state()
|
|
121
109
|
self.instance.migrate_state(
|
|
122
110
|
user_model=self.USER_MODEL,
|
|
123
|
-
entity_slug=self.
|
|
111
|
+
entity_slug=self.ENTITY_MODEL.slug,
|
|
124
112
|
raise_exception=False
|
|
125
113
|
)
|
|
126
114
|
super().save(commit=commit)
|
|
@@ -226,6 +214,10 @@ class BillModelConfigureForm(BaseBillModelUpdateForm):
|
|
|
226
214
|
|
|
227
215
|
class BillItemTransactionForm(ModelForm):
|
|
228
216
|
|
|
217
|
+
# def __init__(self, entity_unit_qs, *args, **kwargs):
|
|
218
|
+
# super().__init__(self, *args, **kwargs)
|
|
219
|
+
# self.fields['entity_unit'].queryset = entity_unit_qs
|
|
220
|
+
|
|
229
221
|
def clean(self):
|
|
230
222
|
cleaned_data = super(BillItemTransactionForm, self).clean()
|
|
231
223
|
itemtxs_model: ItemTransactionModel = self.instance
|
|
@@ -262,28 +254,24 @@ class BillItemTransactionForm(ModelForm):
|
|
|
262
254
|
class BaseBillItemTransactionFormset(BaseModelFormSet):
|
|
263
255
|
|
|
264
256
|
def __init__(self, *args,
|
|
265
|
-
|
|
257
|
+
entity_model: EntityModel,
|
|
266
258
|
bill_model: BillModel,
|
|
267
|
-
user_model,
|
|
268
259
|
**kwargs):
|
|
269
260
|
super().__init__(*args, **kwargs)
|
|
270
|
-
self.USER_MODEL = user_model
|
|
271
261
|
self.BILL_MODEL = bill_model
|
|
272
|
-
self.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
)
|
|
262
|
+
self.ENTITY_MODEL = entity_model
|
|
263
|
+
self.queryset = self.BILL_MODEL.itemtransactionmodel_set.select_related(
|
|
264
|
+
'item_model',
|
|
265
|
+
'po_model',
|
|
266
|
+
'bill_model'
|
|
267
|
+
).order_by('-total_amount')
|
|
278
268
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
user_model=self.USER_MODEL
|
|
282
|
-
)
|
|
269
|
+
self.items_qs = self.ENTITY_MODEL.itemmodel_set.bills()
|
|
270
|
+
self.entity_unit_qs = self.ENTITY_MODEL.entityunitmodel_set.all()
|
|
283
271
|
|
|
284
272
|
for form in self.forms:
|
|
285
|
-
form.fields['item_model'].queryset = items_qs
|
|
286
|
-
form.fields['entity_unit'].queryset =
|
|
273
|
+
form.fields['item_model'].queryset = self.items_qs
|
|
274
|
+
form.fields['entity_unit'].queryset = self.entity_unit_qs
|
|
287
275
|
|
|
288
276
|
if not self.BILL_MODEL.can_edit_items():
|
|
289
277
|
form.fields['item_model'].disabled = True
|
django_ledger/forms/customer.py
CHANGED
|
@@ -37,7 +37,7 @@ class CustomerModelForm(ModelForm):
|
|
|
37
37
|
'hidden'
|
|
38
38
|
]
|
|
39
39
|
help_texts = {
|
|
40
|
-
'sales_tax_rate': _('Example: 3.50% should be entered as 0.
|
|
40
|
+
'sales_tax_rate': _('Example: 3.50% should be entered as 0.035')
|
|
41
41
|
}
|
|
42
42
|
widgets = {
|
|
43
43
|
'customer_name': TextInput(attrs={
|
|
@@ -130,10 +130,10 @@ class BaseStagedTransactionModelFormSet(BaseModelFormSet):
|
|
|
130
130
|
self.IMPORT_DISABLED = not exclude_account
|
|
131
131
|
self.CASH_ACCOUNT = exclude_account
|
|
132
132
|
|
|
133
|
-
account_model_qs = AccountModel.objects.
|
|
133
|
+
account_model_qs = AccountModel.objects.for_entity(
|
|
134
134
|
user_model=self.USER_MODEL,
|
|
135
|
-
|
|
136
|
-
).order_by('role', 'name')
|
|
135
|
+
entity_model=self.ENTITY_SLUG
|
|
136
|
+
).available().order_by('role', 'name')
|
|
137
137
|
|
|
138
138
|
unit_model_qs = EntityUnitModel.objects.for_entity(
|
|
139
139
|
user_model=self.USER_MODEL,
|
django_ledger/forms/estimate.py
CHANGED
django_ledger/forms/invoice.py
CHANGED
|
@@ -31,7 +31,7 @@ class InvoiceModelCreateForEstimateForm(ModelForm):
|
|
|
31
31
|
customer_qs = CustomerModel.objects.for_entity(
|
|
32
32
|
user_model=self.USER_MODEL,
|
|
33
33
|
entity_slug=self.ENTITY_SLUG
|
|
34
|
-
)
|
|
34
|
+
).active()
|
|
35
35
|
self.fields['customer'].queryset = customer_qs
|
|
36
36
|
|
|
37
37
|
def get_accounts_queryset(self):
|
|
@@ -41,13 +41,11 @@ class InvoiceModelCreateForEstimateForm(ModelForm):
|
|
|
41
41
|
'prepaid_account' in self.fields,
|
|
42
42
|
'unearned_account' in self.fields,
|
|
43
43
|
]):
|
|
44
|
-
account_qs = AccountModel.objects.for_invoice(
|
|
45
|
-
user_model=self.USER_MODEL,
|
|
46
|
-
entity_slug=self.ENTITY_SLUG
|
|
47
|
-
)
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
account_qs = AccountModel.objects.for_entity(
|
|
46
|
+
user_model=self.USER_MODEL,
|
|
47
|
+
entity_model=self.ENTITY_SLUG
|
|
48
|
+
).for_invoice()
|
|
51
49
|
|
|
52
50
|
self.fields['cash_account'].queryset = account_qs.filter(role__exact=ASSET_CA_CASH)
|
|
53
51
|
self.fields['prepaid_account'].queryset = account_qs.filter(role__exact=ASSET_CA_RECEIVABLES)
|
django_ledger/forms/item.py
CHANGED
|
@@ -46,10 +46,12 @@ class ProductCreateForm(ModelForm):
|
|
|
46
46
|
self.USER_MODEL = user_model
|
|
47
47
|
super().__init__(*args, **kwargs)
|
|
48
48
|
|
|
49
|
-
accounts_qs = AccountModel.objects.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
accounts_qs = AccountModel.objects.for_entity(
|
|
50
|
+
entity_model=self.ENTITY_SLUG,
|
|
51
|
+
user_model=self.USER_MODEL
|
|
52
|
+
).with_roles(
|
|
53
|
+
roles=self.PRODUCT_OR_SERVICE_ROLES
|
|
54
|
+
).active()
|
|
53
55
|
|
|
54
56
|
# caches the QS for filtering...
|
|
55
57
|
len(accounts_qs)
|
|
@@ -139,10 +141,12 @@ class ServiceCreateForm(ModelForm):
|
|
|
139
141
|
self.USER_MODEL = user_model
|
|
140
142
|
super().__init__(*args, **kwargs)
|
|
141
143
|
|
|
142
|
-
accounts_qs = AccountModel.objects.
|
|
144
|
+
accounts_qs = AccountModel.objects.for_entity(
|
|
145
|
+
entity_model=self.ENTITY_SLUG,
|
|
146
|
+
user_model=self.USER_MODEL
|
|
147
|
+
).with_roles(
|
|
143
148
|
roles=self.SERVICE_ROLES,
|
|
144
|
-
|
|
145
|
-
user_model=self.USER_MODEL).active()
|
|
149
|
+
).active()
|
|
146
150
|
|
|
147
151
|
# caches the QS for filtering...
|
|
148
152
|
len(accounts_qs)
|
|
@@ -224,10 +228,12 @@ class ExpenseItemCreateForm(ModelForm):
|
|
|
224
228
|
self.USER_MODEL = user_model
|
|
225
229
|
super().__init__(*args, **kwargs)
|
|
226
230
|
|
|
227
|
-
accounts_qs = AccountModel.objects.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
accounts_qs = AccountModel.objects.for_entity(
|
|
232
|
+
entity_model=self.ENTITY_SLUG,
|
|
233
|
+
user_model=self.USER_MODEL
|
|
234
|
+
).with_roles(
|
|
235
|
+
roles=GROUP_EXPENSES
|
|
236
|
+
).active()
|
|
231
237
|
|
|
232
238
|
self.fields['expense_account'].queryset = accounts_qs.filter(role__in=GROUP_EXPENSES)
|
|
233
239
|
|
|
@@ -295,6 +301,7 @@ class ExpenseItemUpdateForm(ExpenseItemCreateForm):
|
|
|
295
301
|
'sku',
|
|
296
302
|
'default_amount',
|
|
297
303
|
'expense_account',
|
|
304
|
+
'is_active'
|
|
298
305
|
]
|
|
299
306
|
|
|
300
307
|
|
|
@@ -306,10 +313,12 @@ class InventoryItemCreateForm(ModelForm):
|
|
|
306
313
|
self.USER_MODEL = user_model
|
|
307
314
|
super().__init__(*args, **kwargs)
|
|
308
315
|
|
|
309
|
-
accounts_qs = AccountModel.objects.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
316
|
+
accounts_qs = AccountModel.objects.for_entity(
|
|
317
|
+
entity_model=self.ENTITY_SLUG,
|
|
318
|
+
user_model=self.USER_MODEL
|
|
319
|
+
).with_roles(
|
|
320
|
+
roles=[ASSET_CA_INVENTORY]
|
|
321
|
+
).active()
|
|
313
322
|
self.fields['inventory_account'].queryset = accounts_qs
|
|
314
323
|
|
|
315
324
|
if 'uom' in self.fields:
|
|
@@ -51,10 +51,10 @@ class TransactionModelFormSet(BaseModelFormSet):
|
|
|
51
51
|
self.LEDGER_PK = ledger_pk
|
|
52
52
|
self.ENTITY_SLUG = entity_slug
|
|
53
53
|
|
|
54
|
-
account_qs = AccountModel.objects.
|
|
54
|
+
account_qs = AccountModel.objects.for_entity(
|
|
55
55
|
user_model=self.USER_MODEL,
|
|
56
|
-
|
|
57
|
-
).order_by('code')
|
|
56
|
+
entity_model=self.ENTITY_SLUG
|
|
57
|
+
).available().order_by('code')
|
|
58
58
|
|
|
59
59
|
for form in self.forms:
|
|
60
60
|
form.fields['account'].queryset = account_qs
|
django_ledger/io/io_core.py
CHANGED
|
@@ -549,7 +549,7 @@ class IODatabaseMixIn:
|
|
|
549
549
|
force_queryset_sorting: bool = False,
|
|
550
550
|
**kwargs) -> IOResult:
|
|
551
551
|
"""
|
|
552
|
-
Performs the appropriate transaction post-processing after DB aggregation
|
|
552
|
+
Performs the appropriate transaction post-processing after DB aggregation.
|
|
553
553
|
|
|
554
554
|
|
|
555
555
|
Parameters
|
|
@@ -757,7 +757,6 @@ class IODatabaseMixIn:
|
|
|
757
757
|
by_unit=by_unit
|
|
758
758
|
)
|
|
759
759
|
|
|
760
|
-
# idea: change digest() name to something else? maybe aggregate, calculate?...
|
|
761
760
|
io_state = roles_mgr.digest()
|
|
762
761
|
|
|
763
762
|
if any([
|
|
@@ -938,6 +937,7 @@ class IOReportMixIn:
|
|
|
938
937
|
balance_sheet_statement=True,
|
|
939
938
|
txs_queryset=txs_queryset,
|
|
940
939
|
as_io_digest=True,
|
|
940
|
+
signs=True,
|
|
941
941
|
**kwargs
|
|
942
942
|
)
|
|
943
943
|
|
|
@@ -991,6 +991,7 @@ class IOReportMixIn:
|
|
|
991
991
|
income_statement=True,
|
|
992
992
|
txs_queryset=txs_queryset,
|
|
993
993
|
as_io_digest=True,
|
|
994
|
+
sings=True,
|
|
994
995
|
**kwargs
|
|
995
996
|
)
|
|
996
997
|
|
|
@@ -1046,6 +1047,7 @@ class IOReportMixIn:
|
|
|
1046
1047
|
cash_flow_statement=True,
|
|
1047
1048
|
txs_queryset=txs_queryset,
|
|
1048
1049
|
as_io_digest=True,
|
|
1050
|
+
signs=True,
|
|
1049
1051
|
**kwargs
|
|
1050
1052
|
)
|
|
1051
1053
|
|
|
@@ -451,11 +451,16 @@ class CashFlowStatementIOMiddleware:
|
|
|
451
451
|
bal for act, bal in self.IO_DATA[self.CFS_DIGEST_KEY]['net_cash_by_activity'].items()
|
|
452
452
|
])
|
|
453
453
|
|
|
454
|
+
def net_income(self):
|
|
455
|
+
group_balances = self.IO_DATA[AccountGroupIOMiddleware.GROUP_BALANCE_KEY]
|
|
456
|
+
self.IO_DATA[self.CFS_DIGEST_KEY]['net_income'] = group_balances['GROUP_CFS_NET_INCOME']
|
|
457
|
+
|
|
454
458
|
def digest(self):
|
|
455
459
|
self.IO_DATA[self.CFS_DIGEST_KEY] = dict()
|
|
456
460
|
self.check_io_digest()
|
|
457
461
|
self.operating()
|
|
458
462
|
self.financing()
|
|
459
463
|
self.investing()
|
|
464
|
+
self.net_income()
|
|
460
465
|
self.net_cash()
|
|
461
466
|
return self.IO_DATA
|
django_ledger/io/roles.py
CHANGED
|
@@ -660,3 +660,9 @@ def validate_roles(roles: Union[str, List[str]], raise_exception: bool = True) -
|
|
|
660
660
|
if raise_exception:
|
|
661
661
|
raise InvalidRoleError('{rls}) is invalid. Choices are {ch}'.format(ch=', '.join(VALID_ROLES), rls=r))
|
|
662
662
|
return set(roles)
|
|
663
|
+
|
|
664
|
+
VALID_PARENTS = {
|
|
665
|
+
ASSET_PPE_BUILDINGS_ACCUM_DEPRECIATION: [ASSET_PPE_BUILDINGS],
|
|
666
|
+
ASSET_PPE_EQUIPMENT_ACCUM_DEPRECIATION: [ASSET_PPE_EQUIPMENT],
|
|
667
|
+
ASSET_PPE_PLANT_ACCUM_DEPRECIATION: [ASSET_PPE_PLANT],
|
|
668
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Generated by Django 5.1.1 on 2024-10-09 19:40
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('django_ledger', '0016_remove_accountmodel_django_ledg_coa_mod_e19964_idx_and_more'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterUniqueTogether(
|
|
15
|
+
name='accountmodel',
|
|
16
|
+
unique_together=set(),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name='accountmodel',
|
|
20
|
+
name='coa_model',
|
|
21
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_ledger.chartofaccountmodel', verbose_name='Chart of Accounts'),
|
|
22
|
+
),
|
|
23
|
+
migrations.AddConstraint(
|
|
24
|
+
model_name='accountmodel',
|
|
25
|
+
constraint=models.UniqueConstraint(fields=('coa_model', 'code'), name='unique_code_for_coa_model', violation_error_message='Account codes must be unique for each Chart of Accounts Model.'),
|
|
26
|
+
),
|
|
27
|
+
migrations.AddConstraint(
|
|
28
|
+
model_name='accountmodel',
|
|
29
|
+
constraint=models.UniqueConstraint(fields=('coa_model', 'role', 'role_default'), name='only_one_account_assigned_as_default_for_role', violation_error_message='Only one default account for role permitted.'),
|
|
30
|
+
),
|
|
31
|
+
]
|