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.

Files changed (115) hide show
  1. django_ledger/__init__.py +1 -4
  2. django_ledger/admin/coa.py +1 -2
  3. django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
  4. django_ledger/forms/account.py +60 -44
  5. django_ledger/forms/bank_account.py +3 -2
  6. django_ledger/forms/bill.py +24 -36
  7. django_ledger/forms/customer.py +1 -1
  8. django_ledger/forms/data_import.py +3 -3
  9. django_ledger/forms/estimate.py +1 -1
  10. django_ledger/forms/invoice.py +5 -7
  11. django_ledger/forms/item.py +24 -15
  12. django_ledger/forms/transactions.py +3 -3
  13. django_ledger/io/io_core.py +4 -2
  14. django_ledger/io/io_middleware.py +5 -0
  15. django_ledger/io/roles.py +6 -0
  16. django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
  17. django_ledger/models/accounts.py +629 -342
  18. django_ledger/models/bank_account.py +0 -4
  19. django_ledger/models/bill.py +0 -3
  20. django_ledger/models/closing_entry.py +0 -3
  21. django_ledger/models/coa.py +59 -48
  22. django_ledger/models/coa_default.py +9 -8
  23. django_ledger/models/customer.py +0 -4
  24. django_ledger/models/data_import.py +0 -3
  25. django_ledger/models/entity.py +80 -40
  26. django_ledger/models/estimate.py +0 -9
  27. django_ledger/models/invoice.py +0 -3
  28. django_ledger/models/items.py +4 -6
  29. django_ledger/models/journal_entry.py +2 -5
  30. django_ledger/models/ledger.py +0 -3
  31. django_ledger/models/mixins.py +0 -3
  32. django_ledger/models/purchase_order.py +0 -4
  33. django_ledger/models/signals.py +0 -3
  34. django_ledger/models/transactions.py +2 -5
  35. django_ledger/models/unit.py +0 -3
  36. django_ledger/models/utils.py +0 -3
  37. django_ledger/models/vendor.py +0 -3
  38. django_ledger/report/cash_flow_statement.py +1 -1
  39. django_ledger/static/.DS_Store +0 -0
  40. django_ledger/static/django_ledger/.DS_Store +0 -0
  41. django_ledger/static/django_ledger/logo_2/.DS_Store +0 -0
  42. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark.png +0 -0
  43. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@0.5x.png +0 -0
  44. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@2x.png +0 -0
  45. django_ledger/static/django_ledger/logo_2/django_ledger_logo_dark@3x.png +0 -0
  46. django_ledger/static/django_ledger/logo_2/djl-full-vert.png +0 -0
  47. django_ledger/static/django_ledger/logo_2/djl-full-vert@0.5x.png +0 -0
  48. django_ledger/static/django_ledger/logo_2/djl-full-vert@2x.png +0 -0
  49. django_ledger/static/django_ledger/logo_2/djl-full-vert@3x.png +0 -0
  50. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz.png +0 -0
  51. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@0.5x.png +0 -0
  52. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@2x.png +0 -0
  53. django_ledger/static/django_ledger/logo_2/djl-logo-full-horiz@3x.png +0 -0
  54. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert.png +0 -0
  55. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@0.5x.png +0 -0
  56. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@2x.png +0 -0
  57. django_ledger/static/django_ledger/logo_2/djl-logo-full-vert@3x.png +0 -0
  58. django_ledger/static/django_ledger/logo_2/djl-logo.png +0 -0
  59. django_ledger/static/django_ledger/logo_2/djl-logo@0.5x.png +0 -0
  60. django_ledger/static/django_ledger/logo_2/djl-logo@2x.png +0 -0
  61. django_ledger/static/django_ledger/logo_2/djl-logo@3x.png +0 -0
  62. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz.png +0 -0
  63. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@0.5x.png +0 -0
  64. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@2x.png +0 -0
  65. django_ledger/static/django_ledger/logo_2/djl-txt-full-horiz@3x.png +0 -0
  66. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert.png +0 -0
  67. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@0.5x.png +0 -0
  68. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@2x.png +0 -0
  69. django_ledger/static/django_ledger/logo_2/djl-txt-full-vert@3x.png +0 -0
  70. django_ledger/static/django_ledger/logo_2/djl-txt-horiz.png +0 -0
  71. django_ledger/static/django_ledger/logo_2/djl-txt-horiz@0.5x.png +0 -0
  72. django_ledger/static/django_ledger/logo_2/djl-txt-horiz@2x.png +0 -0
  73. django_ledger/static/django_ledger/logo_2/djl-txt-horiz@3x.png +0 -0
  74. django_ledger/templates/django_ledger/account/account_create.html +2 -2
  75. django_ledger/templates/django_ledger/account/account_update.html +1 -1
  76. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
  77. django_ledger/templates/django_ledger/account/tags/accounts_table.html +27 -18
  78. django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
  79. django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
  80. django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
  81. django_ledger/templatetags/django_ledger.py +7 -1
  82. django_ledger/tests/base.py +23 -7
  83. django_ledger/tests/test_accounts.py +145 -9
  84. django_ledger/urls/account.py +17 -24
  85. django_ledger/utils.py +8 -0
  86. django_ledger/views/__init__.py +1 -1
  87. django_ledger/views/account.py +80 -118
  88. django_ledger/views/auth.py +1 -1
  89. django_ledger/views/bank_account.py +9 -11
  90. django_ledger/views/bill.py +91 -80
  91. django_ledger/views/closing_entry.py +8 -0
  92. django_ledger/views/coa.py +2 -1
  93. django_ledger/views/customer.py +1 -1
  94. django_ledger/views/data_import.py +1 -1
  95. django_ledger/views/entity.py +1 -1
  96. django_ledger/views/estimate.py +13 -8
  97. django_ledger/views/feedback.py +1 -1
  98. django_ledger/views/financial_statement.py +1 -1
  99. django_ledger/views/home.py +1 -1
  100. django_ledger/views/inventory.py +9 -0
  101. django_ledger/views/invoice.py +5 -2
  102. django_ledger/views/item.py +58 -68
  103. django_ledger/views/journal_entry.py +1 -1
  104. django_ledger/views/ledger.py +3 -1
  105. django_ledger/views/mixins.py +9 -8
  106. django_ledger/views/purchase_order.py +1 -1
  107. django_ledger/views/transactions.py +1 -1
  108. django_ledger/views/unit.py +9 -0
  109. django_ledger/views/vendor.py +1 -1
  110. {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/AUTHORS.md +8 -2
  111. {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/METADATA +34 -43
  112. {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/RECORD +115 -79
  113. {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/WHEEL +1 -1
  114. {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/top_level.txt +0 -1
  115. {django_ledger-0.6.3.dist-info → django_ledger-0.7.0.dist-info}/LICENSE +0 -0
@@ -1,16 +1,152 @@
1
- from django_ledger.models import EntityModelValidationError, AccountModel
1
+ from random import choice
2
+ from urllib.parse import urlparse
3
+
4
+ from django.urls import reverse
5
+
6
+ from django_ledger.forms.account import AccountModelCreateForm
7
+ from django_ledger.io import roles, CREDIT
8
+ from django_ledger.models import EntityModel
9
+ from django_ledger.models.accounts import AccountModel
2
10
  from django_ledger.tests.base import DjangoLedgerBaseTest
11
+ from django_ledger.urls.account import urlpatterns as account_urls
3
12
 
4
13
 
5
14
  class AccountModelTests(DjangoLedgerBaseTest):
15
+ N = 2
16
+
17
+ def setUp(self):
18
+ self.resolve_url_patterns(
19
+ url_patterns=account_urls
20
+ )
21
+
22
+ def test_protected_views(self):
23
+
24
+ self.logout_client()
25
+ entity_model = self.get_random_entity_model()
26
+ account_model: AccountModel = self.get_random_account(entity_model=entity_model)
27
+
28
+ for path, kwargs in self.URL_PATTERNS.items():
29
+ url_kwargs = dict()
30
+ url_kwargs['entity_slug'] = entity_model.slug
31
+
32
+ if 'coa_slug' in kwargs:
33
+ url_kwargs['coa_slug'] = account_model.coa_slug
34
+ if 'account_pk' in kwargs:
35
+ url_kwargs['account_pk'] = account_model.uuid
36
+ if 'year' in kwargs:
37
+ url_kwargs['year'] = self.get_random_date().year
38
+ if 'quarter' in kwargs:
39
+ url_kwargs['quarter'] = choice(range(1, 5))
40
+ if 'month' in kwargs:
41
+ url_kwargs['month'] = self.get_random_date().month
42
+ if 'day' in kwargs:
43
+ url_kwargs['day'] = choice(range(1, 29))
44
+
45
+ url = reverse(f'django_ledger:{path}', kwargs=url_kwargs)
46
+ response = self.CLIENT.get(url, follow=False)
47
+ redirect_url = urlparse(response.url)
48
+ redirect_path = redirect_url.path
49
+ login_path = reverse(viewname='django_ledger:login')
50
+
51
+ self.assertEqual(response.status_code, 302, msg=f'{path} view is not protected.')
52
+ self.assertEqual(redirect_path, login_path, msg=f'{path} view not redirecting to correct auth URL.')
53
+
54
+ def test_account_create(self):
55
+
56
+ entity_model = self.get_random_entity_model()
57
+ account_create_url = reverse(
58
+ viewname='django_ledger:account-create',
59
+ kwargs={
60
+ 'entity_slug': entity_model.slug,
61
+ 'coa_slug': entity_model.default_coa_slug
62
+ }
63
+ )
64
+
65
+ self.login_client()
66
+ response = self.CLIENT.get(account_create_url)
67
+
68
+ # check if user can access page...
69
+ self.assertEqual(response.status_code, 200, msg="Fail to GET Account Create page.")
70
+
71
+ # check if account create form is rendered...
72
+ account_create_form: AccountModelCreateForm = response.context['form']
73
+ self.assertContains(response, account_create_form.form_id, count=1)
74
+
75
+ # check if all fields are rendered...
76
+ self.assertContains(response, 'name="code"')
77
+ self.assertContains(response, 'name="name"')
78
+ self.assertContains(response, 'name="role"')
79
+ self.assertContains(response, 'name="role_default"')
80
+ self.assertContains(response, 'name="balance_type"')
81
+ self.assertContains(response, 'name="active"')
82
+ self.assertContains(response, 'name="coa_model"')
83
+
84
+ # creates new account...
85
+ NEW_ACCOUNT_CODE = '404000'
86
+ form_data = {
87
+ 'code': NEW_ACCOUNT_CODE,
88
+ 'name': 'Test Income Account',
89
+ 'role': roles.INCOME_OPERATIONAL,
90
+ 'role_default': False,
91
+ 'balance_type': CREDIT,
92
+ 'active': True
93
+ }
94
+ response_create = self.CLIENT.post(account_create_url, data=form_data)
95
+ self.assertEqual(response_create.status_code, 302)
96
+ self.assertTrue(AccountModel.objects.for_entity(
97
+ entity_model=entity_model,
98
+ user_model=self.user_model,
99
+ coa_slug=entity_model.default_coa_slug,
100
+ ).with_codes(codes=NEW_ACCOUNT_CODE).exists())
101
+
102
+ # cannot create an account with same code again...
103
+ response_create = self.CLIENT.post(account_create_url, data=form_data)
104
+ self.assertEqual(response_create.status_code, 200)
105
+ self.assertContains(response_create, 'Account with this Chart of Accounts and Account Code already exists')
106
+
107
+ def test_account_activation(self):
108
+
109
+ entity_model: EntityModel = self.get_random_entity_model()
110
+ account_model: AccountModel = self.get_random_account(entity_model=entity_model, active=True)
111
+
112
+ self.assertTrue(account_model.can_deactivate())
113
+ self.assertTrue(account_model.active)
114
+
115
+ account_model.deactivate(commit=True)
116
+ self.assertFalse(account_model.can_deactivate())
117
+ self.assertFalse(account_model.active)
118
+
119
+ account_model.activate(commit=True)
120
+ self.assertTrue(account_model.can_deactivate())
121
+ self.assertTrue(account_model.active)
122
+
123
+ def test_account_lock(self):
124
+ entity_model: EntityModel = self.get_random_entity_model()
125
+ account_model: AccountModel = self.get_random_account(entity_model=entity_model, active=True, locked=False)
126
+
127
+ self.assertTrue(account_model.can_lock())
128
+ self.assertFalse(account_model.can_unlock())
129
+
130
+ account_model.lock(commit=True)
131
+ self.assertFalse(account_model.can_lock())
132
+ self.assertTrue(account_model.can_unlock())
133
+
134
+ account_model.unlock(commit=True)
135
+ self.assertTrue(account_model.can_lock())
136
+ self.assertFalse(account_model.can_unlock())
6
137
 
7
- def test_no_default_coa(self):
8
- entity_model = self.create_entity_model()
9
- self.assertFalse(entity_model.has_default_coa(), msg='New entities do not had default coa')
138
+ def test_annotations(self):
139
+ entity_model: EntityModel = self.get_random_entity_model()
140
+ account_model: AccountModel = self.get_random_account(entity_model=entity_model, active=True, locked=False)
10
141
 
11
- with self.assertRaises(EntityModelValidationError, msg='New entities do not have default coa'):
12
- entity_model.get_default_coa()
142
+ self.assertEqual(account_model.entity_slug, entity_model.slug)
143
+ self.assertEqual(account_model.coa_slug, account_model.coa_model.slug)
144
+ self.assertEqual(account_model.coa_model.active, account_model.is_coa_active())
13
145
 
14
- self.assertEqual(entity_model.get_default_coa(raise_exception=False),
15
- None,
16
- msg='No exception should be raised when raise_exception is False')
146
+ def test_can_transact(self):
147
+ entity_model: EntityModel = self.get_random_entity_model()
148
+ account_model: AccountModel = self.get_random_account(entity_model=entity_model, active=True, locked=False)
149
+ self.assertTrue(account_model.can_transact())
150
+ account_model.lock(commit=False)
151
+ self.assertFalse(account_model.can_transact())
152
+ account_model.unlock(commit=False)
@@ -4,54 +4,47 @@ from django_ledger import views
4
4
 
5
5
  urlpatterns = [
6
6
 
7
- # NO COA SLUG USES DEFAULT COA....
8
- path('<slug:entity_slug>/create/',
9
- views.AccountModelCreateView.as_view(),
10
- name='account-create'),
11
- path('<slug:entity_slug>/list/',
12
- views.AccountModelListView.as_view(),
13
- name='account-list'),
14
- path('<slug:entity_slug>/list/active/',
15
- views.AccountModelListView.as_view(active_only=True),
16
- name='account-list-active'),
17
-
18
- # EXPLICIT COA...
19
7
  path('<slug:entity_slug>/<slug:coa_slug>/create/',
20
8
  views.AccountModelCreateView.as_view(),
21
- name='account-create-coa'),
9
+ name='account-create'),
22
10
  path('<slug:entity_slug>/<slug:coa_slug>/list/',
23
11
  views.AccountModelListView.as_view(),
24
- name='account-list-coa'),
12
+ name='account-list'),
25
13
  path('<slug:entity_slug>/<slug:coa_slug>/list/active/',
26
14
  views.AccountModelListView.as_view(active_only=True),
27
- name='account-list-active-coa'),
15
+ name='account-list-active'),
28
16
 
29
17
  # Account Transaction Detail....
30
- path('<slug:entity_slug>/update/<uuid:account_pk>/',
18
+ path('<slug:entity_slug>/<slug:coa_slug>/update/<uuid:account_pk>/',
31
19
  views.AccountModelUpdateView.as_view(),
32
20
  name='account-update'),
33
- path('<slug:entity_slug>/detail/<uuid:account_pk>/',
21
+ path('<slug:entity_slug>/<slug:coa_slug>/detail/<uuid:account_pk>/',
34
22
  views.AccountModelDetailView.as_view(),
35
23
  name='account-detail'),
36
- path('<slug:entity_slug>/detail/<uuid:account_pk>/year/<int:year>/',
24
+ path('<slug:entity_slug>/<slug:coa_slug>/detail/<uuid:account_pk>/year/<int:year>/',
37
25
  views.AccountModelYearDetailView.as_view(),
38
26
  name='account-detail-year'),
39
- path('<slug:entity_slug>/detail/<uuid:account_pk>/quarter/<int:year>/<int:quarter>/',
27
+ path('<slug:entity_slug>/<slug:coa_slug>/detail/<uuid:account_pk>/quarter/<int:year>/<int:quarter>/',
40
28
  views.AccountModelQuarterDetailView.as_view(),
41
29
  name='account-detail-quarter'),
42
- path('<slug:entity_slug>/detail/<uuid:account_pk>/month/<int:year>/<int:month>/',
30
+ path('<slug:entity_slug>/<slug:coa_slug>/detail/<uuid:account_pk>/month/<int:year>/<int:month>/',
43
31
  views.AccountModelMonthDetailView.as_view(),
44
32
  name='account-detail-month'),
45
- path('<slug:entity_slug>/detail/<uuid:account_pk>/date/<int:year>/<int:month>/<int:day>/',
33
+ path('<slug:entity_slug>/<slug:coa_slug>/detail/<uuid:account_pk>/date/<int:year>/<int:month>/<int:day>/',
46
34
  views.AccountModelDateDetailView.as_view(),
47
35
  name='account-detail-date'),
48
36
 
49
37
  # Account Actions...
50
- path('<slug:entity_slug>/action/<uuid:account_pk>/activate/',
38
+ path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/activate/',
51
39
  views.AccountModelModelActionView.as_view(action_name='activate'),
52
40
  name='account-action-activate'),
53
- path('<slug:entity_slug>/action/<uuid:account_pk>/deactivate/',
41
+ path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/deactivate/',
54
42
  views.AccountModelModelActionView.as_view(action_name='deactivate'),
55
43
  name='account-action-deactivate'),
56
-
44
+ path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/lock/',
45
+ views.AccountModelModelActionView.as_view(action_name='lock'),
46
+ name='account-action-lock'),
47
+ path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/unlock/',
48
+ views.AccountModelModelActionView.as_view(action_name='unlock'),
49
+ name='account-action-unlock')
57
50
  ]
django_ledger/utils.py CHANGED
@@ -1,3 +1,11 @@
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
+
1
9
  from datetime import date
2
10
  from importlib import import_module
3
11
  from itertools import groupby
@@ -3,7 +3,7 @@ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
5
  Contributions to this module:
6
- Miguel Sanda <msanda@arrobalytics.com>
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
 
9
9
  from django_ledger.views.account import *
@@ -3,12 +3,11 @@ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
5
  Contributions to this module:
6
- Miguel Sanda <msanda@arrobalytics.com>
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
  from django.contrib import messages
9
9
  from django.core.exceptions import ImproperlyConfigured, ValidationError
10
10
  from django.http import HttpResponseRedirect
11
- from django.shortcuts import get_object_or_404
12
11
  from django.urls import reverse
13
12
  from django.utils.translation import gettext as _
14
13
  from django.views.generic import ListView, UpdateView, CreateView, DetailView
@@ -17,7 +16,7 @@ from django.views.generic.detail import SingleObjectMixin
17
16
 
18
17
  from django_ledger.forms.account import AccountModelUpdateForm, AccountModelCreateForm
19
18
  from django_ledger.io.io_core import get_localdate
20
- from django_ledger.models import ChartOfAccountModel
19
+ from django_ledger.models import EntityModel, ChartOfAccountModel
21
20
  from django_ledger.models.accounts import AccountModel
22
21
  from django_ledger.views.mixins import (
23
22
  YearlyReportMixIn, MonthlyReportMixIn, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn,
@@ -25,58 +24,51 @@ from django_ledger.views.mixins import (
25
24
  )
26
25
 
27
26
 
28
- class BaseAccountModelViewQuerySetMixIn:
27
+ class BaseAccountModelBaseView(DjangoLedgerSecurityMixIn):
29
28
  queryset = None
30
29
  coa_model = None
31
30
 
32
- def get_coa_model(self) -> ChartOfAccountModel:
33
- if self.coa_model is None:
34
- coa_slug = self.kwargs.get('coa_slug')
35
- if coa_slug:
36
- coa_model_qs = self.AUTHORIZED_ENTITY_MODEL.chartofaccountmodel_set.all().active()
37
- coa_model = get_object_or_404(coa_model_qs, slug__exact=coa_slug)
38
- else:
39
- coa_model = self.AUTHORIZED_ENTITY_MODEL.default_coa
40
- self.coa_model = coa_model
31
+ def get_authorized_entity_queryset(self):
32
+ qs = super().get_authorized_entity_queryset()
33
+ return qs.select_related('admin', 'default_coa', 'default_coa__entity')
34
+
35
+ def get_coa_model(self):
36
+ if not self.coa_model:
37
+ entity_model: EntityModel = self.get_authorized_entity_instance()
38
+ self.coa_model = entity_model.chartofaccountmodel_set.get(slug__exact=self.kwargs['coa_slug'])
41
39
  return self.coa_model
42
40
 
43
41
  def get_queryset(self):
44
42
  if self.queryset is None:
45
- qs = AccountModel.objects.for_entity(
46
- entity_slug=self.kwargs['entity_slug'],
47
- user_model=self.request.user,
48
- ).select_related(
43
+ entity_model: EntityModel = self.get_authorized_entity_instance()
44
+ coa_slug = self.kwargs['coa_slug']
45
+
46
+ coa_model, account_model_qs = entity_model.get_coa_accounts(
47
+ coa_model=entity_model.default_coa if coa_slug == entity_model.default_coa_slug else coa_slug,
48
+ return_coa_model=True,
49
+ active=False
50
+ )
51
+
52
+ account_model_qs = account_model_qs.select_related(
49
53
  'coa_model',
50
54
  'coa_model__entity'
51
55
  ).order_by(
52
- 'coa_model', 'role', 'code').not_coa_root()
56
+ 'coa_model', 'role', 'code'
57
+ ).not_coa_root()
53
58
 
54
- coa_slug = self.kwargs.get('coa_slug')
55
- account_pk = self.kwargs.get('account_pk')
56
-
57
- if coa_slug:
58
- qs = qs.filter(coa_model__slug__exact=coa_slug)
59
- elif account_pk:
60
- qs = qs.filter(uuid__exact=account_pk)
61
- else:
62
- qs = qs.filter(coa_model__slug__exact=self.AUTHORIZED_ENTITY_MODEL.default_coa.slug)
59
+ self.coa_model = coa_model
60
+ self.queryset = account_model_qs
63
61
 
64
- self.queryset = qs
65
62
  return super().get_queryset()
66
63
 
67
- def get_context_data(self, *args, **kwargs):
68
- context = super().get_context_data(*args, **kwargs)
69
- entity_model = self.get_authorized_entity_instance()
70
- if self.kwargs.get('coa_slug'):
71
- coa_model_qs = entity_model.chartofaccountmodel_set.all()
72
- context['coa_model'] = get_object_or_404(coa_model_qs, slug__exact=self.kwargs['coa_slug'])
73
- else:
74
- context['coa_model'] = entity_model.default_coa
64
+ def get_context_data(self, **kwargs):
65
+ context = super().get_context_data(**kwargs)
66
+ context['coa_model'] = self.get_coa_model()
75
67
  return context
76
68
 
77
69
 
78
70
  # Account Views ----
79
- class AccountModelListView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuerySetMixIn, ListView):
71
+ class AccountModelListView(BaseAccountModelBaseView, ListView):
80
72
  template_name = 'django_ledger/account/account_list.html'
81
73
  context_object_name = 'accounts'
82
74
  PAGE_TITLE = _('Entity Accounts')
@@ -93,40 +85,7 @@ class AccountModelListView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQueryS
93
85
  return qs
94
86
 
95
87
 
96
- class AccountModelUpdateView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuerySetMixIn, UpdateView):
97
- context_object_name = 'account'
98
- template_name = 'django_ledger/account/account_update.html'
99
- slug_url_kwarg = 'account_pk'
100
- slug_field = 'uuid'
101
-
102
- def get_context_data(self, **kwargs):
103
- context = super().get_context_data(**kwargs)
104
- context['page_title'] = _('Update Account')
105
- context['header_title'] = _(f'Update Account: {self.object.code} - {self.object.name}')
106
- context['header_subtitle_icon'] = 'ic:twotone-account-tree'
107
- return context
108
-
109
- def get_form(self, form_class=None):
110
- account_model = self.object
111
-
112
- # Set here because user_model is needed to instantiate an instance of MoveNodeForm (AccountModelUpdateForm)
113
- account_model.USER_MODEL = self.request.user
114
- return AccountModelUpdateForm(
115
- entity_model=self.AUTHORIZED_ENTITY_MODEL,
116
- coa_model=self.get_coa_model(),
117
- user_model=self.request.user,
118
- **self.get_form_kwargs()
119
- )
120
-
121
- def get_success_url(self):
122
- entity_slug = self.kwargs['entity_slug']
123
- return reverse('django_ledger:account-list',
124
- kwargs={
125
- 'entity_slug': entity_slug,
126
- })
127
-
128
-
129
- class AccountModelCreateView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuerySetMixIn, CreateView):
88
+ class AccountModelCreateView(BaseAccountModelBaseView, CreateView):
130
89
  template_name = 'django_ledger/account/account_create.html'
131
90
  PAGE_TITLE = _('Create Account')
132
91
  extra_context = {
@@ -138,58 +97,63 @@ class AccountModelCreateView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuer
138
97
 
139
98
  def get_form(self, form_class=None):
140
99
  return AccountModelCreateForm(
141
- user_model=self.request.user,
142
- entity_model=self.AUTHORIZED_ENTITY_MODEL,
143
100
  coa_model=self.get_coa_model(),
144
101
  **self.get_form_kwargs()
145
102
  )
146
103
 
147
104
  def get_context_data(self, *args, **kwargs):
148
105
  context = super().get_context_data(*args, **kwargs)
149
- context['header_subtitle'] = f'CoA: {context["coa_model"].name}'
106
+ coa_model = self.get_coa_model()
107
+ context['coa_model'] = coa_model
108
+ context['header_subtitle'] = f'CoA: {coa_model.name}'
150
109
  return context
151
110
 
152
- def form_valid(self, form):
153
- entity_model = self.AUTHORIZED_ENTITY_MODEL
111
+ def form_valid(self, form: AccountModelCreateForm):
154
112
  account_model: AccountModel = form.save(commit=False)
155
-
156
- if not entity_model.has_default_coa():
157
- entity_model.create_chart_of_accounts(assign_as_default=True, commit=True)
158
-
159
- coa_model = self.get_coa_model()
113
+ coa_model = account_model.coa_model
160
114
  coa_model.insert_account(account_model=account_model)
161
115
  return HttpResponseRedirect(self.get_success_url())
162
116
 
163
117
  def get_success_url(self):
164
- entity_slug = self.kwargs.get('entity_slug')
165
- coa_slug = self.kwargs.get('coa_slug')
166
- if coa_slug:
167
- return reverse('django_ledger:account-list-coa',
168
- kwargs={
169
- 'entity_slug': entity_slug,
170
- 'coa_slug': coa_slug
171
- })
172
- return reverse('django_ledger:account-list',
173
- kwargs={
174
- 'entity_slug': entity_slug,
175
- })
118
+ coa_model: ChartOfAccountModel = self.get_coa_model()
119
+ return coa_model.get_account_list_url()
120
+
121
+
122
+ class AccountModelUpdateView(BaseAccountModelBaseView, UpdateView):
123
+ context_object_name = 'account'
124
+ template_name = 'django_ledger/account/account_update.html'
125
+ slug_url_kwarg = 'account_pk'
126
+ slug_field = 'uuid'
127
+ form_class = AccountModelUpdateForm
128
+
129
+ def get_context_data(self, **kwargs):
130
+ context = super().get_context_data(**kwargs)
131
+ context['page_title'] = _('Update Account')
132
+ context['header_title'] = _(f'Update Account: {self.object.code} - {self.object.name}')
133
+ context['header_subtitle_icon'] = 'ic:twotone-account-tree'
134
+ return context
135
+
136
+ def get_success_url(self):
137
+ coa_model: ChartOfAccountModel = self.get_coa_model()
138
+ return coa_model.get_account_list_url()
176
139
 
177
140
 
178
- class AccountModelDetailView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuerySetMixIn, RedirectView):
141
+ class AccountModelDetailView(BaseAccountModelBaseView, RedirectView):
179
142
 
180
143
  def get_redirect_url(self, *args, **kwargs):
181
144
  loc_date = get_localdate()
145
+ entity_model: EntityModel = self.get_authorized_entity_instance()
182
146
  return reverse('django_ledger:account-detail-month',
183
147
  kwargs={
184
- 'entity_slug': self.kwargs['entity_slug'],
148
+ 'entity_slug': entity_model.slug,
185
149
  'account_pk': self.kwargs['account_pk'],
150
+ 'coa_slug': self.kwargs['coa_slug'],
186
151
  'year': loc_date.year,
187
152
  'month': loc_date.month,
188
153
  })
189
154
 
190
155
 
191
- class AccountModelYearDetailView(DjangoLedgerSecurityMixIn,
192
- BaseAccountModelViewQuerySetMixIn,
156
+ class AccountModelYearDetailView(BaseAccountModelBaseView,
193
157
  BaseDateNavigationUrlMixIn,
194
158
  EntityUnitMixIn,
195
159
  YearlyReportMixIn,
@@ -205,23 +169,23 @@ class AccountModelYearDetailView(DjangoLedgerSecurityMixIn,
205
169
  }
206
170
 
207
171
  def get_context_data(self, **kwargs):
208
- account = self.object
209
172
  context = super().get_context_data(**kwargs)
210
- context['header_title'] = f'Account {account.code} - {account.name}'
211
- context['page_title'] = f'Account {account.code} - {account.name}'
212
- account_model: AccountModel = self.object
173
+ account_model: AccountModel = context['object']
174
+ context['header_title'] = f'Account {account_model.code} - {account_model.name}'
175
+ context['page_title'] = f'Account {account_model.code} - {account_model.name}'
213
176
  txs_qs = account_model.transactionmodel_set.all().posted().order_by(
214
- 'journal_entry__timestamp').select_related(
215
- 'journal_entry', 'journal_entry__entity_unit')
177
+ 'journal_entry__timestamp'
178
+ ).select_related(
179
+ 'journal_entry',
180
+ 'journal_entry__entity_unit',
181
+ 'journal_entry__ledger__billmodel',
182
+ 'journal_entry__ledger__invoicemodel',
183
+ )
216
184
  txs_qs = txs_qs.from_date(self.get_from_date())
217
185
  txs_qs = txs_qs.to_date(self.get_to_date())
218
186
  context['transactions'] = txs_qs
219
187
  return context
220
188
 
221
- def get_queryset(self):
222
- qs = super().get_queryset()
223
- return qs.prefetch_related('transactionmodel_set')
224
-
225
189
 
226
190
  class AccountModelQuarterDetailView(QuarterlyReportMixIn, AccountModelYearDetailView):
227
191
  """
@@ -242,9 +206,8 @@ class AccountModelDateDetailView(DateReportMixIn, AccountModelYearDetailView):
242
206
 
243
207
 
244
208
  # ACTIONS...
245
- class AccountModelModelActionView(DjangoLedgerSecurityMixIn,
209
+ class AccountModelModelActionView(BaseAccountModelBaseView,
246
210
  RedirectView,
247
- BaseAccountModelViewQuerySetMixIn,
248
211
  SingleObjectMixin):
249
212
  http_method_names = ['get']
250
213
  pk_url_kwarg = 'account_pk'
@@ -252,11 +215,8 @@ class AccountModelModelActionView(DjangoLedgerSecurityMixIn,
252
215
  commit = True
253
216
 
254
217
  def get_redirect_url(self, *args, **kwargs):
255
- return reverse('django_ledger:account-list',
256
- kwargs={
257
- 'entity_slug': kwargs['entity_slug'],
258
- # 'account_pk': kwargs['account_pk']
259
- })
218
+ account_model: AccountModel = self.get_object()
219
+ return account_model.get_coa_account_list_url()
260
220
 
261
221
  def get(self, request, *args, **kwargs):
262
222
  kwargs['user_model'] = self.request.user
@@ -268,8 +228,10 @@ class AccountModelModelActionView(DjangoLedgerSecurityMixIn,
268
228
  try:
269
229
  getattr(account_model, self.action_name)(commit=self.commit, **kwargs)
270
230
  except ValidationError as e:
271
- messages.add_message(request,
272
- message=e.message,
273
- level=messages.ERROR,
274
- extra_tags='is-danger')
231
+ messages.add_message(
232
+ request,
233
+ message=e.message,
234
+ level=messages.ERROR,
235
+ extra_tags='is-danger'
236
+ )
275
237
  return response
@@ -3,7 +3,7 @@ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
5
  Contributions to this module:
6
- Miguel Sanda <msanda@arrobalytics.com>
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
 
9
9
  from django.contrib.auth.views import LoginView, LogoutView
@@ -3,7 +3,7 @@ Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
3
3
  Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
4
4
 
5
5
  Contributions to this module:
6
- Miguel Sanda <msanda@arrobalytics.com>
6
+ * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
  from django.contrib import messages
9
9
  from django.core.exceptions import ImproperlyConfigured, ValidationError
@@ -13,23 +13,22 @@ from django.views.generic import ListView, CreateView, UpdateView, RedirectView
13
13
  from django.views.generic.detail import SingleObjectMixin
14
14
 
15
15
  from django_ledger.forms.bank_account import BankAccountCreateForm, BankAccountUpdateForm
16
+ from django_ledger.models import EntityModel
16
17
  from django_ledger.models.bank_account import BankAccountModel
17
18
  from django_ledger.views.mixins import DjangoLedgerSecurityMixIn
18
19
 
19
20
 
20
- class BankAccountModelModelViewQuerySetMixIn:
21
+ class BankAccountModelModelBaseView(DjangoLedgerSecurityMixIn):
21
22
  queryset = None
22
23
 
23
24
  def get_queryset(self):
24
25
  if self.queryset is None:
25
- self.queryset = BankAccountModel.objects.for_entity(
26
- entity_slug=self.kwargs['entity_slug'],
27
- user_model=self.request.user
28
- ).select_related('cash_account', 'entity_model')
26
+ entity_model: EntityModel = self.get_authorized_entity_instance()
27
+ self.queryset = entity_model.bankaccountmodel_set.select_related('cash_account', 'entity_model')
29
28
  return super().get_queryset()
30
29
 
31
30
 
32
- class BankAccountModelListView(DjangoLedgerSecurityMixIn, BankAccountModelModelViewQuerySetMixIn, ListView):
31
+ class BankAccountModelListView(BankAccountModelModelBaseView, ListView):
33
32
  template_name = 'django_ledger/bank_account/bank_account_list.html'
34
33
  PAGE_TITLE = _('Bank Accounts')
35
34
  context_object_name = 'bank_accounts'
@@ -40,7 +39,7 @@ class BankAccountModelListView(DjangoLedgerSecurityMixIn, BankAccountModelModelV
40
39
  }
41
40
 
42
41
 
43
- class BankAccountModelCreateView(DjangoLedgerSecurityMixIn, BankAccountModelModelViewQuerySetMixIn, CreateView):
42
+ class BankAccountModelCreateView(BankAccountModelModelBaseView, CreateView):
44
43
  template_name = 'django_ledger/bank_account/bank_account_create.html'
45
44
  PAGE_TITLE = _('Create Bank Account')
46
45
  extra_context = {
@@ -71,7 +70,7 @@ class BankAccountModelCreateView(DjangoLedgerSecurityMixIn, BankAccountModelMode
71
70
  return super(BankAccountModelCreateView, self).form_valid(form)
72
71
 
73
72
 
74
- class BankAccountModelUpdateView(DjangoLedgerSecurityMixIn, BankAccountModelModelViewQuerySetMixIn, UpdateView):
73
+ class BankAccountModelUpdateView(BankAccountModelModelBaseView, UpdateView):
75
74
  template_name = 'django_ledger/bank_account/bank_account_update.html'
76
75
  pk_url_kwarg = 'bank_account_pk'
77
76
  PAGE_TITLE = _('Update Bank Account')
@@ -97,8 +96,7 @@ class BankAccountModelUpdateView(DjangoLedgerSecurityMixIn, BankAccountModelMode
97
96
 
98
97
 
99
98
  # ACTION VIEWS...
100
- class BaseBankAccountModelActionView(DjangoLedgerSecurityMixIn,
101
- BankAccountModelModelViewQuerySetMixIn,
99
+ class BaseBankAccountModelActionView(BankAccountModelModelBaseView,
102
100
  RedirectView,
103
101
  SingleObjectMixin):
104
102
  http_method_names = ['get']