You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

86 lines
3.7 KiB

from uuid import UUID
from django import forms
from finance.models import Account, Category, Payee, Transaction
from finance.queries import active_accounts, active_categories, active_payees
def _reference_choices(model, *, include=None):
rows = {Account: active_accounts, Category: active_categories, Payee: active_payees}[model]()
if include is not None:
rows = model.objects.filter(pk=include.pk) | rows
rows = rows.order_by("name", "id")
return [(str(row.pk), row.name) for row in rows]
def _uuid(value):
return None if value == "" else UUID(value)
class ManualTransactionForm(forms.Form):
account = forms.TypedChoiceField(coerce=UUID)
amount = forms.DecimalField(max_digits=18, decimal_places=2)
posting_date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
occurrence_date = forms.DateField(required=False, widget=forms.DateInput(attrs={"type": "date"}))
kind = forms.ChoiceField(choices=Transaction.Kind.choices)
description = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": 3}))
payee = forms.TypedChoiceField(required=False, empty_value=None, coerce=_uuid)
category = forms.TypedChoiceField(required=False, empty_value=None, coerce=_uuid)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["account"].choices = _reference_choices(Account)
self.fields["payee"].choices = [("", "None"), *_reference_choices(Payee)]
self.fields["category"].choices = [("", "Uncategorized"), *_reference_choices(Category)]
def clean(self):
values = super().clean()
amount = values.get("amount")
kind = values.get("kind")
if amount is not None and kind == Transaction.Kind.EXPENSE and amount >= 0:
self.add_error("amount", "Expense amounts must be negative.")
if amount is not None and kind in {Transaction.Kind.INCOME, Transaction.Kind.REFUND} and amount <= 0:
self.add_error("amount", "Income and refund amounts must be positive.")
return values
class AnnotationForm(forms.Form):
description = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": 3}))
payee = forms.TypedChoiceField(required=False, empty_value=None, coerce=_uuid)
category = forms.TypedChoiceField(required=False, empty_value=None, coerce=_uuid)
def __init__(self, *args, transaction, **kwargs):
super().__init__(*args, **kwargs)
self.transaction = transaction
self.fields["payee"].choices = [("", "None"), *_reference_choices(Payee, include=transaction.payee)]
self.fields["category"].choices = [("", "Uncategorized"), *_reference_choices(Category, include=transaction.category)]
if not self.is_bound:
self.initial = {
"description": transaction.description,
"payee": transaction.payee_id,
"category": transaction.category_id,
}
def changes(self):
values = self.cleaned_data
changes = {}
for form_name, model_name in (("description", "description"), ("payee", "payee_id"), ("category", "category_id")):
if values[form_name] != getattr(self.transaction, model_name):
changes[model_name] = values[form_name]
return changes
class AccountForm(forms.Form):
name = forms.CharField(max_length=255)
kind = forms.ChoiceField(choices=Account.Kind.choices)
currency = forms.RegexField(regex=r"^[A-Z]{3}$", max_length=3)
class CategoryForm(forms.Form):
name = forms.CharField(max_length=255)
kind = forms.ChoiceField(choices=Category.Kind.choices)
class PayeeForm(forms.Form):
name = forms.CharField(max_length=255)