4 parallele Review-Agenten (Reuse/Simplification/Efficiency/Altitude) gegen den Diff dieser Session (c7dd47f...HEAD) laufen lassen, echte Funde angewendet: - lager.py: 5x wiederholtes "db.get(...) or 404 raisen" durch _get_or_404()- Helper ersetzt - akte.py: zwei Queries für Geräte-Instanzen (erst Positions-IDs, dann Geräte) zu einer Query mit Subquery zusammengefasst - akte.py: manuelle Feld-für-Feld-Rekonstruktion von ObjektRead/HistorieRead durch model_validate()+model_copy() ersetzt (HistorieRead.benutzer_name bekommt dafür einen Default, harmlos für den bestehenden Endpunkt) - vorlagen.py: doppelte "Positionen löschen + flush"-Logik (Vorlage-Löschen und Positionen-Ersetzen) in loesche_alle_positionen() zusammengeführt Bewusst nicht angewendet: - Zentrale Session-Rollback-Vereinheitlichung für die drei Pre-Check-Stellen (objekte.py Selbstbezug, geraet_instanz.py SN-Duplikat, lager.py Eltern- Selbstbezug) - der Altitude-Review schlug das vor, aber get_db() rollt in Produktion bei jeder Exception bereits korrekt zurück (jede Anfrage hat eine eigene Session); das PendingRollbackError-Problem trat nur in der geteilten Test-Session auf und wurde bereits gezielt per Pre-Check vermieden - eine zusätzliche zentrale Rollback-Schicht würde nichts mehr reparieren, was nicht schon repariert ist. - Postgres-Cluster-Encoding (template1) auf beiden Hosts fixen, nicht nur in CI/für die eine migrierte DB - echter Infra-Eingriff, braucht Rücksprache. - LagerSection.tsx "inkonsistente" Formular-Resets - beim genaueren Hinsehen gewolltes UX (Ort/Typ bleiben ausgewählt für mehrere Anlagen hintereinander). 140 Tests weiterhin grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
114 lines
4.7 KiB
Python
114 lines
4.7 KiB
Python
from sqlalchemy import or_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.auth import Benutzer
|
|
from app.models.dokument import Dokument
|
|
from app.models.fahrzeugdetails import Fahrzeugdetails
|
|
from app.models.geraet_instanz import GeraetInstanz
|
|
from app.models.historie import Historie
|
|
from app.models.mangel import Mangel
|
|
from app.models.objekt import Objekt
|
|
from app.models.objektposition import Objektposition
|
|
from app.models.stammdaten import Objekttyp, Standort
|
|
from app.models.zustaendigkeit import Kontrollverantwortung, Zustaendigkeit
|
|
from app.schemas.akte import AkteRead, ZustaendigerBenutzer
|
|
from app.schemas.dokument import DokumentRead
|
|
from app.schemas.geraet_instanz import GeraetInstanzRead
|
|
from app.schemas.historie import HistorieRead
|
|
from app.schemas.mangel import MangelRead
|
|
from app.schemas.objekt import FahrzeugdetailsRead, ObjektRead
|
|
from app.schemas.zustaendigkeit import KontrollverantwortungRead
|
|
from app.services.objekte import letzte_kontrollen_map
|
|
|
|
|
|
async def _zustaendige_benutzer(db: AsyncSession, objekt: Objekt) -> list[ZustaendigerBenutzer]:
|
|
"""Karte 04: Umkehrung von zustaendige_objekte() - wer ist für DIESES Objekt
|
|
zuständig (Vereinigung Standort-Vererbung + objektspezifische Zeile)."""
|
|
stmt = (
|
|
select(Benutzer.id, Benutzer.name, Zustaendigkeit.standort_id, Zustaendigkeit.objekt_id)
|
|
.join(
|
|
Zustaendigkeit,
|
|
or_(
|
|
Zustaendigkeit.standort_id == objekt.standort_id,
|
|
Zustaendigkeit.objekt_id == objekt.id,
|
|
),
|
|
)
|
|
.where(Benutzer.id == Zustaendigkeit.benutzer_id)
|
|
)
|
|
result = await db.execute(stmt)
|
|
gesehen: set[int] = set()
|
|
ergebnis: list[ZustaendigerBenutzer] = []
|
|
for benutzer_id, name, standort_id, objekt_id in result.all():
|
|
if benutzer_id in gesehen:
|
|
continue
|
|
gesehen.add(benutzer_id)
|
|
quelle = "objekt" if objekt_id == objekt.id else "standort"
|
|
ergebnis.append(ZustaendigerBenutzer(id=benutzer_id, name=name, quelle=quelle))
|
|
return ergebnis
|
|
|
|
|
|
async def akte_fuer_objekt(db: AsyncSession, objekt_id: int) -> AkteRead | None:
|
|
"""FILE-001: bündelt Stammdaten/Standort/Verantwortlichkeit/Historie/
|
|
Dokumente/Prüfungen/Mängel für ein Objekt zu einer Aggregations-Sicht."""
|
|
objekt = await db.get(Objekt, objekt_id)
|
|
if objekt is None:
|
|
return None
|
|
|
|
objekttyp = await db.get(Objekttyp, objekt.objekttyp_id)
|
|
standort = await db.get(Standort, objekt.standort_id)
|
|
fahrzeugdetails = await db.get(Fahrzeugdetails, objekt_id)
|
|
|
|
letzte_map = await letzte_kontrollen_map(db, [objekt_id])
|
|
|
|
zustaendige = await _zustaendige_benutzer(db, objekt)
|
|
|
|
kontrollverantwortung_result = await db.execute(
|
|
select(Kontrollverantwortung).where(Kontrollverantwortung.objekt_id == objekt_id)
|
|
)
|
|
|
|
historie_result = await db.execute(
|
|
select(Historie, Benutzer.name)
|
|
.outerjoin(Benutzer, Benutzer.id == Historie.benutzer_id)
|
|
.where(Historie.entitaet_typ == "objekt", Historie.entitaet_id == str(objekt_id))
|
|
.order_by(Historie.zeitpunkt.desc())
|
|
.limit(50)
|
|
)
|
|
|
|
dokumente_result = await db.execute(
|
|
select(Dokument).where(Dokument.entitaet_typ == "objekt", Dokument.entitaet_id == str(objekt_id))
|
|
)
|
|
|
|
geraete_result = await db.execute(
|
|
select(GeraetInstanz).where(
|
|
GeraetInstanz.objektposition_id.in_(
|
|
select(Objektposition.id).where(Objektposition.objekt_id == objekt_id)
|
|
)
|
|
)
|
|
)
|
|
geraete = list(geraete_result.scalars().all())
|
|
|
|
maengel_result = await db.execute(select(Mangel).where(Mangel.objekt_id == objekt_id))
|
|
|
|
objekt_read = ObjektRead.model_validate(objekt).model_copy(
|
|
update={"letzte_kontrolle_am": letzte_map.get(objekt_id)}
|
|
)
|
|
|
|
return AkteRead(
|
|
objekt=objekt_read,
|
|
objekttyp_name=objekttyp.name if objekttyp else "",
|
|
standort_name=standort.name if standort else "",
|
|
fahrzeugdetails=FahrzeugdetailsRead.model_validate(fahrzeugdetails) if fahrzeugdetails else None,
|
|
zustaendige_benutzer=zustaendige,
|
|
kontrollverantwortung=[
|
|
KontrollverantwortungRead.model_validate(k) for k in kontrollverantwortung_result.scalars().all()
|
|
],
|
|
letzte_kontrolle_am=letzte_map.get(objekt_id),
|
|
historie=[
|
|
HistorieRead.model_validate(eintrag).model_copy(update={"benutzer_name": benutzer_name})
|
|
for eintrag, benutzer_name in historie_result.all()
|
|
],
|
|
dokumente=[DokumentRead.model_validate(d) for d in dokumente_result.scalars().all()],
|
|
geraete=[GeraetInstanzRead.model_validate(g) for g in geraete],
|
|
maengel=[MangelRead.model_validate(m) for m in maengel_result.scalars().all()],
|
|
)
|