"""Schemata für Budgets, Budgetvorlagen und Sparziele.""" from datetime import date, datetime from decimal import Decimal from pydantic import Field, field_validator from app.schemas.common import ( ApiModel, HexColor, InputModel, Money, NonNegativeMoney, PositiveMoney, ) def _to_month_start(value: date) -> date: """Budgets gelten immer für einen ganzen Monat.""" return value.replace(day=1) class BudgetCreate(InputModel): category_id: int period_month: date = Field(description="Beliebiger Tag im Monat; wird auf den Ersten gesetzt.") limit_amount: PositiveMoney rollover: bool = Field( default=False, description="Nicht verbrauchtes Budget in den Folgemonat übernehmen." ) @field_validator("period_month") @classmethod def _normalise(cls, value: date) -> date: return _to_month_start(value) class BudgetUpdate(InputModel): limit_amount: PositiveMoney | None = None rollover: bool | None = None class BudgetOut(ApiModel): id: int category_id: int period_month: date limit_amount: Money rollover: bool created_at: datetime class BudgetTemplateCreate(InputModel): category_id: int valid_from: date = Field(description="Ab diesem Monat gilt die Vorlage dauerhaft.") valid_until: date | None = Field(default=None, description="Letzter Monat, sonst unbegrenzt.") limit_amount: PositiveMoney rollover: bool = False @field_validator("valid_from", "valid_until") @classmethod def _normalise(cls, value: date | None) -> date | None: return _to_month_start(value) if value else None class BudgetTemplateUpdate(InputModel): valid_until: date | None = None limit_amount: PositiveMoney | None = None rollover: bool | None = None @field_validator("valid_until") @classmethod def _normalise(cls, value: date | None) -> date | None: return _to_month_start(value) if value else None class BudgetTemplateOut(ApiModel): id: int category_id: int valid_from: date valid_until: date | None limit_amount: Money rollover: bool class SavingsGoalCreate(InputModel): name: str = Field(min_length=1, max_length=160, examples=["Neues Fahrrad"]) target_amount: PositiveMoney target_date: date | None = None current_amount: NonNegativeMoney = Decimal("0.00") account_id: int | None = None monthly_contribution: PositiveMoney | None = None color: HexColor = "#10b981" icon: str = Field(default="piggy-bank", max_length=64) is_archived: bool = False class SavingsGoalUpdate(InputModel): name: str | None = Field(default=None, min_length=1, max_length=160) target_amount: PositiveMoney | None = None target_date: date | None = None current_amount: NonNegativeMoney | None = None account_id: int | None = None monthly_contribution: PositiveMoney | None = None color: HexColor | None = None icon: str | None = Field(default=None, max_length=64) is_archived: bool | None = None class SavingsGoalOut(ApiModel): id: int name: str target_amount: Money target_date: date | None current_amount: Money account_id: int | None monthly_contribution: Money | None color: str icon: str is_archived: bool created_at: datetime updated_at: datetime