fix: Zweiter Uebertrag fuer dieselbe Bewegung wurde nicht erkannt

Die Sperre sah auf dem Zielkonto nur nach Ausgaben und Einnahmen. Steht dort
schon ein UEBERTRAG, der dasselbe Geld zwischen denselben beiden Konten
bewegt, fand sie nichts – und der zweite ging durch.

Genau das passiert im Alltag: Die eine Seite kommt von der Bank, die andere
aus der CSV-Datei, und beide sehen einzeln nach einem Uebertrag aus. Wer beide
umwidmet, hat das Quellkonto doppelt belastet und das Ziel doppelt
gutgeschrieben – bei 1'575 macht das 3'150 Unterschied auf zwei Konten.

Jetzt wird zuerst geprueft, ob zwischen denselben beiden Konten schon ein
Uebertrag ueber denselben Betrag in derselben Woche steht. Die Richtung ist
dabei egal, ein Uebertrag hat kein Vorzeichen. Dieser Fall laesst sich auch
nicht mit "trotzdem" uebergehen: Dasselbe Geld zweimal zu verschieben ist
keine Ansichtssache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-30 12:58:37 +02:00
co-authored by Claude Opus 5
parent c1561acdce
commit 7b3ed3e63c
2 changed files with 77 additions and 0 deletions
+39
View File
@@ -118,9 +118,48 @@ def _gebuchte_gegenseite(db: Session, user: User, konto: Account, ziel: Account,
)
def _schon_als_uebertrag(db: Session, user: User, konto: Account, ziel: Account,
betrag: int | None, datum, exakt: bool) -> Transaction | None:
"""Bewegt schon ein Uebertrag dasselbe Geld zwischen diesen beiden Konten?
Beide Seiten kommen oft getrennt herein – die eine von der Bank, die andere
aus der Datei. Wer beide einzeln zum Uebertrag macht, verschiebt das Geld
zweimal. Die Richtung ist dabei egal: Ein Uebertrag hat kein Vorzeichen,
nur ein Von und ein Nach.
"""
if not betrag:
return None
spielraum = 0 if exakt else max(1, round(betrag * FX_TOLERANZ))
paar = {konto.id, ziel.id}
return next(
(t for t in db.query(Transaction).filter(
Transaction.owner_id == user.id,
Transaction.type == TxnType.transfer,
Transaction.date >= datum - timedelta(days=INBOX_TAGE),
Transaction.date <= datum + timedelta(days=INBOX_TAGE),
) if {t.account_id, t.to_account_id} == paar
and min(abs(t.amount_cents - betrag),
abs((t.to_amount_cents or t.amount_cents) - betrag)) <= spielraum),
None,
)
def _pruefe_gegenseite(db: Session, user: User, konto: Account, ziel: Account,
eigener_typ: TxnType, erwartet: int | None, datum,
exakt: bool, trotzdem: bool) -> None:
# Zuerst der Fall, der sich NICHT uebergehen laesst: Zwischen denselben
# beiden Konten bewegt schon ein Uebertrag dasselbe Geld. Ein zweiter
# verschoebe es ein zweites Mal – das ist keine Ansichtssache.
doppelt = _schon_als_uebertrag(db, user, konto, ziel, erwartet, datum, exakt)
if doppelt is not None:
raise UebertragFehler(
f"Zwischen '{konto.name}' und '{ziel.name}' steht für den "
f"{doppelt.date.strftime('%d.%m.%Y')} schon ein Übertrag über "
f"denselben Betrag. Diese Bewegung ist damit bereits erfasst – ein "
f"zweiter Übertrag verschöbe das Geld noch einmal. Wenn diese "
f"Buchung dieselbe Zahlung ist, lösch sie; wenn es wirklich zwei "
f"Zahlungen waren, lösch zuerst den vorhandenen Übertrag."
)
if trotzdem:
return
schon = _gebuchte_gegenseite(db, user, konto, ziel, eigener_typ, erwartet,