Beladungsvorlage/Vorlagenposition haben keine ORM-relationship() (nur rohe FK-Spalten), SQLAlchemy kannte die Lösch-Reihenfolge nicht und schlug beim echten Löschversuch mit FK-Verletzung fehl. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
111 lines
4.7 KiB
Python
111 lines
4.7 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.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)
|