feat(objekt): letzte abgeschlossene Kontrolle je Objekt anzeigen
Bisher nirgends vermerkt/abrufbar, wann ein Objekt zuletzt komplett
kontrolliert wurde (nur roh in kontrolle.beendet_am ableitbar). Neues Feld
ObjektRead.letzte_kontrolle_am (jüngste Kontrolle mit status=abgeschlossen),
angezeigt in Objektliste und Admin-Objektsektion ("noch nie kontrolliert"
falls keine).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
@@ -24,12 +24,27 @@ from app.schemas.objekt import (
|
||||
)
|
||||
from app.services.objekte import (
|
||||
dupliziere_objekt,
|
||||
letzte_kontrollen_map,
|
||||
naechster_freier_code,
|
||||
objekt_aus_vorlage,
|
||||
sollmenge_effektiv,
|
||||
sollmenge_map,
|
||||
)
|
||||
|
||||
|
||||
def _objekt_read(objekt: Objekt, letzte_kontrolle_am=None) -> ObjektRead:
|
||||
return ObjektRead(
|
||||
id=objekt.id,
|
||||
code=objekt.code,
|
||||
name=objekt.name,
|
||||
objekttyp_id=objekt.objekttyp_id,
|
||||
vorlage_id=objekt.vorlage_id,
|
||||
standort_id=objekt.standort_id,
|
||||
status=objekt.status,
|
||||
fahrzeug_id=objekt.fahrzeug_id,
|
||||
letzte_kontrolle_am=letzte_kontrolle_am,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
_admin_only = require_roles(RolleTyp.administration)
|
||||
@@ -57,25 +72,28 @@ async def liste_objekte(
|
||||
standort_id: int | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_=Depends(get_current_user),
|
||||
) -> list[Objekt]:
|
||||
) -> list[ObjektRead]:
|
||||
stmt = select(Objekt)
|
||||
if standort_id is not None:
|
||||
stmt = stmt.where(Objekt.standort_id == standort_id)
|
||||
result = await db.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
objekte = list(result.scalars().all())
|
||||
letzte_map = await letzte_kontrollen_map(db, [o.id for o in objekte])
|
||||
return [_objekt_read(o, letzte_map.get(o.id)) for o in objekte]
|
||||
|
||||
|
||||
@router.get("/objekte/code/{code}", response_model=ObjektRead)
|
||||
async def hole_objekt_per_code(
|
||||
code: str, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
|
||||
) -> Objekt:
|
||||
) -> ObjektRead:
|
||||
"""Lookup per QR/Barcode-Code (Karte 10). Muss VOR /objekte/{id} registriert
|
||||
sein, sonst greift FastAPI/Starlette-Routing zuerst die generische ID-Route."""
|
||||
result = await db.execute(select(Objekt).where(Objekt.code == code))
|
||||
objekt = result.scalar_one_or_none()
|
||||
if objekt is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
|
||||
return objekt
|
||||
letzte_map = await letzte_kontrollen_map(db, [objekt.id])
|
||||
return _objekt_read(objekt, letzte_map.get(objekt.id))
|
||||
|
||||
|
||||
@router.get("/objekte/naechster-code")
|
||||
@@ -129,11 +147,12 @@ async def hole_objektposition_per_code(
|
||||
@router.get("/objekte/{objekt_id}", response_model=ObjektRead)
|
||||
async def hole_objekt(
|
||||
objekt_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
|
||||
) -> Objekt:
|
||||
) -> ObjektRead:
|
||||
objekt = await db.get(Objekt, objekt_id)
|
||||
if objekt is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
|
||||
return objekt
|
||||
letzte_map = await letzte_kontrollen_map(db, [objekt.id])
|
||||
return _objekt_read(objekt, letzte_map.get(objekt.id))
|
||||
|
||||
|
||||
@router.get("/objekte/{objekt_id}/label.pdf")
|
||||
@@ -205,7 +224,7 @@ async def liste_objektpositionen(
|
||||
@router.post("/objekte", response_model=ObjektRead, status_code=status.HTTP_201_CREATED)
|
||||
async def erstelle_objekt(
|
||||
payload: ObjektCreate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only)
|
||||
) -> Objekt:
|
||||
) -> ObjektRead:
|
||||
"""Prompt 09: neues Objekt aus einer Beladungsvorlage, startet "nicht kontrolliert"
|
||||
(Ist-Menge 0 in allen übernommenen Positionen)."""
|
||||
vorlage = await db.get(Beladungsvorlage, payload.vorlage_id)
|
||||
@@ -218,7 +237,7 @@ async def erstelle_objekt(
|
||||
zustaendiger_server_id = hauptserver.scalar_one()
|
||||
|
||||
try:
|
||||
return await objekt_aus_vorlage(
|
||||
objekt = await objekt_aus_vorlage(
|
||||
db,
|
||||
code=payload.code,
|
||||
name=payload.name,
|
||||
@@ -229,12 +248,13 @@ async def erstelle_objekt(
|
||||
)
|
||||
except IntegrityError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") from exc
|
||||
return _objekt_read(objekt)
|
||||
|
||||
|
||||
@router.patch("/objekte/{objekt_id}", response_model=ObjektRead)
|
||||
async def aendere_objekt(
|
||||
objekt_id: int, payload: ObjektUpdate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only)
|
||||
) -> Objekt:
|
||||
) -> ObjektRead:
|
||||
"""Fahrzeug-Zuordnung nachträglich setzen/lösen: jedes Objekt gehört zu
|
||||
jedem Zeitpunkt höchstens einem Fahrzeug (1:n). Zielobjekt-Existenz und
|
||||
direkter Zyklus (A↔B) werden geprüft, mehrstufige Ketten sind fachlich
|
||||
@@ -259,7 +279,8 @@ async def aendere_objekt(
|
||||
await db.flush()
|
||||
except IntegrityError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Ungültige Zuordnung") from exc
|
||||
return objekt
|
||||
letzte_map = await letzte_kontrollen_map(db, [objekt.id])
|
||||
return _objekt_read(objekt, letzte_map.get(objekt.id))
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -270,7 +291,7 @@ async def dupliziere(
|
||||
payload: ObjektDuplizierenRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_=Depends(_admin_only),
|
||||
) -> Objekt:
|
||||
) -> ObjektRead:
|
||||
"""Prompt 09: Struktur/Sollmengen kopiert, Ist-Menge/SN/Ablauf/Charge NICHT,
|
||||
neuer eindeutiger Code Pflicht."""
|
||||
quelle = await db.get(Objekt, objekt_id)
|
||||
@@ -278,7 +299,7 @@ async def dupliziere(
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quell-Objekt nicht gefunden")
|
||||
|
||||
try:
|
||||
return await dupliziere_objekt(
|
||||
neues_objekt = await dupliziere_objekt(
|
||||
db,
|
||||
quelle=quelle,
|
||||
neuer_code=payload.neuer_code,
|
||||
@@ -288,6 +309,7 @@ async def dupliziere(
|
||||
)
|
||||
except IntegrityError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") from exc
|
||||
return _objekt_read(neues_objekt)
|
||||
|
||||
|
||||
@router.patch("/objekte/{objekt_id}/positionen/{position_id}", response_model=ObjektpositionRead)
|
||||
|
||||
Reference in New Issue
Block a user