fix: Duplikat-Erkennung als Multiset – echte gleich aussehende Buchungen bleiben
Bisher wurde eine zweite Buchung mit gleichem Tag/Betrag/Text (z. B. zwei Gutschriften à 324 am selben Tag) fälschlich als Duplikat verworfen. Jetzt gilt eine Zeile nur als Duplikat, wenn bereits GENAU SO VIELE identische Buchungen in der DB stehen – ein erneuter Import derselben Datei bleibt weiterhin Duplikat-sicher. - csv_import.detect_rows: existing/seen als Counter (Multiset) statt Set - Tests angepasst + Re-Import-Test ergänzt; an echtem Raiffeisen-Auszug verifiziert (254 Zeilen, 0 fälschliche Duplikate, Saldo == Bank-Endsaldo) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import csv
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from collections import Counter
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
|
||||
|
||||
@@ -282,12 +283,16 @@ def detect_rows(db: Session, user: User, account: Account, candidates: list[dict
|
||||
cid: kind
|
||||
for cid, kind in db.query(Category.id, Category.type).filter(Category.owner_id == user.id)
|
||||
}
|
||||
existing = {
|
||||
# Duplikate als Multiset zählen: eine Zeile gilt nur dann als Duplikat, wenn
|
||||
# bereits GENAU SO VIELE identische Buchungen in der DB stehen. So werden echte,
|
||||
# gleich aussehende Buchungen desselben Tags (z. B. zwei Gutschriften über 324)
|
||||
# behalten, ein erneuter Import derselben Datei aber weiter erkannt.
|
||||
existing: Counter = Counter(
|
||||
h[0]
|
||||
for h in db.query(Transaction.import_hash).filter(
|
||||
Transaction.owner_id == user.id, Transaction.import_hash.isnot(None)
|
||||
)
|
||||
}
|
||||
)
|
||||
# Bestehende Transfers je Konto-Paar: erkennt die Gegenbuchung, wenn beide
|
||||
# Auszüge eines Übertrags importiert werden (sonst zählt die Bewegung doppelt).
|
||||
transfer_index: dict[frozenset, list[tuple]] = {}
|
||||
@@ -300,7 +305,7 @@ def detect_rows(db: Session, user: User, account: Account, candidates: list[dict
|
||||
transfer_index.setdefault(frozenset((t_acc, t_to)), []).append(
|
||||
(t_date, t_amt, t_to_amt if t_to_amt is not None else t_amt)
|
||||
)
|
||||
seen: set[str] = set()
|
||||
seen: Counter = Counter()
|
||||
|
||||
out: list[dict] = []
|
||||
for i, cand in enumerate(candidates):
|
||||
@@ -341,8 +346,8 @@ def detect_rows(db: Session, user: User, account: Account, candidates: list[dict
|
||||
category_id = rule.category_id
|
||||
|
||||
h = import_hash(account.id, d, amount, payee)
|
||||
is_dup = h in existing or h in seen
|
||||
seen.add(h)
|
||||
is_dup = seen[h] < existing[h] # nur so viele Duplikate wie schon in der DB
|
||||
seen[h] += 1
|
||||
|
||||
# Gegenbuchung eines bereits gebuchten Transfers? Dann nicht erneut buchen.
|
||||
is_counterpart = (
|
||||
|
||||
Reference in New Issue
Block a user