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 Material, Objekttyp, Standort from app.models.vorlage import Vorlagenposition from app.models.zustaendigkeit import Kontrollverantwortung, Zustaendigkeit from app.schemas.akte import AkteRead, BeladungsPosition, 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.dashboard import objekt_readiness from app.services.objekte import letzte_kontrollen_map, sollmenge_effektiv 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)) fach_by_material_id: dict[int, str | None] = {} if objekt.vorlage_id is not None: vorlagenpositionen_result = await db.execute( select(Vorlagenposition).where(Vorlagenposition.vorlage_id == objekt.vorlage_id) ) fach_by_material_id = {vp.material_id: vp.fach for vp in vorlagenpositionen_result.scalars().all()} beladung_result = await db.execute( select(Objektposition, Material) .join(Material, Objektposition.material_id == Material.id) .where(Objektposition.objekt_id == objekt_id) ) beladung = [ BeladungsPosition( objektposition_id=position.id, material_id=material.id, material_name=material.name, einheit=material.einheit, fach=fach_by_material_id.get(material.id), sollmenge=sollmenge_effektiv(position), istmenge=position.istmenge, ) for position, material in beladung_result.all() ] readiness = await objekt_readiness(db, objekt_id) objekt_read = ObjektRead.model_validate(objekt).model_copy( update={"letzte_kontrolle_am": letzte_map.get(objekt_id)} ) return AkteRead( objekt=objekt_read, einsatzbereitschaft_status=readiness["status"], einsatzbereitschaft_gruende=readiness["gruende"], 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()], beladung=beladung, )