Files
certain/backend/tests/test_budget.py
T
GuidoandClaude Opus 4.8 49b927e2eb feat: Budget – mehrere Monate nebeneinander + Kopieren Monat<->Vorlage
- Umschalter 1·2·3: Monate nebeneinander (responsives Raster, auf schmalem
  Schirm gestapelt); prev/next verschiebt das ganze Fenster
- Je Monat: „Als Standard festlegen" (Monat -> Vorlage) und „Standard
  übernehmen" (Vorlage -> Monat, überschreibt), plus Zurücksetzen
- Backend: copy_month_to_template / copy_template_to_month + Endpunkte
  POST /budget/template/from-month/{label} und /budget/{label}/from-template
- Test fürs Kopieren; globale .grow-Utility ergänzt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 17:07:49 +02:00

129 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from datetime import date
def test_period_bounds_and_labels():
from app.services import budget
# Gehalt am 25.: September-Budget läuft 25.08.–24.09.
assert budget.period_bounds("2026-09", 25) == (date(2026, 8, 25), date(2026, 9, 24))
# Starttag 1: Kalendermonat.
assert budget.period_bounds("2026-09", 1) == (date(2026, 9, 1), date(2026, 9, 30))
# Label-Zuordnung
assert budget.period_of(date(2026, 8, 27), 25) == "2026-09"
assert budget.period_of(date(2026, 9, 10), 25) == "2026-09"
assert budget.period_of(date(2026, 9, 26), 25) == "2026-10"
assert budget.period_of(date(2026, 9, 15), 1) == "2026-09"
assert budget.shift_label("2026-09", 1) == "2026-10"
assert budget.shift_label("2026-01", -1) == "2025-12"
def _lebensmittel(admin):
groups = admin.get("/api/categories").json()
for g in groups:
for c in g["categories"]:
if c["name"] == "Lebensmittel":
return c["id"]
raise AssertionError("Lebensmittel-Kategorie fehlt")
def _gehalt(admin):
groups = admin.get("/api/categories").json()
for g in groups:
for c in g["categories"]:
if c["name"] == "Gehalt":
return c["id"]
raise AssertionError("Gehalt-Kategorie fehlt")
def test_budget_soll_ist_and_override(admin):
# Starttag 1 -> aktueller Zeitraum = Kalendermonat, heutige Buchungen zählen.
admin.put("/api/settings/budget_start_day", params={"value": "1"})
today = date.today()
label = f"{today.year:04d}-{today.month:02d}"
acc = admin.post("/api/accounts", json={"name": "Budgetkonto", "currency": "EUR"}).json()
leb = _lebensmittel(admin)
# Standard-Budget für Lebensmittel: 500,00 EUR.
admin.put(f"/api/budget/template/category/{leb}", params={"planned_cents": 50000})
# Ausgabe 300,00 EUR heute.
admin.post("/api/transactions", json={"type": "expense", "date": str(today), "account_id": acc["id"], "amount_cents": 30000, "category_id": leb})
v = admin.get("/api/budget/current").json()
row = next(c for g in v["groups"] for c in g["categories"] if c["category_id"] == leb)
assert row["planned_cents"] == 50000
assert row["actual_cents"] == 30000
assert row["over"] is False
assert v["customized"] is False
# Noch 300 EUR -> insgesamt 600 > 500 -> über Budget.
admin.post("/api/transactions", json={"type": "expense", "date": str(today), "account_id": acc["id"], "amount_cents": 30000, "category_id": leb})
v = admin.get(f"/api/budget/{label}").json()
row = next(c for g in v["groups"] for c in g["categories"] if c["category_id"] == leb)
assert row["actual_cents"] == 60000
assert row["over"] is True
# Monats-Override auf 700 EUR -> nicht mehr über Budget, Monat individualisiert.
v = admin.put(f"/api/budget/{label}/category/{leb}", params={"planned_cents": 70000}).json()
row = next(c for g in v["groups"] for c in g["categories"] if c["category_id"] == leb)
assert row["planned_cents"] == 70000
assert row["over"] is False
assert v["customized"] is True
# Vorlage bleibt unverändert.
tpl = admin.get("/api/budget/template").json()
tpl_leb = next(l for l in tpl["lines"] if l["category_id"] == leb)
assert tpl_leb["planned_cents"] == 50000
# Zurücksetzen -> wieder Vorlagenwert, nicht mehr individualisiert.
v = admin.delete(f"/api/budget/{label}").json()
row = next(c for g in v["groups"] for c in g["categories"] if c["category_id"] == leb)
assert row["planned_cents"] == 50000
assert v["customized"] is False
def test_budget_copy_month_and_template(admin):
admin.put("/api/settings/budget_start_day", params={"value": "1"})
today = date.today()
label = f"{today.year:04d}-{today.month:02d}"
leb = _lebensmittel(admin)
admin.put(f"/api/budget/template/category/{leb}", params={"planned_cents": 50000})
# Monat individuell auf 700.
admin.put(f"/api/budget/{label}/category/{leb}", params={"planned_cents": 70000})
# Monat -> Vorlage: Vorlage übernimmt den 700er Wert.
r = admin.post(f"/api/budget/template/from-month/{label}")
assert r.status_code == 200, r.text
tpl = r.json()
assert next(l for l in tpl["lines"] if l["category_id"] == leb)["planned_cents"] == 70000
# Vorlage auf 900 ändern, dann Vorlage -> Monat (überschreibt den angepassten Monat).
admin.put(f"/api/budget/template/category/{leb}", params={"planned_cents": 90000})
v = admin.post(f"/api/budget/{label}/from-template").json()
row = next(c for g in v["groups"] for c in g["categories"] if c["category_id"] == leb)
assert row["planned_cents"] == 90000
assert v["customized"] is True
def test_planned_income(admin):
admin.put("/api/settings/budget_start_day", params={"value": "1"})
today = date.today()
acc = admin.post("/api/accounts", json={"name": "Lohnkonto", "currency": "EUR"}).json()
gehalt = _gehalt(admin)
r = admin.post("/api/budget/income", json={
"label": "Gehalt", "category_id": gehalt, "target_account_id": acc["id"], "planned_cents": 300000,
})
assert r.status_code == 201
# Tatsächliche Einnahme 3000 EUR.
admin.post("/api/transactions", json={"type": "income", "date": str(today), "account_id": acc["id"], "amount_cents": 300000, "category_id": gehalt})
v = admin.get("/api/budget/current").json()
line = next(i for i in v["income"] if i["label"] == "Gehalt")
assert line["planned_cents"] == 300000
assert line["received_cents"] == 300000
assert v["totals"]["planned_income"] >= 300000