feat: Konto-Saldo-Anker mit ankerbasierter Saldenrechnung

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 21:25:04 +02:00
parent 41d556bcde
commit f6a53b7e56
9 changed files with 297 additions and 47 deletions

View File

@@ -1,3 +1,4 @@
from datetime import date
from decimal import Decimal
from fastapi import APIRouter, Depends, HTTPException
@@ -25,14 +26,23 @@ class AccountOut(AccountIn):
model_config = ConfigDict(from_attributes=True)
id: int
balance: Decimal = Decimal("0")
anchor_date: date | None = None
anchor_balance: Decimal | None = None
class AccountPatch(BaseModel):
name: str
# Alle Felder optional; model_fields_set entscheidet, was tatsaechlich
# geaendert wird (Muster wie TransactionPatch.category_id). anchor_date
# und anchor_balance muessen gemeinsam gesetzt oder gemeinsam null sein.
name: str | None = None
anchor_date: date | None = None
anchor_balance: Decimal | None = None
@field_validator("name")
@classmethod
def name_not_blank(cls, v: str) -> str:
def name_not_blank(cls, v: str | None) -> str | None:
if v is None:
return v
v = v.strip()
if not v or len(v) > 100:
raise ValueError("Name muss 1100 Zeichen lang sein")
@@ -75,7 +85,25 @@ def patch_account(account_id: int, data: AccountPatch, session: Session = Depend
acc = session.get(Account, account_id)
if acc is None:
raise HTTPException(404, "Konto nicht gefunden")
acc.name = data.name
# Validierung zuerst, VOR jeder Mutation - sonst haengt bei einem 422 auf
# dem Anker eine bereits geschriebene name-Aenderung im Session-State.
fields = data.model_fields_set
if "name" in fields and data.name is None:
raise HTTPException(422, "Name darf nicht leer sein")
anchor_fields = {"anchor_date", "anchor_balance"} & fields
if anchor_fields and (
anchor_fields != {"anchor_date", "anchor_balance"}
or (data.anchor_date is None) != (data.anchor_balance is None)):
raise HTTPException(422, "Anker braucht Datum und Betrag (oder beide leeren)")
if "name" in fields:
acc.name = data.name
if anchor_fields:
acc.anchor_date = data.anchor_date
acc.anchor_balance = data.anchor_balance
session.commit()
session.refresh(acc)
item = AccountOut.model_validate(acc)