from datetime import date, datetime, timedelta, timezone from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.fehlbestand import Fehlbestand, FehlbestandStatus from app.models.mindermenge import MindermengeStatus, MindermengenGenehmigung from app.models.objekt import Objekt from app.models.objektposition import Objektposition from app.models.stammdaten import Material # Prompt 12.2: offen/in_bearbeitung/nachgefuellt_teilweise zählen gemeinsam als "offen" # (technischer Substatus ist keine Erledigung, Prompt 03/20). OFFEN_STATI = (FehlbestandStatus.offen, FehlbestandStatus.in_bearbeitung, FehlbestandStatus.nachgefuellt_teilweise) STANDARD_WARNZEITRAUM_TAGE = 30 async def kennzahlen(db: AsyncSession) -> dict: jetzt = datetime.now(timezone.utc) result = await db.execute(select(func.count()).select_from(Fehlbestand).where(Fehlbestand.status.in_(OFFEN_STATI))) anzahl_offen = result.scalar_one() result = await db.execute( select(func.count(func.distinct(MindermengenGenehmigung.fehlbestand_id))).where( MindermengenGenehmigung.status == MindermengeStatus.aktiv ) ) anzahl_genehmigt = result.scalar_one() result = await db.execute( select(func.count()) .select_from(Fehlbestand) .where(Fehlbestand.status == FehlbestandStatus.erledigt, Fehlbestand.erledigt_am >= jetzt - timedelta(days=7)) ) anzahl_kuerzlich_erledigt = result.scalar_one() result = await db.execute(select(func.count(func.distinct(Fehlbestand.objekt_id))).where(Fehlbestand.status.in_(OFFEN_STATI))) anzahl_problematische_objekte = result.scalar_one() result = await db.execute(select(func.count()).select_from(Objekt)) anzahl_objekte_gesamt = result.scalar_one() ablaufdaten_liste = await bevorstehende_ablaufdaten(db) return { "anzahl_offener_fehlbestaende": anzahl_offen, "anzahl_genehmigter_mindermengen": anzahl_genehmigt, "anzahl_kuerzlich_erledigter_fehlbestaende": anzahl_kuerzlich_erledigt, "anzahl_bevorstehender_ablaufdaten": len(ablaufdaten_liste), "anzahl_problematischer_objekte": anzahl_problematische_objekte, "anzahl_objekte_gesamt": anzahl_objekte_gesamt, } def _ablauf_status(ablaufdatum: date, warnzeitraum_tage: int, heute: date) -> tuple[str, int]: verbleibende_tage = (ablaufdatum - heute).days if verbleibende_tage < 0: return "abgelaufen", verbleibende_tage if verbleibende_tage <= warnzeitraum_tage: return "bald_ablaufend", verbleibende_tage return "gueltig", verbleibende_tage async def bevorstehende_ablaufdaten(db: AsyncSession) -> list[dict]: """Prompt 14.5: nur bald_ablaufend/abgelaufen, sortiert nach verbleibenden Tagen aufsteigend (dringendste zuerst). "gueltig" gehört nicht in diese Liste.""" heute = datetime.now(timezone.utc).date() result = await db.execute( select(Objektposition, Material) .join(Material, Objektposition.material_id == Material.id) .where(Objektposition.ablaufdatum.is_not(None)) ) eintraege = [] for objektposition, material in result.all(): warnzeitraum = material.warnzeitraum_tage or STANDARD_WARNZEITRAUM_TAGE status_wert, verbleibende_tage = _ablauf_status(objektposition.ablaufdatum, warnzeitraum, heute) if status_wert == "gueltig": continue eintraege.append( { "objekt_id": objektposition.objekt_id, "material_id": objektposition.material_id, "ablaufdatum": objektposition.ablaufdatum, "verbleibende_tage": verbleibende_tage, "status": status_wert, } ) eintraege.sort(key=lambda e: e["verbleibende_tage"]) return eintraege