feat(api): Core-API mit Authentifizierung, CRUD und Monatsreport
- 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
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user