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.lager import Bestand, Lagerort, Lagerplatz, Materialbewegung from app.schemas.lager import ( BestandRead, EinAuslagernRequest, LagerortCreate, LagerortRead, LagerortUpdate, LagerplatzCreate, LagerplatzRead, MaterialbewegungRead, ) from app.services.lager import NichtGenugBestandError, auslagern, einlagern router = APIRouter() _admin_only = require_roles(RolleTyp.administration) _materialverantwortliche = require_roles(RolleTyp.administration, RolleTyp.materialverantwortlicher) # -- Lagerort (WH-001) --------------------------------------------------------- @router.get("/lagerorte", response_model=list[LagerortRead]) async def liste_lagerorte(db: AsyncSession = Depends(get_db), _=Depends(get_current_user)) -> list[Lagerort]: result = await db.execute(select(Lagerort)) return list(result.scalars().all()) @router.post("/lagerorte", response_model=LagerortRead, status_code=status.HTTP_201_CREATED) async def erstelle_lagerort( payload: LagerortCreate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only) ) -> Lagerort: if payload.parent_id is not None and await db.get(Lagerort, payload.parent_id) is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Übergeordneter Lagerort nicht gefunden") lagerort = Lagerort(**payload.model_dump()) db.add(lagerort) await db.flush() return lagerort @router.patch("/lagerorte/{lagerort_id}", response_model=LagerortRead) async def aendere_lagerort( lagerort_id: int, payload: LagerortUpdate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only) ) -> Lagerort: lagerort = await db.get(Lagerort, lagerort_id) if lagerort is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lagerort nicht gefunden") daten = payload.model_dump(exclude_unset=True) if daten.get("parent_id") == lagerort_id: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Lagerort kann nicht sein eigener Elternknoten sein") for feld, wert in daten.items(): setattr(lagerort, feld, wert) await db.flush() return lagerort # -- Lagerplatz (WH-002) -------------------------------------------------------- @router.get("/lagerplaetze", response_model=list[LagerplatzRead]) async def liste_lagerplaetze( lagerort_id: int | None = None, db: AsyncSession = Depends(get_db), _=Depends(get_current_user) ) -> list[Lagerplatz]: stmt = select(Lagerplatz) if lagerort_id is not None: stmt = stmt.where(Lagerplatz.lagerort_id == lagerort_id) result = await db.execute(stmt) return list(result.scalars().all()) @router.post("/lagerplaetze", response_model=LagerplatzRead, status_code=status.HTTP_201_CREATED) async def erstelle_lagerplatz( payload: LagerplatzCreate, db: AsyncSession = Depends(get_db), _=Depends(_materialverantwortliche) ) -> Lagerplatz: if await db.get(Lagerort, payload.lagerort_id) is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lagerort nicht gefunden") vorhanden = await db.execute(select(Lagerplatz).where(Lagerplatz.code == payload.code)) if vorhanden.scalar_one_or_none() is not None: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") lagerplatz = Lagerplatz(**payload.model_dump()) db.add(lagerplatz) await db.flush() return lagerplatz # -- Bestand (WH-003) + Ein-/Auslagerung (WH-004) ------------------------------- @router.get("/lagerplaetze/{lagerplatz_id}/bestand", response_model=list[BestandRead]) async def hole_bestand( lagerplatz_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user) ) -> list[Bestand]: if await db.get(Lagerplatz, lagerplatz_id) is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lagerplatz nicht gefunden") result = await db.execute(select(Bestand).where(Bestand.lagerplatz_id == lagerplatz_id)) return list(result.scalars().all()) @router.post( "/lagerplaetze/{lagerplatz_id}/einlagern", response_model=MaterialbewegungRead, status_code=status.HTTP_201_CREATED, ) async def route_einlagern( lagerplatz_id: int, payload: EinAuslagernRequest, db: AsyncSession = Depends(get_db), current_user=Depends(get_current_user), ) -> Materialbewegung: if await db.get(Lagerplatz, lagerplatz_id) is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lagerplatz nicht gefunden") return await einlagern( db, lagerplatz_id=lagerplatz_id, material_id=payload.material_id, menge=payload.menge, benutzer_id=current_user.id, grund=payload.grund, ) @router.post( "/lagerplaetze/{lagerplatz_id}/auslagern", response_model=MaterialbewegungRead, status_code=status.HTTP_201_CREATED, ) async def route_auslagern( lagerplatz_id: int, payload: EinAuslagernRequest, db: AsyncSession = Depends(get_db), current_user=Depends(get_current_user), ) -> Materialbewegung: if await db.get(Lagerplatz, lagerplatz_id) is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lagerplatz nicht gefunden") try: return await auslagern( db, lagerplatz_id=lagerplatz_id, material_id=payload.material_id, menge=payload.menge, benutzer_id=current_user.id, grund=payload.grund, ) except NichtGenugBestandError as exc: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Nicht genug Bestand am Lagerplatz") from exc # -- Materialbewegung (WH-007) -------------------------------------------------- @router.get("/materialbewegungen", response_model=list[MaterialbewegungRead]) async def liste_materialbewegungen( lagerplatz_id: int | None = None, material_id: int | None = None, db: AsyncSession = Depends(get_db), _=Depends(get_current_user), ) -> list[Materialbewegung]: stmt = select(Materialbewegung) if lagerplatz_id is not None: stmt = stmt.where( (Materialbewegung.von_lagerplatz_id == lagerplatz_id) | (Materialbewegung.nach_lagerplatz_id == lagerplatz_id) ) if material_id is not None: stmt = stmt.where(Materialbewegung.material_id == material_id) stmt = stmt.order_by(Materialbewegung.zeitpunkt.desc()).limit(200) result = await db.execute(stmt) return list(result.scalars().all())