django-ledger 0.5.6.3__py3-none-any.whl → 0.5.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -1
- django_ledger/admin/coa.py +3 -3
- django_ledger/forms/account.py +1 -1
- django_ledger/forms/coa.py +1 -6
- django_ledger/io/roles.py +25 -9
- django_ledger/migrations/0015_remove_chartofaccountmodel_locked_and_more.py +22 -0
- django_ledger/models/accounts.py +9 -9
- django_ledger/models/coa.py +244 -35
- django_ledger/models/entity.py +43 -28
- django_ledger/templates/django_ledger/account/account_create.html +17 -11
- django_ledger/templates/django_ledger/account/account_list.html +12 -9
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +97 -93
- django_ledger/templates/django_ledger/chart_of_accounts/coa_list.html +17 -0
- django_ledger/templates/django_ledger/{code_of_accounts → chart_of_accounts}/coa_update.html +1 -4
- django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html +74 -0
- django_ledger/templates/django_ledger/invoice/invoice_list.html +91 -94
- django_ledger/templatetags/django_ledger.py +1 -1
- django_ledger/tests/base.py +23 -1
- django_ledger/tests/test_accounts.py +16 -0
- django_ledger/tests/test_chart_of_accounts.py +46 -0
- django_ledger/urls/account.py +18 -3
- django_ledger/urls/chart_of_accounts.py +21 -1
- django_ledger/views/account.py +26 -3
- django_ledger/views/coa.py +79 -4
- django_ledger/views/mixins.py +3 -0
- {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/METADATA +1 -1
- {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/RECORD +31 -26
- {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/WHEEL +0 -0
- {django_ledger-0.5.6.3.dist-info → django_ledger-0.5.6.4.dist-info}/top_level.txt +0 -0
|
@@ -4,116 +4,113 @@
|
|
|
4
4
|
{% load django_ledger %}
|
|
5
5
|
|
|
6
6
|
{% block view_content %}
|
|
7
|
-
<div class="
|
|
8
|
-
<div class="
|
|
9
|
-
<div class="level">
|
|
10
|
-
<div class="level-
|
|
7
|
+
<div class="box">
|
|
8
|
+
<div class="level">
|
|
9
|
+
<div class="level-left">
|
|
10
|
+
<div class="level-item">
|
|
11
|
+
{% if month %}
|
|
12
|
+
<h1 class="is-size-1 has-text-weight-thin">{{ month | date:'F Y' }}</h1>
|
|
13
|
+
{% elif year %}
|
|
14
|
+
<h1 class="is-size-1 has-text-weight-thin">Year {{ year | date:'Y' }}
|
|
15
|
+
{% trans 'Invoices' %}</h1>
|
|
16
|
+
{% else %}
|
|
17
|
+
<h1 class="is-size-1 has-text-weight-thin">{% trans 'Latest Invoices' %}</h1>
|
|
18
|
+
{% endif %}
|
|
19
|
+
</div>
|
|
20
|
+
<div class="level-item">
|
|
21
|
+
<a href="{% url 'django_ledger:invoice-create' entity_slug=view.kwargs.entity_slug %}">
|
|
22
|
+
<span class="icon is-large has-text-success">{% icon 'carbon:add-alt' 60 %}</span></a>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="level-right">
|
|
26
|
+
{% if previous_month %}
|
|
11
27
|
<div class="level-item">
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
{% else %}
|
|
18
|
-
<h1 class="is-size-1 has-text-weight-thin">{% trans 'Latest Invoices' %}</h1>
|
|
19
|
-
{% endif %}
|
|
28
|
+
<a class="button is-small is-dark is-outlined"
|
|
29
|
+
href="{% url 'django_ledger:bill-list-month' year=previous_month.year month=previous_month.month entity_slug=view.kwargs.entity_slug %}">
|
|
30
|
+
<span class="icon is-small">{% icon 'ant-design:left-circle-outlined' 16 %}</span>
|
|
31
|
+
<span>{{ previous_month | date:'F Y' }}</span>
|
|
32
|
+
</a>
|
|
20
33
|
</div>
|
|
34
|
+
{% endif %}
|
|
35
|
+
{% if next_month %}
|
|
21
36
|
<div class="level-item">
|
|
22
|
-
<a
|
|
23
|
-
|
|
37
|
+
<a class="button is-small is-dark is-outlined"
|
|
38
|
+
href="{% url 'django_ledger:bill-list-month' year=next_month.year month=next_month.month entity_slug=view.kwargs.entity_slug %}">
|
|
39
|
+
<span class="icon is-small">{% icon 'ant-design:right-circle-outlined' 16 %}</span>
|
|
40
|
+
<span>{{ next_month | date:'F Y' }}</span>
|
|
41
|
+
</a>
|
|
24
42
|
</div>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
{% endif %}
|
|
44
|
+
|
|
45
|
+
{% if previous_year %}
|
|
46
|
+
<div class="level-item">
|
|
47
|
+
<a class="button is-small is-dark is-outlined"
|
|
48
|
+
href="{% url 'django_ledger:invoice-list-year' year=previous_year.year entity_slug=view.kwargs.entity_slug %}">
|
|
49
|
+
<span class="icon is-small">{% icon 'ant-design:left-circle-outlined' 16 %}</span>
|
|
50
|
+
<span>{{ previous_year | date:'Y' }}</span>
|
|
51
|
+
</a>
|
|
52
|
+
</div>
|
|
53
|
+
{% endif %}
|
|
54
|
+
{% if next_year %}
|
|
55
|
+
<div class="level-item">
|
|
56
|
+
<a class="button is-small is-dark is-outlined"
|
|
57
|
+
href="{% url 'django_ledger:invoice-list-year' year=next_year.year entity_slug=view.kwargs.entity_slug %}">
|
|
58
|
+
<span class="icon is-small">{% icon 'ant-design:right-circle-outlined' 16 %}</span>
|
|
59
|
+
<span>{{ next_year | date:'Y' }}</span>
|
|
60
|
+
</a>
|
|
61
|
+
</div>
|
|
62
|
+
{% endif %}
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
{% if page_obj %}
|
|
65
|
+
{% if page_obj.has_previous %}
|
|
47
66
|
<div class="level-item">
|
|
48
|
-
<a
|
|
49
|
-
|
|
50
|
-
<span class="icon is-small">{% icon 'ant-design:left-circle-outlined'
|
|
51
|
-
<span>{{ previous_year | date:'Y' }}</span>
|
|
67
|
+
<a href="?page={{ page_obj.previous_page_number }}"
|
|
68
|
+
class="button is-small is-dark is-outlined">
|
|
69
|
+
<span class="icon is-small">{% icon 'ant-design:left-circle-outlined' 24 %}</span>
|
|
52
70
|
</a>
|
|
53
71
|
</div>
|
|
54
72
|
{% endif %}
|
|
55
|
-
|
|
73
|
+
<div class="level-item">
|
|
74
|
+
<p class="is-italic">page {{ page_obj.number }}
|
|
75
|
+
of {{ page_obj.paginator.num_pages }}</p>
|
|
76
|
+
</div>
|
|
77
|
+
{% if page_obj.has_next %}
|
|
56
78
|
<div class="level-item">
|
|
57
|
-
<a
|
|
58
|
-
|
|
59
|
-
<span class="icon is-small">{% icon 'ant-design:right-circle-outlined'
|
|
60
|
-
<span>{{ next_year | date:'Y' }}</span>
|
|
79
|
+
<a href="?page={{ page_obj.next_page_number }}"
|
|
80
|
+
class="button is-small is-dark is-outlined">
|
|
81
|
+
<span class="icon is-small">{% icon 'ant-design:right-circle-outlined' 24 %}</span>
|
|
61
82
|
</a>
|
|
62
83
|
</div>
|
|
63
84
|
{% endif %}
|
|
64
85
|
|
|
65
|
-
|
|
66
|
-
{% if page_obj.has_previous %}
|
|
67
|
-
<div class="level-item">
|
|
68
|
-
<a href="?page={{ page_obj.previous_page_number }}"
|
|
69
|
-
class="button is-small is-dark is-outlined">
|
|
70
|
-
<span class="icon is-small">{% icon 'ant-design:left-circle-outlined' 24 %}</span>
|
|
71
|
-
</a>
|
|
72
|
-
</div>
|
|
73
|
-
{% endif %}
|
|
74
|
-
<div class="level-item">
|
|
75
|
-
<p class="is-italic">page {{ page_obj.number }}
|
|
76
|
-
of {{ page_obj.paginator.num_pages }}</p>
|
|
77
|
-
</div>
|
|
78
|
-
{% if page_obj.has_next %}
|
|
79
|
-
<div class="level-item">
|
|
80
|
-
<a href="?page={{ page_obj.next_page_number }}"
|
|
81
|
-
class="button is-small is-dark is-outlined">
|
|
82
|
-
<span class="icon is-small">{% icon 'ant-design:right-circle-outlined' 24 %}</span>
|
|
83
|
-
</a>
|
|
84
|
-
</div>
|
|
85
|
-
{% endif %}
|
|
86
|
-
|
|
87
|
-
{% endif %}
|
|
88
|
-
</div>
|
|
86
|
+
{% endif %}
|
|
89
87
|
</div>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
</div>
|
|
89
|
+
{% invoice_table invoice_list %}
|
|
90
|
+
{% if year %}
|
|
91
|
+
<h5 class="is-size-5">Go to month:</h5>
|
|
92
|
+
<p>
|
|
93
|
+
<a href="{% url 'django_ledger:invoice-list' entity_slug=view.kwargs.entity_slug %}">
|
|
94
|
+
{% trans 'All' %} |
|
|
95
|
+
</a>
|
|
96
|
+
{% for date in date_list %}
|
|
97
|
+
<a href="{% url 'django_ledger:invoice-list-month' entity_slug=view.kwargs.entity_slug year=date.year month=date.month %}">
|
|
98
|
+
{{ date | date:'F' }} {% if not forloop.last %}>{% endif %}
|
|
96
99
|
</a>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<a href="{% url 'django_ledger:invoice-list' entity_slug=view.kwargs.entity_slug %}">
|
|
107
|
-
{%
|
|
100
|
+
{% endfor %}
|
|
101
|
+
</p>
|
|
102
|
+
{% else %}
|
|
103
|
+
<h5 class="is-size-5">Go to year:</h5>
|
|
104
|
+
<p>
|
|
105
|
+
<a href="{% url 'django_ledger:invoice-list' entity_slug=view.kwargs.entity_slug %}">
|
|
106
|
+
{% trans 'All' %} |
|
|
107
|
+
</a>
|
|
108
|
+
{% for date in date_list %}
|
|
109
|
+
<a href="{% url 'django_ledger:invoice-list-year' entity_slug=view.kwargs.entity_slug year=date.year %}">
|
|
110
|
+
{{ date.year }} {% if not forloop.last %}>{% endif %}
|
|
108
111
|
</a>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</a>
|
|
113
|
-
{% endfor %}
|
|
114
|
-
</p>
|
|
115
|
-
{% endif %}
|
|
116
|
-
|
|
117
|
-
</div>
|
|
112
|
+
{% endfor %}
|
|
113
|
+
</p>
|
|
114
|
+
{% endif %}
|
|
118
115
|
</div>
|
|
119
116
|
{% endblock %}
|
|
@@ -738,7 +738,7 @@ def navigation_menu(context, style):
|
|
|
738
738
|
{
|
|
739
739
|
'type': 'link',
|
|
740
740
|
'title': 'Chart of Accounts',
|
|
741
|
-
'url': reverse('django_ledger:
|
|
741
|
+
'url': reverse('django_ledger:coa-list', kwargs={'entity_slug': ENTITY_SLUG})
|
|
742
742
|
},
|
|
743
743
|
{
|
|
744
744
|
'type': 'link',
|
django_ledger/tests/base.py
CHANGED
|
@@ -6,6 +6,7 @@ from random import randint, choice
|
|
|
6
6
|
from typing import Optional, Literal
|
|
7
7
|
from zoneinfo import ZoneInfo
|
|
8
8
|
|
|
9
|
+
from django.conf import settings
|
|
9
10
|
from django.contrib.auth import get_user_model
|
|
10
11
|
from django.core.exceptions import ObjectDoesNotExist
|
|
11
12
|
from django.test import TestCase
|
|
@@ -15,7 +16,6 @@ from django.utils.timezone import get_default_timezone
|
|
|
15
16
|
from django_ledger.io.io_generator import EntityDataGenerator
|
|
16
17
|
from django_ledger.models import JournalEntryModel, LedgerModel, TransactionModel, AccountModel, AccountModelQuerySet
|
|
17
18
|
from django_ledger.models.entity import EntityModel, EntityModelQuerySet, UserModel
|
|
18
|
-
from django.conf import settings
|
|
19
19
|
|
|
20
20
|
UserModel = get_user_model()
|
|
21
21
|
|
|
@@ -138,6 +138,28 @@ class DjangoLedgerBaseTest(TestCase):
|
|
|
138
138
|
return choice(self.ENTITY_MODEL_QUERYSET)
|
|
139
139
|
raise ValueError('EntityModels have not been populated.')
|
|
140
140
|
|
|
141
|
+
def create_entity_model(self, use_accrual_method: bool = False, fy_start_month: int = 1) -> EntityModel:
|
|
142
|
+
"""
|
|
143
|
+
Creates a new blank EntityModel for testing purposes.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
use_accrual_method: bool
|
|
148
|
+
Whether to use the accrual method. Defaults to False.
|
|
149
|
+
fy_start_month:
|
|
150
|
+
The month to start the financial year. Defaults to 1 (January).
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
EntityModel
|
|
155
|
+
"""
|
|
156
|
+
return EntityModel.create_entity(
|
|
157
|
+
name='Testing Inc-{randint(100000, 999999)',
|
|
158
|
+
use_accrual_method=use_accrual_method,
|
|
159
|
+
fy_start_month=fy_start_month,
|
|
160
|
+
admin=self.user_model
|
|
161
|
+
)
|
|
162
|
+
|
|
141
163
|
@classmethod
|
|
142
164
|
def create_entity_models(cls, save=True, n: int = 5):
|
|
143
165
|
cls.refresh_test_data(n)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django_ledger.models import EntityModelValidationError, AccountModel
|
|
2
|
+
from django_ledger.tests.base import DjangoLedgerBaseTest
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AccountModelTests(DjangoLedgerBaseTest):
|
|
6
|
+
|
|
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')
|
|
10
|
+
|
|
11
|
+
with self.assertRaises(EntityModelValidationError, msg='New entities do not have default coa'):
|
|
12
|
+
entity_model.get_default_coa()
|
|
13
|
+
|
|
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')
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from django_ledger.models import EntityModelValidationError, ChartOfAccountModel
|
|
2
|
+
from django_ledger.tests.base import DjangoLedgerBaseTest
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ChartOfAccountsTests(DjangoLedgerBaseTest):
|
|
6
|
+
|
|
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')
|
|
10
|
+
|
|
11
|
+
with self.assertRaises(EntityModelValidationError, msg='New entities do not have default coa'):
|
|
12
|
+
entity_model.get_default_coa()
|
|
13
|
+
|
|
14
|
+
self.assertEqual(entity_model.get_default_coa(raise_exception=False), None,
|
|
15
|
+
msg='No exception should be raised when raise_exception is False')
|
|
16
|
+
|
|
17
|
+
def test_set_default_coa(self):
|
|
18
|
+
entity_model = self.create_entity_model()
|
|
19
|
+
coa_model = entity_model.create_chart_of_accounts(coa_name='Now CoA For Testing', assign_as_default=False)
|
|
20
|
+
self.assertTrue(isinstance(coa_model, ChartOfAccountModel))
|
|
21
|
+
self.assertFalse(entity_model.has_default_coa())
|
|
22
|
+
|
|
23
|
+
def test_create_coa(self):
|
|
24
|
+
entity_model = self.create_entity_model()
|
|
25
|
+
coa_model = entity_model.create_chart_of_accounts(coa_name='Now CoA For Testing', assign_as_default=False)
|
|
26
|
+
|
|
27
|
+
account_model_qs = coa_model.accountmodel_set.all()
|
|
28
|
+
|
|
29
|
+
account_count = account_model_qs.count()
|
|
30
|
+
self.assertTrue(account_count, 7)
|
|
31
|
+
|
|
32
|
+
ROOT_ACCOUNT_CODES = [
|
|
33
|
+
'00000000', # root_account
|
|
34
|
+
'01000000', # asset account root
|
|
35
|
+
'02000000', # liability accounts root
|
|
36
|
+
'03000000', # capital accounts root
|
|
37
|
+
'04000000', # income accounts root
|
|
38
|
+
'05000000', # cogs accounts root
|
|
39
|
+
'06000000', # expenses accounts root
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
for account in account_model_qs:
|
|
43
|
+
self.assertTrue(account.code in ROOT_ACCOUNT_CODES)
|
|
44
|
+
|
|
45
|
+
# todo: cannot transact on root account
|
|
46
|
+
# todo: validate parent/child relationship...
|
django_ledger/urls/account.py
CHANGED
|
@@ -3,15 +3,30 @@ from django.urls import path
|
|
|
3
3
|
from django_ledger import views
|
|
4
4
|
|
|
5
5
|
urlpatterns = [
|
|
6
|
+
|
|
7
|
+
# NO COA SLUG USES DEFAULT COA....
|
|
8
|
+
path('<slug:entity_slug>/create/',
|
|
9
|
+
views.AccountModelCreateView.as_view(),
|
|
10
|
+
name='account-create'),
|
|
6
11
|
path('<slug:entity_slug>/list/',
|
|
7
12
|
views.AccountModelListView.as_view(),
|
|
8
13
|
name='account-list'),
|
|
9
14
|
path('<slug:entity_slug>/list/active/',
|
|
10
15
|
views.AccountModelListView.as_view(active_only=True),
|
|
11
16
|
name='account-list-active'),
|
|
12
|
-
|
|
17
|
+
|
|
18
|
+
# EXPLICIT COA...
|
|
19
|
+
path('<slug:entity_slug>/<slug:coa_slug>/create/',
|
|
13
20
|
views.AccountModelCreateView.as_view(),
|
|
14
|
-
name='account-create'),
|
|
21
|
+
name='account-create-coa'),
|
|
22
|
+
path('<slug:entity_slug>/<slug:coa_slug>/list/',
|
|
23
|
+
views.AccountModelListView.as_view(),
|
|
24
|
+
name='account-list-coa'),
|
|
25
|
+
path('<slug:entity_slug>/<slug:coa_slug>/list/active/',
|
|
26
|
+
views.AccountModelListView.as_view(active_only=True),
|
|
27
|
+
name='account-list-active-coa'),
|
|
28
|
+
|
|
29
|
+
# Account Transaction Detail....
|
|
15
30
|
path('<slug:entity_slug>/update/<uuid:account_pk>/',
|
|
16
31
|
views.AccountModelUpdateView.as_view(),
|
|
17
32
|
name='account-update'),
|
|
@@ -31,7 +46,7 @@ urlpatterns = [
|
|
|
31
46
|
views.AccountModelDateDetailView.as_view(),
|
|
32
47
|
name='account-detail-date'),
|
|
33
48
|
|
|
34
|
-
# Actions...
|
|
49
|
+
# Account Actions...
|
|
35
50
|
path('<slug:entity_slug>/action/<uuid:account_pk>/activate/',
|
|
36
51
|
views.AccountModelModelActionView.as_view(action_name='activate'),
|
|
37
52
|
name='account-action-activate'),
|
|
@@ -3,5 +3,25 @@ from django.urls import path
|
|
|
3
3
|
from django_ledger import views
|
|
4
4
|
|
|
5
5
|
urlpatterns = [
|
|
6
|
-
path('<slug:entity_slug
|
|
6
|
+
path('<slug:entity_slug>/list/',
|
|
7
|
+
views.ChartOfAccountsListView.as_view(),
|
|
8
|
+
name='coa-list'),
|
|
9
|
+
path('<slug:entity_slug>/detail/<slug:coa_slug>/',
|
|
10
|
+
views.ChartOfAccountsListView.as_view(),
|
|
11
|
+
name='coa-detail'),
|
|
12
|
+
path('<slug:entity_slug>/update/<slug:coa_slug>/',
|
|
13
|
+
views.ChartOfAccountsUpdateView.as_view(),
|
|
14
|
+
name='coa-update'),
|
|
15
|
+
|
|
16
|
+
# ACTIONS....
|
|
17
|
+
path('<slug:entity_slug>/action/<slug:coa_slug>/mark-as-default/',
|
|
18
|
+
views.CharOfAccountModelActionView.as_view(action_name='mark_as_default'),
|
|
19
|
+
name='coa-action-mark-as-default'),
|
|
20
|
+
path('<slug:entity_slug>/action/<slug:coa_slug>/mark-as-active/',
|
|
21
|
+
views.CharOfAccountModelActionView.as_view(action_name='mark_as_active'),
|
|
22
|
+
name='coa-action-mark-as-active'),
|
|
23
|
+
path('<slug:entity_slug>/action/<slug:coa_slug>/mark-as-inactive/',
|
|
24
|
+
views.CharOfAccountModelActionView.as_view(action_name='mark_as_inactive'),
|
|
25
|
+
name='coa-action-mark-as-inactive'),
|
|
26
|
+
|
|
7
27
|
]
|
django_ledger/views/account.py
CHANGED
|
@@ -30,13 +30,31 @@ class BaseAccountModelViewQuerySetMixIn:
|
|
|
30
30
|
|
|
31
31
|
def get_queryset(self):
|
|
32
32
|
if self.queryset is None:
|
|
33
|
-
|
|
33
|
+
qs = AccountModel.objects.for_entity(
|
|
34
34
|
entity_slug=self.kwargs['entity_slug'],
|
|
35
35
|
user_model=self.request.user,
|
|
36
|
-
).select_related(
|
|
36
|
+
).select_related(
|
|
37
|
+
'coa_model',
|
|
38
|
+
'coa_model__entity'
|
|
39
|
+
).order_by(
|
|
37
40
|
'coa_model', 'role', 'code').not_coa_root()
|
|
41
|
+
|
|
42
|
+
if self.kwargs.get('coa_slug'):
|
|
43
|
+
qs = qs.filter(coa_model__slug__exact=self.kwargs.get('coa_slug'))
|
|
44
|
+
|
|
45
|
+
self.queryset = qs
|
|
38
46
|
return super().get_queryset()
|
|
39
47
|
|
|
48
|
+
def get_context_data(self, *args, **kwargs):
|
|
49
|
+
context = super().get_context_data(*args, **kwargs)
|
|
50
|
+
entity_model = self.get_authorized_entity_instance()
|
|
51
|
+
if self.kwargs.get('coa_slug'):
|
|
52
|
+
coa_model_qs = entity_model.chartofaccountmodel_set.all()
|
|
53
|
+
context['coa_model'] = get_object_or_404(coa_model_qs, slug__exact=self.kwargs['coa_slug'])
|
|
54
|
+
else:
|
|
55
|
+
context['coa_model'] = entity_model.default_coa
|
|
56
|
+
return context
|
|
57
|
+
|
|
40
58
|
|
|
41
59
|
# Account Views ----
|
|
42
60
|
class AccountModelListView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuerySetMixIn, ListView):
|
|
@@ -105,6 +123,11 @@ class AccountModelCreateView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuer
|
|
|
105
123
|
**self.get_form_kwargs()
|
|
106
124
|
)
|
|
107
125
|
|
|
126
|
+
def get_context_data(self, *args, **kwargs):
|
|
127
|
+
context = super().get_context_data(*args, **kwargs)
|
|
128
|
+
context['header_subtitle'] = f'CoA: {context["coa_model"].name}'
|
|
129
|
+
return context
|
|
130
|
+
|
|
108
131
|
def form_valid(self, form):
|
|
109
132
|
EntityModel = lazy_loader.get_entity_model()
|
|
110
133
|
entity_model_qs = EntityModel.objects.for_user(user_model=self.request.user).select_related('default_coa')
|
|
@@ -115,7 +138,7 @@ class AccountModelCreateView(DjangoLedgerSecurityMixIn, BaseAccountModelViewQuer
|
|
|
115
138
|
entity_model.create_chart_of_accounts(assign_as_default=True, commit=True)
|
|
116
139
|
|
|
117
140
|
coa_model = entity_model.default_coa
|
|
118
|
-
coa_model.
|
|
141
|
+
coa_model.allocate_account(account_model=account_model)
|
|
119
142
|
return HttpResponseRedirect(self.get_success_url())
|
|
120
143
|
|
|
121
144
|
def get_success_url(self):
|
django_ledger/views/coa.py
CHANGED
|
@@ -5,10 +5,13 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
|
5
5
|
Contributions to this module:
|
|
6
6
|
Miguel Sanda <msanda@arrobalytics.com>
|
|
7
7
|
"""
|
|
8
|
-
|
|
8
|
+
from django.contrib import messages
|
|
9
|
+
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
|
10
|
+
from django.db.models import Count, Q
|
|
9
11
|
from django.urls import reverse
|
|
10
12
|
from django.utils.translation import gettext_lazy as _
|
|
11
|
-
from django.views.generic import UpdateView
|
|
13
|
+
from django.views.generic import UpdateView, ListView, RedirectView
|
|
14
|
+
from django.views.generic.detail import SingleObjectMixin
|
|
12
15
|
|
|
13
16
|
from django_ledger.forms.coa import ChartOfAccountsModelUpdateForm
|
|
14
17
|
from django_ledger.models.coa import ChartOfAccountModel
|
|
@@ -23,14 +26,49 @@ class ChartOfAccountsModelModelViewQuerySetMixIn:
|
|
|
23
26
|
self.queryset = ChartOfAccountModel.objects.for_entity(
|
|
24
27
|
entity_slug=self.kwargs['entity_slug'],
|
|
25
28
|
user_model=self.request.user,
|
|
26
|
-
).select_related('entity')
|
|
29
|
+
).select_related('entity').order_by('-updated')
|
|
27
30
|
return super().get_queryset()
|
|
28
31
|
|
|
29
32
|
|
|
33
|
+
class ChartOfAccountsListView(DjangoLedgerSecurityMixIn, ChartOfAccountsModelModelViewQuerySetMixIn, ListView):
|
|
34
|
+
template_name = 'django_ledger/chart_of_accounts/coa_list.html'
|
|
35
|
+
extra_context = {
|
|
36
|
+
'header_title': _('Chart of Account List'),
|
|
37
|
+
}
|
|
38
|
+
context_object_name = 'coa_list'
|
|
39
|
+
|
|
40
|
+
def get_context_data(self, *, object_list=None, **kwargs):
|
|
41
|
+
context = super().get_context_data(object_list=None, **kwargs)
|
|
42
|
+
context['header_subtitle'] = self.AUTHORIZED_ENTITY_MODEL.name
|
|
43
|
+
context['header_subtitle_icon'] = 'gravity-ui:hierarchy'
|
|
44
|
+
return context
|
|
45
|
+
|
|
46
|
+
def get_queryset(self):
|
|
47
|
+
qs = super().get_queryset()
|
|
48
|
+
return qs.annotate(
|
|
49
|
+
accountmodel_total__count=Count(
|
|
50
|
+
'accountmodel',
|
|
51
|
+
# excludes coa root accounts...
|
|
52
|
+
filter=Q(accountmodel__depth__gt=2)
|
|
53
|
+
),
|
|
54
|
+
accountmodel_locked__count=Count(
|
|
55
|
+
'accountmodel',
|
|
56
|
+
# excludes coa root accounts...
|
|
57
|
+
filter=Q(accountmodel__depth__gt=2) & Q(accountmodel__locked=True)
|
|
58
|
+
),
|
|
59
|
+
accountmodel_active__count=Count(
|
|
60
|
+
'accountmodel',
|
|
61
|
+
# excludes coa root accounts...
|
|
62
|
+
filter=Q(accountmodel__depth__gt=2) & Q(accountmodel__active=True)
|
|
63
|
+
),
|
|
64
|
+
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
30
68
|
class ChartOfAccountsUpdateView(DjangoLedgerSecurityMixIn, ChartOfAccountsModelModelViewQuerySetMixIn, UpdateView):
|
|
31
69
|
context_object_name = 'coa'
|
|
32
70
|
slug_url_kwarg = 'coa_slug'
|
|
33
|
-
template_name = 'django_ledger/
|
|
71
|
+
template_name = 'django_ledger/chart_of_accounts/coa_update.html'
|
|
34
72
|
form_class = ChartOfAccountsModelUpdateForm
|
|
35
73
|
|
|
36
74
|
def get_context_data(self, **kwargs):
|
|
@@ -45,3 +83,40 @@ class ChartOfAccountsUpdateView(DjangoLedgerSecurityMixIn, ChartOfAccountsModelM
|
|
|
45
83
|
kwargs={
|
|
46
84
|
'entity_slug': entity_slug
|
|
47
85
|
})
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# todo: centralize this functionality into a separate class for ALL Action views...
|
|
89
|
+
class CharOfAccountModelActionView(DjangoLedgerSecurityMixIn,
|
|
90
|
+
RedirectView,
|
|
91
|
+
ChartOfAccountsModelModelViewQuerySetMixIn,
|
|
92
|
+
SingleObjectMixin):
|
|
93
|
+
http_method_names = ['get']
|
|
94
|
+
slug_url_kwarg = 'coa_slug'
|
|
95
|
+
action_name = None
|
|
96
|
+
commit = True
|
|
97
|
+
|
|
98
|
+
def get_redirect_url(self, *args, **kwargs):
|
|
99
|
+
return reverse('django_ledger:coa-list',
|
|
100
|
+
kwargs={
|
|
101
|
+
'entity_slug': kwargs['entity_slug']
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
def get(self, request, *args, **kwargs):
|
|
105
|
+
kwargs['user_model'] = self.request.user
|
|
106
|
+
if not self.action_name:
|
|
107
|
+
raise ImproperlyConfigured('View attribute action_name is required.')
|
|
108
|
+
response = super(CharOfAccountModelActionView, self).get(request, *args, **kwargs)
|
|
109
|
+
coa_model: ChartOfAccountModel = self.get_object()
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
getattr(coa_model, self.action_name)(commit=self.commit, **kwargs)
|
|
113
|
+
messages.add_message(request, level=messages.SUCCESS, extra_tags='is-success',
|
|
114
|
+
message=_('Successfully updated {} Default Chart of Account to '.format(
|
|
115
|
+
self.AUTHORIZED_ENTITY_MODEL.name) +
|
|
116
|
+
'{}'.format(coa_model.name)))
|
|
117
|
+
except ValidationError as e:
|
|
118
|
+
messages.add_message(request,
|
|
119
|
+
message=e.message,
|
|
120
|
+
level=messages.ERROR,
|
|
121
|
+
extra_tags='is-danger')
|
|
122
|
+
return response
|
django_ledger/views/mixins.py
CHANGED
|
@@ -304,6 +304,9 @@ class DjangoLedgerSecurityMixIn(PermissionRequiredMixin):
|
|
|
304
304
|
user_model=self.request.user).only(
|
|
305
305
|
'uuid', 'slug', 'name', 'default_coa', 'admin')
|
|
306
306
|
|
|
307
|
+
def get_authorized_entity_instance(self) -> Optional[EntityModel]:
|
|
308
|
+
return self.AUTHORIZED_ENTITY_MODEL
|
|
309
|
+
|
|
307
310
|
def has_permission(self):
|
|
308
311
|
if self.request.user.is_superuser:
|
|
309
312
|
if 'entity_slug' in self.kwargs:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-ledger
|
|
3
|
-
Version: 0.5.6.
|
|
3
|
+
Version: 0.5.6.4
|
|
4
4
|
Summary: Bookkeeping & Financial analysis backend for Django. Balance Sheet, Income Statements, Chart of Accounts, Entities
|
|
5
5
|
Author-email: Miguel Sanda <msanda@arrobalytics.com>
|
|
6
6
|
Maintainer-email: Miguel Sanda <msanda@arrobalytics.com>
|