feat: API fuer Szenario-Ende-Modifikator und szenario-eigene Einmalzahlungen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ from decimal import Decimal
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -11,7 +11,8 @@ from app.auth import require_auth
|
||||
from app.config import get_settings
|
||||
from app.db import get_session
|
||||
from app.models.tables import (Loan, ProjectionPoint, ProjectionResult,
|
||||
Scenario, ScenarioLoan, ScenarioModifier)
|
||||
Scenario, ScenarioLoan, ScenarioModifier,
|
||||
ScenarioPlannedItem)
|
||||
from app.services.projection_service import run_projection
|
||||
|
||||
router = APIRouter(prefix="/api/scenarios", tags=["scenarios"],
|
||||
@@ -36,12 +37,26 @@ class ScenarioPatch(BaseModel):
|
||||
include_recurring: bool | None = None
|
||||
include_planned: bool | None = None
|
||||
|
||||
@field_validator("description")
|
||||
@classmethod
|
||||
def _leere_beschreibung(cls, v):
|
||||
return "" if v is None else v
|
||||
|
||||
|
||||
class ModifierIn(BaseModel):
|
||||
target_type: Literal["category", "recurring"]
|
||||
target_id: int
|
||||
kind: Literal["percent", "absolute", "remove"]
|
||||
kind: Literal["percent", "absolute", "remove", "ende"]
|
||||
value: Decimal = Decimal("0")
|
||||
end_date: date | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _ende_braucht_datum(self):
|
||||
if self.kind == "ende" and self.end_date is None:
|
||||
raise ValueError("Art »Ende« erfordert ein Datum")
|
||||
if self.kind != "ende":
|
||||
self.end_date = None
|
||||
return self
|
||||
|
||||
|
||||
class ModifierOut(ModifierIn):
|
||||
@@ -117,6 +132,8 @@ def delete_scenario(scenario_id: int, session: Session = Depends(get_session)):
|
||||
ScenarioLoan.scenario_id == scenario_id))
|
||||
session.execute(delete(ScenarioModifier).where(
|
||||
ScenarioModifier.scenario_id == scenario_id))
|
||||
session.execute(delete(ScenarioPlannedItem).where(
|
||||
ScenarioPlannedItem.scenario_id == scenario_id))
|
||||
session.delete(scenario)
|
||||
session.commit()
|
||||
|
||||
@@ -164,6 +181,50 @@ def delete_modifier(scenario_id: int, mod_id: int,
|
||||
session.commit()
|
||||
|
||||
|
||||
class ScenarioPlannedIn(BaseModel):
|
||||
name: str
|
||||
amount: Decimal
|
||||
due: date
|
||||
|
||||
|
||||
class ScenarioPlannedOut(ScenarioPlannedIn):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
id: int
|
||||
scenario_id: int
|
||||
|
||||
|
||||
@router.get("/{scenario_id}/planned", response_model=list[ScenarioPlannedOut])
|
||||
def list_scenario_planned(scenario_id: int, session: Session = Depends(get_session)):
|
||||
_get_scenario(session, scenario_id)
|
||||
return [ScenarioPlannedOut.model_validate(p) for p in session.execute(
|
||||
select(ScenarioPlannedItem)
|
||||
.where(ScenarioPlannedItem.scenario_id == scenario_id)
|
||||
.order_by(ScenarioPlannedItem.due)).scalars()]
|
||||
|
||||
|
||||
@router.post("/{scenario_id}/planned", response_model=ScenarioPlannedOut,
|
||||
status_code=201)
|
||||
def add_scenario_planned(scenario_id: int, data: ScenarioPlannedIn,
|
||||
session: Session = Depends(get_session)):
|
||||
_get_scenario(session, scenario_id)
|
||||
item = ScenarioPlannedItem(scenario_id=scenario_id, **data.model_dump())
|
||||
session.add(item)
|
||||
session.commit()
|
||||
session.refresh(item)
|
||||
return ScenarioPlannedOut.model_validate(item)
|
||||
|
||||
|
||||
@router.delete("/{scenario_id}/planned/{item_id}", status_code=204)
|
||||
def delete_scenario_planned(scenario_id: int, item_id: int,
|
||||
session: Session = Depends(get_session)):
|
||||
_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")
|
||||
session.delete(item)
|
||||
session.commit()
|
||||
|
||||
|
||||
@router.post("/{scenario_id}/project", response_model=ProjectionOut)
|
||||
def project_scenario(scenario_id: int, horizon_days: int | None = None,
|
||||
start_date: date | None = None,
|
||||
|
||||
Reference in New Issue
Block a user