DELETE /vorlagen/{id}: echtes Löschen nur wenn unbenutzt
CI / backend-tests (push) Failing after 1m19s
CI / frontend-build (push) Successful in 19s

Vorlagen sind bewusst versioniert (Prompt 08) - normalerweise wird eine
Version durch eine neue ersetzt (status=veraltet), nie gelöscht. Echtes
Löschen jetzt möglich, aber nur wenn kein Objekt diese Version referenziert
(409 sonst) - für versehentlich angelegte/Test-Versionen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-05 00:13:57 +02:00
co-authored by Claude Sonnet 5
parent 13ff2838fa
commit 86665034be
2 changed files with 54 additions and 1 deletions
+28 -1
View File
@@ -5,7 +5,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_current_user, require_roles from app.api.deps import get_current_user, require_roles
from app.db.session import get_db from app.db.session import get_db
from app.models.auth import RolleTyp from app.models.auth import RolleTyp
from app.models.vorlage import Beladungsvorlage from app.models.objekt import Objekt
from app.models.vorlage import Beladungsvorlage, Vorlagenposition
from app.schemas.vorlage import BeladungsvorlageCreate, BeladungsvorlageRead, VorlagenAenderung from app.schemas.vorlage import BeladungsvorlageCreate, BeladungsvorlageRead, VorlagenAenderung
from app.services.vorlagen import erstelle_vorlage, hole_positionen, neue_version from app.services.vorlagen import erstelle_vorlage, hole_positionen, neue_version
@@ -76,3 +77,29 @@ async def erzeuge_neue_version(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vorlage nicht gefunden") 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) neue = await neue_version(db, aktuelle_vorlage=aktuelle_vorlage, positionen=payload.positionen)
return await _mit_positionen(db, neue) 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)
await db.delete(vorlage)
+26
View File
@@ -32,3 +32,29 @@ async def test_neue_version_alte_bleibt_lesbar(db_session, objekttyp_factory, ma
v2_positionen = await hole_positionen(db_session, v2.id) v2_positionen = await hole_positionen(db_session, v2.id)
assert v1_positionen[0].sollmenge == 10 assert v1_positionen[0].sollmenge == 10
assert v2_positionen[0].sollmenge == 15 assert v2_positionen[0].sollmenge == 15
@pytest.mark.asyncio
async def test_loesche_unbenutzte_vorlage(client, db_session, objekttyp_factory, admin_user):
from tests.conftest import auth_header, login
objekttyp = await objekttyp_factory("Rucksack Loeschtest")
vorlage = await erstelle_vorlage(db_session, objekttyp_id=objekttyp.id, name="Loesch-Vorlage", positionen=[])
token = await login(client, "admin1")
response = await client.delete(f"/api/v1/vorlagen/{vorlage.id}", headers=auth_header(token))
assert response.status_code == 204
nachher = await client.get(f"/api/v1/vorlagen/{vorlage.id}", headers=auth_header(token))
assert nachher.status_code == 404
@pytest.mark.asyncio
async def test_loeschen_von_benutzter_vorlage_ist_409(client, db_session, objekt_mit_position, admin_user):
from tests.conftest import auth_header, login
objekt, _material = objekt_mit_position
token = await login(client, "admin1")
response = await client.delete(f"/api/v1/vorlagen/{objekt.vorlage_id}", headers=auth_header(token))
assert response.status_code == 409