- Anmeldung über Argon2id und JWT in httpOnly-Cookies, Refresh mit echter Rotation über die neue Tabelle refresh_token - AuthProvider-Protokoll als Vorbereitung für OIDC, Administrator-Anlage beim Erststart mit erzwungenem Passwortwechsel - CRUD für Konten, Kategorien (zweistufiger Baum), Firmen, Recurrences, Preisversionen, Buchungen, Budgets, Vorlagen und Sparziele - Fälligkeiten mit Overlay-Logik: abrufen, bestätigen, auslassen, zurücksetzen - Kontosalden zum Stichtag, Monatsübersicht mit Plan-Ist-Vergleich - SECRET_KEY jetzt mindestens 32 Zeichen; Platzhalter in Produktion abgelehnt - 61 neue Integrationstests, insgesamt 148 grün Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
121 lines
3.3 KiB
Python
121 lines
3.3 KiB
Python
"""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
|