feat(mangel): Mangel-Ticket-System (Roadmap Phase 2)
CI / backend-tests (push) Failing after 1m37s
CI / frontend-build (push) Successful in 18s

Eigener Workflow für Schäden/Defekte, getrennt von Fehlbestand (Mengen-
Abweichung Soll/Ist). Status: neu → in_bearbeitung → ersatzteil_bestellt/
reparatur_geplant → erledigt. entitaet_typ/entitaet_id optional (Objektposition
oder Geräteinstanz), analog zum bestehenden historie.entitaet_typ-Muster,
objekt_id immer gesetzt für Dashboard-Abfragen.

Ein offener Mangel mit prioritaet=hoch macht das Objekt "nicht einsatzbereit"
(neuer Grund mangel_kritisch_offen in dashboard.einsatzbereitschaft) -
niedrige/normale Mängel blockieren nicht.

Melden/Lesen: alle Mitarbeiter+. Status ändern: Materialverantwortliche+.
Neue Seite /maengel für alle eingeloggten Nutzer (Nav-Link "Mängel").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
2026-09-05 13:23:38 +02:00
co-authored by Claude Sonnet 5
parent d71d95e17f
commit eb20affd5d
14 changed files with 630 additions and 0 deletions
+5
View File
@@ -10,6 +10,7 @@ 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
from app.services.mangel import objekt_ids_mit_kritischem_offenem_mangel
# Prompt 12.2: offen/in_bearbeitung/nachgefuellt_teilweise zählen gemeinsam als "offen"
# (technischer Substatus ist keine Erledigung, Prompt 03/20).
@@ -160,6 +161,8 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict:
.all()
)
objekt_ids_kritischer_mangel = await objekt_ids_mit_kritischem_offenem_mangel(db)
bereit = eingeschraenkt = 0
details: list[dict] = []
for objekt in objekte:
@@ -174,6 +177,8 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict:
gruende.append("pruefung_ueberfaellig")
if objekt.id in objekt_ids_gesperrt:
gruende.append("kontrolle_laeuft")
if objekt.id in objekt_ids_kritischer_mangel:
gruende.append("mangel_kritisch_offen")
if not gruende and fehlbestand_ids and fehlbestand_ids <= fehlbestand_ids_genehmigt:
eingeschraenkt += 1
+33
View File
@@ -0,0 +1,33 @@
from datetime import datetime, timezone
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.mangel import Mangel, MangelPrioritaet, MangelStatus
OFFENE_MANGEL_STATI = (
MangelStatus.neu,
MangelStatus.in_bearbeitung,
MangelStatus.ersatzteil_bestellt,
MangelStatus.reparatur_geplant,
)
async def objekt_ids_mit_kritischem_offenem_mangel(db: AsyncSession) -> set[int]:
"""Dashboard/Einsatzbereitschaft (Nutzer-Vorgabe): nur ein offener Mangel mit
prioritaet=hoch macht ein Objekt "nicht einsatzbereit" - niedrige/normale
Mängel sind Hinweise, aber kein Einsatzbereitschafts-Blocker."""
result = await db.execute(
select(func.distinct(Mangel.objekt_id)).where(
Mangel.status.in_(OFFENE_MANGEL_STATI), Mangel.prioritaet == MangelPrioritaet.hoch
)
)
return set(result.scalars().all())
async def erledige_mangel(db: AsyncSession, *, mangel: Mangel, benutzer_id: int) -> Mangel:
mangel.status = MangelStatus.erledigt
mangel.erledigt_von = benutzer_id
mangel.erledigt_am = datetime.now(timezone.utc)
await db.flush()
return mangel