mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-01-14 21:23:29 +01:00
351 lines
12 KiB
Python
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
|