Files
MABEA/backend/app/api/v1/endpoints/wartung.py
T
patrickandClaude Sonnet 5 847c303a97
CI / backend-tests (push) Successful in 2m22s
CI / frontend-build (push) Successful in 25s
feat(wartung): MAINT-005 Ersatzteile/Kosten je Wartungsauftrag
Neue Tabelle wartungsauftrag_teil (Migration 0033), CRUD-Endpunkte
/wartungsauftraege/{id}/teile, UI-Panel in WartungSection.tsx mit
Summenanzeige. Backlog Epic 22 (UI-Redesign Runde 2, Kacheln UI2-001..005)
als geplant ergänzt, Umsetzung folgt separat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-08 22:06:02 +02:00

201 lines
7.6 KiB
Python

import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_current_user, require_roles
from app.db.session import get_db
from app.models.auth import Benutzer, RolleTyp
from app.models.objekt import Objekt
from app.models.wartung import Wartungsauftrag, WartungsauftragTeil, Wartungsplan, WartungsauftragStatus
from app.schemas.wartung import (
FaelligeWartungRead,
WartungsauftragCreate,
WartungsauftragErledigen,
WartungsauftragRead,
WartungsauftragStatusUpdate,
WartungsauftragTeilCreate,
WartungsauftragTeilRead,
WartungsplanCreate,
WartungsplanPositionRead,
WartungsplanRead,
)
from app.services.wartung import (
aendere_status,
entferne_wartungsauftrag_teil,
erledige_wartungsauftrag,
erstelle_wartungsauftrag,
erstelle_wartungsauftrag_teil,
erstelle_wartungsplan,
faellige_wartungen_fuer_objekt,
hole_intervalle,
hole_positionen_mit_intervallen,
liste_wartungsauftraege,
liste_wartungsauftrag_teile,
)
router = APIRouter()
_verantwortliche = require_roles(
RolleTyp.administration, RolleTyp.materialverantwortlicher, RolleTyp.leitungsverantwortlicher
)
async def _wartungsplan_read(db: AsyncSession, plan: Wartungsplan) -> WartungsplanRead:
positionen = await hole_positionen_mit_intervallen(db, plan.id)
positionen_read = []
for position in positionen:
intervalle = await hole_intervalle(db, position.id)
positionen_read.append(
WartungsplanPositionRead(
id=position.id,
wartungsplan_id=position.wartungsplan_id,
wartungsart=position.wartungsart,
beschreibung=position.beschreibung,
intervalle=list(intervalle),
)
)
return WartungsplanRead(
id=plan.id, objekttyp_id=plan.objekttyp_id, name=plan.name, positionen=positionen_read
)
@router.get("/wartungsplaene", response_model=list[WartungsplanRead])
async def liste_wartungsplaene(
objekttyp_id: int | None = None,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
) -> list[WartungsplanRead]:
stmt = select(Wartungsplan)
if objekttyp_id is not None:
stmt = stmt.where(Wartungsplan.objekttyp_id == objekttyp_id)
result = await db.execute(stmt)
return [await _wartungsplan_read(db, p) for p in result.scalars().all()]
@router.post("/wartungsplaene", response_model=WartungsplanRead, status_code=status.HTTP_201_CREATED)
async def erstelle_neuer_wartungsplan(
payload: WartungsplanCreate, db: AsyncSession = Depends(get_db), _=Depends(_verantwortliche)
) -> WartungsplanRead:
plan = await erstelle_wartungsplan(
db, objekttyp_id=payload.objekttyp_id, name=payload.name, positionen=payload.positionen
)
return await _wartungsplan_read(db, plan)
@router.get("/objekte/{objekt_id}/faellige-wartungen", response_model=list[FaelligeWartungRead])
async def hole_faellige_wartungen(
objekt_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
) -> list[dict]:
objekt = await db.get(Objekt, objekt_id)
if objekt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
return await faellige_wartungen_fuer_objekt(db, objekt)
@router.get("/wartungsauftraege", response_model=list[WartungsauftragRead])
async def liste_auftraege(
objekt_id: int | None = None,
status_filter: WartungsauftragStatus | None = None,
db: AsyncSession = Depends(get_db),
_=Depends(get_current_user),
) -> list[Wartungsauftrag]:
return await liste_wartungsauftraege(db, objekt_id=objekt_id, status=status_filter)
@router.post("/wartungsauftraege", response_model=WartungsauftragRead, status_code=status.HTTP_201_CREATED)
async def erstelle_auftrag(
payload: WartungsauftragCreate, db: AsyncSession = Depends(get_db), _=Depends(_verantwortliche)
) -> Wartungsauftrag:
objekt = await db.get(Objekt, payload.objekt_id)
if objekt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
return await erstelle_wartungsauftrag(
db,
objekt_id=payload.objekt_id,
geraet_instanz_id=payload.geraet_instanz_id,
wartungsplan_position_id=payload.wartungsplan_position_id,
wartungsart=payload.wartungsart,
beschreibung=payload.beschreibung,
werkstatt_intern=payload.werkstatt_intern,
werkstatt_name=payload.werkstatt_name,
faelligkeit_am=payload.faelligkeit_am,
)
@router.patch("/wartungsauftraege/{auftrag_id}/status", response_model=WartungsauftragRead)
async def route_status_aendern(
auftrag_id: uuid.UUID,
payload: WartungsauftragStatusUpdate,
db: AsyncSession = Depends(get_db),
_=Depends(_verantwortliche),
) -> Wartungsauftrag:
auftrag = await db.get(Wartungsauftrag, auftrag_id)
if auftrag is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Wartungsauftrag nicht gefunden")
return await aendere_status(db, auftrag=auftrag, status=payload.status)
@router.post("/wartungsauftraege/{auftrag_id}/erledigen", response_model=WartungsauftragRead)
async def route_erledigen(
auftrag_id: uuid.UUID,
payload: WartungsauftragErledigen,
db: AsyncSession = Depends(get_db),
current_user: Benutzer = Depends(_verantwortliche),
) -> Wartungsauftrag:
auftrag = await db.get(Wartungsauftrag, auftrag_id)
if auftrag is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Wartungsauftrag nicht gefunden")
return await erledige_wartungsauftrag(
db,
auftrag=auftrag,
benutzer_id=current_user.id,
erledigt_am=payload.erledigt_am,
kosten=payload.kosten,
kilometerstand_bei_erledigung=payload.kilometerstand_bei_erledigung,
betriebsstunden_bei_erledigung=payload.betriebsstunden_bei_erledigung,
)
@router.get("/wartungsauftraege/{auftrag_id}/teile", response_model=list[WartungsauftragTeilRead])
async def liste_teile(
auftrag_id: uuid.UUID, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
) -> list[WartungsauftragTeil]:
auftrag = await db.get(Wartungsauftrag, auftrag_id)
if auftrag is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Wartungsauftrag nicht gefunden")
return await liste_wartungsauftrag_teile(db, auftrag_id=auftrag_id)
@router.post(
"/wartungsauftraege/{auftrag_id}/teile",
response_model=WartungsauftragTeilRead,
status_code=status.HTTP_201_CREATED,
)
async def erstelle_teil(
auftrag_id: uuid.UUID,
payload: WartungsauftragTeilCreate,
db: AsyncSession = Depends(get_db),
_=Depends(_verantwortliche),
) -> WartungsauftragTeil:
auftrag = await db.get(Wartungsauftrag, auftrag_id)
if auftrag is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Wartungsauftrag nicht gefunden")
return await erstelle_wartungsauftrag_teil(
db, auftrag_id=auftrag_id, bezeichnung=payload.bezeichnung, menge=payload.menge, kosten=payload.kosten
)
@router.delete("/wartungsauftraege/{auftrag_id}/teile/{teil_id}", status_code=status.HTTP_204_NO_CONTENT)
async def loesche_teil(
auftrag_id: uuid.UUID,
teil_id: uuid.UUID,
db: AsyncSession = Depends(get_db),
_=Depends(_verantwortliche),
) -> None:
teil = await db.get(WartungsauftragTeil, teil_id)
if teil is None or teil.auftrag_id != auftrag_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ersatzteil nicht gefunden")
await entferne_wartungsauftrag_teil(db, teil=teil)