feat: API für Szenario-Zeitraum, Neuer Betrag und szenario-eigene wiederkehrende Zahlungen (A14-T3)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -225,11 +225,11 @@ def test_modifier_ende_validierung(client, db):
|
||||
"end_date": "2026-08-31"})
|
||||
assert r.status_code == 201
|
||||
assert r.json()["end_date"] == "2026-08-31"
|
||||
# andere Arten nullen end_date
|
||||
# andere Arten behalten end_date als „Gültig bis"
|
||||
r = client.post(f"/api/scenarios/{sc['id']}/modifiers", headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "remove",
|
||||
"end_date": "2026-08-31"})
|
||||
assert r.status_code == 201 and r.json()["end_date"] is None
|
||||
assert r.status_code == 201 and r.json()["end_date"] == "2026-08-31"
|
||||
|
||||
|
||||
def test_scenario_planned_crud_und_cleanup(client, db):
|
||||
@@ -269,11 +269,11 @@ def test_modifier_patch(client, db):
|
||||
r = client.patch(f"/api/scenarios/{sc['id']}/modifiers/{m['id']}", headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "ende"})
|
||||
assert r.status_code == 422
|
||||
# zurueck auf remove: end_date wird genullt
|
||||
# zurueck auf remove: end_date bleibt als „Gültig bis" erhalten
|
||||
r = client.patch(f"/api/scenarios/{sc['id']}/modifiers/{m['id']}", headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "remove",
|
||||
"end_date": "2026-08-31"})
|
||||
assert r.status_code == 200 and r.json()["end_date"] is None
|
||||
assert r.status_code == 200 and r.json()["end_date"] == "2026-08-31"
|
||||
# fremdes Szenario -> 404
|
||||
other = client.post("/api/scenarios", headers=H, json={"name": "ModPatch2"}).json()
|
||||
r = client.patch(f"/api/scenarios/{other['id']}/modifiers/{m['id']}", headers=H, json={
|
||||
@@ -318,3 +318,117 @@ def test_szenario_name_system_ist_ist_reserviert_patch(client):
|
||||
def test_szenario_normale_namen_weiter_erlaubt(client):
|
||||
assert client.post("/api/scenarios", json={"name": "Ist-Zustand plus"},
|
||||
headers=H).status_code == 201
|
||||
|
||||
|
||||
def _msgs(r):
|
||||
return " ".join(e["msg"] for e in r.json()["detail"])
|
||||
|
||||
|
||||
def test_modifier_zeitraum_und_neuer_betrag(client):
|
||||
sc = client.post("/api/scenarios", headers=H, json={"name": "Zeitraum"}).json()
|
||||
url = f"/api/scenarios/{sc['id']}/modifiers"
|
||||
r = client.post(url, headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "percent", "value": "50",
|
||||
"start_date": "2026-10-16", "end_date": "2027-03-31"})
|
||||
assert r.status_code == 201
|
||||
assert (r.json()["start_date"], r.json()["end_date"]) == ("2026-10-16", "2027-03-31")
|
||||
r = client.post(url, headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "percent", "value": "50",
|
||||
"start_date": "2027-04-01", "end_date": "2027-03-31"})
|
||||
assert r.status_code == 422 and "»Gültig ab« liegt nach »Gültig bis«" in _msgs(r)
|
||||
r = client.post(url, headers=H, json={
|
||||
"target_type": "category", "target_id": 1, "kind": "set", "value": "-80.00"})
|
||||
assert r.status_code == 422 and "Art »Neuer Betrag« ist nur für Posten möglich" in _msgs(r)
|
||||
r = client.post(url, headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "set", "value": "-137.50",
|
||||
"start_date": "2026-10-16"})
|
||||
assert r.status_code == 201
|
||||
assert (r.json()["kind"], r.json()["value"], r.json()["end_date"]) == ("set", "-137.50", None)
|
||||
r = client.post(url, headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "ende",
|
||||
"start_date": "2026-10-01", "end_date": "2026-10-15"})
|
||||
assert r.status_code == 201
|
||||
assert (r.json()["start_date"], r.json()["end_date"]) == (None, "2026-10-15")
|
||||
|
||||
|
||||
def test_modifier_patch_artwechsel_zeitraum(client):
|
||||
sc = client.post("/api/scenarios", headers=H, json={"name": "Artwechsel"}).json()
|
||||
m = client.post(f"/api/scenarios/{sc['id']}/modifiers", headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "percent", "value": "50",
|
||||
"start_date": "2026-10-16", "end_date": "2027-03-31"}).json()
|
||||
url = f"/api/scenarios/{sc['id']}/modifiers/{m['id']}"
|
||||
r = client.patch(url, headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "ende",
|
||||
"start_date": "2026-10-16", "end_date": "2026-12-31"})
|
||||
assert r.status_code == 200
|
||||
assert (r.json()["start_date"], r.json()["end_date"]) == (None, "2026-12-31")
|
||||
r = client.patch(url, headers=H, json={
|
||||
"target_type": "recurring", "target_id": 1, "kind": "percent", "value": "10",
|
||||
"end_date": "2026-12-31"})
|
||||
assert r.status_code == 200
|
||||
assert (r.json()["kind"], r.json()["start_date"], r.json()["end_date"]) == \
|
||||
("percent", None, "2026-12-31")
|
||||
|
||||
|
||||
def test_scenario_recurring_crud_und_cleanup(client, db):
|
||||
from app.models.tables import Category, ScenarioRecurringItem
|
||||
cat = Category(name="Wohnen-SR")
|
||||
db.add(cat)
|
||||
db.commit()
|
||||
sc = client.post("/api/scenarios", headers=H, json={"name": "SR-CRUD"}).json()
|
||||
url = f"/api/scenarios/{sc['id']}/recurring"
|
||||
body = {"name": "Miete neu", "amount": "-137.50", "rhythm": "monthly",
|
||||
"due_day": 14, "start_date": "2026-10-16", "category_id": cat.id}
|
||||
r = client.post(url, headers=H, json=body)
|
||||
assert r.status_code == 201
|
||||
item = r.json()
|
||||
assert (item["scenario_id"], item["amount"], item["end_date"], item["category_id"]) == \
|
||||
(sc["id"], "-137.50", None, cat.id)
|
||||
assert [i["name"] for i in client.get(url, headers=H).json()] == ["Miete neu"]
|
||||
r = client.patch(f"{url}/{item['id']}", headers=H, json={
|
||||
"name": "Miete neu", "amount": "-250.00", "rhythm": "quarterly", "due_day": 1,
|
||||
"start_date": None, "end_date": "2027-12-31", "category_id": None})
|
||||
assert r.status_code == 200
|
||||
assert (r.json()["amount"], r.json()["rhythm"], r.json()["category_id"]) == \
|
||||
("-250.00", "quarterly", None)
|
||||
# Validierung wie globale Fixposten
|
||||
assert client.post(url, headers=H, json={**body, "due_day": 0}).status_code == 422
|
||||
assert client.post(url, headers=H, json={**body, "rhythm": "weekly"}).status_code == 422
|
||||
assert client.post(url, headers=H, json={**body, "end_date": "2026-10-01"}).status_code == 422
|
||||
r = client.post(url, headers=H, json={**body, "category_id": 9999})
|
||||
assert r.status_code == 404 and r.json()["detail"] == "Kategorie nicht gefunden"
|
||||
# fremdes/fehlendes Szenario
|
||||
other = client.post("/api/scenarios", headers=H, json={"name": "SR-Other"}).json()
|
||||
r = client.delete(f"/api/scenarios/{other['id']}/recurring/{item['id']}", headers=H)
|
||||
assert r.status_code == 404 and r.json()["detail"] == "Wiederkehrende Zahlung nicht gefunden"
|
||||
assert client.get("/api/scenarios/99999/recurring", headers=H).status_code == 404
|
||||
# Szenario löschen räumt mit ab (kein FK-Fehler)
|
||||
assert client.delete(f"/api/scenarios/{sc['id']}", headers=H).status_code == 204
|
||||
assert db.query(ScenarioRecurringItem).count() == 0
|
||||
|
||||
|
||||
def test_scenario_recurring_delete(client):
|
||||
sc = client.post("/api/scenarios", headers=H, json={"name": "SR-Del"}).json()
|
||||
url = f"/api/scenarios/{sc['id']}/recurring"
|
||||
item = client.post(url, headers=H, json={
|
||||
"name": "X", "amount": "-1.00", "rhythm": "monthly", "due_day": 1}).json()
|
||||
assert client.delete(f"{url}/{item['id']}", headers=H).status_code == 204
|
||||
assert client.get(url, headers=H).json() == []
|
||||
|
||||
|
||||
def test_scenario_planned_mit_kategorie(client, db):
|
||||
from app.models.tables import Category
|
||||
cat = Category(name="Kat-SPI")
|
||||
db.add(cat)
|
||||
db.commit()
|
||||
sc = client.post("/api/scenarios", headers=H, json={"name": "SPI-Kat"}).json()
|
||||
url = f"/api/scenarios/{sc['id']}/planned"
|
||||
r = client.post(url, headers=H, json={
|
||||
"name": "E", "amount": "-5.00", "due": "2026-11-01", "category_id": cat.id})
|
||||
assert r.status_code == 201 and r.json()["category_id"] == cat.id
|
||||
assert client.post(url, headers=H, json={
|
||||
"name": "E", "amount": "-5.00", "due": "2026-11-01",
|
||||
"category_id": 9999}).status_code == 404
|
||||
r = client.patch(f"{url}/{r.json()['id']}", headers=H, json={
|
||||
"name": "E", "amount": "-5.00", "due": "2026-11-01", "category_id": None})
|
||||
assert r.status_code == 200 and r.json()["category_id"] is None
|
||||
|
||||
Reference in New Issue
Block a user