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 CHANGED
@@ -6,7 +6,7 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
6
6
  default_app_config = 'django_ledger.apps.DjangoLedgerConfig'
7
7
 
8
8
  """Django Ledger"""
9
- __version__ = '0.8.2.1'
9
+ __version__ = '0.8.2.3'
10
10
  __license__ = 'GPLv3 License'
11
11
 
12
12
  __author__ = 'Miguel Sanda'
@@ -1,3 +1,5 @@
1
+ from itertools import groupby
2
+
1
3
  from django import forms
2
4
  from django.forms import (
3
5
  ModelForm,
@@ -11,7 +13,7 @@ from django.forms import (
11
13
  )
12
14
  from django.utils.translation import gettext_lazy as _
13
15
 
14
- from django_ledger.io import GROUP_EXPENSES, GROUP_INCOME
16
+ from django_ledger.io import GROUP_EXPENSES, GROUP_INCOME, GROUP_TRANSFERS, GROUP_DEBT_PAYMENT
15
17
  from django_ledger.models import (
16
18
  StagedTransactionModel,
17
19
  ImportJobModel,
@@ -24,9 +26,7 @@ class ImportJobModelCreateForm(ModelForm):
24
26
  def __init__(self, entity_model: EntityModel, *args, **kwargs):
25
27
  super().__init__(*args, **kwargs)
26
28
  self.ENTITY_MODEL: EntityModel = entity_model
27
- self.fields[
28
- 'bank_account_model'
29
- ].queryset = self.ENTITY_MODEL.bankaccountmodel_set.all().active()
29
+ self.fields['bank_account_model'].queryset = self.ENTITY_MODEL.bankaccountmodel_set.all().active()
30
30
 
31
31
  ofx_file = forms.FileField(
32
32
  label='Select File...',
@@ -54,9 +54,7 @@ class ImportJobModelCreateForm(ModelForm):
54
54
  ),
55
55
  }
56
56
  help_texts = {
57
- 'bank_account_model': _(
58
- 'Select the bank account to import transactions from.'
59
- ),
57
+ 'bank_account_model': _('Select the bank account to import transactions from.'),
60
58
  }
61
59
 
62
60
 
@@ -64,9 +62,7 @@ class ImportJobModelUpdateForm(ModelForm):
64
62
  class Meta:
65
63
  model = ImportJobModel
66
64
  fields = ['description']
67
- widgets = {
68
- 'description': TextInput(attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES})
69
- }
65
+ widgets = {'description': TextInput(attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES})}
70
66
 
71
67
 
72
68
  class StagedTransactionModelForm(ModelForm):
@@ -75,59 +71,49 @@ class StagedTransactionModelForm(ModelForm):
75
71
 
76
72
  def __init__(self, base_formset_instance, *args, **kwargs):
77
73
  super().__init__(*args, **kwargs)
78
- self.BASE_FORMSET_INSTANCE: 'BaseStagedTransactionModelFormSet' = (
79
- base_formset_instance
80
- )
74
+ self.BASE_FORMSET_INSTANCE: 'BaseStagedTransactionModelFormSet' = base_formset_instance
81
75
  self.VENDOR_CHOICES = self.BASE_FORMSET_INSTANCE.VENDOR_CHOICES
82
76
  self.VENDOR_MAP = self.BASE_FORMSET_INSTANCE.VENDOR_MAP
83
77
 
84
78
  self.CUSTOMER_CHOICES = self.BASE_FORMSET_INSTANCE.CUSTOMER_CHOICES
85
79
  self.CUSTOMER_MAP = self.BASE_FORMSET_INSTANCE.CUSTOMER_MAP
86
80
 
87
- self.EXPENSE_ACCOUNT_CHOICES = (
88
- self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_EXPENSES_CHOICES
89
- )
90
- self.SALES_ACCOUNT_CHOICES = (
91
- self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_SALES_CHOICES
92
- )
93
- self.SHOW_VENDOR_FIELD: bool = False
94
- self.SHOW_CUSTOMER_FIELD: bool = False
81
+ self.EXPENSE_ACCOUNT_CHOICES = self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_EXPENSES_CHOICES
82
+ self.SALES_ACCOUNT_CHOICES = self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_SALES_CHOICES
83
+ self.TRANSFER_ACCOUNT_CHOICES = self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_TRANSFERS_CHOICES
84
+ self.CC_PAYMENT_ACCOUNT_CHOICES = self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_TRANSFERS_CC_PAYMENT
95
85
 
96
86
  staged_tx_model: StagedTransactionModel = getattr(self, 'instance', None)
97
87
 
