feat(maintenance): MAINT-Epic (Wartungspläne/-aufträge, Werkstatt) + Akte-Redesign
Neues Wartungskonzept: Wartungsplan+Position+Intervall (zeit/km/ Betriebsstunden, kombinierbar - fällig sobald ein Kriterium erreicht ist), Wartungsauftrag mit intern/externer Werkstatt, Status offen/ in_arbeit/erledigt, Kosten, Historie, Dokument-Anhang. Ad-hoc-Aufträge ohne Plan möglich (z.B. kleinere Reparatur). Admin-Tab "Wartungspläne". Objektakte-Redesign (Epic 21, Nutzer-Feedback "wirkt nicht modern"): CSS-Grid statt Karten-Stapel, einspaltig mobil, zweispaltig ab 900px; Fahrzeug-Objekte zeigen Wartung/Fahrzeugdaten prominent, Geräte/ Rucksäcke die Beladung. Dabei gefundene Lücke: Beladung (Soll/Ist je Position) fehlte in der Akte komplett, nur über eine laufende Kontrolle sichtbar - jetzt eigene Karte. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
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, Wartungsplan, WartungsauftragStatus
|
||||
from app.schemas.wartung import (
|
||||
FaelligeWartungRead,
|
||||
WartungsauftragCreate,
|
||||
WartungsauftragErledigen,
|
||||
WartungsauftragRead,
|
||||
WartungsauftragStatusUpdate,
|
||||
WartungsplanCreate,
|
||||
WartungsplanPositionRead,
|
||||
WartungsplanRead,
|
||||
)
|
||||
from app.services.wartung import (
|
||||
aendere_status,
|
||||
erledige_wartungsauftrag,
|
||||
erstelle_wartungsauftrag,
|
||||
erstelle_wartungsplan,
|
||||
faellige_wartungen_fuer_objekt,
|
||||
hole_intervalle,
|
||||
hole_positionen_mit_intervallen,
|
||||
liste_wartungsauftraege,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user