fix: Dieselbe Zahlung ueber zwei Bankzugaenge kam doppelt herein

Wer dieselbe Bank zweimal verbindet, bekommt fuer dieselbe Zahlung zwei
verschiedene Kennungen – Enable Banking fuehrt eine Bank je Land getrennt.
Genau das passiert bei Revolut: Ueber den DE-Eintrag kommen drei Konten, ueber
den LT-Eintrag zusaetzlich die Pockets, also verbindet man beide. Zeigen dann
zwei Bankkonten auf dasselbe Konto in Certain, holt jeder Zugang dieselben
Umsaetze, und der Duplikatschutz kann sie nicht als dieselben erkennen.

Drei Aenderungen:

- Ein Konto in Certain wird nur noch von EINEM Bankkonto gefuellt. Ordnet man
  ein zweites zu, wird die alte Zuordnung geloest. Sie ist ohnehin nie richtig,
  und stillschweigend doppelt zu buchen ist schlimmer als eine geloeste
  Zuordnung.
- Die Ausnahme im Ueberschneidungsschutz gilt nur noch fuer DASSELBE Bankkonto.
  Dort entscheidet die Kennung der Bank, und die ist exakt – zwei Aufrundungen
  ueber 1.00 duerfen sich nicht verschlucken. Buchungen aus einem ANDEREN
  Zugang zaehlen jetzt mit.
- Das Aufraeumskript kennt den Fall: Findet es in einer Gruppe (Konto, Art,
  Tag, Betrag) Buchungen aus zwei Zugaengen, bleibt die Fassung EINES Zugangs
  stehen. Aufrundungen aus einem einzigen Zugang bleiben unangetastet, auch
  wenn sie gleich aussehen.

An nachgestellten Daten geprueft: Aus -579.50 wird -290.50; die drei gleichen
Aufrundungen aus einem Zugang bleiben alle stehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-27 21:39:57 +02:00
co-authored by Claude Opus 5
parent a159546218
commit 4000082df4
4 changed files with 143 additions and 14 deletions
+15 -6
View File
@@ -532,21 +532,29 @@ def _startdatum(db: Session, user: User, konto: Account,
return min(heute, max(frueheste, letzte + timedelta(days=1)))
def _fremde_buchungen(db: Session, user: User, konto: Account,
seit: date) -> dict[tuple[str, int], list]:
"""Buchungen des Kontos, die NICHT von der Bank kamen, nach Art und Betrag.
def _fremde_buchungen(db: Session, user: User, konto: Account, seit: date,
remote_uid: str) -> dict[tuple[str, int], list]:
"""Buchungen des Kontos, die NICHT aus DIESEM Bankkonto stammen.
Ein Abruf ueber einen laengeren Zeitraum greift in Monate zurueck, die
schon per CSV eingelesen sind. Der Duplikatschutz erkennt das nicht: Sein
Schluessel enthaelt den Empfaengertext, und der lautet bei der Bank anders
als im Auszug. Ohne diese Pruefung stuende die Zahlung zweimal da.
Ausgenommen sind nur Buchungen aus DEMSELBEN Bankkonto: Dort entscheidet
die Kennung der Bank, und die ist exakt – zwei Aufrundungen ueber 1.00
duerfen sich nicht gegenseitig verschlucken. Buchungen aus einem ANDEREN
Bankzugang zaehlen dagegen mit. Genau das passiert, wenn dieselbe Bank
zweimal verbunden ist (Enable Banking vergibt je Land eigene Kennungen):
Dieselbe Zahlung kaeme sonst ueber beide Zugaenge herein.
"""
aus_bank = {
aus_diesem_konto = {
txn_id
for (txn_id,) in db.query(InboxItem.transaction_id).filter(
InboxItem.owner_id == user.id,
InboxItem.source == InboxSource.bank,
InboxItem.transaction_id.isnot(None),
InboxItem.external_id.startswith(f"{remote_uid}:"),
)
}
je_schluessel: dict[tuple[str, int], list] = {}
@@ -556,7 +564,7 @@ def _fremde_buchungen(db: Session, user: User, konto: Account,
Transaction.type != TxnType.transfer,
Transaction.date >= seit - timedelta(days=csv_import.INBOX_TAGE),
):
if txn.id in aus_bank:
if txn.id in aus_diesem_konto:
continue
je_schluessel.setdefault((txn.type.value, txn.amount_cents), []).append(txn)
return je_schluessel
@@ -610,7 +618,8 @@ def hole_umsaetze(db: Session, user: User, verbindung: BankConnection,
verbraucht: set[int] = set()
# ... und eine fuer schon vorhandene, nicht von der Bank stammende
# Buchungen. Beide muessen ueber alle Zeilen des Kontos halten.
fremde = _fremde_buchungen(db, user, konto, seit)
fremde = _fremde_buchungen(db, user, konto, seit,
verknuepfung.remote_uid)
fremde_verbraucht: set[int] = set()
for umsatz in client.umsaetze(verknuepfung.remote_uid, seit):
geholt += 1