98
- if staged_tx_model.can_migrate_receipt():
99
- if staged_tx_model.is_expense():
100
- self.fields['customer_model'].widget = forms.HiddenInput()
101
- self.fields['vendor_model'].choices = self.VENDOR_CHOICES
102
- self.fields['account_model'].choices = self.EXPENSE_ACCOUNT_CHOICES
103
- self.SHOW_VENDOR_FIELD = True
104
- elif staged_tx_model.is_sales():
105
- self.fields['vendor_model'].widget = forms.HiddenInput()
106
- self.fields['customer_model'].choices = self.CUSTOMER_CHOICES
107
- self.fields['account_model'].choices = self.SALES_ACCOUNT_CHOICES
108
- self.SHOW_CUSTOMER_FIELD = True
109
- else:
110
- self.fields['customer_model'].widget = forms.HiddenInput()
111
- self.fields['vendor_model'].widget = forms.HiddenInput()
112
- # avoids multiple DB queries rendering the formset...
113
- self.fields[
114
- 'account_model'
115
- ].choices = self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_CHOICES
88
+ self.fields['vendor_model'].choices = self.VENDOR_CHOICES
89
+ self.fields['customer_model'].choices = self.CUSTOMER_CHOICES
90
+ self.fields['account_model'].choices = self.BASE_FORMSET_INSTANCE.ACCOUNT_MODEL_CHOICES
116
91
 
117
92
  # avoids multiple DB queries rendering the formset...
118
- self.fields[
119
- 'unit_model'
120
- ].choices = self.BASE_FORMSET_INSTANCE.UNIT_MODEL_CHOICES
93
+ self.fields['unit_model'].choices = self.BASE_FORMSET_INSTANCE.UNIT_MODEL_CHOICES
121
94
 
122
95
  if staged_tx_model:
123
- if not staged_tx_model.is_children():
96
+ if staged_tx_model.is_sales():
97
+ self.fields['account_model'].choices = self.SALES_ACCOUNT_CHOICES
98
+
99
+ if staged_tx_model.is_expense():
100
+ self.fields['account_model'].choices = self.EXPENSE_ACCOUNT_CHOICES
101
+
102
+ if staged_tx_model.is_transfer():
103
+ self.fields['account_model'].choices = self.TRANSFER_ACCOUNT_CHOICES
104
+
105
+ if staged_tx_model.is_debt_payment():
106
+ self.fields['account_model'].choices = self.CC_PAYMENT_ACCOUNT_CHOICES
107
+
108
+ if not staged_tx_model.can_have_amount_split():
124
109
  self.fields['amount_split'].widget = HiddenInput()
125
110
  self.fields['amount_split'].disabled = True
126
- else:
111
+
112
+ if not staged_tx_model.can_have_bundle_split():
127
113
  self.fields['bundle_split'].widget = HiddenInput()
128
114
  self.fields['bundle_split'].disabled = True
129
115
 
130
- if staged_tx_model.has_children():
116
+ if not staged_tx_model.can_have_receipt():
131
117
  self.fields['receipt_type'].widget = HiddenInput()
132
118
  self.fields['receipt_type'].disabled = True
133
119
 
@@ -139,22 +125,15 @@ class StagedTransactionModelForm(ModelForm):
139
125
  self.fields['unit_model'].widget = HiddenInput()
140
126
  self.fields['unit_model'].disabled = True
141
127
 
142
- # receipt-related field visibility: not allowed for children rows
143
- if staged_tx_model.is_children():
144
- for f in [
145
- 'receipt_type',
146
- 'vendor_model',
147
- 'customer_model',
148
- 'bundle_split',
149
- ]:
150
- self.fields[f].widget = HiddenInput()
151
- self.fields[f].disabled = True
152
-
153
- if staged_tx_model.is_single():
154
- self.fields['bundle_split'].widget = HiddenInput()
155
- self.fields['bundle_split'].disabled = True
128
+ if not staged_tx_model.can_have_vendor():
129
+ self.fields['vendor_model'].widget = HiddenInput()
130
+ self.fields['vendor_model'].disabled = True
156
131
 
157
- if not staged_tx_model.can_migrate():
132
+ if not staged_tx_model.can_have_customer():
133
+ self.fields['customer_model'].widget = HiddenInput()
134
+ self.fields['customer_model'].disabled = True
135
+
136
+ if not staged_tx_model.can_import():
158
137
  self.fields['tx_import'].widget = HiddenInput()
159
138
  self.fields['tx_import'].disabled = True
160
139
 
