fix: Die Meldung sagt jetzt, wenn die Gegenseite schon anderweitig verbunden ist

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-19 06:53:47 +02:00
co-authored by Claude Opus 5
parent b1f5eb9a51
commit 14254b483e
2 changed files with 72 additions and 0 deletions
+14
View File
@@ -173,6 +173,20 @@ def _pruefe_gegenseite(db: Session, user: User, konto: Account, ziel: Account,
datum, exakt) datum, exakt)
if schon is None: if schon is None:
return return
# Haengt die gefundene Buchung schon an einer anderen, ist sie NICHT die
# Gegenseite dieser hier. Der Rat „verbinden" ginge dann ins Leere: Die
# Vorschlagsliste laesst vergebene Buchungen aus, es kaeme nie ein
# Vorschlag – und der Nutzer sucht etwas, das es nicht gibt.
if _schon_verknuepft(db, user, schon):
raise UebertragFehler(
f"Auf '{ziel.name}' steht zwar eine passende Buchung vom "
f"{schon.date.strftime('%d.%m.%Y')}, die ist aber schon mit einer "
f"anderen Buchung verbunden – also nicht die Gegenseite dieser "
f"hier. Verbinden geht deshalb nicht. Sieh auf '{ziel.name}' nach, "
f"ob die vorhandene Verknüpfung stimmt, oder ob der zweite Zugang "
f"dort schlicht fehlt.",
nachfragen=True,
)
raise UebertragFehler( raise UebertragFehler(
f"Auf '{ziel.name}' steht schon eine passende Buchung vom " f"Auf '{ziel.name}' steht schon eine passende Buchung vom "
f"{schon.date.strftime('%d.%m.%Y')}. Beide Seiten sind also bereits " f"{schon.date.strftime('%d.%m.%Y')}. Beide Seiten sind also bereits "
+58
View File
@@ -455,3 +455,61 @@ def test_verknuepfte_buchungen_landen_nicht_in_uebersprungen(admin):
# Und sie bleibt unangetastet: kein Uebertrag, kein verschobenes Geld. # Und sie bleibt unangetastet: kein Uebertrag, kein verschobenes Geld.
assert admin.get(f"/api/transactions/{raus['id']}").json()["type"] == "expense" assert admin.get(f"/api/transactions/{raus['id']}").json()["type"] == "expense"
assert admin.get(f"/api/accounts/{pocket['id']}").json()["balance_cents"] == betrag assert admin.get(f"/api/accounts/{pocket['id']}").json()["balance_cents"] == betrag
def _tx(admin, konto, typ, datum, cents, payee):
return admin.post("/api/transactions", json={
"type": typ, "date": datum, "account_id": konto["id"],
"amount_cents": cents, "payee": payee}).json()
def test_vergebene_gegenseite_wird_als_solche_benannt(admin):
"""Die Pruefung fragt „gibt es auf dem Zielkonto eine passende Buchung?",
nicht „ist sie noch frei?". Haengt die gefundene schon an einer anderen
Buchung, ist sie NICHT die Gegenseite dieser hier – und der Rat „verbinden"
geht ins Leere, denn die Vorschlagsliste laesst vergebene Buchungen aus.
Dann muss die Meldung sagen, was wirklich los ist."""
quelle = _kto(admin, name=f"Revolut {secrets.token_hex(3)}")
pocket = _kto(admin, name=f"Sparen Pocket {secrets.token_hex(3)}")
anderes = _kto(admin, name=f"Anderes {secrets.token_hex(3)}")
kennung = f"um chf sparen {secrets.token_hex(6)}"
betrag = 62 + secrets.randbelow(900)
# Der Zugang im Pocket gehoert schon zu einer Buchung vom dritten Konto.
zugang = _tx(admin, pocket, "income", "2026-05-20", betrag, "Pocket")
fremd = _tx(admin, anderes, "expense", "2026-05-20", betrag, "Fremd")
assert admin.post("/api/uebertraege/verbinden",
json={"a_id": fremd["id"], "b_id": zugang["id"]}).status_code == 200
# Und jetzt kommt die Revolut-Ausgabe, gleicher Tag, gleicher Betrag.
raus = _tx(admin, quelle, "expense", "2026-05-20", betrag, kennung.upper())
assert admin.post("/api/categories/transfer-keywords", json={
"word": kennung, "target_account_id": pocket["id"]}).status_code == 201
r = admin.post("/api/categories/keywords/anwenden", params={"vorschau": False}).json()
grund = next(z["grund"] for z in r["uebersprungen"] if z["text"] == kennung.upper())
assert "schon mit einer anderen Buchung verbunden" in grund
assert "Richtig ist hier, die beiden zu verbinden" not in grund, \
"zum Verbinden raten waere hier falsch – die Gegenseite ist vergeben"
# Geblockt wird weiter: kein Uebertrag, kein verschobenes Geld.
assert admin.get(f"/api/transactions/{raus['id']}").json()["type"] == "expense"
assert admin.get(f"/api/accounts/{pocket['id']}").json()["balance_cents"] == betrag
def test_freie_gegenseite_raet_weiter_zum_verbinden(admin):
"""Die Gegenprobe: Ist die Gegenseite noch frei, bleibt es beim alten Rat.
Sonst schluckte der neue Zweig alle Faelle."""
quelle = _kto(admin, name=f"Revolut {secrets.token_hex(3)}")
pocket = _kto(admin, name=f"Sparen Pocket {secrets.token_hex(3)}")
kennung = f"um chf sparen {secrets.token_hex(6)}"
betrag = 62 + secrets.randbelow(900)
_tx(admin, pocket, "income", "2026-05-20", betrag, "Pocket")
_tx(admin, quelle, "expense", "2026-05-20", betrag, kennung.upper())
assert admin.post("/api/categories/transfer-keywords", json={
"word": kennung, "target_account_id": pocket["id"]}).status_code == 201
r = admin.post("/api/categories/keywords/anwenden", params={"vorschau": False}).json()
grund = next(z["grund"] for z in r["uebersprungen"] if z["text"] == kennung.upper())
assert "Richtig ist hier, die beiden zu verbinden" in grund