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

Files changed (93) hide show
  1. django_ledger/__init__.py +1 -4
  2. django_ledger/admin/__init__.py +1 -1
  3. django_ledger/admin/{coa.py → chart_of_accounts.py} +1 -1
  4. django_ledger/admin/entity.py +1 -1
  5. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
  6. django_ledger/forms/account.py +43 -38
  7. django_ledger/forms/bank_account.py +5 -2
  8. django_ledger/forms/bill.py +24 -36
  9. django_ledger/forms/chart_of_accounts.py +82 -0
  10. django_ledger/forms/customer.py +1 -1
  11. django_ledger/forms/data_import.py +3 -3
  12. django_ledger/forms/estimate.py +1 -1
  13. django_ledger/forms/invoice.py +5 -7
  14. django_ledger/forms/item.py +24 -15
  15. django_ledger/forms/transactions.py +3 -3
  16. django_ledger/io/io_core.py +4 -2
  17. django_ledger/io/io_library.py +1 -1
  18. django_ledger/io/io_middleware.py +5 -0
  19. django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
  20. django_ledger/migrations/0018_transactionmodel_cleared_transactionmodel_reconciled_and_more.py +37 -0
  21. django_ledger/models/__init__.py +1 -1
  22. django_ledger/models/accounts.py +229 -265
  23. django_ledger/models/bank_account.py +6 -6
  24. django_ledger/models/bill.py +7 -6
  25. django_ledger/models/{coa.py → chart_of_accounts.py} +187 -72
  26. django_ledger/models/closing_entry.py +5 -10
  27. django_ledger/models/coa_default.py +10 -9
  28. django_ledger/models/customer.py +6 -6
  29. django_ledger/models/data_import.py +12 -8
  30. django_ledger/models/entity.py +96 -39
  31. django_ledger/models/estimate.py +6 -10
  32. django_ledger/models/invoice.py +14 -11
  33. django_ledger/models/items.py +23 -14
  34. django_ledger/models/journal_entry.py +73 -30
  35. django_ledger/models/ledger.py +8 -8
  36. django_ledger/models/mixins.py +0 -3
  37. django_ledger/models/purchase_order.py +9 -9
  38. django_ledger/models/signals.py +0 -3
  39. django_ledger/models/transactions.py +24 -7
  40. django_ledger/models/unit.py +4 -3
  41. django_ledger/models/utils.py +0 -3
  42. django_ledger/models/vendor.py +4 -3
  43. django_ledger/settings.py +28 -3
  44. django_ledger/templates/django_ledger/account/account_create.html +2 -2
  45. django_ledger/templates/django_ledger/account/account_update.html +1 -1
  46. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
  47. django_ledger/templates/django_ledger/account/tags/accounts_table.html +29 -19
  48. django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
  49. django_ledger/templates/django_ledger/chart_of_accounts/coa_create.html +25 -0
  50. django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +25 -6
  51. django_ledger/templates/django_ledger/chart_of_accounts/coa_update.html +2 -2
  52. django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +10 -4
  53. django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
  54. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +2 -2
  55. django_ledger/templates/django_ledger/includes/footer.html +2 -2
  56. django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
  57. django_ledger/templatetags/django_ledger.py +7 -1
  58. django_ledger/tests/base.py +23 -7
  59. django_ledger/tests/test_accounts.py +145 -9
  60. django_ledger/urls/account.py +17 -24
  61. django_ledger/urls/chart_of_accounts.py +6 -0
  62. django_ledger/utils.py +9 -36
  63. django_ledger/views/__init__.py +2 -2
  64. django_ledger/views/account.py +91 -116
  65. django_ledger/views/auth.py +1 -1
  66. django_ledger/views/bank_account.py +9 -11
  67. django_ledger/views/bill.py +91 -80
  68. django_ledger/views/{coa.py → chart_of_accounts.py} +49 -44
  69. django_ledger/views/closing_entry.py +8 -0
  70. django_ledger/views/customer.py +1 -1
  71. django_ledger/views/data_import.py +1 -1
  72. django_ledger/views/entity.py +1 -1
  73. django_ledger/views/estimate.py +13 -8
  74. django_ledger/views/feedback.py +1 -1
  75. django_ledger/views/financial_statement.py +1 -1
  76. django_ledger/views/home.py +1 -1
  77. django_ledger/views/inventory.py +9 -0
  78. django_ledger/views/invoice.py +5 -2
  79. django_ledger/views/item.py +58 -68
  80. django_ledger/views/journal_entry.py +1 -1
  81. django_ledger/views/ledger.py +3 -1
  82. django_ledger/views/mixins.py +25 -13
  83. django_ledger/views/purchase_order.py +1 -1
  84. django_ledger/views/transactions.py +1 -1
  85. django_ledger/views/unit.py +9 -0
  86. django_ledger/views/vendor.py +1 -1
  87. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/AUTHORS.md +8 -2
  88. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/METADATA +33 -44
  89. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/RECORD +92 -89
  90. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/WHEEL +1 -1
  91. django_ledger/forms/coa.py +0 -47
  92. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/LICENSE +0 -0
  93. {django_ledger-0.6.4.dist-info → django_ledger-0.7.1.dist-info}/top_level.txt +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.6.4'