@@ -162,6 +141,10 @@ class StagedTransactionModelForm(ModelForm):
162
141
  self.fields['tx_split'].widget = HiddenInput()
163
142
  self.fields['tx_split'].disabled = True
164
143
 
144
+ if not staged_tx_model.can_unbundle():
145
+ self.fields['bundle_split'].widget = HiddenInput()
146
+ self.fields['bundle_split'].disabled = True
147
+
165
148
  def clean_account_model(self):
166
149
  staged_txs_model: StagedTransactionModel = self.instance
167
150
  if staged_txs_model.has_children():
@@ -179,26 +162,41 @@ class StagedTransactionModelForm(ModelForm):
179
162
  def clean_tx_import(self):
180
163
  staged_txs_model: StagedTransactionModel = self.instance
181
164
  if staged_txs_model.is_children():
182
- return False
165
+ parent_form = self.BASE_FORMSET_INSTANCE.FORMS_BY_ID[staged_txs_model.parent_id]
166
+ if all(
167
+ [
168
+ any(
169
+ [
170
+ staged_txs_model.is_child_not_bundled_has_receipt(),
171
+ staged_txs_model.is_child_not_bundled_no_receipt(),
172
+ ]
173
+ ),
174
+ parent_form.cleaned_data['tx_import'] is True,
175
+ ]
176
+ ):
177
+ return True
183
178
  return self.cleaned_data['tx_import']
184
179
 
180
+ def clean_bundle_split(self):
181
+ staged_txs_model: StagedTransactionModel = self.instance
182
+ if staged_txs_model.is_single():
183
+ return True
184
+ if staged_txs_model.is_children():
185
+ parent_form = self.BASE_FORMSET_INSTANCE.FORMS_BY_ID[staged_txs_model.parent.uuid]
186
+ return parent_form.cleaned_data['bundle_split']
187
+ return self.cleaned_data['bundle_split']
188
+
185
189
  def clean(self):
186
190
  if self.cleaned_data['tx_import'] and self.cleaned_data['tx_split']:
187
191
  raise ValidationError(message=_('Cannot import and split at the same time'))
188
192
 
193
+ def has_changed(self):
194
+ has_changed = super().has_changed()
189
195
  staged_txs_model: StagedTransactionModel = self.instance
190
- if all(
191
- [
192
- staged_txs_model.has_children(),
193
- staged_txs_model.has_receipt(),
194
- not staged_txs_model.bundle_split,
195
- ]
196
- ):
197
- raise ValidationError(
198
- message=_(
199
- 'Receipt transactions cannot be split into multiple receipts.'
200
- )
201
- )
196
+ if not has_changed and staged_txs_model.is_children():
197
+ parent_form = self.BASE_FORMSET_INSTANCE.FORMS_BY_ID[staged_txs_model.parent.uuid]
198
+ return parent_form.has_changed()
199
+ return has_changed
202
200
 
203
201
  class Meta:
204
202
  model = StagedTransactionModel
@@ -223,18 +221,10 @@ class StagedTransactionModelForm(ModelForm):
223
221
  'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small',
224
222
  }
225
223
  ),
226
- 'amount_split': NumberInput(
227
- attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}
228
- ),
229
- 'vendor_model': Select(
230
- attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}
231
- ),
232
- 'customer_model': Select(
233
- attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}
234
- ),
235
- 'receipt_type': Select(
236
- attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}
237
- ),
224
+ 'amount_split': NumberInput(attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}),
225
+ 'vendor_model': Select(attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}),
226
+ 'customer_model': Select(attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}),
227
+ 'receipt_type': Select(attrs={'class': DJANGO_LEDGER_FORM_INPUT_CLASSES + ' is-small'}),
238
228
  }
239
229
 
240
230
 
@@ -250,29 +240,23 @@ class BaseStagedTransactionModelFormSet(BaseModelFormSet):
250
240
 
251
241
  # validates that the job import model belongs to the entity model...
252
242
  if import_job_model.entity_uuid != entity_model.uuid:
253
- raise ValidationError(
254
- message=_('Import job does not belong to this entity')
255
- )
243
+ raise ValidationError(message=_('Import job does not belong to this entity'))
256
244
 
257
245
  self.ENTITY_MODEL = entity_model
258
246
  self.IMPORT_JOB_MODEL: ImportJobModel = import_job_model
259
247
 
260
248
  self.queryset = (
261
- self.IMPORT_JOB_MODEL.stagedtransactionmodel_set.all().is_pending()
249
+ StagedTransactionModel.objects.for_entity(entity_model=entity_model)
250
+ .for_import_job(import_job_model=import_job_model)
251
+ .is_pending()
262
252
  )
