feat(objekt): Zugfahrzeug-Flag am Objekttyp + Leitungs-Dashboard-Kacheln
Objekttyp.ist_zugfahrzeug (Migration 0011) grenzt die Fahrzeug-Zuordnung serverseitig auf tatsächliche Zugfahrzeuge ein (422 bei Verstoß), Frontend filtert die Fahrzeug-Dropdowns entsprechend. Neue Dashboard-Endpunkte /dashboard/einsatzbereitschaft und /dashboard/prueftermine plus DashboardPage mit drei Kacheln (Einsatzbereitschaft, Prüftermine, Ablaufdaten). Aktive Mindermengen-Genehmigung zählt als "eingeschränkt einsatzbereit", nicht als voll einsatzbereit (U2/Karte 08). Neue FehlbestandListePage: Fehlbestände eigenständig nachfüllen/Mindermenge genehmigen, ohne komplette Kontrolle des Objekts zu starten (Backend existierte bereits, nur das Frontend fehlte). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
@@ -10,7 +10,7 @@ from app.db.session import get_db
|
||||
from app.models.auth import KnotenTyp, RolleTyp, Systemknoten
|
||||
from app.models.objekt import Objekt
|
||||
from app.models.objektposition import Objektposition
|
||||
from app.models.stammdaten import Material
|
||||
from app.models.stammdaten import Material, Objekttyp
|
||||
from app.models.vorlage import Beladungsvorlage
|
||||
from app.services.label import generiere_code128_label_pdf
|
||||
from app.schemas.objekt import (
|
||||
@@ -36,6 +36,22 @@ _admin_only = require_roles(RolleTyp.administration)
|
||||
_materialverantwortliche = require_roles(RolleTyp.administration, RolleTyp.materialverantwortlicher)
|
||||
|
||||
|
||||
async def _pruefe_zugfahrzeug(db: AsyncSession, fahrzeug_id: int) -> Objekt:
|
||||
"""Karte 11 Nachtrag (Option A+C): Fahrzeug-Zuordnung nur auf Objekte, deren
|
||||
Objekttyp ist_zugfahrzeug=True ist - grenzt Rucksäcke/Geräte serverseitig aus,
|
||||
unabhängig vom (evtl. umgangenen) Frontend-Dropdown."""
|
||||
ziel = await db.get(Objekt, fahrzeug_id)
|
||||
if ziel is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Fahrzeug nicht gefunden")
|
||||
objekttyp = await db.get(Objekttyp, ziel.objekttyp_id)
|
||||
if objekttyp is None or not objekttyp.ist_zugfahrzeug:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Zielobjekt ist kein Zugfahrzeug (Objekttyp.ist_zugfahrzeug=False)",
|
||||
)
|
||||
return ziel
|
||||
|
||||
|
||||
@router.get("/objekte", response_model=list[ObjektRead])
|
||||
async def liste_objekte(
|
||||
standort_id: int | None = None,
|
||||
@@ -196,8 +212,7 @@ async def erstelle_objekt(
|
||||
if vorlage is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Vorlage nicht gefunden")
|
||||
if payload.fahrzeug_id is not None:
|
||||
if await db.get(Objekt, payload.fahrzeug_id) is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Fahrzeug nicht gefunden")
|
||||
await _pruefe_zugfahrzeug(db, payload.fahrzeug_id)
|
||||
|
||||
hauptserver = await db.execute(select(Systemknoten.id).where(Systemknoten.typ == KnotenTyp.haupt))
|
||||
zustaendiger_server_id = hauptserver.scalar_one()
|
||||
@@ -231,9 +246,7 @@ async def aendere_objekt(
|
||||
daten = payload.model_dump(exclude_unset=True)
|
||||
ziel_id = daten.get("fahrzeug_id")
|
||||
if ziel_id is not None:
|
||||
ziel = await db.get(Objekt, ziel_id)
|
||||
if ziel is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Fahrzeug nicht gefunden")
|
||||
ziel = await _pruefe_zugfahrzeug(db, ziel_id)
|
||||
if ziel.fahrzeug_id == objekt_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
|
||||
Reference in New Issue
Block a user