Objekt bleibt der Materialträger (Rucksack/Gerät/Fahrzeug, Karte 11) -
"Material verschieben" heißt: das Objekt bekommt einen neuen Standort, jede
Bewegung wird append-only protokolliert (Wer/Wann/Von/Nach/Grund, Nutzer-
Vorgabe Modul 5).
POST /objekte/{id}/lagerbewegungen setzt Standort + protokolliert in einem
Zug, GET /lagerbewegungen (Filter objekt_id/standort_id) für die Historie.
Rollen wie beim Mangel-Modul: Materialverantwortliche+Leitung+Admin dürfen
verschieben, alle Eingeloggten dürfen lesen.
Frontend: "Verschieben"-Aktion in ObjektSection, neuer Admin-Tab
"Lagerbewegungen" (reine Anzeige, analog HistorieSection).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
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.lagerbewegung import Lagerbewegung
|
|
from app.models.objekt import Objekt
|
|
from app.models.stammdaten import Standort
|
|
from app.schemas.lagerbewegung import LagerbewegungCreate, LagerbewegungRead
|
|
from app.services.lagerbewegung import verschiebe_objekt
|
|
|
|
router = APIRouter()
|
|
|
|
# Deckt sich mit der Rollen-Vorgabe aus dem Mangel-Modul (Materialwart-
|
|
# Berechtigung "Lagerbewegungen durchführen" aus der Ursprungs-Anforderung).
|
|
_materialverantwortliche = require_roles(
|
|
RolleTyp.administration, RolleTyp.materialverantwortlicher, RolleTyp.leitungsverantwortlicher
|
|
)
|
|
|
|
|
|
@router.get("/lagerbewegungen", response_model=list[LagerbewegungRead])
|
|
async def liste_lagerbewegungen(
|
|
objekt_id: int | None = None,
|
|
standort_id: int | None = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
_=Depends(get_current_user),
|
|
) -> list[Lagerbewegung]:
|
|
stmt = select(Lagerbewegung)
|
|
if objekt_id is not None:
|
|
stmt = stmt.where(Lagerbewegung.objekt_id == objekt_id)
|
|
if standort_id is not None:
|
|
stmt = stmt.where(
|
|
(Lagerbewegung.von_standort_id == standort_id) | (Lagerbewegung.nach_standort_id == standort_id)
|
|
)
|
|
stmt = stmt.order_by(Lagerbewegung.zeitpunkt.desc())
|
|
result = await db.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
@router.post(
|
|
"/objekte/{objekt_id}/lagerbewegungen",
|
|
response_model=LagerbewegungRead,
|
|
status_code=status.HTTP_201_CREATED,
|
|
)
|
|
async def verschiebe(
|
|
objekt_id: int,
|
|
payload: LagerbewegungCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user=Depends(_materialverantwortliche),
|
|
) -> Lagerbewegung:
|
|
objekt = await db.get(Objekt, objekt_id)
|
|
if objekt is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
|
|
ziel = await db.get(Standort, payload.nach_standort_id)
|
|
if ziel is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Standort nicht gefunden")
|
|
if payload.nach_standort_id == objekt.standort_id:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Objekt ist bereits an diesem Standort")
|
|
|
|
return await verschiebe_objekt(
|
|
db,
|
|
objekt=objekt,
|
|
nach_standort_id=payload.nach_standort_id,
|
|
benutzer_id=current_user.id,
|
|
grund=payload.grund,
|
|
)
|