from datetime import date, datetime, timezone from decimal import Decimal from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.fahrzeugdetails import Fahrzeugdetails from app.models.objekt import Objekt from app.models.wartung import ( IntervallTyp, Wartungsauftrag, WartungsauftragStatus, Wartungsintervall, Wartungsplan, WartungsplanPosition, ) from app.schemas.wartung import WartungsplanPositionCreate from app.services.datumshilfen import plus_monate async def erstelle_wartungsplan( db: AsyncSession, *, objekttyp_id: int, name: str, positionen: list[WartungsplanPositionCreate] ) -> Wartungsplan: plan = Wartungsplan(objekttyp_id=objekttyp_id, name=name) db.add(plan) await db.flush() for pos in positionen: position = WartungsplanPosition( wartungsplan_id=plan.id, wartungsart=pos.wartungsart, beschreibung=pos.beschreibung ) db.add(position) await db.flush() for intervall in pos.intervalle: db.add( Wartungsintervall( wartungsplan_position_id=position.id, typ=intervall.typ, wert=intervall.wert ) ) await db.flush() return plan async def hole_positionen_mit_intervallen(db: AsyncSession, wartungsplan_id: int) -> list[WartungsplanPosition]: result = await db.execute( select(WartungsplanPosition).where(WartungsplanPosition.wartungsplan_id == wartungsplan_id) ) return list(result.scalars().all()) async def hole_intervalle(db: AsyncSession, wartungsplan_position_id: int) -> list[Wartungsintervall]: result = await db.execute( select(Wartungsintervall).where(Wartungsintervall.wartungsplan_position_id == wartungsplan_position_id) ) return list(result.scalars().all()) async def erstelle_wartungsauftrag( db: AsyncSession, *, objekt_id: int, geraet_instanz_id, wartungsplan_position_id: int | None, wartungsart: str, beschreibung: str | None, werkstatt_intern: bool, werkstatt_name: str | None, faelligkeit_am: date | None, ) -> Wartungsauftrag: auftrag = Wartungsauftrag( objekt_id=objekt_id, geraet_instanz_id=geraet_instanz_id, wartungsplan_position_id=wartungsplan_position_id, wartungsart=wartungsart, beschreibung=beschreibung, status=WartungsauftragStatus.offen, werkstatt_intern=werkstatt_intern, werkstatt_name=werkstatt_name, faelligkeit_am=faelligkeit_am, erstellt_am=datetime.now(timezone.utc), ) db.add(auftrag) await db.flush() return auftrag async def aendere_status(db: AsyncSession, *, auftrag: Wartungsauftrag, status: WartungsauftragStatus) -> Wartungsauftrag: auftrag.status = status await db.flush() return auftrag async def erledige_wartungsauftrag( db: AsyncSession, *, auftrag: Wartungsauftrag, benutzer_id: int, erledigt_am: date | None, kosten: Decimal | None, kilometerstand_bei_erledigung: int | None, betriebsstunden_bei_erledigung: Decimal | None, ) -> Wartungsauftrag: auftrag.status = WartungsauftragStatus.erledigt auftrag.erledigt_am = ( datetime.combine(erledigt_am, datetime.min.time(), tzinfo=timezone.utc) if erledigt_am else datetime.now(timezone.utc) ) auftrag.durchgefuehrt_von = benutzer_id auftrag.kosten = kosten if kilometerstand_bei_erledigung is None or betriebsstunden_bei_erledigung is None: fahrzeugdetails = await db.get(Fahrzeugdetails, auftrag.objekt_id) if fahrzeugdetails is not None: if kilometerstand_bei_erledigung is None: kilometerstand_bei_erledigung = fahrzeugdetails.kilometerstand if betriebsstunden_bei_erledigung is None: betriebsstunden_bei_erledigung = fahrzeugdetails.betriebsstunden auftrag.kilometerstand_bei_erledigung = kilometerstand_bei_erledigung auftrag.betriebsstunden_bei_erledigung = betriebsstunden_bei_erledigung await db.flush() return auftrag async def liste_wartungsauftraege( db: AsyncSession, *, objekt_id: int | None = None, status: WartungsauftragStatus | None = None ) -> list[Wartungsauftrag]: stmt = select(Wartungsauftrag) if objekt_id is not None: stmt = stmt.where(Wartungsauftrag.objekt_id == objekt_id) if status is not None: stmt = stmt.where(Wartungsauftrag.status == status) stmt = stmt.order_by(Wartungsauftrag.erstellt_am.desc()) result = await db.execute(stmt) return list(result.scalars().all()) async def faellige_wartungen_fuer_objekt(db: AsyncSession, objekt: Objekt) -> list[dict]: """MAINT-002: kombiniert Zeit-/km-/Betriebsstunden-Intervalle je Position - fällig, sobald das erste Kriterium erreicht ist. Nie durchgeführt gilt immer als fällig (wie "nie kontrolliert" bei der Einsatzbereitschaft).""" plaene = ( await db.execute(select(Wartungsplan).where(Wartungsplan.objekttyp_id == objekt.objekttyp_id)) ).scalars().all() if not plaene: return [] fahrzeugdetails = await db.get(Fahrzeugdetails, objekt.id) heute = datetime.now(timezone.utc).date() ergebnisse: list[dict] = [] for plan in plaene: positionen = await hole_positionen_mit_intervallen(db, plan.id) for position in positionen: intervalle = await hole_intervalle(db, position.id) letzter = ( await db.execute( select(Wartungsauftrag) .where( Wartungsauftrag.objekt_id == objekt.id, Wartungsauftrag.wartungsplan_position_id == position.id, Wartungsauftrag.status == WartungsauftragStatus.erledigt, ) .order_by(Wartungsauftrag.erledigt_am.desc()) .limit(1) ) ).scalar_one_or_none() if letzter is None: ergebnisse.append( { "wartungsplan_position_id": position.id, "wartungsart": position.wartungsart, "objekt_id": objekt.id, "faellig": True, "faellig_am": None, "faellig_bei_km": None, "faellig_bei_betriebsstunden": None, } ) continue faellig = False faellig_am = None faellig_bei_km = None faellig_bei_bs = None for intervall in intervalle: if intervall.typ == IntervallTyp.zeit: faellig_am = plus_monate(letzter.erledigt_am.date(), intervall.wert) if heute >= faellig_am: faellig = True elif intervall.typ == IntervallTyp.km and fahrzeugdetails is not None: basis_km = letzter.kilometerstand_bei_erledigung or 0 faellig_bei_km = basis_km + intervall.wert if (fahrzeugdetails.kilometerstand or 0) >= faellig_bei_km: faellig = True elif intervall.typ == IntervallTyp.betriebsstunden and fahrzeugdetails is not None: basis_bs = letzter.betriebsstunden_bei_erledigung or Decimal(0) faellig_bei_bs = basis_bs + intervall.wert if (fahrzeugdetails.betriebsstunden or Decimal(0)) >= faellig_bei_bs: faellig = True ergebnisse.append( { "wartungsplan_position_id": position.id, "wartungsart": position.wartungsart, "objekt_id": objekt.id, "faellig": faellig, "faellig_am": faellig_am, "faellig_bei_km": faellig_bei_km, "faellig_bei_betriebsstunden": faellig_bei_bs, } ) return ergebnisse