"""Eingang extern erfasster Zahlungen und die Gerätemarken dafür. Der Eingang zeigt, was per Kurzbefehl (später auch per Mail) hereinkam. Die Einträge bleiben nach dem Buchen bestehen: Der spätere CSV-Import gleicht gegen sie ab, damit dieselbe Zahlung nicht ein zweites Mal verbucht wird. """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from ..database import get_db from ..deps import get_current_user, get_owned from ..models import Account, ApiToken, InboxItem, InboxStatus, Transaction, User from ..schemas import ApiTokenCreate, ApiTokenCreated, ApiTokenOut, InboxItemOut from ..security import generate_session_token, hash_session_token router = APIRouter(prefix="/inbox", tags=["inbox"]) # --- Gerätemarken ------------------------------------------------------------ @router.get("/tokens", response_model=list[ApiTokenOut]) def list_tokens(db: Session = Depends(get_db), user: User = Depends(get_current_user)): return ( db.query(ApiToken) .filter(ApiToken.owner_id == user.id) .order_by(ApiToken.created_at.desc()) .all() ) @router.post("/tokens", response_model=ApiTokenCreated, status_code=status.HTTP_201_CREATED) def create_token( payload: ApiTokenCreate, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): """Legt eine Marke an. Der Klartext ist NUR in dieser Antwort zu sehen.""" if payload.default_account_id is not None: get_owned(db, Account, payload.default_account_id, user) klartext = generate_session_token() marke = ApiToken( owner_id=user.id, name=payload.name.strip(), token_hash=hash_session_token(klartext), default_account_id=payload.default_account_id, ) db.add(marke) db.commit() db.refresh(marke) return ApiTokenCreated(**ApiTokenOut.model_validate(marke).model_dump(), token=klartext) @router.delete("/tokens/{token_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_token(token_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)): marke = get_owned(db, ApiToken, token_id, user) db.delete(marke) db.commit() # --- Eingang ----------------------------------------------------------------- @router.get("", response_model=list[InboxItemOut]) def list_inbox( limit: int = 200, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): return ( db.query(InboxItem) .filter(InboxItem.owner_id == user.id, InboxItem.status != InboxStatus.discarded) .order_by(InboxItem.date.desc(), InboxItem.id.desc()) .limit(min(limit, 1000)) .all() ) @router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT) def discard_item(item_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user)): """Eintrag verwerfen – samt der Buchung, die daraus entstanden ist. Der Eintrag selbst bleibt als ``discarded`` stehen. Sonst wäre die Zahlung beim nächsten Auszug wieder neu, obwohl der Nutzer sie bewusst weggeworfen hat; so kann der Abgleich sie weiterhin erkennen. """ eintrag = get_owned(db, InboxItem, item_id, user) if eintrag.transaction_id is not None: txn = db.get(Transaction, eintrag.transaction_id) if txn is not None and txn.owner_id == user.id: db.delete(txn) eintrag.status = InboxStatus.discarded eintrag.transaction_id = None db.commit() @router.post("/{item_id}/category/{category_id}", response_model=InboxItemOut) def set_category( item_id: int, category_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): """Kategorie setzen – auf dem Eintrag UND auf der erzeugten Buchung.""" from ..models import Category eintrag = get_owned(db, InboxItem, item_id, user) kategorie = get_owned(db, Category, category_id, user) eintrag.category_id = kategorie.id if eintrag.transaction_id is not None: txn = db.get(Transaction, eintrag.transaction_id) if txn is not None and txn.owner_id == user.id: txn.category_id = kategorie.id db.commit() db.refresh(eintrag) return eintrag