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, KnotenTyp, RolleTyp, Systemknoten from app.models.kontrolle import Kontrolle, KontrollStatus, Kontrollposition from app.models.objekt import Objekt from app.schemas.kontrolle import ( KontrolleAbbrechenRequest, KontrolleCreate, KontrolleFortschritt, KontrolleRead, KontrollpositionInput, KontrollpositionRead, ) from app.services.kontrolle import ( ObjektGesperrtError, UnvollstaendigError, abbreche_kontrolle, abschliesse_kontrolle, erfasse_position, fehlende_material_ids, starte_kontrolle, ) router = APIRouter() _mitarbeiter_plus = require_roles( RolleTyp.mitarbeiter, RolleTyp.materialverantwortlicher, RolleTyp.leitungsverantwortlicher, RolleTyp.administration, ) async def _hauptserver_id(db: AsyncSession) -> int: result = await db.execute(select(Systemknoten.id).where(Systemknoten.typ == KnotenTyp.haupt)) return result.scalar_one() async def _hole_kontrolle_oder_404(db: AsyncSession, kontrolle_id: uuid.UUID) -> Kontrolle: kontrolle = await db.get(Kontrolle, kontrolle_id) if kontrolle is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kontrolle nicht gefunden") return kontrolle def _pruefe_besitz(kontrolle: Kontrolle, current_user: Benutzer) -> None: """Objekt-Sperre (Prompt 02.8) wirkt auch hier: nur wer die Kontrolle gestartet (oder übernommen) hat, darf sie fortführen/abschließen/abbrechen.""" if kontrolle.benutzer_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Diese Kontrolle gehört einem anderen Benutzer – ggf. Übernahme nötig", ) if kontrolle.status != KontrollStatus.in_bearbeitung: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Kontrolle ist nicht mehr aktiv") @router.post( "/objekte/{objekt_id}/kontrollen", response_model=KontrolleRead, status_code=status.HTTP_201_CREATED ) async def starte_neue_kontrolle( objekt_id: int, payload: KontrolleCreate, db: AsyncSession = Depends(get_db), current_user: Benutzer = Depends(_mitarbeiter_plus), ) -> Kontrolle: objekt = await db.get(Objekt, objekt_id) if objekt is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden") zustaendiger_server_id = await _hauptserver_id(db) try: return await starte_kontrolle( db, objekt_id=objekt_id, benutzer_id=current_user.id, uebernehmen=payload.uebernehmen, zustaendiger_server_id=zustaendiger_server_id, ) except ObjektGesperrtError as exc: aktive = exc.aktive_kontrolle besitzer = await db.get(Benutzer, aktive.benutzer_id) raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail={ "nachricht": "wird gerade kontrolliert", "benutzer": besitzer.name if besitzer else None, "seit": aktive.gestartet_am.isoformat(), }, ) from exc @router.get("/kontrollen/{kontrolle_id}", response_model=KontrolleFortschritt) async def hole_kontroll_fortschritt( kontrolle_id: uuid.UUID, db: AsyncSession = Depends(get_db), _=Depends(get_current_user) ) -> KontrolleFortschritt: kontrolle = await _hole_kontrolle_oder_404(db, kontrolle_id) result = await db.execute( select(Kontrollposition).where(Kontrollposition.kontrolle_id == kontrolle_id) ) positionen = list(result.scalars().all()) fehlende = await fehlende_material_ids(db, kontrolle) return KontrolleFortschritt( kontrolle=KontrolleRead.model_validate(kontrolle), positionen=[KontrollpositionRead.model_validate(p) for p in positionen], anzahl_positionen_gesamt=len(positionen) + len(fehlende), anzahl_positionen_erfasst=len(positionen), ) @router.put("/kontrollen/{kontrolle_id}/positionen/{material_id}", response_model=KontrollpositionRead) async def erfasse_kontrollposition( kontrolle_id: uuid.UUID, material_id: int, payload: KontrollpositionInput, db: AsyncSession = Depends(get_db), current_user: Benutzer = Depends(_mitarbeiter_plus), ) -> KontrollpositionRead: kontrolle = await _hole_kontrolle_oder_404(db, kontrolle_id) _pruefe_besitz(kontrolle, current_user) objekt = await db.get(Objekt, kontrolle.objekt_id) zustaendiger_server_id = await _hauptserver_id(db) kontrollposition, fehlbestand_id = await erfasse_position( db, kontrolle=kontrolle, objekt=objekt, material_id=material_id, istmenge=payload.istmenge, zustaendiger_server_id=zustaendiger_server_id, ablaufdatum=payload.ablaufdatum, chargennummer=payload.chargennummer, seriennummer=payload.seriennummer, ) return KontrollpositionRead( id=kontrollposition.id, kontrolle_id=kontrollposition.kontrolle_id, material_id=kontrollposition.material_id, sollmenge_snapshot=kontrollposition.sollmenge_snapshot, istmenge_erfasst=kontrollposition.istmenge_erfasst, abweichung=kontrollposition.abweichung, fehlbestand_id=fehlbestand_id, ) @router.post("/kontrollen/{kontrolle_id}/abschliessen", response_model=KontrolleRead) async def abschliesse( kontrolle_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: Benutzer = Depends(_mitarbeiter_plus), ) -> Kontrolle: kontrolle = await _hole_kontrolle_oder_404(db, kontrolle_id) _pruefe_besitz(kontrolle, current_user) zustaendiger_server_id = await _hauptserver_id(db) try: await abschliesse_kontrolle(db, kontrolle=kontrolle, zustaendiger_server_id=zustaendiger_server_id) except UnvollstaendigError as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail={ "nachricht": "nicht alle Positionen erfasst", "fehlende_material_ids": exc.fehlende_material_ids, }, ) from exc return kontrolle @router.post("/kontrollen/{kontrolle_id}/abbrechen", response_model=KontrolleRead) async def abbreche( kontrolle_id: uuid.UUID, payload: KontrolleAbbrechenRequest, db: AsyncSession = Depends(get_db), current_user: Benutzer = Depends(_mitarbeiter_plus), ) -> Kontrolle: kontrolle = await _hole_kontrolle_oder_404(db, kontrolle_id) _pruefe_besitz(kontrolle, current_user) zustaendiger_server_id = await _hauptserver_id(db) await abbreche_kontrolle( db, kontrolle=kontrolle, grund=payload.grund, zustaendiger_server_id=zustaendiger_server_id ) return kontrolle