feat: PATCH-Endpunkte fuer Szenario-Modifikatoren und -Einmalzahlungen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -181,6 +181,21 @@ def delete_modifier(scenario_id: int, mod_id: int,
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{scenario_id}/modifiers/{mod_id}", response_model=ModifierOut)
|
||||||
|
def patch_modifier(scenario_id: int, mod_id: int, data: ModifierIn,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
|
# Full-Body-Update: das Edit-Formular sendet immer alle Felder des Typs.
|
||||||
|
_get_scenario(session, scenario_id)
|
||||||
|
modifier = session.get(ScenarioModifier, mod_id)
|
||||||
|
if modifier is None or modifier.scenario_id != scenario_id:
|
||||||
|
raise HTTPException(404, "Modifikator nicht gefunden")
|
||||||
|
for key, value in data.model_dump().items():
|
||||||
|
setattr(modifier, key, value)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(modifier)
|
||||||
|
return ModifierOut.model_validate(modifier)
|
||||||
|
|
||||||
|
|
||||||
class ScenarioPlannedIn(BaseModel):
|
class ScenarioPlannedIn(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
amount: Decimal
|
amount: Decimal
|
||||||
@@ -225,6 +240,21 @@ def delete_scenario_planned(scenario_id: int, item_id: int,
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{scenario_id}/planned/{item_id}", response_model=ScenarioPlannedOut)
|
||||||
|
def patch_scenario_planned(scenario_id: int, item_id: int, data: ScenarioPlannedIn,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
|
# Full-Body-Update, Muster patch_modifier.
|
||||||
|
_get_scenario(session, scenario_id)
|
||||||
|
item = session.get(ScenarioPlannedItem, item_id)
|
||||||
|
if item is None or item.scenario_id != scenario_id:
|
||||||
|
raise HTTPException(404, "Einmalzahlung nicht gefunden")
|
||||||
|
for key, value in data.model_dump().items():
|
||||||
|
setattr(item, key, value)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(item)
|
||||||
|
return ScenarioPlannedOut.model_validate(item)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{scenario_id}/project", response_model=ProjectionOut)
|
@router.post("/{scenario_id}/project", response_model=ProjectionOut)
|
||||||
def project_scenario(scenario_id: int, horizon_days: int | None = None,
|
def project_scenario(scenario_id: int, horizon_days: int | None = None,
|
||||||
start_date: date | None = None,
|
start_date: date | None = None,
|
||||||
|
|||||||
@@ -229,3 +229,51 @@ def test_scenario_planned_crud_und_cleanup(client, db):
|
|||||||
assert client.delete(f"/api/scenarios/{sc['id']}", headers=H).status_code == 204
|
assert client.delete(f"/api/scenarios/{sc['id']}", headers=H).status_code == 204
|
||||||
from app.models.tables import ScenarioPlannedItem
|
from app.models.tables import ScenarioPlannedItem
|
||||||
assert db.query(ScenarioPlannedItem).count() == 0
|
assert db.query(ScenarioPlannedItem).count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_modifier_patch(client, db):
|
||||||
|
sc = client.post("/api/scenarios", headers=H, json={"name": "ModPatch"}).json()
|
||||||
|
m = client.post(f"/api/scenarios/{sc['id']}/modifiers", headers=H, json={
|
||||||
|
"target_type": "recurring", "target_id": 1, "kind": "percent",
|
||||||
|
"value": "10.00"}).json()
|
||||||
|
# Wert aendern
|
||||||
|
r = client.patch(f"/api/scenarios/{sc['id']}/modifiers/{m['id']}", headers=H, json={
|
||||||
|
"target_type": "recurring", "target_id": 1, "kind": "percent", "value": "25.00"})
|
||||||
|
assert r.status_code == 200 and r.json()["value"] == "25.00"
|
||||||
|
# Art auf ende wechseln (mit Datum); value wird vom Validator genullt
|
||||||
|
r = client.patch(f"/api/scenarios/{sc['id']}/modifiers/{m['id']}", headers=H, json={
|
||||||
|
"target_type": "recurring", "target_id": 1, "kind": "ende",
|
||||||
|
"end_date": "2026-08-31"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json()["kind"] == "ende" and r.json()["end_date"] == "2026-08-31"
|
||||||
|
# ende ohne Datum -> 422
|
||||||
|
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
|
||||||
|
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
|
||||||
|
# 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={
|
||||||
|
"target_type": "recurring", "target_id": 1, "kind": "remove"})
|
||||||
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_scenario_planned_patch(client, db):
|
||||||
|
sc = client.post("/api/scenarios", headers=H, json={"name": "SpiPatch"}).json()
|
||||||
|
p = client.post(f"/api/scenarios/{sc['id']}/planned", headers=H, json={
|
||||||
|
"name": "Zufluss", "amount": "5000.00", "due": "2026-07-30"}).json()
|
||||||
|
r = client.patch(f"/api/scenarios/{sc['id']}/planned/{p['id']}", headers=H, json={
|
||||||
|
"name": "Zufluss geaendert", "amount": "6000.00", "due": "2026-08-15"})
|
||||||
|
assert r.status_code == 200
|
||||||
|
body = r.json()
|
||||||
|
assert body["name"] == "Zufluss geaendert"
|
||||||
|
assert body["amount"] == "6000.00" and body["due"] == "2026-08-15"
|
||||||
|
# fremdes Szenario -> 404
|
||||||
|
other = client.post("/api/scenarios", headers=H, json={"name": "SpiPatch2"}).json()
|
||||||
|
r = client.patch(f"/api/scenarios/{other['id']}/planned/{p['id']}", headers=H, json={
|
||||||
|
"name": "x", "amount": "1.00", "due": "2026-08-15"})
|
||||||
|
assert r.status_code == 404
|
||||||
|
|||||||
Reference in New Issue
Block a user