9
+ __version__ = '0.7.1'
13
10
  __license__ = 'GPLv3 License'
14
11
 
15
12
  __author__ = 'Miguel Sanda'
@@ -1,6 +1,6 @@
1
1
  from django.contrib import admin
2
2
 
3
- from django_ledger.admin.coa import ChartOfAccountsModelAdmin
3
+ from django_ledger.admin.chart_of_accounts import ChartOfAccountsModelAdmin
4
4
  from django_ledger.admin.entity import EntityModelAdmin
5
5
  from django_ledger.admin.ledger import LedgerModelAdmin
6
6
  from django_ledger.models import EntityModel, ChartOfAccountModel, LedgerModel
@@ -3,7 +3,7 @@ from django.db.models import Count
3
3
  from django.forms import ModelForm, BooleanField, BaseInlineFormSet
4
4
 
5
5
  from django_ledger.models.accounts import AccountModel
6
- from django_ledger.models.coa import ChartOfAccountModel
6
+ from django_ledger.models.chart_of_accounts import ChartOfAccountModel
7
7
  from django_ledger.models.entity import EntityModel
8
8
 
9
9
 
@@ -7,7 +7,7 @@ from django.forms import BaseInlineFormSet
7
7
  from django.urls import reverse
8
8
  from django.utils.html import format_html
9
9
 
10
- from django_ledger.admin.coa import ChartOfAccountsInLine
10
+ from django_ledger.admin.chart_of_accounts import ChartOfAccountsInLine
11
11
  from django_ledger.io.io_core import get_localtime
12
12
  from django_ledger.models import EntityUnitModel
13
13
  from django_ledger.models.entity import EntityModel, EntityManagementModel
@@ -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
- entity_slug=slug_name,
28
+ entity_model=slug_name,
29
29
  user_model=info.context.user,
30
30
  ).select_related('parent').order_by('code')
31
31
  else:
@@ -1,47 +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
- AccountModelCreateForm
23
- ======================
24
-
25
23
  A form for creating and managing account models within the system.
26
24
 
27
25
  Attributes
28
26
  ----------
29
- ENTITY : Model
27
+ ENTITY_MODEL : Model
30
28
  The entity model being used in the form.
31
29
  COA_MODEL : Model
32
30
  The Chart of Account Model being used in the form.
33
- USER_MODEL : Model
34
- The user model being used in the form.
35
-
36
31
  """
37
32
 
38
- def __init__(self, entity_model, coa_model, user_model, *args, **kwargs):
39
- self.ENTITY = entity_model
40
- self.COA_MODEL = coa_model
41
- self.USER_MODEL = user_model
33
+ FORM_ID_SEP = '___'
34
+
35
+ def __init__(self, coa_model: ChartOfAccountModel, *args, **kwargs):
36
+ self.COA_MODEL: ChartOfAccountModel = coa_model
42
37
  super().__init__(*args, **kwargs)
43
38
  self.fields['role'].choices = ACCOUNT_CHOICES_NO_ROOT
44
39
  self.fields['code'].required = False
40
+ self.fields['coa_model'].disabled = True
41
+ self.fields['coa_model'].required = True
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)}'
45
47
 
46
48
  def clean_role_default(self):
47
49
  role_default = self.cleaned_data['role_default']
@@ -49,12 +51,8 @@ class AccountModelCreateForm(ModelForm):
49
51
  return None
50
52
  return role_default
51
53
 
52
- def clean_code(self):
53
- code = self.cleaned_data['code']
54
- is_code_valid = not self.COA_MODEL.accountmodel_set.filter(code=code).exists()
55
- if not is_code_valid:
56
- raise ValidationError(message=_('Code {} already exists for CoA {}').format(code, self.COA_MODEL.slug))
57
- return code
54
+ def clean_coa_model(self):
55
+ return self.COA_MODEL
58
56
 
59
57
  class Meta:
60
58
  model = AccountModel
@@ -65,7 +63,8 @@ class AccountModelCreateForm(ModelForm):
65
63
  'role_default',
66
64
  'balance_type',
67
65
  'active',
68
- 'active'
66
+ 'active',
67
+ 'coa_model'
69
68
  ]
70
69
  widgets = {
71
70
  'code': TextInput(attrs={
@@ -83,6 +82,7 @@ class AccountModelCreateForm(ModelForm):
83
82
  'balance_type': Select(attrs={
84
83
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
85
84
  }),
85
+ 'coa_model': HiddenInput()
86
86
  }
87
87
 
88
88
 
@@ -101,22 +101,22 @@ class AccountModelUpdateForm(MoveNodeForm):
101
101
  """
