feat: Regeln (Betrag/Transfer/Konto-Texte) + Münz-Icon + pastellgrünes Theme
Regeln: - Schlagwörter mit optionaler Betragsbedingung (z. B. „SV = 5.00 → Restaurant"); Betrags-Regeln gewinnen vor betragslosen - Transfer-Schlagwörter (Text → Transfer) auf der Konten-Seite - Konto-Identifikator-Art „Text": erkennt das Zielkonto eines Transfers aus dem Buchungstext (längster passender Text gewinnt) - Migration 0007, Endpunkte, Tests (Betrag/Transfer/Konflikt), gesamt 30 grün Optik: - App-Icon: Münzhaufen (aus Nutzer-SVG) weiß auf grünem Grund, klein gehalten - Farbschema auf pastelliges Grün (Akzent hell/dunkel + Favicon) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -148,6 +148,52 @@ def test_below_threshold_not_flagged(admin):
|
||||
assert kw["id"] not in [f["id"] for f in admin.get("/api/categories/keywords/flagged").json()]
|
||||
|
||||
|
||||
def test_amount_condition_rule(admin):
|
||||
giro = _account(admin, name="Giro Amt")
|
||||
a, _ = _expense_cats(admin, 2)
|
||||
# „sv" nur bei genau 5.00 -> Kategorie a
|
||||
r = admin.post(f"/api/categories/{a['id']}/keywords", json={"word": "sv", "amount_cents": 500})
|
||||
assert r.status_code == 201, r.text
|
||||
assert r.json()["amount_cents"] == 500
|
||||
|
||||
csv = (
|
||||
"Datum;Betrag;Empfaenger;Zweck\n"
|
||||
"01.03.2026;-5,00;SV Restaurant;Mittag\n" # 5.00 -> a
|
||||
"02.03.2026;-8,00;SV Restaurant;Produkt\n" # anderer Betrag -> keine Regel
|
||||
)
|
||||
rows = _preview_csv(admin, giro["id"], csv, COLMAP)
|
||||
assert rows[0]["category_id"] == a["id"]
|
||||
assert rows[1]["category_id"] is None
|
||||
|
||||
|
||||
def test_transfer_rule_and_target_guess(admin):
|
||||
giro = _account(admin, name="Giro TR")
|
||||
spar = _account(admin, name="Sparkonto TR", type="sparkonto",
|
||||
identifiers=[{"kind": "text", "value": "Sparen"}])
|
||||
assert any(i["kind"] == "text" for i in spar["identifiers"])
|
||||
|
||||
r = admin.post("/api/categories/transfer-keywords", json={"word": "uebertrag"})
|
||||
assert r.status_code == 201, r.text
|
||||
|
||||
csv = (
|
||||
"Datum;Betrag;Empfaenger;Zweck\n"
|
||||
"01.03.2026;-100,00;Max;Uebertrag Sparen\n" # -> Transfer, Ziel = Sparkonto
|
||||
"02.03.2026;-50,00;Max;Uebertrag woandershin\n" # -> Transfer, kein Ziel erkannt
|
||||
)
|
||||
rows = _preview_csv(admin, giro["id"], csv, COLMAP)
|
||||
assert rows[0]["type"] == "transfer"
|
||||
assert rows[0]["to_account_id"] == spar["id"]
|
||||
assert rows[1]["type"] == "transfer"
|
||||
assert rows[1]["to_account_id"] is None
|
||||
|
||||
|
||||
def test_transfer_keyword_conflicts_with_category_keyword(admin):
|
||||
a, _ = _expense_cats(admin, 2)
|
||||
admin.post(f"/api/categories/{a['id']}/keywords", json={"word": "doppelwort"})
|
||||
# gleiches Wort als Transfer-Regel -> Konflikt
|
||||
assert admin.post("/api/categories/transfer-keywords", json={"word": "doppelwort"}).status_code == 409
|
||||
|
||||
|
||||
def test_no_retroactive_apply(admin):
|
||||
giro = _account(admin, name="Giro Retro")
|
||||
a, _ = _expense_cats(admin, 2)
|
||||
|
||||
Reference in New Issue
Block a user