Kategorien erben ihre Art von der Gruppe, und die ist entweder Ausgabe oder Einnahme. Für einen Kontoübertrag passt beides nicht: Auf dem einen Konto geht Geld raus, auf dem anderen kommt es rein – man musste also zwei Kategorien in zwei Gruppen anlegen. Als „nicht in Auswertungen einrechnen" markierte Kategorien werden jetzt in beiden Richtungen angeboten (Buchungsformular und Import-Prüfansicht), und ihre Schlagwörter greifen unabhängig vom Vorzeichen. Eine Kategorie „Kontoübertrag" in einer Ausgabe-Gruppe genügt damit für beide Seiten. Test für beide Richtungen; 94 grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
"""Kategorien, die nicht in Auswertungen zählen (z. B. „Kontoübertrag").
|
||
|
||
Eine Umbuchung zwischen eigenen Konten ist keine Ausgabe – das Geld liegt nur
|
||
woanders. Der Kontostand muss sie trotzdem abbilden.
|
||
|
||
Gemessen wird als Differenz: Die Testdatenbank lebt über den ganzen Lauf, es
|
||
liegen also schon Buchungen anderer Tests im laufenden Monat."""
|
||
|
||
import datetime
|
||
|
||
|
||
def _expense_cats(admin, n=2):
|
||
out = []
|
||
for g in admin.get("/api/categories").json():
|
||
if g["kind"] == "expense":
|
||
out += g["categories"]
|
||
assert len(out) >= n
|
||
return out[:n]
|
||
|
||
|
||
def _ausgaben(admin):
|
||
return admin.get("/api/dashboard/income-expense").json()["expense"]
|
||
|
||
|
||
def _anteile(admin):
|
||
d = admin.get("/api/dashboard/expense-share?group_by=category").json()
|
||
return {r["name"]: r["amount"] for r in d["rows"]}
|
||
|
||
|
||
def test_neutral_category_out_of_stats_but_in_balance(admin):
|
||
acc = admin.post("/api/accounts", json={
|
||
"name": "Giro Neutral", "type": "girokonto", "currency": "EUR",
|
||
"opening_balance_cents": 100000,
|
||
}).json()
|
||
normal, uebertrag = _expense_cats(admin, 2)
|
||
for c in (normal, uebertrag): # definierter Ausgangszustand
|
||
admin.patch(f"/api/categories/{c['id']}", json={"excluded_from_stats": False})
|
||
|
||
vorher_ausgaben = _ausgaben(admin)
|
||
vorher_anteile = _anteile(admin)
|
||
|
||
heute = datetime.date.today().isoformat()
|
||
for cat, cents in ((normal, 5000), (uebertrag, 30000)):
|
||
r = admin.post("/api/transactions", json={
|
||
"type": "expense", "date": heute, "account_id": acc["id"],
|
||
"amount_cents": cents, "category_id": cat["id"],
|
||
})
|
||
assert r.status_code == 201, r.text
|
||
|
||
# Beide Buchungen wirken auf den Saldo: 1000.00 - 50.00 - 300.00 = 650.00
|
||
assert admin.get(f"/api/accounts/{acc['id']}").json()["balance_cents"] == 65000
|
||
assert _ausgaben(admin) == vorher_ausgaben + 35000
|
||
|
||
# Jetzt „Kontoübertrag" aus den Auswertungen nehmen.
|
||
r = admin.patch(f"/api/categories/{uebertrag['id']}", json={"excluded_from_stats": True})
|
||
assert r.status_code == 200, r.text
|
||
assert r.json()["excluded_from_stats"] is True
|
||
|
||
# Nur noch die echte Ausgabe zählt – der Saldo bleibt unverändert.
|
||
assert _ausgaben(admin) == vorher_ausgaben + 5000
|
||
assert admin.get(f"/api/accounts/{acc['id']}").json()["balance_cents"] == 65000
|
||
|
||
jetzt = _anteile(admin)
|
||
assert jetzt.get(normal["name"], 0) == vorher_anteile.get(normal["name"], 0) + 5000
|
||
assert jetzt.get(uebertrag["name"], 0) == 0, "neutrale Kategorie darf nicht auftauchen"
|
||
|
||
# Häkchen entfernen -> zählt sofort wieder mit.
|
||
admin.patch(f"/api/categories/{uebertrag['id']}", json={"excluded_from_stats": False})
|
||
assert _ausgaben(admin) == vorher_ausgaben + 35000
|
||
|
||
|
||
def test_neutral_keyword_matches_both_directions(admin):
|
||
"""Ein Kontoübertrag geht auf dem einen Konto raus und kommt auf dem anderen
|
||
rein. Die neutrale Kategorie muss deshalb für BEIDE Richtungen greifen –
|
||
obwohl sie in einer Ausgabe-Gruppe liegt."""
|
||
import io, json
|
||
acc = admin.post("/api/accounts", json={
|
||
"name": "Giro Beide", "type": "girokonto", "currency": "EUR",
|
||
}).json()
|
||
uebertrag = _expense_cats(admin, 2)[1]
|
||
admin.patch(f"/api/categories/{uebertrag['id']}", json={"excluded_from_stats": True})
|
||
admin.post(f"/api/categories/{uebertrag['id']}/keywords", json={"word": "kontouebertrag"})
|
||
|
||
csv_text = (
|
||
"Datum;Betrag;Empfaenger;Zweck\n"
|
||
"05.03.2026;-250,00;Kontouebertrag raus;;\n"
|
||
"06.03.2026;250,00;Kontouebertrag rein;;\n"
|
||
)
|
||
rows = admin.post(
|
||
"/api/imports/preview",
|
||
files={"file": ("k.csv", io.BytesIO(csv_text.encode("utf-8")), "text/csv")},
|
||
data={
|
||
"account_id": str(acc["id"]), "delimiter": ";", "decimal_sep": ",",
|
||
"date_format": "%d.%m.%Y", "encoding": "utf-8", "has_header": "true",
|
||
"skip_rows": "0", "amount_mode": "signed",
|
||
"column_map": json.dumps({"date": 0, "amount": 1, "payee": 2, "purpose": 3}),
|
||
},
|
||
).json()["rows"]
|
||
|
||
assert rows[0]["category_id"] == uebertrag["id"], "Ausgabe-Richtung"
|
||
assert rows[1]["category_id"] == uebertrag["id"], "Einnahme-Richtung ebenso"
|