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.
- django_ledger/__init__.py +1 -4
- django_ledger/contrib/django_ledger_graphene/accounts/schema.py +1 -1
- django_ledger/forms/account.py +43 -38
- django_ledger/forms/bank_account.py +3 -2
- django_ledger/forms/bill.py +24 -36
- django_ledger/forms/customer.py +1 -1
- django_ledger/forms/data_import.py +3 -3
- django_ledger/forms/estimate.py +1 -1
- django_ledger/forms/invoice.py +5 -7
- django_ledger/forms/item.py +24 -15
- django_ledger/forms/transactions.py +3 -3
- django_ledger/io/io_core.py +4 -2
- django_ledger/io/io_middleware.py +5 -0
- django_ledger/migrations/0017_alter_accountmodel_unique_together_and_more.py +31 -0
- django_ledger/models/accounts.py +225 -265
- django_ledger/models/bank_account.py +0 -4
- django_ledger/models/bill.py +0 -3
- django_ledger/models/closing_entry.py +0 -3
- django_ledger/models/coa.py +59 -48
- django_ledger/models/coa_default.py +9 -8
- django_ledger/models/customer.py +0 -4
- django_ledger/models/data_import.py +0 -3
- django_ledger/models/entity.py +70 -37
- django_ledger/models/estimate.py +0 -9
- django_ledger/models/invoice.py +0 -3
- django_ledger/models/items.py +4 -6
- django_ledger/models/journal_entry.py +2 -5
- django_ledger/models/ledger.py +0 -3
- django_ledger/models/mixins.py +0 -3
- django_ledger/models/purchase_order.py +0 -4
- django_ledger/models/signals.py +0 -3
- django_ledger/models/transactions.py +2 -5
- django_ledger/models/unit.py +0 -3
- django_ledger/models/utils.py +0 -3
- django_ledger/models/vendor.py +0 -3
- django_ledger/templates/django_ledger/account/account_create.html +2 -2
- django_ledger/templates/django_ledger/account/account_update.html +1 -1
- django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -0
- django_ledger/templates/django_ledger/account/tags/accounts_table.html +27 -18
- django_ledger/templates/django_ledger/bills/bill_detail.html +3 -3
- django_ledger/templates/django_ledger/expense/tags/expense_item_table.html +7 -0
- django_ledger/templates/django_ledger/invoice/invoice_detail.html +3 -3
- django_ledger/templatetags/django_ledger.py +7 -1
- django_ledger/tests/base.py +23 -7
- django_ledger/tests/test_accounts.py +145 -9
- django_ledger/urls/account.py +17 -24
- django_ledger/utils.py +8 -0
- django_ledger/views/__init__.py +1 -1
- django_ledger/views/account.py +80 -118
- django_ledger/views/auth.py +1 -1
- django_ledger/views/bank_account.py +9 -11
- django_ledger/views/bill.py +91 -80
- django_ledger/views/closing_entry.py +8 -0
- django_ledger/views/coa.py +2 -1
- django_ledger/views/customer.py +1 -1
- django_ledger/views/data_import.py +1 -1
- django_ledger/views/entity.py +1 -1
- django_ledger/views/estimate.py +13 -8
- django_ledger/views/feedback.py +1 -1
- django_ledger/views/financial_statement.py +1 -1
- django_ledger/views/home.py +1 -1
- django_ledger/views/inventory.py +9 -0
- django_ledger/views/invoice.py +5 -2
- django_ledger/views/item.py +58 -68
- django_ledger/views/journal_entry.py +1 -1
- django_ledger/views/ledger.py +3 -1
- django_ledger/views/mixins.py +9 -8
- django_ledger/views/purchase_order.py +1 -1
- django_ledger/views/transactions.py +1 -1
- django_ledger/views/unit.py +9 -0
- django_ledger/views/vendor.py +1 -1
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/AUTHORS.md +8 -2
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/METADATA +34 -43
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/RECORD +77 -76
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/WHEEL +1 -1
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/LICENSE +0 -0
- {django_ledger-0.6.4.dist-info → django_ledger-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
4
|
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
-
|
|
8
5
|
The TransactionModel is the lowest accounting level where financial information is recorded. Every transaction with a
|
|
9
6
|
financial implication must be part of a JournalEntryModel, which encapsulates a collection of TransactionModels.
|
|
10
7
|
Transaction models cannot exist without being part of a validated JournalEntryModel. Orphan TransactionModels are not
|
|
@@ -24,7 +21,7 @@ from django.contrib.auth import get_user_model
|
|
|
24
21
|
from django.core.exceptions import ValidationError
|
|
25
22
|
from django.core.validators import MinValueValidator
|
|
26
23
|
from django.db import models
|
|
27
|
-
from django.db.models import Q, QuerySet,
|
|
24
|
+
from django.db.models import Q, QuerySet, Manager
|
|
28
25
|
from django.db.models.signals import pre_save
|
|
29
26
|
from django.utils.translation import gettext_lazy as _
|
|
30
27
|
|
|
@@ -209,7 +206,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
209
206
|
return self.filter(journal_entry__is_closing_entry=True)
|
|
210
207
|
|
|
211
208
|
|
|
212
|
-
class TransactionModelManager(
|
|
209
|
+
class TransactionModelManager(Manager):
|
|
213
210
|
"""
|
|
214
211
|
A manager class for the TransactionModel.
|
|
215
212
|
"""
|
django_ledger/models/unit.py
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
4
|
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
-
|
|
8
5
|
An EntityUnit is a logical, user-defined grouping which is assigned to JournalEntryModels to help segregate business
|
|
9
6
|
operations into separate components. Examples of business units may include Departments (i.e. Human Resources, IT, etc.)
|
|
10
7
|
office locations, a real estate property, or any other label relevant to the business.
|
django_ledger/models/utils.py
CHANGED
django_ledger/models/vendor.py
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
3
|
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
4
|
|
|
5
|
-
Contributions to this module:
|
|
6
|
-
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
-
|
|
8
5
|
A Vendor refers to the person or entity that provides products and services to the business for a fee.
|
|
9
6
|
Vendors are an integral part of the billing process as they are the providers of goods and services for the
|
|
10
7
|
business.
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
</div>
|
|
14
14
|
<div class="column is-8-tablet is-6-desktop">
|
|
15
15
|
<div class="box">
|
|
16
|
-
<form method="post">
|
|
16
|
+
<form method="post" id="{{ form.form_id }}">
|
|
17
17
|
{% csrf_token %}
|
|
18
18
|
{{ form.as_p }}
|
|
19
19
|
<button type="submit" class="button is-primary is-fullwidth djetler_my_1">Submit</button>
|
|
20
20
|
<a class="button is-dark is-small is-fullwidth"
|
|
21
|
-
href="{
|
|
21
|
+
href="{{ coa_model.get_account_list_url }}">Back</a>
|
|
22
22
|
</form>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
{{ form.as_p }}
|
|
19
19
|
<button type="submit" class="button is-primary is-fullwidth djetler_my_1">Submit</button>
|
|
20
20
|
<a class="button is-dark is-small is-fullwidth"
|
|
21
|
-
href="{
|
|
21
|
+
href="{{ coa_model.get_account_list_url }}">Back</a>
|
|
22
22
|
</form>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<div class="dropdown-menu" id="dropdown-menu-{{ tx.uuid }}" role="menu">
|
|
34
34
|
<div class="dropdown-content">
|
|
35
|
+
{# TODO: These URLs need to be replaced with the future mode method that generates it. #}
|
|
35
36
|
<a href="{% url 'django_ledger:je-detail' entity_slug=entity_slug ledger_pk=tx.journal_entry.ledger.uuid je_pk=tx.journal_entry.uuid %}"
|
|
36
37
|
class="dropdown-item has-text-success">View JE</a>
|
|
37
38
|
{% if tx.journal_entry.ledger.billmodel %}
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
<td></td>
|
|
16
16
|
<td></td>
|
|
17
17
|
<td></td>
|
|
18
|
-
{# <td></td>#}
|
|
19
18
|
<td></td>
|
|
20
19
|
</tr>
|
|
21
20
|
{% endif %}
|
|
@@ -25,6 +24,7 @@
|
|
|
25
24
|
<th>{% trans 'CoA' %}</th>
|
|
26
25
|
<th>{% trans 'Balance Type' %}</th>
|
|
27
26
|
<th>{% trans 'Active' %}</th>
|
|
27
|
+
<th>{% trans 'Locked' %}</th>
|
|
28
28
|
<th>{% trans 'CoA Role Default' %}</th>
|
|
29
29
|
<th>{% trans 'Actions' %}</th>
|
|
30
30
|
</tr>
|
|
@@ -49,28 +49,29 @@
|
|
|
49
49
|
<td>{{ account.coa_model.name }}</td>
|
|
50
50
|
<td>{{ account.get_balance_type_display }}</td>
|
|
51
51
|
<td class="has-text-centered">
|
|
52
|
-
{% if account.
|
|
52
|
+
{% if account.is_active %}
|
|
53
53
|
<span class="icon has-text-success-dark">
|
|
54
54
|
{% icon 'ant-design:check-circle-filled' 24 %}
|
|
55
55
|
</span>
|
|
56
|
-
{%
|
|
56
|
+
{% else %}
|
|
57
57
|
<span class="icon has-text-danger-dark">
|
|
58
58
|
{% icon 'mdi:dangerous' 24 %}
|
|
59
59
|
</span>
|
|
60
60
|
{% endif %}
|
|
61
61
|
</td>
|
|
62
|
+
|
|
63
|
+
<td class="has-text-centered">
|
|
64
|
+
{% if account.is_locked %}
|
|
65
|
+
<span class="icon has-text-success-dark">
|
|
66
|
+
{% icon 'ooui:lock' 24 %}
|
|
67
|
+
</span>
|
|
68
|
+
{% else %}
|
|
69
|
+
<span class="icon has-text-danger-dark">
|
|
70
|
+
{% icon 'ooui:un-lock' 24 %}
|
|
71
|
+
</span>
|
|
72
|
+
{% endif %}
|
|
73
|
+
</td>
|
|
62
74
|
|
|
63
|
-
{# <td class="has-text-centered">#}
|
|
64
|
-
{# {% if account.locked %}#}
|
|
65
|
-
{# <span class="icon has-text-success-dark">#}
|
|
66
|
-
{# {% icon 'bi:lock-fill' 24 %}#}
|
|
67
|
-
{# </span>#}
|
|
68
|
-
{# {% elif not account.locked %}#}
|
|
69
|
-
{# <span class="icon has-text-danger-dark">#}
|
|
70
|
-
{# {% icon 'bx:bx-lock-open-alt' 24 %}#}
|
|
71
|
-
{# </span>#}
|
|
72
|
-
{# {% endif %}#}
|
|
73
|
-
{# </td>#}
|
|
74
75
|
<td class="has-text-centered">
|
|
75
76
|
{% if account.role_default %}
|
|
76
77
|
<span class="icon has-text-success-dark">
|
|
@@ -94,18 +95,26 @@
|
|
|
94
95
|
</div>
|
|
95
96
|
<div class="dropdown-menu" id="dropdown-menu-{{ account.uuid }}" role="menu">
|
|
96
97
|
<div class="dropdown-content">
|
|
97
|
-
<a href="{
|
|
98
|
+
<a href="{{ account.get_absolute_url }}"
|
|
98
99
|
class="dropdown-item has-text-success">{% trans 'Detail' %}</a>
|
|
99
|
-
<a href="{
|
|
100
|
+
<a href="{{ account.get_update_url }}"
|
|
100
101
|
class="dropdown-item has-text-warning">{% trans 'Update' %}</a>
|
|
101
102
|
{% if account.can_activate %}
|
|
102
|
-
<a href="{
|
|
103
|
+
<a href="{{ account.get_action_activate_url }}"
|
|
103
104
|
class="dropdown-item has-text-success has-text-weight-bold">{% trans 'Activate' %}</a>
|
|
104
105
|
{% endif %}
|
|
105
106
|
{% if account.can_deactivate %}
|
|
106
|
-
<a href="{
|
|
107
|
+
<a href="{{ account.get_action_deactivate_url }}"
|
|
107
108
|
class="dropdown-item has-text-danger has-text-weight-bold">{% trans 'Deactivate' %}</a>
|
|
108
109
|
{% endif %}
|
|
110
|
+
{% if account.can_lock %}
|
|
111
|
+
<a href="{{ account.get_action_lock_url }}"
|
|
112
|
+
class="dropdown-item has-text-success has-text-weight-bold">{% trans 'Lock' %}</a>
|
|
113
|
+
{% endif %}
|
|
114
|
+
{% if account.can_unlock %}
|
|
115
|
+
<a href="{{ account.get_action_unlock_url }}"
|
|
116
|
+
class="dropdown-item has-text-danger has-text-weight-bold">{% trans 'Unlock' %}</a>
|
|
117
|
+
{% endif %}
|
|
109
118
|
</div>
|
|
110
119
|
</div>
|
|
111
120
|
</div>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<div class="level-item has-text-centered">
|
|
30
30
|
<div>
|
|
31
31
|
<p class="heading">{% trans 'Cash Account' %}:
|
|
32
|
-
<a href="{% url 'django_ledger:account-detail' account_pk=bill.cash_account.uuid entity_slug=view.kwargs.entity_slug %}"
|
|
32
|
+
<a href="{% url 'django_ledger:account-detail' account_pk=bill.cash_account.uuid coa_slug=bill.cash_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
|
33
33
|
class="has-text-danger">{{ bill.cash_account.code }}</a>
|
|
34
34
|
<p class="title" id="djl-bill-detail-amount-paid">
|
|
35
35
|
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}</p>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<div class="level-item has-text-centered">
|
|
41
41
|
<div>
|
|
42
42
|
<p class="heading">{% trans 'Prepaid Account' %}:
|
|
43
|
-
<a href="{% url 'django_ledger:account-detail' account_pk=bill.prepaid_account.uuid entity_slug=view.kwargs.entity_slug %}"
|
|
43
|
+
<a href="{% url 'django_ledger:account-detail' account_pk=bill.prepaid_account.uuid coa_slug=bill.prepaid_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
|
44
44
|
class="has-text-danger">{{ bill.prepaid_account.code }}</a>
|
|
45
45
|
</p>
|
|
46
46
|
<p class="title has-text-success" id="djl-bill-detail-amount-prepaid">
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<div class="level-item has-text-centered">
|
|
51
51
|
<div>
|
|
52
52
|
<p class="heading">{% trans 'Accounts Payable' %}:
|
|
53
|
-
<a href="{% url 'django_ledger:account-detail' account_pk=bill.unearned_account.uuid entity_slug=view.kwargs.entity_slug %}"
|
|
53
|
+
<a href="{% url 'django_ledger:account-detail' account_pk=bill.unearned_account.uuid coa_slug=bill.unearned_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
|
54
54
|
class="has-text-danger">{{ bill.unearned_account.code }}</a>
|
|
55
55
|
</p>
|
|
56
56
|
<p class="title has-text-danger" id="djl-bill-detail-amount-unearned">
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<th>{% trans 'Item' %}</th>
|
|
10
10
|
<th>{% trans 'UOM' %}</th>
|
|
11
11
|
<th>{% trans 'Expense Account' %}</th>
|
|
12
|
+
<th>{% trans 'Is Active' %}</th>
|
|
12
13
|
<th>{% trans 'Actions' %}</th>
|
|
13
14
|
</tr>
|
|
14
15
|
</thead>
|
|
@@ -19,6 +20,12 @@
|
|
|
19
20
|
<td>{{ expense_item.name }}</td>
|
|
20
21
|
<td>{{ expense_item.uom }}</td>
|
|
21
22
|
<td>{{ expense_item.expense_account }}</td>
|
|
23
|
+
<td class="has-text-centered">
|
|
24
|
+
{% if expense_item.is_active %}
|
|
25
|
+
<span class="icon is-small has-text-success">{% icon 'bi:check-circle-fill' 24 %}</span>
|
|
26
|
+
{% else %}
|
|
27
|
+
<span class="icon is-small has-text-danger">{% icon 'healthicons:no' 24 %}</span>
|
|
28
|
+
{% endif %} </td>
|
|
22
29
|
<td>
|
|
23
30
|
<div class="dropdown is-right is-hoverable" id="invoice-action-{{ invoice.uuid }}">
|
|
24
31
|
<div class="dropdown-trigger">
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<div class="level-item has-text-centered">
|
|
29
29
|
<div>
|
|
30
30
|
<p class="heading">{% trans 'Cash Account' %}:
|
|
31
|
-
<a href="{% url 'django_ledger:account-detail' account_pk=invoice.cash_account.uuid entity_slug=view.kwargs.entity_slug %}"
|
|
31
|
+
<a href="{% url 'django_ledger:account-detail' account_pk=invoice.cash_account.uuid coa_slug=invoice.cash_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
|
32
32
|
class="has-text-danger">{{ invoice.cash_account.code }}</a>
|
|
33
33
|
<p class="title">
|
|
34
34
|
{% currency_symbol %}{{ invoice.get_amount_cash | absolute | currency_format }}</p>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<div class="level-item has-text-centered">
|
|
40
40
|
<div>
|
|
41
41
|
<p class="heading">{% trans 'Accounts Receivable' %}:
|
|
42
|
-
<a href="{% url 'django_ledger:account-detail' account_pk=invoice.prepaid_account.uuid entity_slug=view.kwargs.entity_slug %}"
|
|
42
|
+
<a href="{% url 'django_ledger:account-detail' account_pk=invoice.prepaid_account.uuid coa_slug=invoice.prepaid_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
|
43
43
|
class="has-text-danger">{{ invoice.prepaid_account.code }}</a>
|
|
44
44
|
</p>
|
|
45
45
|
<p class="title has-text-success">
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<div class="level-item has-text-centered">
|
|
50
50
|
<div>
|
|
51
51
|
<p class="heading">{% trans 'Deferred Revenue' %}:
|
|
52
|
-
<a href="{% url 'django_ledger:account-detail' account_pk=invoice.unearned_account.uuid entity_slug=view.kwargs.entity_slug %}"
|
|
52
|
+
<a href="{% url 'django_ledger:account-detail' account_pk=invoice.unearned_account.uuid coa_slug=invoice.unearned_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
|
53
53
|
class="has-text-danger">{{ invoice.unearned_account.code }}</a>
|
|
54
54
|
|
|
55
55
|
</p>
|
|
@@ -542,6 +542,9 @@ def period_navigation(context, base_url: str):
|
|
|
542
542
|
if context['view'].kwargs.get('unit_slug'):
|
|
543
543
|
kwargs['unit_slug'] = context['view'].kwargs.get('unit_slug')
|
|
544
544
|
|
|
545
|
+
if context['view'].kwargs.get('coa_slug'):
|
|
546
|
+
kwargs['coa_slug'] = context['view'].kwargs.get('coa_slug')
|
|
547
|
+
|
|
545
548
|
ctx = dict()
|
|
546
549
|
ctx['year'] = context['year']
|
|
547
550
|
ctx['has_year'] = context.get('has_year')
|
|
@@ -567,12 +570,15 @@ def period_navigation(context, base_url: str):
|
|
|
567
570
|
'year': dt.year,
|
|
568
571
|
'month': dt.month
|
|
569
572
|
}
|
|
573
|
+
|
|
570
574
|
if 'unit_slug' in kwargs:
|
|
571
575
|
KWARGS_CURRENT_MONTH['unit_slug'] = kwargs['unit_slug']
|
|
572
576
|
if 'account_pk' in kwargs:
|
|
573
577
|
KWARGS_CURRENT_MONTH['account_pk'] = kwargs['account_pk']
|
|
574
578
|
if 'ledger_pk' in kwargs:
|
|
575
579
|
KWARGS_CURRENT_MONTH['ledger_pk'] = kwargs['ledger_pk']
|
|
580
|
+
if 'coa_slug' in kwargs:
|
|
581
|
+
KWARGS_CURRENT_MONTH['coa_slug'] = kwargs['coa_slug']
|
|
576
582
|
|
|
577
583
|
ctx['current_month_url'] = reverse(f'django_ledger:{base_url}-month',
|
|
578
584
|
kwargs=KWARGS_CURRENT_MONTH)
|
|
@@ -743,7 +749,7 @@ def navigation_menu(context, style):
|
|
|
743
749
|
{
|
|
744
750
|
'type': 'link',
|
|
745
751
|
'title': 'Ledgers',
|
|
746
|
-
'url': reverse('django_ledger:ledger-list', kwargs={'entity_slug': ENTITY_SLUG})
|
|
752
|
+
'url': reverse('django_ledger:ledger-list-visible', kwargs={'entity_slug': ENTITY_SLUG})
|
|
747
753
|
},
|
|
748
754
|
{
|
|
749
755
|
'type': 'link',
|
django_ledger/tests/base.py
CHANGED
|
@@ -8,7 +8,7 @@ from zoneinfo import ZoneInfo
|
|
|
8
8
|
|
|
9
9
|
from django.conf import settings
|
|
10
10
|
from django.contrib.auth import get_user_model
|
|
11
|
-
from django.core.exceptions import ObjectDoesNotExist
|
|
11
|
+
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
12
12
|
from django.test import TestCase
|
|
13
13
|
from django.test.client import Client
|
|
14
14
|
from django.utils.timezone import get_default_timezone
|
|
@@ -30,7 +30,7 @@ class DjangoLedgerBaseTest(TestCase):
|
|
|
30
30
|
TEST_DATA = list()
|
|
31
31
|
CLIENT = None
|
|
32
32
|
TZ = None
|
|
33
|
-
N =
|
|
33
|
+
N = 1
|
|
34
34
|
USER_EMAIL = None
|
|
35
35
|
PASSWORD = None
|
|
36
36
|
USERNAME = None
|
|
@@ -46,7 +46,6 @@ class DjangoLedgerBaseTest(TestCase):
|
|
|
46
46
|
cls.USERNAME: str = 'testuser'
|
|
47
47
|
cls.PASSWORD: str = 'NeverUseThisPassword12345'
|
|
48
48
|
cls.USER_EMAIL: str = 'testuser@djangoledger.com'
|
|
49
|
-
cls.N: int = 1
|
|
50
49
|
|
|
51
50
|
cls.DAYS_FWD: int = randint(180, 180 * 3)
|
|
52
51
|
cls.TZ = get_default_timezone()
|
|
@@ -191,14 +190,15 @@ class DjangoLedgerBaseTest(TestCase):
|
|
|
191
190
|
|
|
192
191
|
def get_random_account(self,
|
|
193
192
|
entity_model: EntityModel,
|
|
194
|
-
balance_type: Literal['credit', 'debit', None] = None
|
|
193
|
+
balance_type: Literal['credit', 'debit', None] = None,
|
|
194
|
+
active: bool = True,
|
|
195
|
+
locked: bool = False) -> AccountModel:
|
|
195
196
|
"""
|
|
196
197
|
Returns 1 random AccountModel with the specified balance_type.
|
|
197
198
|
"""
|
|
198
|
-
account_qs: AccountModelQuerySet = entity_model.get_coa_accounts(active=
|
|
199
|
+
account_qs: AccountModelQuerySet = entity_model.get_coa_accounts(active=active, locked=locked)
|
|
199
200
|
account_qs = account_qs.filter(balance_type=balance_type) if balance_type else account_qs
|
|
200
|
-
|
|
201
|
-
return choice(account_qs[:50])
|
|
201
|
+
return choice(account_qs)
|
|
202
202
|
|
|
203
203
|
def get_random_ledger(self,
|
|
204
204
|
entity_model: EntityModel,
|
|
@@ -278,3 +278,19 @@ class DjangoLedgerBaseTest(TestCase):
|
|
|
278
278
|
entity_model.validate_ledger_model_for_entity(ledger_model)
|
|
279
279
|
txs_model_qs = je_model.transactionmodel_set.all()
|
|
280
280
|
return choice(txs_model_qs[:qs_limit])
|
|
281
|
+
|
|
282
|
+
def resolve_url_patterns(self, url_patterns):
|
|
283
|
+
self.URL_PATTERNS = {
|
|
284
|
+
p.name: set(p.pattern.converters.keys()) for p in url_patterns
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
def resolve_url_kwars(self):
|
|
288
|
+
url_patterns = getattr(self, 'URL_PATTERNS', None)
|
|
289
|
+
if not url_patterns:
|
|
290
|
+
raise ValidationError(
|
|
291
|
+
message='Must call resolve_url_patterns before calling resolve_url_kwars.'
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return set.union(*[
|
|
295
|
+
set(v) for v in self.URL_PATTERNS.values()
|
|
296
|
+
])
|
|
@@ -1,16 +1,152 @@
|
|
|
1
|
-
from
|
|
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
|
|
8
|
-
entity_model = self.
|
|
9
|
-
self.
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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)
|
django_ledger/urls/account.py
CHANGED
|
@@ -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
|
|
9
|
+
name='account-create'),
|
|
22
10
|
path('<slug:entity_slug>/<slug:coa_slug>/list/',
|
|
23
11
|
views.AccountModelListView.as_view(),
|
|
24
|
-
name='account-list
|
|
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
|
|
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
|
django_ledger/views/__init__.py
CHANGED
|
@@ -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 *
|