feat: Buchungen lassen sich von Hand einem Abo zuordnen – und das merkt sich

Die Erkennung lief seit Stufe 3, aber nur automatisch: Was sie nicht
sicher traf, blieb liegen, und sehen konnte man davon nichts. Die
Endpunkte gab es, die Oberflaeche kannte keinen davon.

Jetzt steht in der Buchungsliste eine Spalte "Abo" – dieselbe Form wie
die Kategorie daneben: Die Auswahl zeigt, zu welchem Vertrag die Zahlung
gehoert, aendert es, und leer loest die Zuordnung. Uebertraege bleiben
aussen vor; sie verschieben nur Geld.

Und sie lernt. Musste man von Hand ran, hat der Erkennungstext nicht
gepasst – also wird der Empfaenger dieser Buchung der neue. Beim
naechsten Mal faellt dieselbe Zahlung von selbst zu, statt dass man
jeden Monat dasselbe klickt. Der Vertragsname bleibt als zweite Chance:
_passt_text prueft beide, nicht entweder/oder, sonst verlaere man beim
Lernen, was vorher schon getroffen hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-15 09:59:55 +02:00
co-authored by Claude Opus 5
parent 493a98ab6b
commit 0ea2ab1322
7 changed files with 122 additions and 9 deletions
+53
View File
@@ -329,3 +329,56 @@ def test_zuordnung_ueberlebt_sichern_und_einspielen(admin):
assert verlauf["summe_cents"] == 1990
finally:
pass
# --- Zuordnen von Hand lehrt den Vertrag ------------------------------------
def test_haendische_zuordnung_lernt_den_empfaenger(admin):
"""Musste man von Hand ran, hat der Erkennungstext nicht gepasst.
Beim naechsten Mal soll dieselbe Zahlung von selbst fallen – sonst klickt
man jeden Monat dasselbe.
"""
konto = _konto(admin)
abo = _abo(admin, name="Fitness " + secrets.token_hex(3), amount_cents=4990,
account_id=konto["id"])
# Der Auszug nennt den Anbieter ganz anders als der Vertrag heisst.
t1 = _buchung(admin, konto, 4990, "2026-06-05", payee="ACTIV GYM ZUERICH 8004")
t2 = _buchung(admin, konto, 4990, "2026-07-05", payee="ACTIV GYM ZUERICH 8004")
try:
r = admin.post(f"/api/subscriptions/{abo['id']}/buchungen/{t1['id']}")
assert r.status_code == 200, r.text
assert r.json()["payee_pattern"] == "ACTIV GYM ZUERICH 8004"
_zuordnen(admin)
assert _txn(admin, t2["id"])["subscription_id"] == abo["id"]
finally:
_aufraeumen(admin, abo, (t1["id"], t2["id"]))
def test_lernen_verdraengt_den_namen_nicht(admin):
"""Der Name bleibt als zweite Chance – sonst verliert man beim Lernen,
was vorher schon getroffen hat."""
konto = _konto(admin)
abo = _abo(admin, name="Spotify", amount_cents=1295, account_id=konto["id"])
fremd = _buchung(admin, konto, 1295, "2026-06-07", payee="DIGITAL RIVER GMBH")
klassisch = _buchung(admin, konto, 1295, "2026-07-07", payee="SPOTIFY AB")
try:
admin.post(f"/api/subscriptions/{abo['id']}/buchungen/{fremd['id']}")
_zuordnen(admin)
assert _txn(admin, klassisch["id"])["subscription_id"] == abo["id"]
finally:
_aufraeumen(admin, abo, (fremd["id"], klassisch["id"]))
def test_passende_zuordnung_aendert_den_erkennungstext_nicht(admin):
"""Hat der Text schon gepasst, gibt es nichts zu lernen."""
konto = _konto(admin)
abo = _abo(admin, name="Netflix", amount_cents=1799, account_id=konto["id"])
t = _buchung(admin, konto, 1799, "2026-06-09", payee="NETFLIX.COM 4711")
try:
r = admin.post(f"/api/subscriptions/{abo['id']}/buchungen/{t['id']}")
assert r.json()["payee_pattern"] == "Netflix"
finally:
_aufraeumen(admin, abo, (t["id"],))