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 RolleTyp from app.models.objekt import Objekt from app.models.vorlage import Beladungsvorlage, Vorlagenposition from app.schemas.vorlage import BeladungsvorlageCreate, BeladungsvorlageRead, VorlagenAenderung from app.services.vorlagen import erstelle_vorlage, hole_positionen, neue_version router = APIRouter() _ersteller = require_roles(RolleTyp.administration, RolleTyp.materialverantwortlicher) async def _mit_positionen(db: AsyncSession, vorlage: Beladungsvorlage) -> BeladungsvorlageRead: positionen = await hole_positionen(db, vorlage.id) return BeladungsvorlageRead( id=vorlage.id, objekttyp_id=vorlage.objekttyp_id, name=vorlage.name, version=vorlage.version, status=vorlage.status, positionen=positionen, ) @router.get("/vorlagen", response_model=list[BeladungsvorlageRead]) async def liste_vorlagen( objekttyp_id: int | None = None, db: AsyncSession = Depends(get_db), _=Depends(get_current_user), ) -> list[BeladungsvorlageRead]: stmt = select(Beladungsvorlage) if objekttyp_id is not None: stmt = stmt.where(Beladungsvorlage.objekttyp_id == objekttyp_id) result = await db.execute(stmt) return [await _mit_positionen(db, v) for v in result.scalars().all()] @router.get("/vorlagen/{vorlage_id}", response_model=BeladungsvorlageRead) async def hole_vorlage( vorlage_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user) ) -> BeladungsvorlageRead: vorlage = await db.get(Beladungsvorlage, vorlage_id) if vorlage is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vorlage nicht gefunden") return await _mit_positionen(db, vorlage) @router.post("/vorlagen", response_model=BeladungsvorlageRead, status_code=status.HTTP_201_CREATED) async def erstelle_neue_vorlage( payload: BeladungsvorlageCreate, db: AsyncSession = Depends(get_db), _=Depends(_ersteller) ) -> BeladungsvorlageRead: vorlage = await erstelle_vorlage( db, objekttyp_id=payload.objekttyp_id, name=payload.name, positionen=payload.positionen ) return await _mit_positionen(db, vorlage) @router.post( "/vorlagen/{vorlage_id}/neue-version", response_model=BeladungsvorlageRead, status_code=status.HTTP_201_CREATED, ) async def erzeuge_neue_version( vorlage_id: int, payload: VorlagenAenderung, db: AsyncSession = Depends(get_db), _=Depends(_ersteller), ) -> BeladungsvorlageRead: """Prompt 08 Punkt 4: erzeugt neue Version, alte bleibt unverändert/lesbar.""" aktuelle_vorlage = await db.get(Beladungsvorlage, vorlage_id) if aktuelle_vorlage is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vorlage nicht gefunden") neue = await neue_version(db, aktuelle_vorlage=aktuelle_vorlage, positionen=payload.positionen) return await _mit_positionen(db, neue) @router.delete("/vorlagen/{vorlage_id}", status_code=status.HTTP_204_NO_CONTENT) async def loesche_vorlage( vorlage_id: int, db: AsyncSession = Depends(get_db), _=Depends(_ersteller) ) -> None: """Vorlagen sind bewusst versioniert (Prompt 08) - normalerweise wird eine Version nie gelöscht, nur durch eine neue ersetzt (status=veraltet). Echtes Löschen ist nur erlaubt, wenn KEIN Objekt diese Version referenziert (sonst FK-Verletzung bzw. Datenverlust an echten Objekten) - z.B. versehentlich angelegte/Test-Versionen entfernen.""" vorlage = await db.get(Beladungsvorlage, vorlage_id) if vorlage is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vorlage nicht gefunden") result = await db.execute(select(Objekt.id).where(Objekt.vorlage_id == vorlage_id).limit(1)) if result.scalar_one_or_none() is not None: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Vorlage wird von mindestens einem Objekt verwendet und kann nicht gelöscht werden", ) positionen = await db.execute(select(Vorlagenposition).where(Vorlagenposition.vorlage_id == vorlage_id)) for position in positionen.scalars().all(): await db.delete(position) # Flush zwischen den Deletes nötig: Beladungsvorlage/Vorlagenposition haben # keine ORM-relationship() (nur rohe FK-Spalten), SQLAlchemy kennt die # Abhängigkeit also nicht automatisch und könnte beide DELETEs in falscher # Reihenfolge absetzen (FK-Verletzung, beim echten Löschversuch gefunden). await db.flush() await db.delete(vorlage)