django-ledger 0.6.4__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.

Files changed (77) hide show
  1. django_ledger/__init__.py +1 -4
  2. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
  3. django_ledger/forms/account.py +43 -38
  4. django_ledger/forms/bank_account.py +3 -2
  5. django_ledger/forms/bill.py +24 -36
  6. django_ledger/forms/customer.py +1 -1
  7. django_ledger/forms/data_import.py +3 -3
  8. django_ledger/forms/estimate.py +1 -1
  9. django_ledger/forms/invoice.py +5 -7
  10. django_ledger/forms/item.py +24 -15
  11. django_ledger/forms/transactions.py +3 -3
  12. django_ledger/io/io_core.py +4 -2
  13. django_ledger/io/io_middleware.py +5 -0
  14. django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
  15. django_ledger/models/accounts.py +225 -265
  16. django_ledger/models/bank_account.py +0 -4
  17. django_ledger/models/bill.py +0 -3
  18. django_ledger/models/closing_entry.py +0 -3
  19. django_ledger/models/coa.py +59 -48
  20. django_ledger/models/coa_default.py +9 -8
  21. django_ledger/models/customer.py +0 -4
  22. django_ledger/models/data_import.py +0 -3
  23. django_ledger/models/entity.py +70 -37
  24. django_ledger/models/estimate.py +0 -9
  25. django_ledger/models/invoice.py +0 -3
  26. django_ledger/models/items.py +4 -6
  27. django_ledger/models/journal_entry.py +2 -5
  28. django_ledger/models/ledger.py +0 -3
  29. django_ledger/models/mixins.py +0 -3
  30. django_ledger/models/purchase_order.py +0 -4
  31. django_ledger/models/signals.py +0 -3
  32. django_ledger/models/transactions.py +2 -5
  33. django_ledger/models/unit.py +0 -3
  34. django_ledger/models/utils.py +0 -3
  35. django_ledger/models/vendor.py +0 -3
  36. django_ledger/templates/django_ledger/account/account_create.html +2 -2
  37. django_ledger/templates/django_ledger/account/account_update.html +1 -1
  38. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
  39. django_ledger/templates/django_ledger/account/tags/accounts_table.html +27 -18
  40. django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
  41. django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
  42. django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
  43. django_ledger/templatetags/django_ledger.py +7 -1
  44. django_ledger/tests/base.py +23 -7
  45. django_ledger/tests/test_accounts.py +145 -9
  46. django_ledger/urls/account.py +17 -24
  47. django_ledger/utils.py +8 -0
  48. django_ledger/views/__init__.py +1 -1
  49. django_ledger/views/account.py +80 -118
  50. django_ledger/views/auth.py +1 -1
  51. django_ledger/views/bank_account.py +9 -11
  52. django_ledger/views/bill.py +91 -80
  53. django_ledger/views/closing_entry.py +8 -0
  54. django_ledger/views/coa.py +2 -1
  55. django_ledger/views/customer.py +1 -1
  56. django_ledger/views/data_import.py +1 -1
  57. django_ledger/views/entity.py +1 -1
  58. django_ledger/views/estimate.py +13 -8
  59. django_ledger/views/feedback.py +1 -1
  60. django_ledger/views/financial_statement.py +1 -1
  61. django_ledger/views/home.py +1 -1
  62. django_ledger/views/inventory.py +9 -0
  63. django_ledger/views/invoice.py +5 -2
  64. django_ledger/views/item.py +58 -68
  65. django_ledger/views/journal_entry.py +1 -1
  66. django_ledger/views/ledger.py +3 -1
  67. django_ledger/views/mixins.py +9 -8
  68. django_ledger/views/purchase_order.py +1 -1
  69. django_ledger/views/transactions.py +1 -1
  70. django_ledger/views/unit.py +9 -0
  71. django_ledger/views/vendor.py +1 -1
  72. {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/AUTHORS.md +8 -2
  73. {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/METADATA +34 -43
  74. {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/RECORD +77 -76
  75. {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/WHEEL +1 -1
  76. {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/LICENSE +0 -0
  77. {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.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.0'
13
10
  __license__ = 'GPLv3 License'
14
11
 
15
12
  __author__ = 'Miguel Sanda'
@@ -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 = 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)}'
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,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.for_entity_available(
16
+ account_qs = AccountModel.objects.for_entity(
17
17
  user_model=self.USER_MODEL,
18
- entity_slug=self.ENTITY_SLUG).filter(
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
 
@@ -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
@@ -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
 
@@ -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
@@ -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
+ ]