fix: Saldo-Abgleich meldet Zeilen, die auf ein Gegenkonto warten
Eine Zeile mit „Gegenkonto wählen" wird nicht gebucht, der Abgleich zählte ihren Betrag aber trotzdem mit und meldete „stimmt überein" – das Geld fehlte im Konto, ohne dass es auffiel. Duplikate und Gegenbuchungen zählen weiterhin mit (ihr Betrag steckt bereits in einer früher gebuchten Buchung); wartende Zeilen zählen jetzt als 0 und tauchen als erste Abweichung mit Datum und Empfänger auf. Nachgestellt am echten Raiffeisen-Auszug: Zeigt die Transfer-Regel „Übertrag" auf das importierte Konto selbst, bleibt die Gutschrift über 92.00 ungebucht; der Abgleich nennt jetzt genau diese Zeile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -551,10 +551,15 @@ def reconcile(rows: list[dict]) -> dict:
|
|||||||
return {"available": False}
|
return {"available": False}
|
||||||
|
|
||||||
def gebucht(r: dict) -> int:
|
def gebucht(r: dict) -> int:
|
||||||
"""Betrag, der tatsächlich ins Konto fließt – 0 für nicht gebuchte Zeilen
|
"""Betrag, der im Konto ankommt – 0, wenn die Zeile nirgends landet.
|
||||||
(storniert, ungültig). Nur so fällt auf, wenn die Bank etwas verbucht hat,
|
|
||||||
wir aber nichts (z. B. nicht zugeordnete Gebührenspalte)."""
|
Duplikate und Gegenbuchungen zählen mit: deren Betrag steckt bereits in
|
||||||
return (r.get("amount_cents") or 0) if r.get("valid") else 0
|
einer früher gebuchten Buchung. Eine Zeile, die auf ein Gegenkonto wartet,
|
||||||
|
zählt dagegen NICHT – sie ist unterwegs verloren, und genau das soll der
|
||||||
|
Abgleich melden."""
|
||||||
|
if not r.get("valid") or r.get("needs_target"):
|
||||||
|
return 0
|
||||||
|
return r.get("amount_cents") or 0
|
||||||
|
|
||||||
# Manche Banken exportieren neueste Buchung zuerst – für den Abgleich drehen.
|
# Manche Banken exportieren neueste Buchung zuerst – für den Abgleich drehen.
|
||||||
dated = [r["date"] for r in seq if r.get("date")]
|
dated = [r["date"] for r in seq if r.get("date")]
|
||||||
|
|||||||
@@ -485,3 +485,37 @@ def test_fee_row_direction_from_balance(admin):
|
|||||||
assert rows["rows"][1]["amount_cents"] == -1099, "Gebühr belastet"
|
assert rows["rows"][1]["amount_cents"] == -1099, "Gebühr belastet"
|
||||||
assert rows["rows"][2]["amount_cents"] == 1099, "Gebühr erstattet"
|
assert rows["rows"][2]["amount_cents"] == 1099, "Gebühr erstattet"
|
||||||
assert rows["reconciliation"]["ok"] is True
|
assert rows["reconciliation"]["ok"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_reconciliation_flags_rows_waiting_for_target(admin):
|
||||||
|
"""Eine Transfer-Regel ohne Gegenkonto lässt die Zeile ungebucht. Der
|
||||||
|
Saldo-Abgleich muss das melden – sonst fehlt das Geld unbemerkt."""
|
||||||
|
giro = _account(admin, name="Ziel offen", currency="CHF")
|
||||||
|
admin.post("/api/categories/transfer-keywords",
|
||||||
|
json={"word": "uebertrag", "target_account_id": giro["id"]}) # zeigt auf sich selbst
|
||||||
|
|
||||||
|
csv_text = (
|
||||||
|
"Datum;Betrag;Empfaenger;Zweck;Saldo\n"
|
||||||
|
"01.03.2026;100,00;Lohn;Gehalt;100,00\n"
|
||||||
|
"02.03.2026;92,00;Uebertrag von Sparkonto;;192,00\n"
|
||||||
|
"03.03.2026;-10,00;Kiosk;;182,00\n"
|
||||||
|
)
|
||||||
|
res = admin.post(
|
||||||
|
"/api/imports/preview",
|
||||||
|
files={"file": ("t.csv", io.BytesIO(csv_text.encode("utf-8")), "text/csv")},
|
||||||
|
data={
|
||||||
|
"account_id": str(giro["id"]), "delimiter": ";", "decimal_sep": ",",
|
||||||
|
"date_format": "%d.%m.%Y", "encoding": "utf-8", "has_header": "true",
|
||||||
|
"skip_rows": "0", "amount_mode": "signed",
|
||||||
|
"column_map": json.dumps({"date": 0, "amount": 1, "payee": 2, "purpose": 3, "balance": 4}),
|
||||||
|
},
|
||||||
|
).json()
|
||||||
|
|
||||||
|
wartend = [r for r in res["rows"] if r.get("needs_target")]
|
||||||
|
assert len(wartend) == 1 and wartend[0]["amount_cents"] == 9200
|
||||||
|
|
||||||
|
rec = res["reconciliation"]
|
||||||
|
assert rec["ok"] is False, "die wartende Zeile muss auffallen"
|
||||||
|
assert rec["difference_cents"] == 9200
|
||||||
|
assert rec["first_mismatch"]["expected_cents"] == 9200
|
||||||
|
assert rec["first_mismatch"]["actual_cents"] == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user