feat: Kontostand aufschluesseln – und der Angleich-Knopf bleibt

Zwei Sachen aus derselben Wurzel: Wenn ein Saldo nicht stimmt, sieht man nicht,
warum, und kann auch nichts dagegen tun.

Ein Klick auf den Kontostand zeigt jetzt die Rechnung: Anfangsbestand,
Einnahmen, Ausgaben, Uebertraege raus und rein – jeweils mit der Anzahl der
Buchungen – und was daraus wird. Darunter der Saldo der Bank und, einzeln
aufgefuehrt, alle Uebertraege des Kontos. Genau die sind die haeufigste
Ursache und in einer langen Buchungsliste am schwersten zu ueberblicken.

Rechnung und Kontenliste kommen aus EINER Quelle: ``account_balance_cents``
liest jetzt aus der Aufschluesselung. Sonst koennte die Anzeige etwas anderes
behaupten als der Saldo, und man suchte den Fehler im Falschen.

Ausserdem war der Knopf "Saldo der Bank uebernehmen" verschwunden, sobald die
Bank nur einen verfuegbaren Saldo meldet. Das war zu weit gedacht: Nicht
vergleichbar heisst nicht, dass man nicht angleichen duerfte. Der Knopf ist
zurueck, und die Nachfrage sagt in dem Fall, worauf man sich einlaesst – der
verfuegbare Saldo enthaelt Vorgemerktes, das spaeter noch als Buchung kommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-30 13:33:27 +02:00
co-authored by Claude Opus 5
parent 7fe2678192
commit 7c0d13cc8a
8 changed files with 296 additions and 29 deletions
+48
View File
@@ -241,6 +241,54 @@ def _entkoppele_uebertraege(db: Session, acc: Account) -> None:
db.flush()
@router.get("/{account_id}/aufschluesselung")
def balance_breakdown(account_id: int, db: Session = Depends(get_db),
user: User = Depends(get_current_user)) -> dict:
"""Woraus sich der Saldo zusammensetzt – samt aller Übertrags-Beine.
Stimmt ein Kontostand nicht, liegt es fast immer an einer dieser Zahlen
oder an einem Übertrag, der zu viel oder zu wenig verschiebt. Ohne diese
Ansicht sucht man in einer langen Buchungsliste; mit ihr sieht man sofort,
welcher Teil nicht passt.
"""
konto = get_owned(db, Account, account_id, user)
teile = balances.aufschluesselung(db, konto)
namen = {a.id: a.name for a in db.query(Account).filter(Account.owner_id == user.id)}
# Die Übertrags-Beine einzeln: Sie sind der Teil, den man in der Liste am
# schwersten überblickt, und die haeufigste Ursache.
beine = [
{"id": t.id, "datum": t.date.isoformat(),
"von": namen.get(t.account_id, "?"), "nach": namen.get(t.to_account_id, "?"),
"raus": t.account_id == konto.id,
"cents": (t.amount_cents if t.account_id == konto.id
else (t.to_amount_cents or t.amount_cents)),
"text": t.payee or t.description}
for t in db.query(Transaction).filter(
Transaction.owner_id == user.id,
Transaction.type == TxnType.transfer,
(Transaction.account_id == konto.id) | (Transaction.to_account_id == konto.id),
).order_by(Transaction.date.desc(), Transaction.id.desc()).limit(50)
]
verknuepfung = (
db.query(BankAccountLink)
.filter(BankAccountLink.owner_id == user.id,
BankAccountLink.account_id == konto.id)
.first()
)
return {
"waehrung": konto.currency,
"teile": teile,
"uebertraege": beine,
"bank": {
"cents": verknuepfung.balance_cents if verknuepfung else None,
"art": verknuepfung.balance_type if verknuepfung else None,
"vergleichbar": (bank_sync.saldo_vergleichbar(verknuepfung.balance_type)
if verknuepfung else False),
},
}
@router.post("/{account_id}/saldo-uebernehmen", response_model=AccountOut)
def adopt_bank_balance(account_id: int, db: Session = Depends(get_db),
user: User = Depends(get_current_user)):