from datetime import datetime, timezone from decimal import Decimal from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.fehlbestand import Fehlbestand, FehlbestandStatus from app.models.mindermenge import MindermengeStatus, MindermengenGenehmigung from app.models.nachfuellung import Nachfuellung from app.models.objektposition import Objektposition from app.services import historie as historie_service class FehlbestandBereitsErledigtError(Exception): """Ein bereits erledigter Fehlbestand kann nicht erneut nachgefüllt werden.""" async def nachfuellen( db: AsyncSession, *, fehlbestand: Fehlbestand, menge: Decimal, benutzer_id: int, zustaendiger_server_id: int, ) -> tuple[Nachfuellung, Decimal]: """Vier-Kernbegriffe (Prompt 02.9): Nachfüllung ist die einzige Aktion, die Objektposition.istmenge tatsächlich ändert. Deckt sowohl Sofort-Nachfüllung während der Kontrolle (Karte 07) als auch spätere/externe Nachfüllung (Prompt 02.5) ab - technisch derselbe Vorgang, nur anderer Zeitpunkt. Überbestand durch Nachfüllung ist erlaubt (Prompt 21.9/E4): kein Fehler, nur als Info im Rückgabewert `ueberbestand` gekennzeichnet. Wichtig: `fehlbestand.istmenge` ist die zuletzt GEZÄHLTE Wahrheit (aus der Kontrolle, die den Fehlbestand ausgelöst hat) - Objektposition.istmenge wird hier auf den neuen Wert GESETZT, nicht unabhängig davon inkrementiert. Sonst bliebe Objektposition.istmenge bei ihrem alten (ggf. 0/"nicht kontrolliert") Stand stehen, obwohl die Kontrolle bereits einen realen Zählwert kennt - "Ist-Menge wird auf Soll korrigiert" (Prompt 02.3) meint genau dieses Setzen. """ if fehlbestand.status == FehlbestandStatus.erledigt: raise FehlbestandBereitsErledigtError() neue_istmenge = fehlbestand.istmenge + menge ueberbestand = max(Decimal(0), neue_istmenge - fehlbestand.sollmenge) neue_fehlmenge = max(Decimal(0), fehlbestand.sollmenge - neue_istmenge) result = await db.execute( select(Objektposition).where( Objektposition.objekt_id == fehlbestand.objekt_id, Objektposition.material_id == fehlbestand.material_id, ) ) objektposition = result.scalar_one_or_none() if objektposition is not None: objektposition.istmenge = neue_istmenge fehlbestand.istmenge = neue_istmenge fehlbestand.fehlmenge = neue_fehlmenge wird_erledigt = neue_fehlmenge <= 0 if wird_erledigt: fehlbestand.status = FehlbestandStatus.erledigt fehlbestand.erledigt_am = datetime.now(timezone.utc) else: fehlbestand.status = FehlbestandStatus.nachgefuellt_teilweise nachfuellung = Nachfuellung( fehlbestand_id=fehlbestand.id, objekt_id=fehlbestand.objekt_id, material_id=fehlbestand.material_id, menge=menge, benutzer_id=benutzer_id, zeitpunkt=datetime.now(timezone.utc), ) db.add(nachfuellung) await db.flush() await historie_service.log( db, zustaendiger_server_id=zustaendiger_server_id, benutzer_id=benutzer_id, ereignistyp="nachfuellung_erfasst", entitaet_typ="fehlbestand", entitaet_id=fehlbestand.id, neuer_wert={"menge": str(menge), "neue_istmenge": str(neue_istmenge), "ueberbestand": str(ueberbestand)}, ) if wird_erledigt: await historie_service.log( db, zustaendiger_server_id=zustaendiger_server_id, benutzer_id=benutzer_id, ereignistyp="fehlbestand_erledigt", entitaet_typ="fehlbestand", entitaet_id=fehlbestand.id, neuer_wert={"status": "erledigt"}, ) # U7 (Prompt 04.4): wird die Abweichung behoben, erledigt sich eine noch # aktive Mindermengen-Genehmigung gemeinsam mit dem Fehlbestand. result = await db.execute( select(MindermengenGenehmigung).where( MindermengenGenehmigung.fehlbestand_id == fehlbestand.id, MindermengenGenehmigung.status == MindermengeStatus.aktiv, ) ) genehmigung = result.scalar_one_or_none() if genehmigung is not None: genehmigung.status = MindermengeStatus.beendet_durch_erledigung genehmigung.beendet_am = datetime.now(timezone.utc) await historie_service.log( db, zustaendiger_server_id=zustaendiger_server_id, benutzer_id=benutzer_id, ereignistyp="mindermenge_beendet_durch_erledigung", entitaet_typ="mindermengen_genehmigung", entitaet_id=genehmigung.id, neuer_wert={"status": "beendet_durch_erledigung"}, ) await db.flush() return nachfuellung, ueberbestand