mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-01-15 21:53:28 +01:00
1096 lines
35 KiB
Python
1096 lines
35 KiB
Python
from copy import deepcopy
|
|
|
|
from apps.accounts.models import Account
|
|
from apps.common.fields.forms.dynamic_select import (
|
|
DynamicModelChoiceField,
|
|
DynamicModelMultipleChoiceField,
|
|
)
|
|
from apps.common.widgets.crispy.daisyui import Switch
|
|
from apps.common.widgets.crispy.submit import NoClassSubmit
|
|
from apps.common.widgets.datepicker import AirDatePickerInput, AirMonthYearPickerInput
|
|
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
|
|
from apps.common.widgets.tom_select import TomSelect
|
|
from apps.rules.signals import transaction_created, transaction_updated
|
|
from apps.transactions.models import (
|
|
InstallmentPlan,
|
|
QuickTransaction,
|
|
RecurringTransaction,
|
|
Transaction,
|
|
TransactionCategory,
|
|
TransactionEntity,
|
|
TransactionTag,
|
|
)
|
|
from crispy_forms.bootstrap import AccordionGroup, AppendedText, FormActions, Accordion
|
|
from crispy_forms.helper import FormHelper
|
|
from crispy_forms.layout import (
|
|
HTML,
|
|
Column,
|
|
Div,
|
|
Field,
|
|
Layout,
|
|
Row,
|
|
)
|
|
from django import forms
|
|
from django.db.models import Q
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
class TransactionForm(forms.ModelForm):
|
|
category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
entities = DynamicModelMultipleChoiceField(
|
|
model=TransactionEntity,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Entities"),
|
|
)
|
|
account = forms.ModelChoiceField(
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
label=_("Account"),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
|
|
date = forms.DateField(label=_("Date"))
|
|
|
|
reference_date = forms.DateField(
|
|
widget=AirMonthYearPickerInput(),
|
|
label=_("Reference Date"),
|
|
required=False,
|
|
)
|
|
|
|
class Meta:
|
|
model = Transaction
|
|
fields = [
|
|
"account",
|
|
"type",
|
|
"is_paid",
|
|
"date",
|
|
"reference_date",
|
|
"amount",
|
|
"description",
|
|
"notes",
|
|
"category",
|
|
"tags",
|
|
"entities",
|
|
]
|
|
widgets = {
|
|
"notes": forms.Textarea(attrs={"rows": 3}),
|
|
"account": TomSelect(clear_button=False, group_by="group"),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# if editing a transaction display non-archived items and it's own item even if it's archived
|
|
if self.instance.id:
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
Q(is_archived=False) | Q(transactions=self.instance.id),
|
|
)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
Q(active=True) | Q(transaction=self.instance.id)
|
|
)
|
|
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(
|
|
Q(active=True) | Q(transaction=self.instance.id)
|
|
)
|
|
|
|
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
|
Q(active=True) | Q(transactions=self.instance.id)
|
|
)
|
|
else:
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
is_archived=False,
|
|
)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
self.fields["entities"].queryset = TransactionEntity.objects.all()
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
self.helper.layout = Layout(
|
|
Field(
|
|
"type",
|
|
template="transactions/widgets/income_expense_toggle_buttons.html",
|
|
),
|
|
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
|
|
Row(
|
|
Column("account"),
|
|
Column("entities"),
|
|
),
|
|
Row(
|
|
Column(Field("date")),
|
|
Column(Field("reference_date")),
|
|
),
|
|
"description",
|
|
Field("amount", inputmode="decimal"),
|
|
Row(
|
|
Column("category"),
|
|
Column("tags"),
|
|
),
|
|
"notes",
|
|
)
|
|
|
|
self.helper_simple = FormHelper()
|
|
self.helper_simple.form_tag = False
|
|
self.helper_simple.form_method = "post"
|
|
self.helper_simple.layout = Layout(
|
|
Field(
|
|
"type",
|
|
template="transactions/widgets/income_expense_toggle_buttons.html",
|
|
),
|
|
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
|
|
"account",
|
|
Row(
|
|
Column(Field("date")),
|
|
Column(Field("reference_date")),
|
|
),
|
|
"description",
|
|
Field("amount", inputmode="decimal"),
|
|
Accordion(
|
|
AccordionGroup(
|
|
_("More"),
|
|
"entities",
|
|
Row(
|
|
Column("category"),
|
|
Column("tags"),
|
|
),
|
|
"notes",
|
|
active=False,
|
|
),
|
|
flush=False,
|
|
always_open=False,
|
|
css_class="mb-3",
|
|
),
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
|
|
|
|
if self.instance and self.instance.pk:
|
|
decimal_places = self.instance.account.currency.decimal_places
|
|
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
|
|
decimal_places=decimal_places
|
|
)
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.helper.layout.append(
|
|
Div(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
NoClassSubmit(
|
|
"submit_and_similar",
|
|
_("Save and add similar"),
|
|
css_class="btn btn-primary btn-soft",
|
|
),
|
|
NoClassSubmit(
|
|
"submit_and_another",
|
|
_("Save and add another"),
|
|
css_class="btn btn-primary btn-soft",
|
|
),
|
|
css_class="flex flex-col gap-2 mt-3",
|
|
),
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
date = cleaned_data.get("date")
|
|
reference_date = cleaned_data.get("reference_date")
|
|
|
|
if date and not reference_date:
|
|
cleaned_data["reference_date"] = date.replace(day=1)
|
|
|
|
return cleaned_data
|
|
|
|
def save(self, **kwargs):
|
|
is_new = not self.instance.id
|
|
|
|
if not is_new:
|
|
old_data = deepcopy(Transaction.objects.get(pk=self.instance.id))
|
|
else:
|
|
old_data = None
|
|
|
|
instance = super().save(**kwargs)
|
|
if is_new:
|
|
transaction_created.send(sender=instance)
|
|
else:
|
|
transaction_updated.send(sender=instance, old_data=old_data)
|
|
|
|
return instance
|
|
|
|
|
|
class QuickTransactionForm(forms.ModelForm):
|
|
category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
entities = DynamicModelMultipleChoiceField(
|
|
model=TransactionEntity,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Entities"),
|
|
)
|
|
account = forms.ModelChoiceField(
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
label=_("Account"),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
|
|
class Meta:
|
|
model = QuickTransaction
|
|
fields = [
|
|
"name",
|
|
"account",
|
|
"type",
|
|
"is_paid",
|
|
"amount",
|
|
"description",
|
|
"notes",
|
|
"category",
|
|
"tags",
|
|
"entities",
|
|
"mute",
|
|
]
|
|
widgets = {
|
|
"notes": forms.Textarea(attrs={"rows": 3}),
|
|
"account": TomSelect(clear_button=False, group_by="group"),
|
|
}
|
|
help_texts = {
|
|
"mute": _("Muted transactions won't be displayed on monthly summaries")
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# if editing a transaction display non-archived items and it's own item even if it's archived
|
|
if self.instance.id:
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
Q(is_archived=False) | Q(transactions=self.instance.id),
|
|
)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
Q(active=True) | Q(transaction=self.instance.id)
|
|
)
|
|
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(
|
|
Q(active=True) | Q(transaction=self.instance.id)
|
|
)
|
|
|
|
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
|
Q(active=True) | Q(transactions=self.instance.id)
|
|
)
|
|
else:
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
is_archived=False,
|
|
)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
self.fields["entities"].queryset = TransactionEntity.objects.all()
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
self.helper.layout = Layout(
|
|
Field(
|
|
"type",
|
|
template="transactions/widgets/income_expense_toggle_buttons.html",
|
|
),
|
|
Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
|
|
"name",
|
|
HTML('<hr class="hr my-3" />'),
|
|
Row(
|
|
Column("account"),
|
|
Column("entities"),
|
|
),
|
|
"description",
|
|
Field("amount", inputmode="decimal"),
|
|
Row(
|
|
Column("category"),
|
|
Column("tags"),
|
|
),
|
|
"notes",
|
|
Switch("mute"),
|
|
)
|
|
|
|
if self.instance and self.instance.pk:
|
|
decimal_places = self.instance.account.currency.decimal_places
|
|
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
|
|
decimal_places=decimal_places
|
|
)
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
|
|
class BulkEditTransactionForm(forms.Form):
|
|
type = forms.ChoiceField(
|
|
choices=(Transaction.Type.choices),
|
|
required=False,
|
|
label=_("Type"),
|
|
)
|
|
is_paid = forms.NullBooleanField(
|
|
required=False,
|
|
label=_("Paid"),
|
|
)
|
|
account = DynamicModelChoiceField(
|
|
model=Account,
|
|
required=False,
|
|
label=_("Account"),
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
date = forms.DateField(
|
|
label=_("Date"),
|
|
required=False,
|
|
widget=AirDatePickerInput(clear_button=False),
|
|
)
|
|
reference_date = forms.DateField(
|
|
widget=AirMonthYearPickerInput(),
|
|
label=_("Reference Date"),
|
|
required=False,
|
|
)
|
|
amount = forms.DecimalField(
|
|
max_digits=42,
|
|
decimal_places=30,
|
|
required=False,
|
|
label=_("Amount"),
|
|
widget=ArbitraryDecimalDisplayNumberInput(),
|
|
)
|
|
description = forms.CharField(
|
|
max_length=500, required=False, label=_("Description")
|
|
)
|
|
notes = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(attrs={"rows": 3}),
|
|
label=_("Notes"),
|
|
)
|
|
category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
entities = DynamicModelMultipleChoiceField(
|
|
model=TransactionEntity,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Entities"),
|
|
queryset=TransactionEntity.objects.all(),
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
is_archived=False,
|
|
)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
self.fields["entities"].queryset = TransactionEntity.objects.all()
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
self.helper.layout = Layout(
|
|
Field(
|
|
"type",
|
|
template="transactions/widgets/unselectable_income_expense_toggle_buttons.html",
|
|
),
|
|
Field(
|
|
"is_paid",
|
|
template="transactions/widgets/unselectable_paid_toggle_button.html",
|
|
),
|
|
Row(
|
|
Column("account"),
|
|
Column("entities"),
|
|
),
|
|
Row(
|
|
Column(Field("date")),
|
|
Column(Field("reference_date")),
|
|
),
|
|
"description",
|
|
Field("amount", inputmode="decimal"),
|
|
Row(
|
|
Column("category"),
|
|
Column("tags"),
|
|
),
|
|
"notes",
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
|
|
|
|
|
|
class TransferForm(forms.Form):
|
|
from_account = forms.ModelChoiceField(
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
label=_("From Account"),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
to_account = forms.ModelChoiceField(
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
label=_("To Account"),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
|
|
from_amount = forms.DecimalField(
|
|
max_digits=42,
|
|
decimal_places=30,
|
|
label=_("From Amount"),
|
|
)
|
|
to_amount = forms.DecimalField(
|
|
max_digits=42,
|
|
decimal_places=30,
|
|
label=_("To Amount"),
|
|
required=False,
|
|
)
|
|
|
|
from_category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
to_category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
|
|
from_tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
to_tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
|
|
date = forms.DateField(label=_("Date"))
|
|
|
|
reference_date = forms.DateField(
|
|
widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
|
|
)
|
|
|
|
description = forms.CharField(
|
|
max_length=500, label=_("Description"), required=False
|
|
)
|
|
notes = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"rows": 3,
|
|
}
|
|
),
|
|
label=_("Notes"),
|
|
)
|
|
|
|
mute = forms.BooleanField(
|
|
label=_("Mute"),
|
|
initial=True,
|
|
required=False,
|
|
help_text=_("Muted transactions won't be displayed on monthly summaries"),
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
|
|
self.helper.layout = Layout(
|
|
Row(
|
|
Column(Field("date")),
|
|
Column(
|
|
Field("reference_date"),
|
|
),
|
|
),
|
|
Field("description"),
|
|
Field("notes"),
|
|
Switch("mute"),
|
|
Row(
|
|
Column("from_account"),
|
|
Column(Field("from_amount")),
|
|
Column("from_category"),
|
|
Column("from_tags"),
|
|
css_class="bg-base-100 rounded-box p-4 border-base-content/60 border my-3",
|
|
),
|
|
Row(
|
|
Column(
|
|
"to_account",
|
|
),
|
|
Column(
|
|
Field("to_amount"),
|
|
),
|
|
Column("to_category"),
|
|
Column("to_tags"),
|
|
css_class="bg-base-100 rounded-box p-4 border-base-content/60 border",
|
|
),
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Transfer"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
self.fields["from_amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.fields["to_amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
|
|
|
|
self.fields["from_account"].queryset = Account.objects.filter(
|
|
is_archived=False,
|
|
)
|
|
|
|
self.fields["from_category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
self.fields["from_tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
|
|
self.fields["to_account"].queryset = Account.objects.filter(
|
|
is_archived=False,
|
|
)
|
|
|
|
self.fields["to_category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
self.fields["to_tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
from_account = cleaned_data.get("from_account")
|
|
to_account = cleaned_data.get("to_account")
|
|
|
|
if from_account == to_account:
|
|
raise forms.ValidationError(_("From and To accounts must be different."))
|
|
|
|
return cleaned_data
|
|
|
|
def save(self):
|
|
mute = self.cleaned_data["mute"]
|
|
|
|
from_account = self.cleaned_data["from_account"]
|
|
to_account = self.cleaned_data["to_account"]
|
|
from_amount = self.cleaned_data["from_amount"]
|
|
to_amount = self.cleaned_data["to_amount"] or from_amount
|
|
date = self.cleaned_data["date"]
|
|
reference_date = self.cleaned_data["reference_date"]
|
|
description = self.cleaned_data["description"]
|
|
from_category = self.cleaned_data.get("from_category")
|
|
to_category = self.cleaned_data.get("to_category")
|
|
notes = self.cleaned_data.get("notes")
|
|
|
|
# Create "From" transaction
|
|
from_transaction = Transaction.objects.create(
|
|
account=from_account,
|
|
type=Transaction.Type.EXPENSE,
|
|
is_paid=True,
|
|
date=date,
|
|
reference_date=reference_date,
|
|
amount=from_amount,
|
|
description=description,
|
|
category=from_category,
|
|
notes=notes,
|
|
mute=mute,
|
|
)
|
|
from_transaction.tags.set(self.cleaned_data.get("from_tags", []))
|
|
|
|
# Create "To" transaction
|
|
to_transaction = Transaction.objects.create(
|
|
account=to_account,
|
|
type=Transaction.Type.INCOME,
|
|
is_paid=True,
|
|
date=date,
|
|
reference_date=reference_date,
|
|
amount=to_amount,
|
|
description=description,
|
|
category=to_category,
|
|
notes=notes,
|
|
mute=mute,
|
|
)
|
|
to_transaction.tags.set(self.cleaned_data.get("to_tags", []))
|
|
|
|
return from_transaction, to_transaction
|
|
|
|
|
|
class InstallmentPlanForm(forms.ModelForm):
|
|
account = forms.ModelChoiceField(
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
label=_("Account"),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
entities = DynamicModelMultipleChoiceField(
|
|
model=TransactionEntity,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Entities"),
|
|
queryset=TransactionEntity.objects.filter(active=True),
|
|
)
|
|
type = forms.ChoiceField(choices=Transaction.Type.choices)
|
|
|
|
reference_date = forms.DateField(
|
|
widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
|
|
)
|
|
|
|
class Meta:
|
|
model = InstallmentPlan
|
|
fields = [
|
|
"type",
|
|
"account",
|
|
"start_date",
|
|
"reference_date",
|
|
"description",
|
|
"number_of_installments",
|
|
"recurrence",
|
|
"installment_amount",
|
|
"category",
|
|
"tags",
|
|
"notes",
|
|
"installment_start",
|
|
"entities",
|
|
"add_description_to_transaction",
|
|
"add_notes_to_transaction",
|
|
]
|
|
widgets = {
|
|
"account": TomSelect(),
|
|
"recurrence": TomSelect(clear_button=False),
|
|
"notes": forms.Textarea(attrs={"rows": 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# if editing display non-archived items and it's own item even if it's archived
|
|
if self.instance.id:
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
Q(is_archived=False) | Q(installmentplan=self.instance.id)
|
|
).distinct()
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
Q(active=True) | Q(installmentplan=self.instance.id)
|
|
).distinct()
|
|
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(
|
|
Q(active=True) | Q(installmentplan=self.instance.id)
|
|
).distinct()
|
|
|
|
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
|
Q(active=True) | Q(installmentplan=self.instance.id)
|
|
).distinct()
|
|
else:
|
|
self.fields["account"].queryset = Account.objects.filter(is_archived=False)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
|
|
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
|
active=True
|
|
)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
|
|
self.helper.layout = Layout(
|
|
Field(
|
|
"type",
|
|
template="transactions/widgets/income_expense_toggle_buttons.html",
|
|
),
|
|
Row(
|
|
Column("account"),
|
|
Column("entities"),
|
|
),
|
|
"description",
|
|
Switch("add_description_to_transaction"),
|
|
"notes",
|
|
Switch("add_notes_to_transaction"),
|
|
Row(
|
|
Column("number_of_installments"),
|
|
Column("installment_start"),
|
|
),
|
|
Row(
|
|
Column("start_date", css_class="col-span-12 md:col-span-4"),
|
|
Column("reference_date", css_class="col-span-12 md:col-span-4"),
|
|
Column("recurrence", css_class="col-span-12 md:col-span-4"),
|
|
),
|
|
"installment_amount",
|
|
Row(
|
|
Column("category"),
|
|
Column("tags"),
|
|
),
|
|
)
|
|
|
|
self.fields["installment_amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.fields["start_date"].widget = AirDatePickerInput(clear_button=False)
|
|
|
|
if self.instance and self.instance.pk:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
def save(self, **kwargs):
|
|
is_new = not self.instance.id
|
|
|
|
instance = super().save(**kwargs)
|
|
if is_new:
|
|
instance.create_transactions()
|
|
else:
|
|
instance.update_transactions()
|
|
|
|
return instance
|
|
|
|
|
|
class TransactionTagForm(forms.ModelForm):
|
|
class Meta:
|
|
model = TransactionTag
|
|
fields = ["name", "active"]
|
|
labels = {"name": _("Tag name")}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
self.helper.layout = Layout(Field("name", css_class="mb-3"), Switch("active"))
|
|
|
|
if self.instance and self.instance.pk:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
|
|
class TransactionEntityForm(forms.ModelForm):
|
|
class Meta:
|
|
model = TransactionEntity
|
|
fields = ["name", "active"]
|
|
labels = {"name": _("Entity name")}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
self.helper.layout = Layout(Field("name"), Switch("active"))
|
|
|
|
if self.instance and self.instance.pk:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
|
|
class TransactionCategoryForm(forms.ModelForm):
|
|
class Meta:
|
|
model = TransactionCategory
|
|
fields = ["name", "mute", "active"]
|
|
labels = {"name": _("Category name")}
|
|
help_texts = {
|
|
"mute": _("Muted categories won't be displayed on monthly summaries")
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_tag = False
|
|
self.helper.form_method = "post"
|
|
self.helper.layout = Layout(Field("name"), Switch("mute"), Switch("active"))
|
|
|
|
if self.instance and self.instance.pk:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
|
|
class RecurringTransactionForm(forms.ModelForm):
|
|
account = forms.ModelChoiceField(
|
|
queryset=Account.objects.filter(is_archived=False),
|
|
label=_("Account"),
|
|
widget=TomSelect(clear_button=False, group_by="group"),
|
|
)
|
|
tags = DynamicModelMultipleChoiceField(
|
|
model=TransactionTag,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Tags"),
|
|
queryset=TransactionTag.objects.filter(active=True),
|
|
)
|
|
category = DynamicModelChoiceField(
|
|
create_field="name",
|
|
model=TransactionCategory,
|
|
required=False,
|
|
label=_("Category"),
|
|
queryset=TransactionCategory.objects.filter(active=True),
|
|
)
|
|
entities = DynamicModelMultipleChoiceField(
|
|
model=TransactionEntity,
|
|
to_field_name="name",
|
|
create_field="name",
|
|
required=False,
|
|
label=_("Entities"),
|
|
queryset=TransactionEntity.objects.filter(active=True),
|
|
)
|
|
type = forms.ChoiceField(choices=Transaction.Type.choices)
|
|
|
|
class Meta:
|
|
model = RecurringTransaction
|
|
fields = [
|
|
"account",
|
|
"type",
|
|
"amount",
|
|
"description",
|
|
"add_description_to_transaction",
|
|
"category",
|
|
"tags",
|
|
"start_date",
|
|
"reference_date",
|
|
"end_date",
|
|
"recurrence_type",
|
|
"recurrence_interval",
|
|
"notes",
|
|
"add_notes_to_transaction",
|
|
"entities",
|
|
"keep_at_most",
|
|
]
|
|
widgets = {
|
|
"reference_date": AirMonthYearPickerInput(),
|
|
"recurrence_type": TomSelect(clear_button=False),
|
|
"notes": forms.Textarea(
|
|
attrs={
|
|
"rows": 3,
|
|
}
|
|
),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# if editing display non-archived items and it's own item even if it's archived
|
|
if self.instance.id:
|
|
self.fields["account"].queryset = Account.objects.filter(
|
|
Q(is_archived=False) | Q(recurringtransaction=self.instance.id)
|
|
).distinct()
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
Q(active=True) | Q(recurringtransaction=self.instance.id)
|
|
).distinct()
|
|
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(
|
|
Q(active=True) | Q(recurringtransaction=self.instance.id)
|
|
).distinct()
|
|
|
|
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
|
Q(active=True) | Q(recurringtransaction=self.instance.id)
|
|
).distinct()
|
|
else:
|
|
self.fields["account"].queryset = Account.objects.filter(is_archived=False)
|
|
|
|
self.fields["category"].queryset = TransactionCategory.objects.filter(
|
|
active=True
|
|
)
|
|
|
|
self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
|
|
|
|
self.fields["entities"].queryset = TransactionEntity.objects.filter(
|
|
active=True
|
|
)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = "post"
|
|
self.helper.form_tag = False
|
|
|
|
self.helper.layout = Layout(
|
|
Field(
|
|
"type",
|
|
template="transactions/widgets/income_expense_toggle_buttons.html",
|
|
),
|
|
Row(
|
|
Column("account"),
|
|
Column("entities"),
|
|
),
|
|
"description",
|
|
Switch("add_description_to_transaction"),
|
|
"amount",
|
|
Row(
|
|
Column("category"),
|
|
Column("tags"),
|
|
),
|
|
"notes",
|
|
Switch("add_notes_to_transaction"),
|
|
Row(
|
|
Column("start_date"),
|
|
Column("reference_date"),
|
|
),
|
|
Row(
|
|
Column("recurrence_interval", css_class="col-span-12 md:col-span-4"),
|
|
Column("recurrence_type", css_class="col-span-12 md:col-span-4"),
|
|
Column("end_date", css_class="col-span-12 md:col-span-4"),
|
|
),
|
|
AppendedText("keep_at_most", _("future transactions")),
|
|
)
|
|
|
|
self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
|
self.fields["start_date"].widget = AirDatePickerInput(clear_button=False)
|
|
self.fields["end_date"].widget = AirDatePickerInput()
|
|
|
|
if self.instance and self.instance.pk:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
else:
|
|
self.helper.layout.append(
|
|
FormActions(
|
|
NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
|
|
),
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
start_date = cleaned_data.get("start_date")
|
|
end_date = cleaned_data.get("end_date")
|
|
|
|
if start_date and end_date and start_date > end_date:
|
|
raise forms.ValidationError(_("End date should be after the start date"))
|
|
|
|
return cleaned_data
|
|
|
|
def save(self, **kwargs):
|
|
is_new = not self.instance.id
|
|
|
|
instance = super().save(**kwargs)
|
|
if is_new:
|
|
instance.create_upcoming_transactions()
|
|
else:
|
|
instance.update_unpaid_transactions()
|
|
instance.generate_upcoming_transactions()
|
|
|
|
return instance
|