Bevorstehende Ablaufdaten waren nur im Dashboard und als Einsatzbereitschafts-Grund sichtbar, nicht direkt an der jeweiligen Beladungsposition in der Akte. Neue Spalte "Ablauf", rot bei überfälligem, gelb bei <30 Tagen verbleibender Zeit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
158 lines
6.6 KiB
Python
158 lines
6.6 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, GeraetStatus
|
|
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, MaterialTyp, 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()}
|
|
|
|
einsatzbereite_geraete_je_position: dict = {}
|
|
for geraet in geraete:
|
|
if geraet.status == GeraetStatus.einsatzbereit:
|
|
einsatzbereite_geraete_je_position[geraet.objektposition_id] = (
|
|
einsatzbereite_geraete_je_position.get(geraet.objektposition_id, 0) + 1
|
|
)
|
|
|
|
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=(
|
|
einsatzbereite_geraete_je_position.get(position.id, 0)
|
|
if material.materialtyp == MaterialTyp.geraet_sn
|
|
else position.istmenge
|
|
),
|
|
ablaufdatum=position.ablaufdatum,
|
|
)
|
|
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,
|
|
)
|