Files
WYGIWYH/app/apps/dca/forms.py
2025-11-11 20:21:01 -03:00

351 lines
12 KiB
Python

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
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect, TransactionSelect
from apps.dca.models import DCAEntry, DCAStrategy
from apps.transactions.models import Transaction, TransactionCategory, TransactionTag
from crispy_forms.bootstrap import AccordionGroup, FormActions, Accordion
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Column, Layout, Row
from django import forms
from django.utils.translation import gettext_lazy as _
class DCAStrategyForm(forms.ModelForm):
class Meta:
model = DCAStrategy
fields = ["name", "target_currency", "payment_currency", "notes"]
widgets = {
"target_currency": TomSelect(clear_button=False),
"payment_currency": TomSelect(clear_button=False),
"notes": forms.Textarea(attrs={"rows": 3}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
"name",
Row(
Column("payment_currency"),
Column("target_currency"),
),
"notes",
)
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 DCAEntryForm(forms.ModelForm):
create_transaction = forms.BooleanField(
label=_("Create transaction"), initial=False, required=False
)
from_account = forms.ModelChoiceField(
queryset=Account.objects.filter(is_archived=False),
label=_("From Account"),
widget=TomSelect(clear_button=False, group_by="group"),
required=False,
)
to_account = forms.ModelChoiceField(
queryset=Account.objects.filter(is_archived=False),
label=_("To Account"),
widget=TomSelect(clear_button=False, group_by="group"),
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),
)
expense_transaction = DynamicModelChoiceField(
model=Transaction,
to_field_name="id",
label=_("Expense Transaction"),
required=False,
queryset=Transaction.objects.none(),
widget=TransactionSelect(clear_button=True, income=False, expense=True),
help_text=_("Type to search for a transaction to link to this entry"),
)
income_transaction = DynamicModelChoiceField(
model=Transaction,
to_field_name="id",
label=_("Income Transaction"),
required=False,
queryset=Transaction.objects.none(),
widget=TransactionSelect(clear_button=True, income=True, expense=False),
help_text=_("Type to search for a transaction to link to this entry"),
)
class Meta:
model = DCAEntry
fields = [
"date",
"amount_paid",
"amount_received",
"notes",
"expense_transaction",
"income_transaction",
]
widgets = {
"notes": forms.Textarea(attrs={"rows": 3}),
}
def __init__(self, *args, **kwargs):
strategy = kwargs.pop("strategy", None)
super().__init__(*args, **kwargs)
self.strategy = strategy if strategy else self.instance.strategy
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
"date",
Row(
Column("amount_paid"),
Column("amount_received"),
),
"notes",
Accordion(
AccordionGroup(
_("Create transaction"),
Switch("create_transaction"),
Row(
Column(
Row(
Column(
"from_account",
),
),
Row(
Column("from_category"),
Column("from_tags"),
),
),
css_class="p-1 mx-1 my-3 border rounded-3",
),
Row(
Column(
Row(
Column(
"to_account",
css_class="form-group",
),
),
Row(
Column("to_category"),
Column("to_tags"),
),
),
css_class="p-1 mx-1 my-3 border rounded-3",
),
active=False,
),
AccordionGroup(
_("Link transaction"),
"income_transaction",
"expense_transaction",
),
flush=False,
always_open=False,
css_class="mb-3",
),
)
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"),
),
)
self.fields["amount_paid"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["amount_received"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
expense_transaction = None
income_transaction = None
if self.instance and self.instance.pk:
# Edit mode - get from instance
expense_transaction = self.instance.expense_transaction
income_transaction = self.instance.income_transaction
elif self.data.get("expense_transaction"):
# Form validation - get from submitted data
try:
expense_transaction = Transaction.objects.get(
id=self.data["expense_transaction"]
)
income_transaction = Transaction.objects.get(
id=self.data["income_transaction"]
)
except Transaction.DoesNotExist:
pass
# If we have a current transaction, ensure it's in the queryset
if income_transaction:
self.fields["income_transaction"].queryset = Transaction.objects.filter(
id=income_transaction.id
)
if expense_transaction:
self.fields["expense_transaction"].queryset = Transaction.objects.filter(
id=expense_transaction.id
)
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()
if cleaned_data.get("create_transaction"):
from_account = cleaned_data.get("from_account")
to_account = cleaned_data.get("to_account")
if not from_account and not to_account:
raise forms.ValidationError(
{
"from_account": _("You must provide an account."),
"to_account": _("You must provide an account."),
}
)
elif not from_account and to_account:
raise forms.ValidationError(
{"from_account": _("You must provide an account.")}
)
elif not to_account and from_account:
raise forms.ValidationError(
{"to_account": _("You must provide an account.")}
)
if from_account == to_account:
raise forms.ValidationError(
_("From and To accounts must be different.")
)
return cleaned_data
def save(self, **kwargs):
instance = super().save(commit=False)
if self.cleaned_data.get("create_transaction"):
from_account = self.cleaned_data["from_account"]
to_account = self.cleaned_data["to_account"]
from_amount = instance.amount_paid
to_amount = instance.amount_received
date = instance.date
description = _("DCA for %(strategy_name)s") % {
"strategy_name": self.strategy.name
}
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,
amount=from_amount,
description=description,
category=from_category,
notes=notes,
)
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,
amount=to_amount,
description=description,
category=to_category,
notes=notes,
)
to_transaction.tags.set(self.cleaned_data.get("to_tags", []))
instance.expense_transaction = from_transaction
instance.income_transaction = to_transaction
else:
if instance.expense_transaction:
instance.expense_transaction.amount = instance.amount_paid
instance.expense_transaction.save()
if instance.income_transaction:
instance.income_transaction.amount = instance.amount_received
instance.income_transaction.save()
instance.strategy = self.strategy
instance.save()
return instance