"""Konten inkl. Erkennungs-Identifikatoren. Alles owner-gescoped.""" from datetime import date from fastapi import APIRouter, Depends, status from sqlalchemy.orm import Session from ..database import get_db from ..deps import get_current_user, get_owned from ..models import Account, AccountIdentifier, IdentifierKind, User from ..schemas import AccountCreate, AccountIdentifierOut, AccountOut, AccountUpdate from ..services import balances from ..services.csv_import import normalize_text from .settings import BASE_CURRENCY_KEY, get_user_setting router = APIRouter(prefix="/accounts", tags=["accounts"]) def normalize_identifier(value: str, kind: IdentifierKind) -> str: """Vereinheitlicht einen Identifikator für den Abgleich. IBAN/Nummer: Großbuchstaben, nur alphanumerisch. Freier Text: klein, getrimmt (für die Zielkonto-Erkennung im Buchungstext).""" if kind == IdentifierKind.text: return normalize_text(value) return "".join(ch for ch in value.upper() if ch.isalnum()) def base_currency(db: Session, user: User) -> str: return get_user_setting(db, user, BASE_CURRENCY_KEY) or "EUR" def to_out(db: Session, acc: Account, base: str, on: date) -> AccountOut: return AccountOut( id=acc.id, name=acc.name, type=acc.type, currency=acc.currency, icon=acc.icon, color=acc.color, opening_balance_cents=acc.opening_balance_cents, opening_date=acc.opening_date, is_archived=acc.is_archived, position=acc.position, identifiers=[ AccountIdentifierOut(id=i.id, kind=i.kind, value=i.value) for i in acc.identifiers ], balance_cents=balances.account_balance_cents(db, acc), balance_base_cents=balances.account_balance_base_cents(db, acc, base, on), ) def _set_identifiers(acc: Account, items) -> None: """Ersetzt die Identifikator-Liste (dedupliziert nach Art + Normalform).""" acc.identifiers.clear() seen: set[tuple[str, str]] = set() for it in items: norm = normalize_identifier(it.value, it.kind) key = (it.kind.value, norm) if not norm or key in seen: continue seen.add(key) acc.identifiers.append( AccountIdentifier(kind=it.kind, value=it.value.strip(), value_norm=norm) ) @router.get("", response_model=list[AccountOut]) def list_accounts(db: Session = Depends(get_db), user: User = Depends(get_current_user)): base = base_currency(db, user) today = date.today() accounts = ( db.query(Account) .filter(Account.owner_id == user.id) .order_by(Account.position, Account.name) .all() ) return [to_out(db, a, base, today) for a in accounts] @router.post("", response_model=AccountOut, status_code=status.HTTP_201_CREATED) def create_account( payload: AccountCreate, db: Session = Depends(get_db), user: User = Depends(get_current_user) ): max_pos = ( db.query(Account.position) .filter(Account.owner_id == user.id) .order_by(Account.position.desc()) .first() ) acc = Account( owner_id=user.id, name=payload.name.strip(), type=payload.type, currency=payload.currency, icon=payload.icon, color=payload.color, opening_balance_cents=payload.opening_balance_cents, opening_date=payload.opening_date, position=(max_pos[0] + 1) if max_pos else 0, ) _set_identifiers(acc, payload.identifiers) db.add(acc) db.commit() db.refresh(acc) return to_out(db, acc, base_currency(db, user), date.today()) @router.get("/{account_id}", response_model=AccountOut) def get_account( account_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user) ): acc = get_owned(db, Account, account_id, user) return to_out(db, acc, base_currency(db, user), date.today()) @router.patch("/{account_id}", response_model=AccountOut) def update_account( account_id: int, payload: AccountUpdate, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): acc = get_owned(db, Account, account_id, user) data = payload.model_dump(exclude_unset=True) identifiers = data.pop("identifiers", None) for key, value in data.items(): if value is not None or key in ("opening_date",): setattr(acc, key, value) if identifiers is not None: _set_identifiers(acc, payload.identifiers) db.commit() db.refresh(acc) return to_out(db, acc, base_currency(db, user), date.today()) @router.delete("/{account_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_account( account_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user) ): acc = get_owned(db, Account, account_id, user) db.delete(acc) db.commit()