102
102
 
103
103
  _position = ChoiceField(required=True,
104
- label=_("Position"),
104
+ label=_('Position'),
105
105
  widget=Select(attrs={
106
106
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
107
107
  }))
108
108
  _ref_node_id = ChoiceField(required=False,
109
- label=_("Relative to"),
109
+ label=_('Relative to'),
110
110
  widget=Select(attrs={
111
111
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
112
112
  }))
113
113
 
114
- def __init__(self, entity_model, coa_model, user_model, *args, **kwargs):
115
- self.ENTITY = entity_model
116
- self.COA_MODEL = coa_model
117
- self.USER_MODEL = user_model
114
+
115
+ def __init__(self, *args, **kwargs):
118
116
  super().__init__(*args, **kwargs)
119
- # self.fields['_ref_node_id'].choices = self.mk_dropdown_tree_choices()
117
+ self.fields['role'].disabled = True
118
+ self.fields['coa_model'].disabled = True
119
+
120
120
 
121
121
  @classmethod
122
122
  def mk_dropdown_tree(cls, model, for_node: Optional[AccountModel] = None):
@@ -125,15 +125,18 @@ class AccountModelUpdateForm(MoveNodeForm):
125
125
  if not for_node:
126
126
  raise ValidationError(message='Must provide for_node argument.')
127
127
 
128
- options = list()
129
128
  qs = for_node.get_account_move_choice_queryset()
130
129
 
131
- # for node in qs:
132
- # cls.add_subtree(for_node, node, options)
133
130
  return [
134
131
  (i.uuid, f'{"-" * (i.depth - 1)} {i}') for i in qs
135
132
  ]
136
133
 
134
+ def clean_role(self):
135
+ return self.instance.role
136
+
137
+ def coa_model(self):
138
+ return self.instance.coa_model
139
+
137
140
  def clean_role_default(self):
138
141
  role_default = self.cleaned_data['role_default']
139
142
  if not role_default:
@@ -142,8 +145,10 @@ class AccountModelUpdateForm(MoveNodeForm):
142
145
 
143
146
  class Meta:
144
147
  model = AccountModel
