django-ledger 0.8.2.1__py3-none-any.whl → 0.8.2.3__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/forms/data_import.py +104 -111
- django_ledger/io/roles.py +238 -303
- django_ledger/migrations/0027_alter_accountmodel_role_alter_receiptmodel_amount_and_more.py +159 -0
- django_ledger/models/__init__.py +1 -0
- django_ledger/models/chart_of_accounts.py +32 -90
- django_ledger/models/coa_default.py +1 -0
- django_ledger/models/customer.py +17 -26
- django_ledger/models/data_import.py +562 -182
- django_ledger/models/receipt.py +92 -123
- django_ledger/templates/django_ledger/data_import/data_import_job_txs.html +6 -0
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +68 -36
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +8 -10
- django_ledger/templates/django_ledger/receipt/receipt_detail.html +4 -2
- django_ledger/urls/data_import.py +3 -0
- django_ledger/views/data_import.py +47 -27
- {django_ledger-0.8.2.1.dist-info → django_ledger-0.8.2.3.dist-info}/METADATA +1 -1
- {django_ledger-0.8.2.1.dist-info → django_ledger-0.8.2.3.dist-info}/RECORD +22 -21
- {django_ledger-0.8.2.1.dist-info → django_ledger-0.8.2.3.dist-info}/WHEEL +0 -0
- {django_ledger-0.8.2.1.dist-info → django_ledger-0.8.2.3.dist-info}/licenses/AUTHORS.md +0 -0
- {django_ledger-0.8.2.1.dist-info → django_ledger-0.8.2.3.dist-info}/licenses/LICENSE +0 -0
- {django_ledger-0.8.2.1.dist-info → django_ledger-0.8.2.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Generated by Django 5.2.6 on 2025-10-03 01:36
|
|
2
|
+
|
|
3
|
+
import django.core.validators
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
dependencies = [
|
|
10
|
+
('django_ledger', '0026_stagedtransactionmodel_customer_model_and_more'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='accountmodel',
|
|
16
|
+
name='role',
|
|
17
|
+
field=models.CharField(
|
|
18
|
+
choices=[
|
|
19
|
+
(
|
|
20
|
+
'Assets',
|
|
21
|
+
[
|
|
22
|
+
('asset_ca_cash', 'Current Asset'),
|
|
23
|
+
('asset_ca_mkt_sec', 'Marketable Securities'),
|
|
24
|
+
('asset_ca_recv', 'Receivables'),
|
|
25
|
+
('asset_ca_inv', 'Inventory'),
|
|
26
|
+
('asset_ca_uncoll', 'Uncollectibles'),
|
|
27
|
+
('asset_ca_prepaid', 'Prepaid'),
|
|
28
|
+
('asset_ca_other', 'Other Liquid Assets'),
|
|
29
|
+
('asset_lti_notes', 'Notes Receivable'),
|
|
30
|
+
('asset_lti_land', 'Land'),
|
|
31
|
+
('asset_lti_sec', 'Securities'),
|
|
32
|
+
('asset_ppe_build', 'Buildings'),
|
|
33
|
+
('asset_ppe_build_accum_depr', 'Buildings - Accum. Depreciation'),
|
|
34
|
+
('asset_ppe_plant', 'Plant'),
|
|
35
|
+
('asset_ppe_plant_depr', 'Plant - Accum. Depreciation'),
|
|
36
|
+
('asset_ppe_equip', 'Equipment'),
|
|
37
|
+
('asset_ppe_equip_accum_depr', 'Equipment - Accum. Depreciation'),
|
|
38
|
+
('asset_ia', 'Intangible Assets'),
|
|
39
|
+
('asset_ia_accum_amort', 'Intangible Assets - Accum. Amortization'),
|
|
40
|
+
('asset_adjustment', 'Other Assets'),
|
|
41
|
+
],
|
|
42
|
+
),
|
|
43
|
+
(
|
|
44
|
+
'Liabilities',
|
|
45
|
+
[
|
|
46
|
+
('lia_cl_acc_payable', 'Accounts Payable'),
|
|
47
|
+
('lia_cl_credit_line', 'Credit Line'),
|
|
48
|
+
('lia_cl_wages_payable', 'Wages Payable'),
|
|
49
|
+
('lia_cl_int_payable', 'Interest Payable'),
|
|
50
|
+
('lia_cl_taxes_payable', 'Taxes Payable'),
|
|
51
|
+
('lia_cl_st_notes_payable', 'Short Term Notes Payable'),
|
|
52
|
+
('lia_cl_ltd_mat', 'Current Maturities of Long Tern Debt'),
|
|
53
|
+
('lia_cl_def_rev', 'Deferred Revenue'),
|
|
54
|
+
('lia_cl_other', 'Other Liabilities'),
|
|
55
|
+
('lia_ltl_notes', 'Long Term Notes Payable'),
|
|
56
|
+
('lia_ltl_bonds', 'Bonds Payable'),
|
|
57
|
+
('lia_ltl_mortgage', 'Mortgage Payable'),
|
|
58
|
+
],
|
|
59
|
+
),
|
|
60
|
+
(
|
|
61
|
+
'Equity',
|
|
62
|
+
[
|
|
63
|
+
('eq_capital', 'Capital'),
|
|
64
|
+
('eq_stock_common', 'Common Stock'),
|
|
65
|
+
('eq_stock_preferred', 'Preferred Stock'),
|
|
66
|
+
('eq_adjustment', 'Other Equity Adjustments'),
|
|
67
|
+
('eq_dividends', 'Dividends & Distributions to Shareholders'),
|
|
68
|
+
('in_operational', 'Operational Income'),
|
|
69
|
+
('in_passive', 'Investing/Passive Income'),
|
|
70
|
+
('in_interest', 'Interest Income'),
|
|
71
|
+
('in_gain_loss', 'Capital Gain/Loss Income'),
|
|
72
|
+
('in_other', 'Other Income'),
|
|
73
|
+
('cogs_regular', 'Cost of Goods Sold'),
|
|
74
|
+
('ex_regular', 'Regular Expense'),
|
|
75
|
+
('ex_interest_st', 'Interest Expense - Short Term Debt'),
|
|
76
|
+
('ex_interest', 'Interest Expense - Long Term Debt'),
|
|
77
|
+
('ex_taxes', 'Tax Expense'),
|
|
78
|
+
('ex_capital', 'Capital Expense'),
|
|
79
|
+
('ex_depreciation', 'Depreciation Expense'),
|
|
80
|
+
('ex_amortization', 'Amortization Expense'),
|
|
81
|
+
('ex_other', 'Other Expense'),
|
|
82
|
+
],
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
'Root',
|
|
86
|
+
[
|
|
87
|
+
('root_coa', 'CoA Root Account'),
|
|
88
|
+
('root_assets', 'Assets Root Account'),
|
|
89
|
+
('root_liabilities', 'Liabilities Root Account'),
|
|
90
|
+
('root_capital', 'Capital Root Account'),
|
|
91
|
+
('root_income', 'Income Root Account'),
|
|
92
|
+
('root_cogs', 'COGS Root Account'),
|
|
93
|
+
('root_expenses', 'Expenses Root Account'),
|
|
94
|
+
],
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
max_length=30,
|
|
98
|
+
verbose_name='Account Role',
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
migrations.AlterField(
|
|
102
|
+
model_name='receiptmodel',
|
|
103
|
+
name='amount',
|
|
104
|
+
field=models.DecimalField(
|
|
105
|
+
decimal_places=2,
|
|
106
|
+
help_text='Amount of the receipt.',
|
|
107
|
+
max_digits=20,
|
|
108
|
+
validators=[django.core.validators.MinValueValidator(limit_value=0)],
|
|
109
|
+
verbose_name='Receipt Amount',
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
migrations.AlterField(
|
|
113
|
+
model_name='receiptmodel',
|
|
114
|
+
name='receipt_account',
|
|
115
|
+
field=models.ForeignKey(
|
|
116
|
+
blank=True,
|
|
117
|
+
help_text='The income or expense account where this transaction will be reflected',
|
|
118
|
+
null=True,
|
|
119
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
120
|
+
to='django_ledger.accountmodel',
|
|
121
|
+
verbose_name='PnL Account',
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
migrations.AlterField(
|
|
125
|
+
model_name='receiptmodel',
|
|
126
|
+
name='receipt_type',
|
|
127
|
+
field=models.CharField(
|
|
128
|
+
choices=[
|
|
129
|
+
('sales', 'Sales Receipt'),
|
|
130
|
+
('customer_refund', 'Sales Refund'),
|
|
131
|
+
('expense', 'Expense Receipt'),
|
|
132
|
+
('expense_refund', 'Expense Refund'),
|
|
133
|
+
('transfer', 'Transfer Receipt'),
|
|
134
|
+
('debt_paydown', 'Debt Paydown Receipt'),
|
|
135
|
+
],
|
|
136
|
+
max_length=15,
|
|
137
|
+
verbose_name='Receipt Type',
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
migrations.AlterField(
|
|
141
|
+
model_name='stagedtransactionmodel',
|
|
142
|
+
name='receipt_type',
|
|
143
|
+
field=models.CharField(
|
|
144
|
+
blank=True,
|
|
145
|
+
choices=[
|
|
146
|
+
('sales', 'Sales Receipt'),
|
|
147
|
+
('customer_refund', 'Sales Refund'),
|
|
148
|
+
('expense', 'Expense Receipt'),
|
|
149
|
+
('expense_refund', 'Expense Refund'),
|
|
150
|
+
('transfer', 'Transfer Receipt'),
|
|
151
|
+
('debt_paydown', 'Debt Paydown Receipt'),
|
|
152
|
+
],
|
|
153
|
+
help_text='The receipt type of the transaction.',
|
|
154
|
+
max_length=20,
|
|
155
|
+
null=True,
|
|
156
|
+
verbose_name='Receipt Type',
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
]
|
django_ledger/models/__init__.py
CHANGED
|
@@ -15,5 +15,6 @@ from django_ledger.models.unit import *
|
|
|
15
15
|
from django_ledger.models.purchase_order import *
|
|
16
16
|
from django_ledger.models.closing_entry import *
|
|
17
17
|
from django_ledger.models.entity import *
|
|
18
|
+
|
|
18
19
|
from django_ledger.models.data_import import *
|
|
19
20
|
from django_ledger.models.receipt import *
|
|
@@ -116,9 +116,7 @@ class ChartOfAccountModelQuerySet(QuerySet):
|
|
|
116
116
|
if user_model.is_superuser:
|
|
117
117
|
return self
|
|
118
118
|
|
|
119
|
-
return self.filter(
|
|
120
|
-
(Q(entity__admin=user_model) | Q(entity__managers__in=[user_model]))
|
|
121
|
-
)
|
|
119
|
+
return self.filter((Q(entity__admin=user_model) | Q(entity__managers__in=[user_model])))
|
|
122
120
|
|
|
123
121
|
|
|
124
122
|
class ChartOfAccountModelManager(Manager):
|
|
@@ -148,9 +146,7 @@ class ChartOfAccountModelManager(Manager):
|
|
|
148
146
|
filter=Q(accountmodel__depth__gt=2) & Q(accountmodel__active=True),
|
|
149
147
|
),
|
|
150
148
|
# Root-group presence and uniqueness checks:
|
|
151
|
-
accountmodel_rootgroup__count=Count(
|
|
152
|
-
'accountmodel', filter=Q(accountmodel__role__in=ROOT_GROUP)
|
|
153
|
-
),
|
|
149
|
+
accountmodel_rootgroup__count=Count('accountmodel', filter=Q(accountmodel__role__in=ROOT_GROUP)),
|
|
154
150
|
accountmodel_rootgroup_roles__distinct_count=Count(
|
|
155
151
|
'accountmodel__role',
|
|
156
152
|
filter=Q(accountmodel__role__in=ROOT_GROUP_META),
|
|
@@ -161,11 +157,7 @@ class ChartOfAccountModelManager(Manager):
|
|
|
161
157
|
configured=models.Case(
|
|
162
158
|
models.When(
|
|
163
159
|
Q(accountmodel_rootgroup__count__gte=1)
|
|
164
|
-
& Q(
|
|
165
|
-
accountmodel_rootgroup__count=F(
|
|
166
|
-
'accountmodel_rootgroup_roles__distinct_count'
|
|
167
|
-
)
|
|
168
|
-
),
|
|
160
|
+
& Q(accountmodel_rootgroup__count=F('accountmodel_rootgroup_roles__distinct_count')),
|
|
169
161
|
then=Value(True, output_field=BooleanField()),
|
|
170
162
|
),
|
|
171
163
|
default=Value(False, output_field=BooleanField()),
|
|
@@ -177,7 +169,9 @@ class ChartOfAccountModelManager(Manager):
|
|
|
177
169
|
|
|
178
170
|
@deprecated_entity_slug_behavior
|
|
179
171
|
def for_entity(
|
|
180
|
-
self,
|
|
172
|
+
self,
|
|
173
|
+
entity_model: 'Union[EntityModel | str | UUID]' = None, # noqa: F821
|
|
174
|
+
**kwargs, # noqa: F821
|
|
181
175
|
) -> ChartOfAccountModelQuerySet:
|
|
182
176
|
"""
|
|
183
177
|
Fetches a QuerySet of ChartOfAccountsModel associated with a specific EntityModel & UserModel.
|
|
@@ -240,16 +234,10 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
240
234
|
"""
|
|
241
235
|
|
|
242
236
|
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
|
243
|
-
entity = models.ForeignKey(
|
|
244
|
-
'django_ledger.EntityModel', verbose_name=_('Entity'), on_delete=models.CASCADE
|
|
245
|
-
)
|
|
237
|
+
entity = models.ForeignKey('django_ledger.EntityModel', verbose_name=_('Entity'), on_delete=models.CASCADE)
|
|
246
238
|
active = models.BooleanField(default=True, verbose_name=_('Is Active'))
|
|
247
|
-
description = models.TextField(
|
|
248
|
-
|
|
249
|
-
)
|
|
250
|
-
objects = ChartOfAccountModelManager.from_queryset(
|
|
251
|
-
queryset_class=ChartOfAccountModelQuerySet
|
|
252
|
-
)()
|
|
239
|
+
description = models.TextField(verbose_name=_('CoA Description'), null=True, blank=True)
|
|
240
|
+
objects = ChartOfAccountModelManager.from_queryset(queryset_class=ChartOfAccountModelQuerySet)()
|
|
253
241
|
|
|
254
242
|
class Meta:
|
|
255
243
|
abstract = True
|
|
@@ -299,9 +287,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
299
287
|
|
|
300
288
|
if len(existing_root_roles) > 0:
|
|
301
289
|
if raise_exception:
|
|
302
|
-
raise ChartOfAccountsModelValidationError(
|
|
303
|
-
message=f'Root Nodes already Exist in CoA {self.uuid}...'
|
|
304
|
-
)
|
|
290
|
+
raise ChartOfAccountsModelValidationError(message=f'Root Nodes already Exist in CoA {self.uuid}...')
|
|
305
291
|
return
|
|
306
292
|
|
|
307
293
|
if ROOT_COA not in existing_root_roles:
|
|
@@ -322,9 +308,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
322
308
|
AccountModel.add_root(instance=root_account)
|
|
323
309
|
|
|
324
310
|
# must retrieve root model after added pero django-treebeard documentation...
|
|
325
|
-
coa_root_account_model = AccountModel.objects.get(
|
|
326
|
-
uuid__exact=account_pk
|
|
327
|
-
)
|
|
311
|
+
coa_root_account_model = AccountModel.objects.get(uuid__exact=account_pk)
|
|
328
312
|
|
|
329
313
|
for root_role in ROOT_GROUP_LEVEL_2:
|
|
330
314
|
if root_role not in existing_root_roles:
|
|
@@ -398,9 +382,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
398
382
|
|
|
399
383
|
if account_model.coa_model_id != self.uuid:
|
|
400
384
|
raise ChartOfAccountsModelValidationError(
|
|
401
|
-
message=_(
|
|
402
|
-
f'The account model {account_model} is not part of the chart of accounts {self.name}.'
|
|
403
|
-
),
|
|
385
|
+
message=_(f'The account model {account_model} is not part of the chart of accounts {self.name}.'),
|
|
404
386
|
)
|
|
405
387
|
|
|
406
388
|
# Chart of Accounts hasn't been configured...
|
|
@@ -412,29 +394,17 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
412
394
|
root_account_qs = self.get_coa_root_accounts_qs()
|
|
413
395
|
|
|
414
396
|
if account_model.is_asset():
|
|
415
|
-
qs = root_account_qs.filter(
|
|
416
|
-
code__exact=ROOT_GROUP_META[ROOT_ASSETS]['code']
|
|
417
|
-
)
|
|
397
|
+
qs = root_account_qs.filter(code__exact=ROOT_GROUP_META[ROOT_ASSETS]['code'])
|
|
418
398
|
elif account_model.is_liability():
|
|
419
|
-
qs = root_account_qs.filter(
|
|
420
|
-
code__exact=ROOT_GROUP_META[ROOT_LIABILITIES]['code']
|
|
421
|
-
)
|
|
399
|
+
qs = root_account_qs.filter(code__exact=ROOT_GROUP_META[ROOT_LIABILITIES]['code'])
|
|
422
400
|
elif account_model.is_capital():
|
|
423
|
-
qs = root_account_qs.filter(
|
|
424
|
-
code__exact=ROOT_GROUP_META[ROOT_CAPITAL]['code']
|
|
425
|
-
)
|
|
401
|
+
qs = root_account_qs.filter(code__exact=ROOT_GROUP_META[ROOT_CAPITAL]['code'])
|
|
426
402
|
elif account_model.is_income():
|
|
427
|
-
qs = root_account_qs.filter(
|
|
428
|
-
code__exact=ROOT_GROUP_META[ROOT_INCOME]['code']
|
|
429
|
-
)
|
|
403
|
+
qs = root_account_qs.filter(code__exact=ROOT_GROUP_META[ROOT_INCOME]['code'])
|
|
430
404
|
elif account_model.is_cogs():
|
|
431
|
-
qs = root_account_qs.filter(
|
|
432
|
-
code__exact=ROOT_GROUP_META[ROOT_COGS]['code']
|
|
433
|
-
)
|
|
405
|
+
qs = root_account_qs.filter(code__exact=ROOT_GROUP_META[ROOT_COGS]['code'])
|
|
434
406
|
elif account_model.is_expense():
|
|
435
|
-
qs = root_account_qs.filter(
|
|
436
|
-
code__exact=ROOT_GROUP_META[ROOT_EXPENSES]['code']
|
|
437
|
-
)
|
|
407
|
+
qs = root_account_qs.filter(code__exact=ROOT_GROUP_META[ROOT_EXPENSES]['code'])
|
|
438
408
|
else:
|
|
439
409
|
raise ChartOfAccountsModelValidationError(
|
|
440
410
|
message=f'Unable to locate Balance Sheet'
|
|
@@ -445,9 +415,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
445
415
|
return qs
|
|
446
416
|
return qs.get()
|
|
447
417
|
|
|
448
|
-
raise ChartOfAccountsModelValidationError(
|
|
449
|
-
message='Adding Root account to Chart of Accounts is not allowed.'
|
|
450
|
-
)
|
|
418
|
+
raise ChartOfAccountsModelValidationError(message='Adding Root account to Chart of Accounts is not allowed.')
|
|
451
419
|
|
|
452
420
|
def get_non_root_coa_accounts_qs(self) -> AccountModelQuerySet:
|
|
453
421
|
"""
|
|
@@ -518,13 +486,9 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
518
486
|
"""
|
|
519
487
|
if self.slug:
|
|
520
488
|
if raise_exception:
|
|
521
|
-
raise ChartOfAccountsModelValidationError(
|
|
522
|
-
message=_(f'CoA {self.uuid} already has a slug')
|
|
523
|
-
)
|
|
489
|
+
raise ChartOfAccountsModelValidationError(message=_(f'CoA {self.uuid} already has a slug'))
|
|
524
490
|
return
|
|
525
|
-
self.slug = f'coa-{self.entity.slug[-5:]}-' + ''.join(
|
|
526
|
-
choices(SLUG_SUFFIX, k=15)
|
|
527
|
-
)
|
|
491
|
+
self.slug = f'coa-{self.entity.slug[-5:]}-' + ''.join(choices(SLUG_SUFFIX, k=15))
|
|
528
492
|
|
|
529
493
|
if commit:
|
|
530
494
|
self.save(update_fields=['slug', 'updated'])
|
|
@@ -569,14 +533,10 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
569
533
|
|
|
570
534
|
"""
|
|
571
535
|
if not isinstance(account_model_qs, AccountModelQuerySet):
|
|
572
|
-
raise ChartOfAccountsModelValidationError(
|
|
573
|
-
message='Must pass an instance of AccountModelQuerySet'
|
|
574
|
-
)
|
|
536
|
+
raise ChartOfAccountsModelValidationError(message='Must pass an instance of AccountModelQuerySet')
|
|
575
537
|
for acc_model in account_model_qs:
|
|
576
538
|
if not acc_model.coa_model_id == self.uuid:
|
|
577
|
-
raise ChartOfAccountsModelValidationError(
|
|
578
|
-
message=f'Invalid root queryset for CoA {self.name}'
|
|
579
|
-
)
|
|
539
|
+
raise ChartOfAccountsModelValidationError(message=f'Invalid root queryset for CoA {self.name}')
|
|
580
540
|
|
|
581
541
|
def insert_account(
|
|
582
542
|
self,
|
|
@@ -680,9 +640,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
680
640
|
)
|
|
681
641
|
account_model.clean()
|
|
682
642
|
|
|
683
|
-
account_model = self.insert_account(
|
|
684
|
-
account_model=account_model, root_account_qs=root_account_qs
|
|
685
|
-
)
|
|
643
|
+
account_model = self.insert_account(account_model=account_model, root_account_qs=root_account_qs)
|
|
686
644
|
return account_model
|
|
687
645
|
|
|
688
646
|
# ACTIONS -----
|
|
@@ -697,9 +655,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
697
655
|
account_qs.update(locked=False)
|
|
698
656
|
return account_qs
|
|
699
657
|
|
|
700
|
-
def mark_as_default(
|
|
701
|
-
self, commit: bool = False, raise_exception: bool = False, **kwargs
|
|
702
|
-
):
|
|
658
|
+
def mark_as_default(self, commit: bool = False, raise_exception: bool = False, **kwargs):
|
|
703
659
|
"""
|
|
704
660
|
Marks the current Chart of Accounts instances as default for the EntityModel.
|
|
705
661
|
|
|
@@ -719,9 +675,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
719
675
|
if not self.can_mark_as_default():
|
|
720
676
|
if raise_exception:
|
|
721
677
|
raise ChartOfAccountsModelValidationError(
|
|
722
|
-
message=_(
|
|
723
|
-
f'The Chart of Accounts {self.slug} cannot be marked as default'
|
|
724
|
-
)
|
|
678
|
+
message=_(f'The Chart of Accounts {self.slug} cannot be marked as default')
|
|
725
679
|
)
|
|
726
680
|
return
|
|
727
681
|
self.entity.default_coa_id = self.uuid
|
|
@@ -752,9 +706,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
752
706
|
"""
|
|
753
707
|
return all([self.is_active(), not self.is_default()])
|
|
754
708
|
|
|
755
|
-
def mark_as_active(
|
|
756
|
-
self, commit: bool = False, raise_exception: bool = False, **kwargs
|
|
757
|
-
):
|
|
709
|
+
def mark_as_active(self, commit: bool = False, raise_exception: bool = False, **kwargs):
|
|
758
710
|
"""
|
|
759
711
|
Marks the current Chart of Accounts as Active.
|
|
760
712
|
|
|
@@ -767,9 +719,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
767
719
|
"""
|
|
768
720
|
if self.is_active():
|
|
769
721
|
if raise_exception:
|
|
770
|
-
raise ChartOfAccountsModelValidationError(
|
|
771
|
-
message=_('The Chart of Accounts is currently active.')
|
|
772
|
-
)
|
|
722
|
+
raise ChartOfAccountsModelValidationError(message=_('The Chart of Accounts is currently active.'))
|
|
773
723
|
return
|
|
774
724
|
|
|
775
725
|
self.active = True
|
|
@@ -777,9 +727,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
777
727
|
if commit:
|
|
778
728
|
self.save(update_fields=['active', 'updated'])
|
|
779
729
|
|
|
780
|
-
def mark_as_inactive(
|
|
781
|
-
self, commit: bool = False, raise_exception: bool = False, **kwargs
|
|
782
|
-
):
|
|
730
|
+
def mark_as_inactive(self, commit: bool = False, raise_exception: bool = False, **kwargs):
|
|
783
731
|
"""
|
|
784
732
|
Marks the current Chart of Accounts as Active.
|
|
785
733
|
|
|
@@ -792,9 +740,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
792
740
|
"""
|
|
793
741
|
if not self.is_active():
|
|
794
742
|
if raise_exception:
|
|
795
|
-
raise ChartOfAccountsModelValidationError(
|
|
796
|
-
message=_('The Chart of Accounts is currently not active.')
|
|
797
|
-
)
|
|
743
|
+
raise ChartOfAccountsModelValidationError(message=_('The Chart of Accounts is currently not active.'))
|
|
798
744
|
return
|
|
799
745
|
|
|
800
746
|
self.active = False
|
|
@@ -846,9 +792,7 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
846
792
|
)
|
|
847
793
|
|
|
848
794
|
def get_coa_list_url(self):
|
|
849
|
-
return reverse(
|
|
850
|
-
viewname='django_ledger:coa-list', kwargs={'entity_slug': self.entity_slug}
|
|
851
|
-
)
|
|
795
|
+
return reverse(viewname='django_ledger:coa-list', kwargs={'entity_slug': self.entity_slug})
|
|
852
796
|
|
|
853
797
|
def get_coa_list_inactive_url(self):
|
|
854
798
|
return reverse(
|
|
@@ -906,9 +850,7 @@ class ChartOfAccountModel(ChartOfAccountModelAbstract):
|
|
|
906
850
|
def chartofaccountsmodel_presave(instance: ChartOfAccountModelAbstract, **kwargs):
|
|
907
851
|
instance.generate_slug()
|
|
908
852
|
if instance.is_default() and not instance.active:
|
|
909
|
-
raise ChartOfAccountsModelValidationError(
|
|
910
|
-
_('Default Chart of Accounts cannot be deactivated.')
|
|
911
|
-
)
|
|
853
|
+
raise ChartOfAccountsModelValidationError(_('Default Chart of Accounts cannot be deactivated.'))
|
|
912
854
|
|
|
913
855
|
|
|
914
856
|
@receiver(post_save, sender=ChartOfAccountModel)
|
|
@@ -109,6 +109,7 @@ Default Chart of Accounts Table
|
|
|
109
109
|
4050 in_other credit Other Income root_income
|
|
110
110
|
4020 in_passive credit Investing Income root_income
|
|
111
111
|
2010 lia_cl_acc_payable credit Accounts Payable root_liabilities
|
|
112
|
+
2015 lia_cl_credit_line credit Accounts Payable root_liabilities
|
|
112
113
|
2060 lia_cl_def_rev credit Deferred Revenues root_liabilities
|
|
113
114
|
2030 lia_cl_int_payable credit Interest Payable root_liabilities
|
|
114
115
|
2050 lia_cl_ltd_mat credit Current Maturities LT Debt root_liabilities
|
django_ledger/models/customer.py
CHANGED
|
@@ -54,7 +54,7 @@ class CustomerModelQueryset(QuerySet):
|
|
|
54
54
|
reports.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
-
def for_user(self, user_model):
|
|
57
|
+
def for_user(self, user_model) -> 'CustomerModelQueryset':
|
|
58
58
|
"""
|
|
59
59
|
Fetches a QuerySet of BillModels that the UserModel as access to.
|
|
60
60
|
May include BillModels from multiple Entities.
|
|
@@ -70,12 +70,9 @@ class CustomerModelQueryset(QuerySet):
|
|
|
70
70
|
"""
|
|
71
71
|
if user_model.is_superuser:
|
|
72
72
|
return self
|
|
73
|
-
return self.filter(
|
|
74
|
-
Q(entity_model__admin=user_model)
|
|
75
|
-
| Q(entity_model__managers__in=[user_model])
|
|
76
|
-
)
|
|
73
|
+
return self.filter(Q(entity_model__admin=user_model) | Q(entity_model__managers__in=[user_model]))
|
|
77
74
|
|
|
78
|
-
def active(self) ->
|
|
75
|
+
def active(self) -> 'CustomerModelQueryset':
|
|
79
76
|
"""
|
|
80
77
|
Active customers can be assigned to new Invoices and show on dropdown menus and views.
|
|
81
78
|
|
|
@@ -86,7 +83,7 @@ class CustomerModelQueryset(QuerySet):
|
|
|
86
83
|
"""
|
|
87
84
|
return self.filter(active=True)
|
|
88
85
|
|
|
89
|
-
def inactive(self) ->
|
|
86
|
+
def inactive(self) -> 'CustomerModelQueryset':
|
|
90
87
|
"""
|
|
91
88
|
Active customers can be assigned to new Invoices and show on dropdown menus and views.
|
|
92
89
|
Marking CustomerModels as inactive can help reduce Database load to populate select inputs and also inactivate
|
|
@@ -100,7 +97,7 @@ class CustomerModelQueryset(QuerySet):
|
|
|
100
97
|
"""
|
|
101
98
|
return self.filter(active=False)
|
|
102
99
|
|
|
103
|
-
def hidden(self) ->
|
|
100
|
+
def hidden(self) -> 'CustomerModelQueryset':
|
|
104
101
|
"""
|
|
105
102
|
Hidden customers do not show on dropdown menus, but may be used via APIs or any other method that does not
|
|
106
103
|
involve the UI.
|
|
@@ -112,7 +109,7 @@ class CustomerModelQueryset(QuerySet):
|
|
|
112
109
|
"""
|
|
113
110
|
return self.filter(hidden=True)
|
|
114
111
|
|
|
115
|
-
def visible(self) ->
|
|
112
|
+
def visible(self) -> 'CustomerModelQueryset':
|
|
116
113
|
"""
|
|
117
114
|
Visible customers show on dropdown menus and views. Visible customers are active and not hidden.
|
|
118
115
|
|
|
@@ -130,10 +127,11 @@ class CustomerModelManager(Manager):
|
|
|
130
127
|
CustomerModel.
|
|
131
128
|
"""
|
|
132
129
|
|
|
130
|
+
def get_queryset(self) -> CustomerModelQueryset:
|
|
131
|
+
return CustomerModelQueryset(self.model, using=self._db)
|
|
132
|
+
|
|
133
133
|
@deprecated_entity_slug_behavior
|
|
134
|
-
def for_entity(
|
|
135
|
-
self, entity_model: 'EntityModel | str | UUID' = None, **kwargs
|
|
136
|
-
) -> CustomerModelQueryset:
|
|
134
|
+
def for_entity(self, entity_model: 'EntityModel | str | UUID', **kwargs) -> CustomerModelQueryset: # noqa: F821
|
|
137
135
|
"""
|
|
138
136
|
Fetches a QuerySet of CustomerModel associated with a specific EntityModel & UserModel.
|
|
139
137
|
May pass an instance of EntityModel or a String representing the EntityModel slug.
|
|
@@ -161,16 +159,15 @@ class CustomerModelManager(Manager):
|
|
|
161
159
|
qs = qs.for_user(kwargs['user_model'])
|
|
162
160
|
|
|
163
161
|
if isinstance(entity_model, EntityModel):
|
|
164
|
-
|
|
162
|
+
return qs.filter(entity_model=entity_model)
|
|
165
163
|
elif isinstance(entity_model, str):
|
|
166
|
-
|
|
164
|
+
return qs.filter(entity_model__slug__exact=entity_model)
|
|
167
165
|
elif isinstance(entity_model, UUID):
|
|
168
|
-
|
|
166
|
+
return qs.filter(entity_model_id=entity_model)
|
|
169
167
|
else:
|
|
170
168
|
raise CustomerModelValidationError(
|
|
171
169
|
message='Must pass EntityModel, slug or EntityModel UUID',
|
|
172
170
|
)
|
|
173
|
-
return qs
|
|
174
171
|
|
|
175
172
|
|
|
176
173
|
class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMixIn):
|
|
@@ -232,9 +229,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
|
|
|
232
229
|
description = models.TextField()
|
|
233
230
|
active = models.BooleanField(default=True)
|
|
234
231
|
hidden = models.BooleanField(default=False)
|
|
235
|
-
picture = models.ImageField(
|
|
236
|
-
upload_to=customer_picture_upload_to, null=True, blank=True
|
|
237
|
-
)
|
|
232
|
+
picture = models.ImageField(upload_to=customer_picture_upload_to, null=True, blank=True)
|
|
238
233
|
|
|
239
234
|
additional_info = models.JSONField(null=True, blank=True, default=dict)
|
|
240
235
|
|
|
@@ -292,9 +287,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
|
|
|
292
287
|
'key__exact': EntityStateModel.KEY_CUSTOMER,
|
|
293
288
|
}
|
|
294
289
|
|
|
295
|
-
state_model_qs = EntityStateModel.objects.filter(
|
|
296
|
-
**LOOKUP
|
|
297
|
-
).select_for_update()
|
|
290
|
+
state_model_qs = EntityStateModel.objects.filter(**LOOKUP).select_for_update()
|
|
298
291
|
state_model = state_model_qs.get()
|
|
299
292
|
state_model.sequence = F('sequence') + 1
|
|
300
293
|
state_model.save()
|
|
@@ -344,11 +337,9 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
|
|
|
344
337
|
|
|
345
338
|
return self.customer_number
|
|
346
339
|
|
|
347
|
-
def validate_for_entity(self, entity_model: 'EntityModel'):
|
|
340
|
+
def validate_for_entity(self, entity_model: 'EntityModel'): # noqa: F821
|
|
348
341
|
if entity_model.uuid != self.entity_model_id:
|
|
349
|
-
raise CustomerModelValidationError(
|
|
350
|
-
'EntityModel does not belong to this Vendor'
|
|
351
|
-
)
|
|
342
|
+
raise CustomerModelValidationError('EntityModel does not belong to this Vendor')
|
|
352
343
|
|
|
353
344
|
def clean(self):
|
|
354
345
|
"""
|