django-ledger 0.5.5.4__py3-none-any.whl → 0.5.6.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 -1
- django_ledger/admin/__init__.py +10 -0
- django_ledger/admin/coa.py +135 -0
- django_ledger/admin/entity.py +199 -0
- django_ledger/admin/ledger.py +283 -0
- django_ledger/forms/entity.py +4 -12
- django_ledger/forms/ledger.py +19 -0
- django_ledger/forms/transactions.py +1 -1
- django_ledger/io/__init__.py +4 -1
- django_ledger/io/{io_mixin.py → io_core.py} +95 -35
- django_ledger/io/io_digest.py +15 -0
- django_ledger/io/{data_generator.py → io_generator.py} +51 -8
- django_ledger/io/io_library.py +317 -0
- django_ledger/io/{io_context.py → io_middleware.py} +16 -9
- django_ledger/migrations/0001_initial.py +413 -132
- django_ledger/migrations/0014_ledgermodel_ledger_xid_and_more.py +22 -0
- django_ledger/models/accounts.py +68 -7
- django_ledger/models/bank_account.py +12 -11
- django_ledger/models/bill.py +5 -9
- django_ledger/models/closing_entry.py +14 -14
- django_ledger/models/coa.py +60 -18
- django_ledger/models/customer.py +5 -11
- django_ledger/models/data_import.py +12 -6
- django_ledger/models/entity.py +90 -12
- django_ledger/models/estimate.py +12 -9
- django_ledger/models/invoice.py +10 -4
- django_ledger/models/items.py +11 -6
- django_ledger/models/journal_entry.py +61 -18
- django_ledger/models/ledger.py +90 -24
- django_ledger/models/mixins.py +2 -3
- django_ledger/models/purchase_order.py +11 -7
- django_ledger/models/transactions.py +3 -1
- django_ledger/models/unit.py +22 -13
- django_ledger/models/vendor.py +12 -11
- django_ledger/report/cash_flow_statement.py +1 -1
- django_ledger/report/core.py +3 -2
- django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +1 -1
- django_ledger/templates/django_ledger/journal_entry/je_list.html +3 -0
- django_ledger/templatetags/django_ledger.py +1 -1
- django_ledger/tests/base.py +1 -1
- django_ledger/tests/test_entity.py +1 -1
- django_ledger/urls/ledger.py +3 -0
- django_ledger/views/entity.py +9 -3
- django_ledger/views/ledger.py +14 -7
- django_ledger/views/mixins.py +9 -1
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/METADATA +9 -9
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/RECORD +51 -46
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/top_level.txt +0 -1
- django_ledger/admin.py +0 -160
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/WHEEL +0 -0
django_ledger/__init__.py
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from django_ledger.admin.coa import ChartOfAccountsModelAdmin
|
|
4
|
+
from django_ledger.admin.entity import EntityModelAdmin
|
|
5
|
+
from django_ledger.admin.ledger import LedgerModelAdmin
|
|
6
|
+
from django_ledger.models import EntityModel, ChartOfAccountModel, LedgerModel
|
|
7
|
+
|
|
8
|
+
admin.site.register(EntityModel, EntityModelAdmin)
|
|
9
|
+
admin.site.register(ChartOfAccountModel, ChartOfAccountsModelAdmin)
|
|
10
|
+
admin.site.register(LedgerModel, LedgerModelAdmin)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from django.contrib.admin import TabularInline, ModelAdmin
|
|
2
|
+
from django.db.models import Count
|
|
3
|
+
from django.forms import ModelForm, BooleanField, BaseInlineFormSet
|
|
4
|
+
|
|
5
|
+
from django_ledger.models.accounts import AccountModel
|
|
6
|
+
from django_ledger.models.coa import ChartOfAccountModel
|
|
7
|
+
from django_ledger.models.entity import EntityModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AccountModelInLineForm(ModelForm):
|
|
11
|
+
role_default = BooleanField(initial=False, required=False)
|
|
12
|
+
|
|
13
|
+
class Meta:
|
|
14
|
+
model = AccountModel
|
|
15
|
+
fields = []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AccountModelInLineFormSet(BaseInlineFormSet):
|
|
19
|
+
|
|
20
|
+
def save_new(self, form, commit=True):
|
|
21
|
+
setattr(form.instance, self.fk.name, self.instance)
|
|
22
|
+
if commit:
|
|
23
|
+
account_model = AccountModel.add_root(
|
|
24
|
+
instance=super().save_new(form, commit=False)
|
|
25
|
+
)
|
|
26
|
+
return account_model
|
|
27
|
+
return super().save_new(form, commit=False)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AccountModelInLine(TabularInline):
|
|
31
|
+
extra = 0
|
|
32
|
+
form = AccountModelInLineForm
|
|
33
|
+
formset = AccountModelInLineFormSet
|
|
34
|
+
show_change_link = True
|
|
35
|
+
exclude = [
|
|
36
|
+
'path',
|
|
37
|
+
'depth',
|
|
38
|
+
'numchild'
|
|
39
|
+
]
|
|
40
|
+
model = AccountModel
|
|
41
|
+
fieldsets = [
|
|
42
|
+
('', {
|
|
43
|
+
'fields': [
|
|
44
|
+
'role',
|
|
45
|
+
'balance_type',
|
|
46
|
+
'code',
|
|
47
|
+
'name',
|
|
48
|
+
'role_default',
|
|
49
|
+
'locked',
|
|
50
|
+
'active'
|
|
51
|
+
]
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
def get_queryset(self, request):
|
|
56
|
+
qs = super().get_queryset(request)
|
|
57
|
+
return qs.not_coa_root()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ChartOfAccountsAdminForm(ModelForm):
|
|
61
|
+
assign_as_default = BooleanField(initial=False, required=False)
|
|
62
|
+
|
|
63
|
+
def __init__(self, *args, **kwargs):
|
|
64
|
+
super().__init__(*args, **kwargs)
|
|
65
|
+
if self.instance.is_default():
|
|
66
|
+
self.fields['assign_as_default'].initial = True
|
|
67
|
+
self.fields['assign_as_default'].disabled = True
|
|
68
|
+
|
|
69
|
+
def save(self, commit=True):
|
|
70
|
+
if commit:
|
|
71
|
+
if self.cleaned_data['assign_as_default']:
|
|
72
|
+
entity_model: EntityModel = self.instance.entity
|
|
73
|
+
entity_model.default_coa = self.instance
|
|
74
|
+
entity_model.save(update_fields=[
|
|
75
|
+
'default_coa'
|
|
76
|
+
])
|
|
77
|
+
return super().save(commit=commit)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ChartOfAccountsInLine(TabularInline):
|
|
81
|
+
form = ChartOfAccountsAdminForm
|
|
82
|
+
model = ChartOfAccountModel
|
|
83
|
+
extra = 0
|
|
84
|
+
show_change_link = True
|
|
85
|
+
fields = [
|
|
86
|
+
'name',
|
|
87
|
+
'locked',
|
|
88
|
+
'assign_as_default'
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ChartOfAccountsModelAdmin(ModelAdmin):
|
|
93
|
+
list_filter = [
|
|
94
|
+
'entity__name',
|
|
95
|
+
'locked'
|
|
96
|
+
]
|
|
97
|
+
list_display = [
|
|
98
|
+
'entity_name',
|
|
99
|
+
'name',
|
|
100
|
+
'slug',
|
|
101
|
+
'locked',
|
|
102
|
+
'account_model_count'
|
|
103
|
+
]
|
|
104
|
+
search_fields = [
|
|
105
|
+
'slug',
|
|
106
|
+
'entity__name'
|
|
107
|
+
]
|
|
108
|
+
list_display_links = ['name']
|
|
109
|
+
fields = [
|
|
110
|
+
'name',
|
|
111
|
+
'locked',
|
|
112
|
+
'description',
|
|
113
|
+
]
|
|
114
|
+
inlines = [
|
|
115
|
+
AccountModelInLine
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
class Meta:
|
|
119
|
+
model = ChartOfAccountModel
|
|
120
|
+
|
|
121
|
+
def entity_name(self, obj):
|
|
122
|
+
return obj.entity.name
|
|
123
|
+
|
|
124
|
+
def get_queryset(self, request):
|
|
125
|
+
qs = ChartOfAccountModel.objects.for_user(user_model=request.user)
|
|
126
|
+
ordering = self.get_ordering(request)
|
|
127
|
+
if ordering:
|
|
128
|
+
qs = qs.order_by(*ordering)
|
|
129
|
+
qs = qs.select_related('entity').annotate(Count('accountmodel'))
|
|
130
|
+
return qs
|
|
131
|
+
|
|
132
|
+
def account_model_count(self, obj):
|
|
133
|
+
return obj.accountmodel__count
|
|
134
|
+
|
|
135
|
+
account_model_count.short_description = 'Accounts'
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
from django.contrib.admin import TabularInline, ModelAdmin
|
|
5
|
+
from django.db.models import Count
|
|
6
|
+
from django.forms import BaseInlineFormSet
|
|
7
|
+
from django.urls import reverse
|
|
8
|
+
from django.utils.html import format_html
|
|
9
|
+
from django.utils.timezone import localtime
|
|
10
|
+
|
|
11
|
+
from django_ledger.admin.coa import ChartOfAccountsInLine
|
|
12
|
+
from django_ledger.models import EntityUnitModel, ItemTransactionModel, TransactionModel
|
|
13
|
+
from django_ledger.models.entity import EntityModel, EntityManagementModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EntityManagementInLine(TabularInline):
|
|
17
|
+
model = EntityManagementModel
|
|
18
|
+
extra = 1
|
|
19
|
+
fields = [
|
|
20
|
+
'user'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EntityUnitModelInLineFormSet(BaseInlineFormSet):
|
|
25
|
+
|
|
26
|
+
def save_new(self, form, commit=True):
|
|
27
|
+
setattr(form.instance, self.fk.name, self.instance)
|
|
28
|
+
if commit:
|
|
29
|
+
unit_model = EntityUnitModel.add_root(
|
|
30
|
+
instance=super().save_new(form, commit=False)
|
|
31
|
+
)
|
|
32
|
+
return unit_model
|
|
33
|
+
return super().save_new(form, commit=False)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class EntityUnitModelInLine(TabularInline):
|
|
37
|
+
model = EntityUnitModel
|
|
38
|
+
formset = EntityUnitModelInLineFormSet
|
|
39
|
+
extra = 0
|
|
40
|
+
readonly_fields = [
|
|
41
|
+
'slug'
|
|
42
|
+
]
|
|
43
|
+
fields = [
|
|
44
|
+
'slug',
|
|
45
|
+
'name',
|
|
46
|
+
'document_prefix',
|
|
47
|
+
'active',
|
|
48
|
+
'hidden'
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EntityModelAdmin(ModelAdmin):
|
|
53
|
+
list_display = [
|
|
54
|
+
'slug',
|
|
55
|
+
'name',
|
|
56
|
+
'accrual_method',
|
|
57
|
+
'last_closing_date',
|
|
58
|
+
'hidden',
|
|
59
|
+
'get_coa_count',
|
|
60
|
+
'add_ledger_link',
|
|
61
|
+
'balance_sheet_link',
|
|
62
|
+
'income_statement_link',
|
|
63
|
+
'cash_flow_statement_link'
|
|
64
|
+
]
|
|
65
|
+
readonly_fields = [
|
|
66
|
+
'depth',
|
|
67
|
+
'path',
|
|
68
|
+
'numchild',
|
|
69
|
+
'last_closing_date',
|
|
70
|
+
'default_coa'
|
|
71
|
+
]
|
|
72
|
+
fieldsets = [
|
|
73
|
+
(
|
|
74
|
+
'Entity Information', {
|
|
75
|
+
'fields': [
|
|
76
|
+
'name',
|
|
77
|
+
'admin',
|
|
78
|
+
'fy_start_month',
|
|
79
|
+
'accrual_method',
|
|
80
|
+
'hidden',
|
|
81
|
+
'picture'
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
(
|
|
86
|
+
'Contact Information', {
|
|
87
|
+
'fields': [
|
|
88
|
+
'address_1',
|
|
89
|
+
'address_2',
|
|
90
|
+
'city',
|
|
91
|
+
'state',
|
|
92
|
+
'zip_code',
|
|
93
|
+
'email',
|
|
94
|
+
'website',
|
|
95
|
+
'phone'
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
(
|
|
100
|
+
'Chart of Accounts', {
|
|
101
|
+
'fields': [
|
|
102
|
+
'default_coa'
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
]
|
|
107
|
+
inlines = [
|
|
108
|
+
ChartOfAccountsInLine,
|
|
109
|
+
EntityUnitModelInLine,
|
|
110
|
+
EntityManagementInLine
|
|
111
|
+
]
|
|
112
|
+
actions = [
|
|
113
|
+
'add_code_of_accounts',
|
|
114
|
+
'populate_random_data'
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
class Meta:
|
|
118
|
+
model = EntityModel
|
|
119
|
+
|
|
120
|
+
def get_queryset(self, request):
|
|
121
|
+
qs = super().get_queryset(request=request)
|
|
122
|
+
qs = qs.annotate(Count('chartofaccountmodel'))
|
|
123
|
+
if request.user.is_superuser:
|
|
124
|
+
return qs
|
|
125
|
+
return qs.for_user(user_model=request.user)
|
|
126
|
+
|
|
127
|
+
def add_ledger_link(self, obj):
|
|
128
|
+
add_ledger_url = reverse('admin:django_ledger_ledgermodel_add')
|
|
129
|
+
return format_html('<a class="addlink" href="{url}?entity_slug={slug}">Add Ledger</a>',
|
|
130
|
+
url=add_ledger_url,
|
|
131
|
+
slug=obj.slug)
|
|
132
|
+
|
|
133
|
+
def balance_sheet_link(self, obj: EntityModel):
|
|
134
|
+
add_ledger_url = reverse(
|
|
135
|
+
viewname='django_ledger:entity-bs',
|
|
136
|
+
kwargs={
|
|
137
|
+
'entity_slug': obj.slug
|
|
138
|
+
})
|
|
139
|
+
return format_html('<a class="viewlink" href="{url}">View</a>',
|
|
140
|
+
url=add_ledger_url,
|
|
141
|
+
slug=obj.slug)
|
|
142
|
+
|
|
143
|
+
balance_sheet_link.short_description = 'Balance Sheet'
|
|
144
|
+
|
|
145
|
+
def income_statement_link(self, obj: EntityModel):
|
|
146
|
+
add_ledger_url = reverse(
|
|
147
|
+
viewname='django_ledger:entity-ic',
|
|
148
|
+
kwargs={
|
|
149
|
+
'entity_slug': obj.slug
|
|
150
|
+
})
|
|
151
|
+
return format_html('<a class="viewlink" href="{url}">View</a>',
|
|
152
|
+
url=add_ledger_url,
|
|
153
|
+
slug=obj.slug)
|
|
154
|
+
|
|
155
|
+
income_statement_link.short_description = 'P&L'
|
|
156
|
+
|
|
157
|
+
def cash_flow_statement_link(self, obj: EntityModel):
|
|
158
|
+
add_ledger_url = reverse(
|
|
159
|
+
viewname='django_ledger:entity-cf',
|
|
160
|
+
kwargs={
|
|
161
|
+
'entity_slug': obj.slug
|
|
162
|
+
})
|
|
163
|
+
return format_html('<a class="viewlink" href="{url}">View</a>',
|
|
164
|
+
url=add_ledger_url,
|
|
165
|
+
slug=obj.slug)
|
|
166
|
+
|
|
167
|
+
cash_flow_statement_link.short_description = 'Cash Flow'
|
|
168
|
+
|
|
169
|
+
def add_code_of_accounts(self, request, queryset):
|
|
170
|
+
for entity_model in queryset:
|
|
171
|
+
entity_model.create_chart_of_accounts(
|
|
172
|
+
coa_name=f'{entity_model.name} CoA {localtime().isoformat()}',
|
|
173
|
+
commit=True,
|
|
174
|
+
assign_as_default=False
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def populate_random_data(self, request, queryset):
|
|
178
|
+
for entity_model in queryset:
|
|
179
|
+
start_date = localtime() - timedelta(days=180)
|
|
180
|
+
entity_model.populate_random_data(
|
|
181
|
+
start_date=start_date,
|
|
182
|
+
days_forward=180,
|
|
183
|
+
tx_quantity=25)
|
|
184
|
+
|
|
185
|
+
def get_coa_count(self, obj):
|
|
186
|
+
return obj.chartofaccountmodel__count
|
|
187
|
+
|
|
188
|
+
get_coa_count.short_description = 'CoA Count'
|
|
189
|
+
|
|
190
|
+
def save_model(self, request, obj, form, change):
|
|
191
|
+
if not change:
|
|
192
|
+
if obj.uuid is None:
|
|
193
|
+
obj.uuid = uuid4()
|
|
194
|
+
EntityModel.add_root(instance=obj)
|
|
195
|
+
return
|
|
196
|
+
super().save_model(request, obj, form, change)
|
|
197
|
+
|
|
198
|
+
def has_delete_permission(self, request, obj=None):
|
|
199
|
+
return False
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
from django.contrib import messages
|
|
2
|
+
from django.contrib.admin import ModelAdmin, TabularInline
|
|
3
|
+
from django.db.models import Count
|
|
4
|
+
from django.forms import BaseInlineFormSet
|
|
5
|
+
from django.shortcuts import get_object_or_404
|
|
6
|
+
from django.utils.html import format_html
|
|
7
|
+
|
|
8
|
+
from django_ledger.models import (
|
|
9
|
+
LedgerModel, JournalEntryModel, EntityModel,
|
|
10
|
+
LedgerModelValidationError
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JournalEntryModelInLineFormSet(BaseInlineFormSet):
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
self.ledger_model: LedgerModel = kwargs['instance']
|
|
18
|
+
self.entity_model = self.ledger_model.entity
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
def add_fields(self, form, index):
|
|
22
|
+
super().add_fields(form=form, index=index)
|
|
23
|
+
form.fields['entity_unit'].queryset = self.entity_model.entityunitmodel_set.all()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class JournalEntryModelInLine(TabularInline):
|
|
27
|
+
extra = 0
|
|
28
|
+
fields = [
|
|
29
|
+
'timestamp',
|
|
30
|
+
'description',
|
|
31
|
+
'entity_unit',
|
|
32
|
+
'posted',
|
|
33
|
+
'locked',
|
|
34
|
+
'activity',
|
|
35
|
+
'origin',
|
|
36
|
+
'txs_count',
|
|
37
|
+
'view_txs_link',
|
|
38
|
+
'edit_txs_link'
|
|
39
|
+
]
|
|
40
|
+
readonly_fields = [
|
|
41
|
+
'posted',
|
|
42
|
+
'locked',
|
|
43
|
+
'origin',
|
|
44
|
+
'activity',
|
|
45
|
+
'txs_count',
|
|
46
|
+
'view_txs_link',
|
|
47
|
+
'edit_txs_link'
|
|
48
|
+
]
|
|
49
|
+
model = JournalEntryModel
|
|
50
|
+
formset = JournalEntryModelInLineFormSet
|
|
51
|
+
|
|
52
|
+
def get_queryset(self, request):
|
|
53
|
+
qs = self.model.objects.for_user(request.user)
|
|
54
|
+
ordering = self.get_ordering(request)
|
|
55
|
+
if ordering:
|
|
56
|
+
qs = qs.order_by(*ordering)
|
|
57
|
+
return qs.annotate(
|
|
58
|
+
txs_count=Count('transactionmodel')
|
|
59
|
+
).select_related(
|
|
60
|
+
'ledger',
|
|
61
|
+
'ledger__entity'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def txs_count(self, obj):
|
|
65
|
+
return obj.txs_count
|
|
66
|
+
|
|
67
|
+
txs_count.short_description = 'Transactions'
|
|
68
|
+
|
|
69
|
+
def has_change_permission(self, request, obj=None):
|
|
70
|
+
if obj:
|
|
71
|
+
return all([
|
|
72
|
+
not obj.is_locked(),
|
|
73
|
+
super().has_change_permission(request, obj)
|
|
74
|
+
])
|
|
75
|
+
return super().has_change_permission(request, obj)
|
|
76
|
+
|
|
77
|
+
def has_delete_permission(self, request, obj=None):
|
|
78
|
+
if obj:
|
|
79
|
+
return all([
|
|
80
|
+
obj.can_delete(),
|
|
81
|
+
super().has_delete_permission(request, obj)
|
|
82
|
+
])
|
|
83
|
+
return super().has_delete_permission(request, obj)
|
|
84
|
+
|
|
85
|
+
def view_txs_link(self, obj: JournalEntryModel):
|
|
86
|
+
detail_url = obj.get_detail_url()
|
|
87
|
+
return format_html(
|
|
88
|
+
format_string='<a class="viewlink" target="_blank" href="{url}">View Txs</a>',
|
|
89
|
+
url=detail_url
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def edit_txs_link(self, obj: JournalEntryModel):
|
|
93
|
+
detail_url = obj.get_detail_txs_url()
|
|
94
|
+
next_url = None
|
|
95
|
+
return format_html(
|
|
96
|
+
format_string='<a class="changelink" target="_blank" href="{url}">Edit Txs</a>',
|
|
97
|
+
url=detail_url
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LedgerModelAdmin(ModelAdmin):
|
|
102
|
+
readonly_fields = [
|
|
103
|
+
'entity',
|
|
104
|
+
'posted',
|
|
105
|
+
'locked'
|
|
106
|
+
]
|
|
107
|
+
list_filter = [
|
|
108
|
+
'posted',
|
|
109
|
+
'locked',
|
|
110
|
+
'entity__name'
|
|
111
|
+
]
|
|
112
|
+
list_display = [
|
|
113
|
+
'name',
|
|
114
|
+
'is_posted',
|
|
115
|
+
'is_locked',
|
|
116
|
+
'is_extended',
|
|
117
|
+
'journal_entry_count',
|
|
118
|
+
'earliest_journal_entry'
|
|
119
|
+
]
|
|
120
|
+
actions = [
|
|
121
|
+
'post',
|
|
122
|
+
'unpost',
|
|
123
|
+
'lock',
|
|
124
|
+
'unlock'
|
|
125
|
+
]
|
|
126
|
+
inlines = [
|
|
127
|
+
JournalEntryModelInLine
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
def get_queryset(self, request):
|
|
131
|
+
qs = LedgerModel.objects.for_user(user_model=request.user)
|
|
132
|
+
ordering = self.get_ordering(request)
|
|
133
|
+
if ordering:
|
|
134
|
+
qs = qs.order_by(*ordering)
|
|
135
|
+
return qs.select_related('entity')
|
|
136
|
+
|
|
137
|
+
def get_inlines(self, request, obj):
|
|
138
|
+
if obj is None:
|
|
139
|
+
return []
|
|
140
|
+
return super().get_inlines(request, obj)
|
|
141
|
+
|
|
142
|
+
def get_fieldsets(self, request, obj=None):
|
|
143
|
+
if obj is None:
|
|
144
|
+
return [
|
|
145
|
+
('', {
|
|
146
|
+
'fields': [
|
|
147
|
+
'name',
|
|
148
|
+
'hidden',
|
|
149
|
+
'additional_info'
|
|
150
|
+
]
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
return [
|
|
154
|
+
('', {
|
|
155
|
+
'fields': [
|
|
156
|
+
'entity',
|
|
157
|
+
'name',
|
|
158
|
+
'hidden',
|
|
159
|
+
'additional_info'
|
|
160
|
+
]
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
def get_entity_model(self, request) -> EntityModel:
|
|
165
|
+
entity_slug = request.GET.get('entity_slug')
|
|
166
|
+
entity_model_qs = EntityModel.objects.for_user(user_model=request.user)
|
|
167
|
+
entity_model = get_object_or_404(entity_model_qs, slug__exact=entity_slug)
|
|
168
|
+
return entity_model
|
|
169
|
+
|
|
170
|
+
def has_add_permission(self, request):
|
|
171
|
+
if request.GET.get('entity_slug') is not None:
|
|
172
|
+
return True
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def add_view(self, request, form_url="", extra_context=None):
|
|
176
|
+
entity_model = self.get_entity_model(request)
|
|
177
|
+
extra_context = {
|
|
178
|
+
'entity_model': entity_model,
|
|
179
|
+
'title': f'Add Ledger: {entity_model.name}'
|
|
180
|
+
}
|
|
181
|
+
return super().add_view(request, form_url="", extra_context=extra_context)
|
|
182
|
+
|
|
183
|
+
def is_locked(self, obj):
|
|
184
|
+
return obj.is_locked()
|
|
185
|
+
|
|
186
|
+
is_locked.boolean = True
|
|
187
|
+
|
|
188
|
+
def is_posted(self, obj):
|
|
189
|
+
return obj.is_posted()
|
|
190
|
+
|
|
191
|
+
is_posted.boolean = True
|
|
192
|
+
|
|
193
|
+
def is_extended(self, obj):
|
|
194
|
+
return obj.has_wrapped_model()
|
|
195
|
+
|
|
196
|
+
is_extended.boolean = True
|
|
197
|
+
|
|
198
|
+
# ACTIONS....
|
|
199
|
+
def post(self, request, queryset):
|
|
200
|
+
for obj in queryset:
|
|
201
|
+
try:
|
|
202
|
+
obj.post(raise_exception=True, commit=False)
|
|
203
|
+
except LedgerModelValidationError as e:
|
|
204
|
+
messages.error(
|
|
205
|
+
request=request,
|
|
206
|
+
message=e.message
|
|
207
|
+
)
|
|
208
|
+
queryset.bulk_update(
|
|
209
|
+
objs=queryset,
|
|
210
|
+
fields=[
|
|
211
|
+
'posted',
|
|
212
|
+
'updated'
|
|
213
|
+
])
|
|
214
|
+
|
|
215
|
+
def lock(self, request, queryset):
|
|
216
|
+
for obj in queryset:
|
|
217
|
+
try:
|
|
218
|
+
obj.lock(raise_exception=True, commit=False)
|
|
219
|
+
except LedgerModelValidationError as e:
|
|
220
|
+
messages.error(
|
|
221
|
+
request=request,
|
|
222
|
+
message=e.message
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
queryset.bulk_update(
|
|
226
|
+
objs=queryset,
|
|
227
|
+
fields=[
|
|
228
|
+
'locked',
|
|
229
|
+
'updated'
|
|
230
|
+
])
|
|
231
|
+
|
|
232
|
+
def unpost(self, request, queryset):
|
|
233
|
+
for obj in queryset:
|
|
234
|
+
try:
|
|
235
|
+
obj.unpost(raise_exception=True, commit=False)
|
|
236
|
+
except LedgerModelValidationError as e:
|
|
237
|
+
messages.error(
|
|
238
|
+
request=request,
|
|
239
|
+
message=e.message
|
|
240
|
+
)
|
|
241
|
+
queryset.bulk_update(
|
|
242
|
+
objs=queryset,
|
|
243
|
+
fields=[
|
|
244
|
+
'posted',
|
|
245
|
+
'updated'
|
|
246
|
+
])
|
|
247
|
+
|
|
248
|
+
def unlock(self, request, queryset):
|
|
249
|
+
for obj in queryset:
|
|
250
|
+
try:
|
|
251
|
+
obj.unlock(raise_exception=True, commit=False)
|
|
252
|
+
except LedgerModelValidationError as e:
|
|
253
|
+
messages.error(
|
|
254
|
+
request=request,
|
|
255
|
+
message=e.message
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
queryset.bulk_update(
|
|
259
|
+
objs=queryset,
|
|
260
|
+
fields=[
|
|
261
|
+
'locked',
|
|
262
|
+
'updated'
|
|
263
|
+
])
|
|
264
|
+
|
|
265
|
+
def journal_entry_count(self, obj):
|
|
266
|
+
return obj.journal_entries__count
|
|
267
|
+
|
|
268
|
+
def earliest_journal_entry(self, obj):
|
|
269
|
+
return obj.earliest_timestamp
|
|
270
|
+
|
|
271
|
+
def save_model(self, request, obj: LedgerModel, form, change):
|
|
272
|
+
if not change:
|
|
273
|
+
entity_model = self.get_entity_model(request)
|
|
274
|
+
obj.entity = entity_model
|
|
275
|
+
return super().save_model(
|
|
276
|
+
request=request,
|
|
277
|
+
obj=obj,
|
|
278
|
+
form=form,
|
|
279
|
+
change=change
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
class Meta:
|
|
283
|
+
model = LedgerModel
|
django_ledger/forms/entity.py
CHANGED
|
@@ -20,15 +20,6 @@ class EntityModelCreateForm(ModelForm):
|
|
|
20
20
|
default_coa = BooleanField(required=False, initial=False, label=_('Populate Default CoA'))
|
|
21
21
|
activate_all_accounts = BooleanField(required=False, initial=False, label=_('Activate All Accounts'))
|
|
22
22
|
generate_sample_data = BooleanField(required=False, initial=False, label=_('Fill With Sample Data?'))
|
|
23
|
-
tx_quantity = IntegerField(required=True, initial=50, label=_('Number of Transaction Loops'),
|
|
24
|
-
validators=[
|
|
25
|
-
MinValueValidator(limit_value=0),
|
|
26
|
-
MaxValueValidator(limit_value=100)
|
|
27
|
-
],
|
|
28
|
-
widget=TextInput(attrs={
|
|
29
|
-
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
30
|
-
}))
|
|
31
|
-
|
|
32
23
|
|
|
33
24
|
def clean_name(self):
|
|
34
25
|
name = self.cleaned_data.get('name')
|
|
@@ -200,7 +191,8 @@ class EntityModelUpdateForm(ModelForm):
|
|
|
200
191
|
'placeholder': _('Website...')
|
|
201
192
|
}
|
|
202
193
|
),
|
|
203
|
-
'fy_start_month': Select(
|
|
204
|
-
|
|
205
|
-
|
|
194
|
+
'fy_start_month': Select(
|
|
195
|
+
attrs={
|
|
196
|
+
'class': 'input'
|
|
197
|
+
})
|
|
206
198
|
}
|
django_ledger/forms/ledger.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
from django.core.exceptions import ValidationError
|
|
1
2
|
from django.forms import ModelForm, TextInput, Select
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
4
|
|
|
3
5
|
from django_ledger.models.ledger import LedgerModel
|
|
4
6
|
from django_ledger.settings import DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
@@ -11,10 +13,19 @@ class LedgerModelCreateForm(ModelForm):
|
|
|
11
13
|
self.ENTITY_SLUG: str = entity_slug
|
|
12
14
|
self.USER_MODEL = user_model
|
|
13
15
|
|
|
16
|
+
def validate_unique(self):
|
|
17
|
+
exclude = self._get_validation_exclusions()
|
|
18
|
+
exclude.remove('entity')
|
|
19
|
+
try:
|
|
20
|
+
self.instance.validate_unique(exclude=exclude)
|
|
21
|
+
except ValidationError as e:
|
|
22
|
+
self._update_errors(e)
|
|
23
|
+
|
|
14
24
|
class Meta:
|
|
15
25
|
model = LedgerModel
|
|
16
26
|
fields = [
|
|
17
27
|
'name',
|
|
28
|
+
'ledger_xid'
|
|
18
29
|
]
|
|
19
30
|
widgets = {
|
|
20
31
|
'name': TextInput(
|
|
@@ -22,6 +33,14 @@ class LedgerModelCreateForm(ModelForm):
|
|
|
22
33
|
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
23
34
|
}
|
|
24
35
|
),
|
|
36
|
+
'ledger_xid': TextInput(
|
|
37
|
+
attrs={
|
|
38
|
+
'class': DJANGO_LEDGER_FORM_INPUT_CLASSES
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
labels = {
|
|
43
|
+
'ledger_xid': _('Ledger External ID')
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
|