145
- exclude = ('depth', 'numchild', 'path', 'balance_type', 'role')
148
+ exclude = ('depth', 'numchild', 'path', 'balance_type')
146
149
  widgets = {
150
+ 'role': HiddenInput(),
151
+ 'coa_model': HiddenInput(),
147
152
  'parent': Select(attrs={
148
153
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
149
154
  }),
@@ -13,9 +13,12 @@ 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.for_entity_available(
16
+
17
+ # todo: only the accounts that do not hava an associated bank account should be available to pick from...
18
+ account_qs = AccountModel.objects.for_entity(
17
19
  user_model=self.USER_MODEL,
18
- entity_slug=self.ENTITY_SLUG).filter(
20
+ entity_model=self.ENTITY_SLUG
21
+ ).available().filter(
19
22
  role__exact=ASSET_CA_CASH)
20
23
  self.fields['cash_account'].queryset = account_qs
21
24
 
@@ -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, entity_slug, user_model, **kwargs):
14
+ def __init__(self, *args, entity_model: EntityModel, **kwargs):
15
15
  super().__init__(*args, **kwargs)
16
- self.ENTITY_SLUG = entity_slug
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 = VendorModel.objects.for_entity(
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 = AccountModel.objects.for_bill(
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
- entity_slug,
98
+ entity_model,
111
99
  user_model,
112
100
  **kwargs):
113
- super().__init__(entity_slug=entity_slug, user_model=user_model, *args, **kwargs)
114
- self.ENTITY_SLUG = entity_slug
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.ENTITY_SLUG,
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
- entity_slug,
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.ENTITY_SLUG = entity_slug
273
-
274
- items_qs = ItemModel.objects.for_bill(
275
- entity_slug=self.ENTITY_SLUG,
276
- user_model=self.USER_MODEL
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
- unit_qs = EntityUnitModel.objects.for_entity(
280
- entity_slug=self.ENTITY_SLUG,
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 = unit_qs
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
@@ -0,0 +1,82 @@
1
+ from random import randint
2
+
3
+ from django.forms import ModelForm, TextInput, Textarea, HiddenInput
4
+ from django.utils.translation import gettext_lazy as _
5
+
6
+ from django_ledger.models.chart_of_accounts import ChartOfAccountModel
7
+ from django_ledger.models.entity import EntityModel
8
+ from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
9
+
10
+
11
+ class ChartOfAccountsModelCreateForm(ModelForm):
12
+ FORM_ID_SEP = '___'
13
+
14
+ def __init__(self, entity_model: EntityModel, *args, **kwargs):
15
+ self.ENTITY_MODEL = entity_model
16
+ super().__init__(*args, **kwargs)
17
+ self.fields['entity'].disabled = True
18
+ self.fields['entity'].required = True
19
+ self.form_id: str = self.get_form_id()
20
+
21
+ def clean_entity(self):
22
+ return self.ENTITY_MODEL
23
+
24
+ def get_form_id(self) -> str:
25
+ return f'coa-model-create-form-{self.ENTITY_MODEL.slug}{self.FORM_ID_SEP}{randint(100000, 999999)}'
26
+
27
+ class Meta:
28
+ model = ChartOfAccountModel
29
+ fields = [
30
+ 'entity',
31
+ 'name',
32
+ 'description'
33
+ ]
34
+ labels = {
35
+ 'name': _('Name'),
36
+ 'description': _('Description'),
37
+ }
38
+ widgets = {
39
+ 'entity': HiddenInput(),
40
+ 'name': TextInput(
41
+ attrs={
42
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
43
+ }),
44
+ 'description': Textarea(
45
+ attrs={
46
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
47
+ }
48
+ ),
49
+ }
50
+
51
+
52
+ class ChartOfAccountsModelUpdateForm(ModelForm):
53
+
54
+ FORM_ID_SEP = '___'
55
+
56
+ def __init__(self, *args, **kwargs):
57
+ super().__init__(*args, **kwargs)
58
+ self.form_id: str = self.get_form_id()
59
+
60
+
61
+ def get_form_id(self) -> str:
62
+ instance: ChartOfAccountModel = self.instance
63
+ return f'coa-model-update-form-{instance.slug}{self.FORM_ID_SEP}{randint(100000, 999999)}'
64
+
65
+ class Meta:
66
+ model = ChartOfAccountModel
67
+ fields = [
68
+ 'name',
69
+ 'active'
70
+ ]
71
+ labels = {
72
+ 'name': _('Name'),
73
+ 'description': _('Description'),
74
+ }
75
+ widgets = {
76
+ 'name': TextInput(attrs={
77
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
78
+ }),
79
+ 'description': Textarea(attrs={
80
+ 'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
81
+ }),
82
+ }
@@ -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.0035')
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.for_entity_available(
133
+ account_model_qs = AccountModel.objects.for_entity(
134
134
  user_model=self.USER_MODEL,
135
- entity_slug=self.ENTITY_SLUG
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,
@@ -27,7 +27,7 @@ class EstimateModelCreateForm(forms.ModelForm):
27
27
  return CustomerModel.objects.for_entity(
28
28
  entity_slug=self.ENTITY_SLUG,
29
29
  user_model=self.USER_MODEL
30
- )
30
+ ).active()
31
31
 
32
32
  class Meta:
33
33
  model = EstimateModel
@@ -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
- # forcing evaluation of qs to cache results for fields... (avoids multiple database queries)
50
- len(account_qs)
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)
@@ -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.with_roles(
50
- roles=self.PRODUCT_OR_SERVICE_ROLES,
51
- entity_slug=self.ENTITY_SLUG,
52
- user_model=self.USER_MODEL).active()
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.with_roles(
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
- entity_slug=self.ENTITY_SLUG,
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.with_roles(
228
- roles=GROUP_EXPENSES,
229
- entity_slug=self.ENTITY_SLUG,
230
- user_model=self.USER_MODEL).active()
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.with_roles(
310
- roles=[ASSET_CA_INVENTORY],
311
- entity_slug=self.ENTITY_SLUG,
312
- user_model=self.USER_MODEL).active()
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.for_entity_available(
54
+ account_qs = AccountModel.objects.for_entity(
55
55
  user_model=self.USER_MODEL,
56
- entity_slug=self.ENTITY_SLUG
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
@@ -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
 
@@ -22,7 +22,7 @@ from django.utils.translation import gettext_lazy as _
22
22
 
23
23
  from django_ledger.io.io_core import get_localtime
24
24
  from django_ledger.models.accounts import AccountModel, AccountModelQuerySet, CREDIT, DEBIT
25
- from django_ledger.models.coa import ChartOfAccountModel
25
+ from django_ledger.models.chart_of_accounts import ChartOfAccountModel
26
26
  from django_ledger.models.entity import EntityModel
27
27
  from django_ledger.models.ledger import LedgerModel, LedgerModelQuerySet
28
28
 
@@ -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