263
253
 
264
- self.MAPPED_ACCOUNT_MODEL = (
265
- self.IMPORT_JOB_MODEL.bank_account_model.account_model
266
- )
254
+ self.MAPPED_ACCOUNT_MODEL = self.IMPORT_JOB_MODEL.bank_account_model.account_model
267
255
 
268
256
  self.account_model_qs = (
269
- entity_model.get_coa_accounts()
270
- .available()
271
- .exclude(uuid__exact=self.MAPPED_ACCOUNT_MODEL.uuid)
257
+ entity_model.get_coa_accounts().available().exclude(uuid__exact=self.MAPPED_ACCOUNT_MODEL.uuid)
272
258
  )
273
- self.ACCOUNT_MODEL_CHOICES = [(None, '----')] + [
274
- (a.uuid, a) for a in self.account_model_qs
275
- ]
259
+ self.ACCOUNT_MODEL_CHOICES = [(None, '----')] + [(a.uuid, a) for a in self.account_model_qs]
276
260
  self.ACCOUNT_MODEL_EXPENSES_CHOICES = [(None, '----')] + [
277
261
  (a.uuid, a) for a in self.account_model_qs if a.role in GROUP_EXPENSES
278
262
  ]
@@ -280,26 +264,35 @@ class BaseStagedTransactionModelFormSet(BaseModelFormSet):
280
264
  (a.uuid, a) for a in self.account_model_qs if a.role in GROUP_INCOME
281
265
  ]
282
266
 
283
- self.unit_model_qs = entity_model.entityunitmodel_set.all()
284
- self.UNIT_MODEL_CHOICES = [(None, '----')] + [
285
- (u.uuid, u) for i, u in enumerate(self.unit_model_qs)
267
+ self.ACCOUNT_MODEL_TRANSFERS_CHOICES = [(None, '----')] + [
268
+ (a.uuid, a) for a in self.account_model_qs if a.role in GROUP_TRANSFERS
286
269
  ]
287
270
 
288
- self.VENDOR_MODEL_QS = entity_model.vendormodel_set.visible()
289
- len(self.VENDOR_MODEL_QS)
290
- self.CUSTOMER_MODEL_QS = entity_model.customermodel_set.visible()
291
- len(self.CUSTOMER_MODEL_QS)
292
-
293
- self.VENDOR_CHOICES = [(None, '-----')] + [
294
- (str(v.uuid), v) for v in self.VENDOR_MODEL_QS
295
- ]
296
- self.CUSTOMER_CHOICES = [(None, '-----')] + [
297
- (str(c.uuid), c) for c in self.CUSTOMER_MODEL_QS
271
+ self.ACCOUNT_MODEL_TRANSFERS_CC_PAYMENT = [(None, '----')] + [
272
+ (a.uuid, a) for a in self.account_model_qs if a.role in GROUP_DEBT_PAYMENT
298
273
  ]
299
274
 
275
+ self.unit_model_qs = entity_model.entityunitmodel_set.all()
276
+ self.UNIT_MODEL_CHOICES = [(None, '----')] + [(u.uuid, u) for i, u in enumerate(self.unit_model_qs)]
277
+
278
+ self.VENDOR_MODEL_QS = entity_model.vendormodel_set.visible().order_by('vendor_name')
279
+ self.CUSTOMER_MODEL_QS = entity_model.customermodel_set.visible().order_by('customer_name')
280
+
281
+ self.VENDOR_CHOICES = [(None, '-----')] + [(str(v.uuid), v) for v in self.VENDOR_MODEL_QS]
282
+ self.CUSTOMER_CHOICES = [(None, '-----')] + [(str(c.uuid), c) for c in self.CUSTOMER_MODEL_QS]
283
+
300
284
  self.VENDOR_MAP = dict(self.VENDOR_CHOICES)
301
285
  self.CUSTOMER_MAP = dict(self.CUSTOMER_CHOICES)
302
286
 
287
+ self.FORMS_BY_ID = {
288
+ f.instance.uuid: f for f in self.forms if getattr(f, 'instance', None) and getattr(f.instance, 'uuid', None)
289
+ }
290
+
291
+ form_children = [(f.instance.parent_id, f.instance.uuid) for f in self.forms if f.instance.parent_id]
292
+ form_children.sort(key=lambda f: f[0])
293
+
294
+ self.FORM_CHILDREN = {g: list(j[1] for j in p) for g, p in groupby(form_children, key=lambda i: i[0])}
295
+
303
296
  def get_form_kwargs(self, index):
304
297
  return {
305
298
  'base_formset_instance': self,