fix(kontrolle): doppelten Fehlbestand bei Folge-Kontrolle vermieden
CI / backend-tests (push) Failing after 2m4s
CI / frontend-build (push) Successful in 20s

_synchronisiere_fehlbestand suchte nur nach Fehlbeständen der eigenen
Kontroll-Sitzung. Ein bereits offener Fehlbestand aus einer früheren
Kontrolle für dasselbe Objekt+Material wurde nicht wiedergefunden -
es entstand ein zweiter, paralleler Fehlbestand mit eigener (neuerer)
Sollmenge, der alte blieb mit veralteter Sollmenge offen liegen.
Lookup jetzt über objekt_id+material_id+status=offen statt
kontrolle_id; kontrolle_id am gefundenen Datensatz bleibt unverändert,
damit ein Abbruch der aktuellen Kontrolle ihn nicht fälschlich
mitlöscht. Löst sich die Abweichung über eine andere Kontrolle als
die erzeugende auf, wird der Fehlbestand jetzt sauber als erledigt
markiert (inkl. Mindermenge-Abschluss) statt stillschweigend gelöscht.

Nebenbei: Kommentare mit Kachel-/Datums-Referenzen ("Bug-Fund
2026-09-06", "UI-00X (Epic 20)") aus dem Code dieser Session entfernt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-06 22:26:57 +02:00
co-authored by Claude Sonnet 5
parent 9281840ac1
commit 9721873088
20 changed files with 133 additions and 46 deletions
+42 -8
View File
@@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.models.fehlbestand import Fehlbestand, FehlbestandStatus
from app.models.kontrolle import Kontrolle, Kontrollposition
from app.models.mindermenge import MindermengeStatus, MindermengenGenehmigung
from app.models.objekt import Objekt
from app.models.objektposition import Objektposition
from app.services import benachrichtigung as benachrichtigung_service
@@ -109,12 +110,13 @@ async def _synchronisiere_fehlbestand(
) -> uuid.UUID | None:
result = await db.execute(
select(Fehlbestand).where(
Fehlbestand.kontrolle_id == kontrolle.id,
Fehlbestand.objekt_id == objekt.id,
Fehlbestand.material_id == material_id,
Fehlbestand.status == FehlbestandStatus.offen,
)
)
bestehender = result.scalar_one_or_none()
von_dieser_kontrolle = bestehender is not None and bestehender.kontrolle_id == kontrolle.id
unterdeckung = sollmenge - istmenge
if unterdeckung > 0:
@@ -153,16 +155,48 @@ async def _synchronisiere_fehlbestand(
)
await benachrichtigung_service.benachrichtige_neuer_fehlbestand(db, fehlbestand=bestehender)
else:
# Korrektur vor Abschluss (z. B. Tippfehler nochmal erfasst) - solange die
# Kontrolle noch läuft, ist der von IHR erzeugte Fehlbestand noch nicht
# final und darf berichtigt werden.
# kontrolle_id bleibt unangetastet, sonst würde ein Abbruch dieser
# Kontrolle einen fremden Fehlbestand mitlöschen (_verwirf_kontrolldaten).
bestehender.sollmenge = sollmenge
bestehender.istmenge = istmenge
bestehender.fehlmenge = unterdeckung
elif bestehender is not None:
# Abweichung wurde vor Abschluss korrigiert - der von dieser Kontrolle
# erzeugte Fehlbestand hat sich erledigt, bevor er je "real" wurde.
await db.delete(bestehender)
bestehender = None
if von_dieser_kontrolle:
await db.delete(bestehender)
bestehender = None
else:
bestehender.istmenge = istmenge
bestehender.fehlmenge = Decimal(0)
bestehender.status = FehlbestandStatus.erledigt
bestehender.erledigt_am = datetime.now(timezone.utc)
await historie_service.log(
db,
zustaendiger_server_id=zustaendiger_server_id,
benutzer_id=kontrolle.benutzer_id,
ereignistyp="fehlbestand_erledigt",
entitaet_typ="fehlbestand",
entitaet_id=bestehender.id,
neuer_wert={"status": "erledigt"},
)
genehmigung_result = await db.execute(
select(MindermengenGenehmigung).where(
MindermengenGenehmigung.fehlbestand_id == bestehender.id,
MindermengenGenehmigung.status == MindermengeStatus.aktiv,
)
)
genehmigung = 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=kontrolle.benutzer_id,
ereignistyp="mindermenge_beendet_durch_erledigung",
entitaet_typ="mindermengen_genehmigung",
entitaet_id=genehmigung.id,
neuer_wert={"status": "beendet_durch_erledigung"},
)
bestehender = None
await db.flush()
return bestehender.id if bestehender is not None else None