feat: ein Schlagwort darf mehrere Beträge haben

Ein Wort war je Art nur einmal erlaubt, also ließ sich genau ein Preis
hinterlegen. Im Personalrestaurant zahlt man aber mal 5.00 und mal 7.50 – der
zweite Preis ließ sich nicht erfassen.

Der Betrag gehört jetzt in den Unique-Schlüssel: „sv gruppe = 5.00" und
„sv gruppe = 7.50" sind zwei Regeln derselben Kategorie, dasselbe Wort OHNE
Betrag bleibt der Auffang für alle übrigen Beträge (und darf in einer anderen
Kategorie liegen). Betrags-Regeln greifen vor dem Auffang.

- Migration 0011 (Unique-Schlüssel um amount_cents erweitert)
- Konflikt nur noch bei gleichem Wort UND gleichem Betrag; gilt auch beim
  Umbenennen und für Transfer-Schlagwörter
- Test: zwei Preise treffen die Kategorie, ein dritter Betrag den Auffang; 88 grün

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 08:34:24 +02:00
co-authored by Claude Opus 4.8
parent 187a1119a2
commit 595662e515
4 changed files with 108 additions and 9 deletions
+37
View File
@@ -326,3 +326,40 @@ def test_no_retroactive_apply(admin):
txns = admin.get("/api/transactions").json()
aldi = [t for t in txns if (t.get("payee") or "").upper() == "ALDI"]
assert aldi and aldi[0]["category_id"] is None # bestehende Buchung bleibt unkategorisiert
def test_same_word_multiple_amounts(admin):
"""Ein Wort darf mehrere Preise haben – im Personalrestaurant zahlt man mal
5.00, mal 7.50. Ohne Betrag bleibt es der Auffang für alle übrigen."""
giro = _account(admin, name="Giro Preise")
mensa, buero = _expense_cats(admin, 2)
# Zwei Preise fürs Restaurant …
for cents in (500, 750):
r = admin.post(f"/api/categories/{mensa['id']}/keywords",
json={"word": "sv gruppe", "amount_cents": cents})
assert r.status_code == 201, r.text
# … und derselbe Text ohne Betrag als Auffang in einer anderen Kategorie.
r = admin.post(f"/api/categories/{buero['id']}/keywords", json={"word": "sv gruppe"})
assert r.status_code == 201, r.text
worte = admin.get(f"/api/categories/{mensa['id']}/keywords").json()
betraege = sorted(w["amount_cents"] for w in worte if w["word"] == "sv gruppe")
assert betraege == [500, 750]
csv = (
"Datum;Betrag;Empfaenger;Zweck\n"
"01.03.2026;-5,00;SV GRUPPE;Mittag\n"
"02.03.2026;-7,50;SV GRUPPE;Mittag gross\n"
"03.03.2026;-24,00;SV GRUPPE;Material\n"
)
rows = _preview_csv(admin, giro["id"], csv, COLMAP)
assert rows[0]["category_id"] == mensa["id"], "5.00 -> Restaurant"
assert rows[1]["category_id"] == mensa["id"], "7.50 -> Restaurant"
assert rows[2]["category_id"] == buero["id"], "anderer Betrag -> Auffang"
# Derselbe Preis zweimal bleibt ein Konflikt bzw. liefert dieselbe Regel.
doppelt = admin.post(f"/api/categories/{mensa['id']}/keywords",
json={"word": "sv gruppe", "amount_cents": 500})
assert doppelt.status_code == 201
assert len(admin.get(f"/api/categories/{mensa['id']}/keywords").json()) == len(worte)