feat: Währungstausch wird über beide Auszüge als ein Transfer erkannt
Revolut tauscht Geld zwischen Währungskonten; im CHF-Auszug steht -100.00 CHF, im EUR-Auszug +106.42 EUR. Die Gegenbuchungs-Erkennung verglich exakte Beträge und griff deshalb nie – die Bewegung wurde zweimal gezählt. - _find_counterpart erkennt die Gegenseite bei unterschiedlichen Währungen über Konto-Paar, Zeitfenster und einen plausiblen Betrag (10 % Toleranz, weil wir mit dem EZB-Kurs schätzen und die Bank ihren eigenen nutzt) - die Gegenbuchung nennt den TATSÄCHLICHEN Betrag: er ersetzt beim Buchen die Schätzung im bereits gebuchten Transfer, damit beide Salden exakt stimmen - Test über beide Auszüge: ein Transfer, keine Doppelzählung, EUR-Seite exakt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -289,3 +289,62 @@ def test_date_format_tolerates_time(admin):
|
||||
).json()
|
||||
assert all(r["date"] for r in res["rows"]), "Datum trotz Uhrzeit erkannt"
|
||||
assert res["reconciliation"]["ok"] is True
|
||||
|
||||
|
||||
# --- Währungstausch: beide Auszüge ergeben EINEN Transfer -------------------
|
||||
|
||||
def _rev_preview(admin, account_id, csv_text):
|
||||
return admin.post(
|
||||
"/api/imports/preview",
|
||||
files={"file": ("x.csv", io.BytesIO(csv_text.encode("utf-8")), "text/csv")},
|
||||
data={
|
||||
"account_id": str(account_id), "delimiter": ",", "decimal_sep": ".",
|
||||
"date_format": "%Y-%m-%d %H:%M:%S", "encoding": "utf-8", "has_header": "true",
|
||||
"skip_rows": "0", "amount_mode": "signed",
|
||||
"column_map": json.dumps({"date": 3, "amount": 5, "payee": 4, "fee": 6,
|
||||
"currency": 7, "state": 8, "balance": 9}),
|
||||
},
|
||||
).json()["rows"]
|
||||
|
||||
|
||||
CHF_UMTAUSCH = (
|
||||
"Art,Produkt,Beginn,Abschluss,Beschreibung,Betrag,Gebühr,Währung,Status,Kontostand\n"
|
||||
"Umtausch,Giro,2026-02-01 10:00:00,2026-02-01 10:00:00,Umgetauscht in EUR,-100.00,0.00,CHF,ABGESCHLOSSEN,400.00\n"
|
||||
)
|
||||
EUR_UMTAUSCH = (
|
||||
"Art,Produkt,Beginn,Abschluss,Beschreibung,Betrag,Gebühr,Währung,Status,Kontostand\n"
|
||||
"Umtausch,Giro,2026-02-01 10:00:00,2026-02-01 10:00:00,Umgetauscht in EUR,106.42,0.00,EUR,ABGESCHLOSSEN,106.42\n"
|
||||
)
|
||||
|
||||
|
||||
def test_currency_exchange_pairs_across_statements(admin):
|
||||
chf = _account(admin, name="Rev CHF X", currency="CHF", opening_balance_cents=50000)
|
||||
eur = _account(admin, name="Rev EUR X", currency="EUR", opening_balance_cents=0)
|
||||
|
||||
# Regeln: „umgetauscht" ist ein Transfer zum jeweils anderen Konto.
|
||||
admin.post("/api/categories/transfer-keywords",
|
||||
json={"word": "umgetauscht", "target_account_id": eur["id"]})
|
||||
|
||||
# 1) CHF-Auszug: Umtausch geht als Transfer CHF -> EUR raus.
|
||||
rows = _rev_preview(admin, chf["id"], CHF_UMTAUSCH)
|
||||
assert rows[0]["type"] == "transfer"
|
||||
assert rows[0]["to_account_id"] == eur["id"]
|
||||
assert rows[0]["is_counterpart"] is False
|
||||
admin.post("/api/imports/commit", json={"account_id": chf["id"], "filename": "chf.csv", "rows": rows})
|
||||
assert admin.get(f"/api/accounts/{chf['id']}").json()["balance_cents"] == 40000
|
||||
|
||||
# 2) EUR-Auszug: dieselbe Bewegung – trotz anderem Betrag als Gegenbuchung erkannt.
|
||||
admin.post("/api/categories/transfer-keywords",
|
||||
json={"word": "umgetauscht", "target_account_id": chf["id"]})
|
||||
rows2 = _rev_preview(admin, eur["id"], EUR_UMTAUSCH)
|
||||
assert rows2[0]["type"] == "transfer"
|
||||
assert rows2[0]["is_counterpart"] is True, "Umtausch-Gegenseite muss erkannt werden"
|
||||
assert rows2[0]["include"] is False, "darf nicht doppelt zählen"
|
||||
|
||||
r = admin.post("/api/imports/commit", json={"account_id": eur["id"], "filename": "eur.csv", "rows": rows2})
|
||||
assert r.json()["imported_count"] == 0
|
||||
|
||||
# Der echte Eingang korrigiert die EZB-Schätzung: EUR-Konto steht auf 106.42.
|
||||
assert admin.get(f"/api/accounts/{eur['id']}").json()["balance_cents"] == 10642
|
||||
# CHF-Seite bleibt unverändert.
|
||||
assert admin.get(f"/api/accounts/{chf['id']}").json()["balance_cents"] == 40000
|
||||
|
||||
Reference in New Issue
Block a user