Zum Inhalt springen

Mängel- & Ticket-Management mit Grundriss-Pin, KI-Klassifikation und SLA-Eskalation

In Planung

§4.7 Mängel- & Ticket-Management — Grundriss-Pin, KI-Klassifikation, SLA-Eskalation

Abschnitt betitelt „§4.7 Mängel- & Ticket-Management — Grundriss-Pin, KI-Klassifikation, SLA-Eskalation“

Kernaussage des Beraters: Ein Mangel ist kein Eintrag in einer Excel-Liste, sondern eine örtlich fixierte, fristgebundene, foto-dokumentierte Rechtsbehauptung. Die Mangelrüge nach §13 Abs. 5 VOB/B und die Abnahme nach §640 BGB hängen von drei Dingen ab: Wo (Raum + Punkt auf Grundriss), Was (Klassifikation + Foto) und Bis wann (SLA + Eskalation). Werkszeit liefert alle drei Achsen in einem Workflow — ohne Medienbruch, ohne Rechtstreitigkeiten über Zuordnung.


Mangelrüge, Mangelverfolgung und Mangelnachweis werden bei Bauvorhaben heute typischerweise mit drei bis fünf Werkzeugen parallel bearbeitet: WhatsApp-Foto an den Monteur, handschriftlicher Pin in einer ausgedruckten Grundriss-Kopie, Excel-Liste beim Bauleiter, E-Mail an den Sub, PDF-Abnahmeprotokoll am Ende. Die Folge sind systematische Zuordnungslücken (Raum verwechselt, Pin nicht übertragen), SLA-Verletzungen ohne Eskalation und Beweisprobleme bei der Abnahme.

Werkszeit konsolidiert diesen Workflow in ein fachliches Modell Mangel-Ticket, das (a) räumlich auf einem Grundriss-PDF verankert ist, (b) eine KI-gestützte Klassifikation als Vorschlag anbietet, (c) vor/nach-Fotopflicht erzwingt und (d) SLA-Fristen pro Mangelklasse automatisch eskaliert.

Kennzahlen-Versprechen (Zielwerte nach 6 Monaten Produktivbetrieb, Benchmark Gebr. Schmidt):

KPI Heute (ohne Werkszeit) Ziel Messquelle
Mangel-Erfassungszeit (Ort → Ticket offen) 18 min ≤ 3 min maengel.created_at
Anteil Mängel mit Grundriss-Pin 27 % ≥ 95 % maengel.floorplan_pin_id IS NOT NULL
Anteil “Behoben” mit Nachher-Foto 41 % 100 % Enforced Gate, Reject unter 100 %
SLA-Verletzungen ohne Eskalation 63 % 0 % Cron-Audit
Streitige Abnahmen (§12 VOB/B) 1,8/Projekt ≤ 0,3 Projekt-Retro

In-Scope:

  • Mangel-Ticket erfassen auf Grundriss-PDF (Pin via PDF.js + Canvas-Overlay), Pflichtfelder Raum, Titel, Kategorie, Verantwortlicher, Foto
  • KI-Klassifikation (LLM-basiert) als Vorschlag für Mangel-Kategorie mit Transparenz-Hinweis nach KI-VO Art. 50
  • SLA-Engine mit konfigurierbaren Fristen pro Mangelklasse (Reaktion, Zwischenstand, Behebung)
  • Eskalation per Push/E-Mail an Bauleiter + Objektüberwachung bei Frist-Überschreitung
  • Vorher-Foto bei Ticket-Öffnung, Nachher-Foto bei Statuswechsel auf “Behoben” (Enforced Gate)
  • Mangelbericht-PDF für Abnahmeprotokoll (§12 VOB/B) mit Side-by-Side vorher/nachher
  • Verknüpfung zu §4.1 Bautagebuch (Mangel-Eintrag pro Tag), §4.3 GAEB-LV (Mangel an LV-Position), §4.5 Foto-Doku
  • Offline-Erfassung Mobile (PDF im Cache, Pin + Foto queued) mit Outbox-Sync

Out-of-Scope (bewusst):

  • Gewährleistungs-Rückstellungen als Buchungssatz → §4 Finanzen (später)
  • Mängel an Fremdgewerken mit eigener Rechnung → §4.13 Gutachter-Workflow (V3)
  • 3D-BIM-Koordinaten → V4, Grundriss-PDF 2D ist V2
  • Automatische Fristberechnung Verjährung §634a BGB → V3, Mahnwesen §4 Finanzen

Die häufigste Fehlannahme in Bau-Apps ist, dass ein Mangel ein “Aufgabeneintrag” sei. Juristisch ist ein Mangel mit Mangelrüge nach §13 Abs. 5 VOB/B der Beginn der Gewährleistungsuhr. Wer Mängel ohne revisionssicheren Zeitstempel, ohne räumliche Verankerung und ohne Foto-Nachweis erfasst, verliert im Streitfall vor dem Landgericht. Deshalb ist das Ticket-Modell bei Werkszeit append-only (hashkettenbasiert) und jeder Status-Wechsel ist ein separater, nicht rückdatierbarer Audit-Eintrag — analog zum Buchungsjournal in §4 Finanzen.


  • Thomas Schmidt, Bauleiter (Primär): 47 J., 18 Jahre Erfahrung, iPad auf Baustelle + Laptop im Büro, 4–6 parallele Projekte. Vergibt ca. 40 Mängel/Woche, erwartet: “Pin hin, Foto drauf, Sub anrufen automatisch.”
  • Mehmet Yılmaz, Monteur SHK (Sekundär): Erhält Mangel-Push, arbeitet ab, macht Nachher-Foto, schließt Ticket. Nutzt nur Mobile, kein Desktop.
  • Sandra Berger, Objektüberwachung (AG-Seite, tertiär): Kontrolliert aus AG-Perspektive, eigener schreibgeschützter Zugang, unterzeichnet am Ende das Abnahmeprotokoll.
  • Polier Jürgen Kaya (sekundär): Gesamtkoordination Baustelle, ist Eskalations-Empfänger bei SLA-Überschreitung.
ID Als möchte ich damit Priorität
US-01 Bauleiter Thomas auf dem Grundriss-PDF einen Pin an die Mangelstelle setzen und ein Foto anhängen der Mangel örtlich eindeutig und beweissicher dokumentiert ist Must
US-02 Bauleiter Thomas eine KI-Klassifikations-Empfehlung für die Mangel-Kategorie bekommen, aber überschreiben können ich Zeit spare, aber Verantwortung für die juristisch relevante Klassifikation behalte Must
US-03 Bauleiter Thomas SLA-Regeln pro Mangelklasse konfigurieren (Wasserschaden = 2 h Reaktion, 24 h Behebung) kritische Mängel priorisiert und fristgerecht behoben werden Must
US-04 Monteur Mehmet eine Push-Benachrichtigung bekommen, wenn mir ein Mangel zugewiesen wird, mit direktem Absprungpunkt ich nicht zwischen Apps wechseln muss Must
US-05 Monteur Mehmet beim Statuswechsel auf “Behoben” gezwungen werden, ein Nachher-Foto zu machen die Beweiskette lückenlos bleibt Must
US-06 System Mängel automatisch eskalieren, wenn SLA-Frist überschritten wird der Bauleiter/Polier nicht selbst nachhalten muss Must
US-07 Bauleiter Thomas einen Mangelbericht-PDF für die Abnahme nach §12 VOB/B generieren, mit Vorher/Nachher-Fotos nebeneinander ich im Abnahmetermin rechtssicher dokumentiert bin Must
US-08 Bauleiter Thomas einen Mangel mit einer LV-Position aus §4.3 GAEB-LV verknüpfen Kalkulations-Abweichungen bei Nacharbeit sichtbar werden Should
US-09 Objektüberwachung als schreibgeschützter Gast die Mängelliste sehen und Kommentare hinzufügen der AG parallel prüfen kann, ohne Schreibrechte am Bauherrenmodell Should
US-10 Monteur Mehmet offline einen Mangel erfassen können (Baustellenkeller, kein Empfang), synchronisiert später die Erfassungslücke verschwindet Must
US-11 Bauleiter Thomas bei Kategorie-Wechsel durch Monteur informiert werden (KI-Vorschlag falsch) ich die SLA-Klasse ggf. nachziehen muss Should
US-12 Polier Jürgen bei Krankheit des Verantwortlichen automatisch als Ersatz-Empfänger einspringen (Vertretungsregel) kein Mangel liegen bleibt Should

ID Anforderung
F-M-01 Mangel-Erfassungs-Button global im Projekt-Kontext (FAB unten rechts, orange, 56 dp)
F-M-02 Grundriss-PDF-Viewer mit Pan/Zoom (PDF.js-kompatibles Rendering, Flutter-Paket pdfx oder flutter_pdf_render), Pin-Setzen per Long-Press
F-M-03 Pin-Koordinaten persistieren als {pdf_page: int, x_pct: float (0..1), y_pct: float (0..1)} → Auflösungs- und Zoom-unabhängig
F-M-04 Pflichtfelder bei Ticket-Öffnung: Titel (max. 120 Zeichen), Kategorie (Pflicht, mit KI-Vorschlag), Verantwortlicher (aus Projektteam + Subs), mind. 1 Foto (Kamera oder Galerie)
F-M-05 KI-Klassifikation: Post an /v1/ai/classify-defect mit Titel + Beschreibung + Foto-Thumbnail (base64, max. 256 KB), Response {category: 'elektrik', confidence: 0.87} → Chip mit Disclaimer “Vorschlag — bitte prüfen” (KI-VO Art. 50)
F-M-06 Offline-Fallback: Wenn /v1/ai/classify-defect unerreichbar → keine KI-Kategorie, User wählt manuell, Hinweis “KI derzeit nicht verfügbar”
F-M-07 Foto-Pflicht bei Statuswechsel auf “Behoben”: Dialog “Nachher-Foto erforderlich”, Button “Jetzt fotografieren”, Statuswechsel nur nach erfolgreichem Upload (bzw. Outbox-Queue bei Offline)
F-M-08 Push-Benachrichtigung bei Zuweisung: Deeplink werkszeit://mangel/{id}, Aktionen “Öffnen” und “Angenommen” direkt im Push (Android Notification Action, iOS Notification Action)
F-M-09 Offline-Modus: Grundriss-PDF wird bei Projekt-Öffnen heruntergeladen und lokal gecached (max. 50 MB pro PDF), Pins werden lokal gespeichert, Outbox-Sync bei Verbindung
F-M-10 Mangel-Liste pro Projekt mit Filter: Status (offen/in Bearbeitung/behoben/abgelehnt), Kategorie, Verantwortlicher, “Nur meine Mängel”
F-M-11 Kommentar-Funktion pro Ticket (Text + Foto), append-only, nicht editierbar — jeder Kommentar ist ein eigener Audit-Eintrag
F-M-12 Sprachnotiz-Aufnahme (max. 2 min, AAC, 64 kbps) als alternative Beschreibungsquelle, wird serverseitig mit Whisper-kompatiblem STT transkribiert und als Text-Beschreibung vorbefüllt (Nutzer bestätigt)
F-M-13 SLA-Countdown im Ticket-Detail sichtbar: “Reaktion erforderlich bis 14:30 Uhr”, rote Dringlichkeits-Pille bei < 30 min Restzeit
ID Anforderung
F-W-01 Grundriss-Viewer mit PDF.js + HTML5-Canvas-Overlay; Pins als <svg><circle> mit Tenant-Farbe, Klick öffnet Ticket-Panel rechts
F-W-02 Mängel-Kanban-Board: Spalten “Offen” / “In Bearbeitung” / “Warten auf Prüfung” / “Behoben” / “Abgelehnt”, Drag&Drop löst Statuswechsel mit Audit-Log
F-W-03 Mängel-Listen-Ansicht mit Sortierung nach SLA-Restzeit, Bulk-Aktionen: Kategorie ändern, Verantwortlicher zuweisen, Status ändern (alle mit Audit-Begründung Pflicht)
F-W-04 SLA-Konfigurator: Tabelle Mangelklasse × Frist (Reaktion/Behebung), editierbar nur durch Rolle projektleiter, Änderung greift nur für neue Mängel (Immutable History)
F-W-05 Mangelbericht-PDF-Export: Auswahl “Offene Mängel” / “Alle Mängel” / “Abnahme-Bericht §12 VOB/B”, Format A4 quer, Seitenlayout: Pro Mangel eine Seite mit Pin-Screenshot, Vorher/Nachher-Foto, Metadaten, Unterschriftenfeld
F-W-06 Eskalations-Dashboard: KPIs “SLA-Verletzungen heute/Woche/Monat”, Heatmap “Eskalationen pro Gewerk”
F-W-07 Grundriss-Historie: Wenn ein neues Grundriss-PDF hochgeladen wird (Revision), bleiben bestehende Pins an alter Revision, neuer Pin zwingt User zu Revisionsauswahl — kein automatisches Remapping
F-W-08 Rolle objektueberwachung (AG-Seite): Read-Only auf Tickets, schreibt nur Kommentare, kann signieren (§12 VOB/B Abnahmeprotokoll)
ID Anforderung
F-A-01 LLM-basierte Klassifikation (Claude 3.5 Haiku oder GPT-4o-mini, konfigurierbar pro Tenant) mit System-Prompt „Du bist Mangel-Klassifikator. Kategorien: elektrik, sanitaer, heizung, lueftung, putz, maler, fliesen, trockenbau, bodenbelag, fenster, tueren, dach, estrich, sonstiges. Antwort in JSON mit {category, confidence, reasoning}.“
F-A-02 Input-Obergrenze: Title 120 Zeichen + Description 2000 Zeichen + 1 Foto-Thumbnail (base64, 512×512, JPEG Qualität 70, max. 256 KB). DSGVO: Foto wird NICHT persistiert im LLM-Provider (Anthropic/OpenAI “no training” API-Konfiguration, Auftragsverarbeitungsvertrag)
F-A-03 Transparenz-Pflicht (KI-VO Art. 50): Jede KI-Kategorie wird im UI mit Badge “KI-Vorschlag · bitte prüfen” angezeigt, in maengel.category_source wird `‘ki-suggested’
F-A-04 Fallback: Bei LLM-Timeout (> 3 s) oder HTTP ≥ 500 → kein Vorschlag, User wählt manuell. Fehler wird im Audit-Log als ki.classify.failed geloggt, aber nicht blockierend
F-A-05 Rate-Limit pro Tenant: 1000 Klassifikationen/Tag (skaliert mit Tarif), Überschreitung → 429 mit Retry-After, User wählt manuell
F-A-06 Audit: ai_audit_log persistiert pro Request {tenant_id, user_id, input_hash (SHA-256), response_category, confidence, latency_ms} — kein Klartext, DSGVO-konform
ID Anforderung
F-S-01 Konfigurierbare SLA-Regeln pro Mangelklasse × Projekt (Override) mit drei Zeitstempeln: reaction_deadline, intermediate_deadline (optional), resolution_deadline
F-S-02 Beispiel-Defaults (gelten global, überschreibbar pro Projekt): Wasserschaden 2h/24h, Elektrik kritisch 4h/48h, Standard-Mangel 48h/14 Tage
F-S-03 Deadlines berücksichtigen Werktage, Feiertage (Bundesland aus Projekt-Stammdaten), Arbeitszeit 07:00–17:00 — konfigurierbar in sla_calendar_rules
F-S-04 Scheduler-Cron (jede 5 min, BullMQ) prüft überfällige Mängel → Statuswechsel auf eskaliert, Push+E-Mail an Bauleiter, Polier, Fallback-Empfänger
F-S-05 Eskalations-Kette konfigurierbar: 1. Frist-Ablauf → Verantwortlicher+Bauleiter, 2. Frist+24h → Polier+Projektleiter, 3. Frist+48h → Geschäftsführung
F-S-06 Vertretungsregel: Bei Abwesenheit (aus §4 Urlaubs-/Krankmeldungs-Modul) → Ersatz-Verantwortlicher automatisch. Wenn kein Vertreter gesetzt → Polier als Fallback
F-S-07 SLA-Pausieren: Status warten_auf_materialanlieferung pausiert SLA (Grund Pflicht, Audit-Log); Resume bei Statuswechsel zurück
ID Anforderung
F-P-01 PDF-Export A4 quer, Corporate-CI (Logo, Projekt-Kopfzeile, Seitenzahlen), Deckblatt mit Bauvorhaben, BV-Nr., Datum, Bauleiter, AG, Unterschriftenfeld
F-P-02 Pro Mangel eine Seite: Oben Pin-Screenshot (Grundriss-Ausschnitt, 300×300 dpi), Mitte Side-by-Side Vorher/Nachher-Foto (je 800×600), unten Metadaten-Tabelle (Mangel-Nr., Kategorie, Raum, Erfasst am, Verantwortlicher, Status, Behoben am)
F-P-03 Abnahme-Kontext §12 VOB/B: Deckblatt enthält Haken-Felder “Ohne Mängel abgenommen” / “Mit Vorbehalt abgenommen (siehe Mängelliste)” / “Abnahme verweigert (siehe §12 Abs. 3 VOB/B)”
F-P-04 Fußzeile jeder Seite: “Erstellt mit Werkszeit am {datum}, {uhrzeit}, Hash: {sha256_first12}”, GoBD-konforme Hash-Kette aller enthaltenen Tickets
F-P-05 Unterschriften-Seite (letzte Seite): Felder für AG, AN, Objektüberwachung; bei Signatur via qualifizierter elektronischer Signatur (eIDAS QES) → Hash im PDF-Metadatum DocumentSignature
F-P-06 PDF/A-3 konform (Long-Term-Archivierung), eingebettete Metadaten XMP mit Projekt-UUID, Erstell-Hash, Tenant-ID

┌────────────────────────────────────┐
│ ← Projekt: Kita Bremen-Nord │
│ │
│ Grundriss · EG · Revision 3 │
│ ┌──────────────────────────────┐ │
│ │ │ │
│ │ ░░░░░ │ │
│ │ ┌─────┐ ┌──────┐ │ │
│ │ │ 1.01│ │ 1.02 │ │ │
│ │ │ Bad │ ● │ WC │ │ │
│ │ └─────┘ ↑ └──────┘ │ │
│ │ PIN hier │ │
│ │ │ │
│ │ ┌──────────────┐ │ │
│ │ │ 1.03 Flur │ │ │
│ │ └──────────────┘ │ │
│ └──────────────────────────────┘ │
│ [◎ Pin setzen · Long-Press] │
│ │
│ ──────────────────────────── │
│ ●●● offener Pin, ●●● behoben │
│ │
│ [Mangel-Liste] [Neuer Mangel] │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ ← Neuer Mangel · Raum 1.01 Bad │
│ │
│ Titel * │
│ ┌──────────────────────────────┐ │
│ │ Steckdose lose, Wackelkontakt│ │
│ └──────────────────────────────┘ │
│ │
│ Kategorie * │
│ ┌──────────────────────────────┐ │
│ │ ⚡ Elektrik [KI-Vorschlag]│ │
│ │ » Vorschlag — bitte prüfen «│ │
│ └──────────────────────────────┘ │
│ [Andere Kategorie wählen ▼] │
│ │
│ Beschreibung │
│ ┌──────────────────────────────┐ │
│ │ Steckdose neben Waschbecken │ │
│ │ sitzt nicht bündig, Abdeckung│ │
│ │ wackelt, Kontakt unsicher. │ │
│ └──────────────────────────────┘ │
│ 🎙 Sprachnotiz 📷 Foto · 1/3 │
│ │
│ Verantwortlicher * │
│ [Mehmet Yılmaz (Sub Elektro) ▼] │
│ │
│ Priorität │
│ ( ) niedrig ( ) mittel (●) hoch │
│ │
│ SLA: Reaktion 4h, Behebung 48h │
│ │
│ [ Abbrechen ] [ Speichern ] │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ ← Mangel #2026-042-17 │
│ │
│ ┌─ Titel ───────────────────────┐ │
│ │ Steckdose Bad lose │ │
│ │ Wackelkontakt │ │
│ └───────────────────────────────┘ │
│ │
│ ⚡ Elektrik · 🔴 Hoch · ⏱ 3h 42m │
│ Projekt Kita Bremen-Nord │
│ Raum 1.01 Bad │
│ │
│ [Grundriss zeigen ›] │
│ │
│ Vorher-Foto: │
│ ┌──────────────────────────────┐ │
│ │ [foto_vorher.jpg] │ │
│ │ │ │
│ └──────────────────────────────┘ │
│ │
│ Status: offen │
│ Erfasst: 19.04.2026 08:15 · Thomas│
│ Frist: 19.04.2026 12:15 (4h) │
│ Behebung: 21.04.2026 17:00 │
│ │
│ ───────────────────────────── │
│ [Annehmen] [Behoben melden] │
│ [Zurückweisen (Grund Pflicht)] │
└────────────────────────────────────┘

4.4 Mobile: Statuswechsel „Behoben“ — Nachher-Foto erzwungen

Abschnitt betitelt „4.4 Mobile: Statuswechsel „Behoben“ — Nachher-Foto erzwungen“
┌────────────────────────────────────┐
│ Behoben melden · #2026-042-17 │
│ │
│ ⚠ Nachher-Foto erforderlich │
│ │
│ Die Beweiskette für die │
│ Mangelbehebung nach §13 VOB/B │
│ verlangt ein Foto der │
│ behobenen Stelle. │
│ │
│ Vorher-Foto (Referenz): │
│ ┌──────────────────────────────┐ │
│ │ [klein_vorher.jpg] │ │
│ └──────────────────────────────┘ │
│ │
│ Nachher-Foto: │
│ ┌──────────────────────────────┐ │
│ │ │ │
│ │ [ 📷 Jetzt fotografieren ]│ │
│ │ │ │
│ └──────────────────────────────┘ │
│ │
│ Kommentar (optional) │
│ ┌──────────────────────────────┐ │
│ │ Steckdose erneuert, Putz │ │
│ │ ausgebessert. Ausgleich mit │ │
│ │ Feinputz. │ │
│ └──────────────────────────────┘ │
│ │
│ [ Abbrechen ] [ Fertig ] │
│ (grau, bis Foto vorhanden) │
└────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│ Werkszeit > Projekt Kita Bremen-Nord > Mängel │
│ 🔔 42 offen │
├───────────────────────────────────────────────────────────────────────────┤
│ [+ Neuer Mangel] [SLA-Config] [Bericht exportieren ▼] │
├──────────────┬──────────────┬──────────────┬────────────────┬────────────┤
│ OFFEN │ IN BEARBEITUNG│ ZUR PRÜFUNG │ BEHOBEN │ ABGELEHNT │
│ (18) │ (12) │ (5) │ (7) │ (0) │
├──────────────┼──────────────┼──────────────┼────────────────┼────────────┤
│ #2026-042-17 │ #2026-042-14 │ #2026-042-11 │ #2026-042-08 │ │
│ Steckdose… │ Rissbildung… │ Lüftung... │ Tür klemmt │ │
│ ⚡ Elektrik │ Putz │ Lüftung │ ✓ Tueren │ │
│ 🔴 4h / 48h │ ⏱ 12h │ ⏱ ok │ 16.04. ✓ │ │
│ Mehmet │ Werner Y. │ Rolf K. │ Peter S. │ │
│ │ │ │ │ │
│ #2026-042-16 │ #2026-042-13 │ │ │ │
│ Fliese ... │ Wasserfleck …│ │ │ │
│ Fliesen │ 🔴 Sanitär │ │ │ │
│ ⏱ 36h │ ESKALIERT! │ │ │ │
│ Thorsten R. │ Uwe M. │ │ │ │
├──────────────┴──────────────┴──────────────┴────────────────┴────────────┤
│ │
│ ┌─ Grundriss EG, Revision 3 ──────────────────────────────────────────┐ │
│ │ │ │
│ │ [SVG mit 42 Pins, farbcodiert nach Status] │ │
│ │ │ │
│ │ ● offen (18 rot) ● in Bearb. (12 orange) │ │
│ │ ● zur Prüfung (5) ● behoben (7 grün) │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘

4.6 Web: Mangelbericht-PDF Vorschau (für §12 VOB/B Abnahme)

Abschnitt betitelt „4.6 Web: Mangelbericht-PDF Vorschau (für §12 VOB/B Abnahme)“
┌───────────────────────────────────────────────────────────────────────────┐
│ Mangelbericht-Generator · §12 VOB/B Abnahmeprotokoll │
├───────────────────────────────────────────────────────────────────────────┤
│ │
│ Auswahl Mängel: ( ) Alle 42 (●) Nur behobene 7 ( ) Offene │
│ Sortierung: (●) Nach Raum ( ) Nach Datum ( ) Nach Gewerk │
│ Format: (●) PDF/A-3 ( ) PDF/1.7 │
│ Unterschrift: [✓] Unterschriftenseite AG/AN/OU am Ende │
│ Abnahme-Votum: (●) Mit Vorbehalt (Mängel siehe Liste) │
│ │
│ ─── Vorschau Seite 2 von 9 ─────────────────────────────────────────── │
│ │
│ Mangel #2026-042-08 · Tür klemmt │
│ Projekt: Kita Bremen-Nord · Raum 1.12 Gruppenraum │
│ │
│ ┌───Grundriss-Ausschnitt────┐ │
│ │ [Pin auf Tür markiert]│ │
│ └────────────────────────────┘ │
│ │
│ VORHER (16.04.2026 09:22) NACHHER (16.04.2026 16:44) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ [Tür klemmt] │ │ [Tür o.B.] │ │
│ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ Kategorie: Türen · Priorität: mittel · Erfasst: Thomas Schmidt │
│ Verantwortlich: Peter S. (Sub Trockenbau) │
│ Behoben am: 16.04.2026 16:44 durch Peter S. │
│ Kommentar: Scharniere nachgezogen, Tür gehobelt 3mm. │
│ │
│ SLA: Reaktion 4h (erfolgt 09:45, ok) · Behebung 48h (erfolgt 7h 22m, ok)│
│ │
│ ────────────────────────────────────────────────────────────────────── │
│ Erstellt mit Werkszeit · 19.04.2026 14:38 · Hash: a7f3b9… │
│ │
│ [Zurück] [Seite 1 ‹ 2 › 3 ... 9] [PDF generieren] │
└───────────────────────────────────────────────────────────────────────────┘

packages/db/schema/maengel.ts
import { pgTable, uuid, text, varchar, timestamp, integer, real,
boolean, jsonb, pgEnum, index, primaryKey } from 'drizzle-orm/pg-core';
import { tenantsTable, projectsTable, usersTable } from './core';
import { lvPositionsTable } from './gaeb';
// ──────────────────────────────────────────────────────────────
// Enums
// ──────────────────────────────────────────────────────────────
export const mangelStatusEnum = pgEnum('mangel_status', [
'offen', // neu erfasst
'angenommen', // von Verantwortlichem angenommen
'in_bearbeitung',
'warten_auf_material', // SLA pausiert
'zur_pruefung', // behoben, wartet auf Abnahme durch Bauleiter
'behoben', // abgenommen (Nachher-Foto vorhanden)
'abgelehnt', // vom Verantwortlichen abgelehnt (Grund Pflicht)
'eskaliert', // SLA überschritten, automatisch gesetzt
]);
export const mangelKategorieEnum = pgEnum('mangel_kategorie', [
'elektrik', 'sanitaer', 'heizung', 'lueftung', 'putz', 'maler',
'fliesen', 'trockenbau', 'bodenbelag', 'fenster', 'tueren',
'dach', 'estrich', 'metallbau', 'beton', 'sonstiges',
]);
export const kategorieSourceEnum = pgEnum('kategorie_source', [
'user-manual', // Nutzer hat ohne KI-Vorschlag gewählt
'ki-suggested', // KI-Vorschlag angenommen ohne Änderung
'user-confirmed', // KI-Vorschlag bestätigt (identisch)
'user-overridden', // Nutzer hat KI-Vorschlag überschrieben
]);
// ──────────────────────────────────────────────────────────────
// Hauptentität: Mangel-Ticket (append-only Status-History separat)
// ──────────────────────────────────────────────────────────────
export const maengelTable = pgTable('maengel', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
projectId: uuid('project_id').notNull().references(() => projectsTable.id),
// Lokale Mangel-Nr. pro Projekt, menschenlesbar
mangelNr: varchar('mangel_nr', { length: 32 }).notNull(), // z.B. 2026-042-17
titel: varchar('titel', { length: 120 }).notNull(),
beschreibung: text('beschreibung'),
sprachnotizUrl: text('sprachnotiz_url'), // S3-URL, AAC
sprachnotizTranskript: text('sprachnotiz_transkript'),
kategorie: mangelKategorieEnum('kategorie').notNull(),
kategorieSource: kategorieSourceEnum('kategorie_source').notNull(),
kiConfidence: real('ki_confidence'), // 0..1, nur wenn kategorieSource = ki-*
kiModel: varchar('ki_model', { length: 64 }), // z.B. 'claude-3-5-haiku-20241022'
prioritaet: varchar('prioritaet', { length: 16 }).notNull().default('mittel'),
// niedrig/mittel/hoch/kritisch
// Grundriss-Pin (nullable, falls Raum ohne PDF)
floorplanPdfId: uuid('floorplan_pdf_id'), // FK auf documents
floorplanPage: integer('floorplan_page'), // 1-basiert
pinXPct: real('pin_x_pct'), // 0..1
pinYPct: real('pin_y_pct'), // 0..1
raumBezeichnung: varchar('raum_bezeichnung', { length: 64 }), // z.B. "1.01 Bad"
// Verknüpfungen
lvPositionId: uuid('lv_position_id').references(() => lvPositionsTable.id),
// Verantwortlichkeit
verantwortlicherId: uuid('verantwortlicher_id').notNull()
.references(() => usersTable.id),
erfasstDurchId: uuid('erfasst_durch_id').notNull()
.references(() => usersTable.id),
// SLA
slaReaktionsFrist: timestamp('sla_reaktions_frist', { withTimezone: true }).notNull(),
slaBehebungsFrist: timestamp('sla_behebungs_frist', { withTimezone: true }).notNull(),
slaPausiertVon: timestamp('sla_pausiert_von', { withTimezone: true }),
slaPausiertBis: timestamp('sla_pausiert_bis', { withTimezone: true }),
slaPausierungsgrund: text('sla_pausierungsgrund'),
// Aktueller Status (denormalisiert, Source-of-Truth ist status_history)
status: mangelStatusEnum('status').notNull().default('offen'),
// Zeitstempel
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
behobenAt: timestamp('behoben_at', { withTimezone: true }),
// Immutability / Audit
hashPrev: varchar('hash_prev', { length: 64 }),
hashSelf: varchar('hash_self', { length: 64 }).notNull(), // SHA-256 über alle Felder + hashPrev
}, (t) => ({
idxTenantProject: index('idx_maengel_tenant_project').on(t.tenantId, t.projectId),
idxStatus: index('idx_maengel_status').on(t.tenantId, t.status),
idxVerantwortlicher: index('idx_maengel_verantw').on(t.verantwortlicherId),
idxSla: index('idx_maengel_sla').on(t.slaBehebungsFrist, t.status),
uniqMangelNr: index('uniq_maengel_nr').on(t.projectId, t.mangelNr),
}));
// ──────────────────────────────────────────────────────────────
// Status-Historie (append-only, hash-chained)
// ──────────────────────────────────────────────────────────────
export const mangelStatusHistoryTable = pgTable('mangel_status_history', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id),
statusVon: mangelStatusEnum('status_von'),
statusZu: mangelStatusEnum('status_zu').notNull(),
begruendung: text('begruendung'), // Pflicht bei Ablehnung
nachherFotoId: uuid('nachher_foto_id'), // FK documents, Pflicht bei Status 'behoben'
aenderungDurchId: uuid('aenderung_durch_id').notNull().references(() => usersTable.id),
aenderungZeitpunkt: timestamp('aenderung_zeitpunkt', { withTimezone: true }).notNull().defaultNow(),
hashPrev: varchar('hash_prev', { length: 64 }),
hashSelf: varchar('hash_self', { length: 64 }).notNull(),
}, (t) => ({
idxMangel: index('idx_mangel_hist_mangel').on(t.mangelId, t.aenderungZeitpunkt),
}));
// ──────────────────────────────────────────────────────────────
// Kommentare (append-only)
// ──────────────────────────────────────────────────────────────
export const mangelKommentareTable = pgTable('mangel_kommentare', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id),
autorId: uuid('autor_id').notNull().references(() => usersTable.id),
text: text('text').notNull(),
fotoId: uuid('foto_id'), // FK documents
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
hashPrev: varchar('hash_prev', { length: 64 }),
hashSelf: varchar('hash_self', { length: 64 }).notNull(),
});
// ──────────────────────────────────────────────────────────────
// Fotos (vorher + nachher, mit 1:n Verknüpfung zu Mangel)
// ──────────────────────────────────────────────────────────────
export const mangelFotosTable = pgTable('mangel_fotos', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id),
rolle: varchar('rolle', { length: 16 }).notNull(), // vorher | nachher | kommentar
documentId: uuid('document_id').notNull(), // FK documents
exifAufnahmeZeit: timestamp('exif_aufnahme_zeit', { withTimezone: true }),
exifLatitude: real('exif_latitude'),
exifLongitude: real('exif_longitude'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});
// ──────────────────────────────────────────────────────────────
// SLA-Regelwerk
// ──────────────────────────────────────────────────────────────
export const slaRulesTable = pgTable('sla_rules', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
projectId: uuid('project_id').references(() => projectsTable.id), // null = tenant-default
kategorie: mangelKategorieEnum('kategorie').notNull(),
prioritaet: varchar('prioritaet', { length: 16 }).notNull(),
reaktionsMinuten: integer('reaktions_minuten').notNull(), // 120 = 2h
behebungsMinuten: integer('behebungs_minuten').notNull(), // 1440 = 24h
zwischenstandMinuten: integer('zwischenstand_minuten'),
eskalationsKette: jsonb('eskalations_kette').notNull(), // [{stufe:1, offset_min:0, empfaenger_rollen:['bauleiter']}]
aktiv: boolean('aktiv').notNull().default(true),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
}, (t) => ({
uniqRule: index('uniq_sla_rule').on(t.tenantId, t.projectId, t.kategorie, t.prioritaet),
}));
// ──────────────────────────────────────────────────────────────
// Eskalations-Audit
// ──────────────────────────────────────────────────────────────
export const slaEskalationenTable = pgTable('sla_eskalationen', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id),
stufe: integer('stufe').notNull(), // 1, 2, 3
benachrichtigteUserIds: jsonb('benachrichtigte_user_ids').notNull(), // uuid[]
ausgeloestAm: timestamp('ausgeloest_am', { withTimezone: true }).notNull().defaultNow(),
quittungAm: timestamp('quittung_am', { withTimezone: true }),
quittungUserId: uuid('quittung_user_id').references(() => usersTable.id),
});
// ──────────────────────────────────────────────────────────────
// KI-Audit (DSGVO-konform, nur Hashes)
// ──────────────────────────────────────────────────────────────
export const kiAuditLogTable = pgTable('ki_audit_log', {
id: uuid('id').defaultRandom().primaryKey(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
userId: uuid('user_id').notNull().references(() => usersTable.id),
zweck: varchar('zweck', { length: 64 }).notNull(), // 'classify-defect'
modell: varchar('modell', { length: 64 }).notNull(),
inputHash: varchar('input_hash', { length: 64 }).notNull(), // SHA-256
responseKategorie: varchar('response_kategorie', { length: 32 }),
confidence: real('confidence'),
latenzMs: integer('latenz_ms').notNull(),
erfolgreich: boolean('erfolgreich').notNull(),
fehlerCode: varchar('fehler_code', { length: 32 }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});
-- RLS enable
alter table maengel enable row level security;
alter table mangel_status_history enable row level security;
alter table mangel_kommentare enable row level security;
alter table mangel_fotos enable row level security;
alter table sla_rules enable row level security;
alter table sla_eskalationen enable row level security;
alter table ki_audit_log enable row level security;
-- Tenant-Isolation
create policy maengel_tenant_isolation on maengel
using (tenant_id = current_setting('app.tenant_id')::uuid);
-- Scope: Monteur nur eigene + zugewiesene, Bauleiter alle im Projekt
create policy maengel_scope_select on maengel
for select
using (
tenant_id = current_setting('app.tenant_id')::uuid
and (
current_setting('app.role') in ('admin','bauleiter','projektleiter','objektueberwachung')
or verantwortlicher_id = current_setting('app.user_id')::uuid
or erfasst_durch_id = current_setting('app.user_id')::uuid
)
);
-- Append-only: KEIN UPDATE/DELETE auf Historie
create policy mangel_history_readonly on mangel_status_history
for update using (false);
create policy mangel_history_nodel on mangel_status_history
for delete using (false);
-- Objektüberwachung: Read-Only auf Tickets, Schreiben nur auf Kommentare
create policy ou_no_mangel_write on maengel
for insert
with check (current_setting('app.role') <> 'objektueberwachung');
create policy ou_no_mangel_update on maengel
for update
using (current_setting('app.role') <> 'objektueberwachung');
  1. 2026_04_19_100000_create_mangel_enums.sql
  2. 2026_04_19_100100_create_maengel_table.sql
  3. 2026_04_19_100200_create_mangel_history_fotos.sql
  4. 2026_04_19_100300_create_sla_rules_eskalationen.sql
  5. 2026_04_19_100400_create_ki_audit_log.sql
  6. 2026_04_19_100500_enable_rls_maengel.sql
  7. 2026_04_19_100600_seed_default_sla_rules.sql

Methode Pfad Zweck Scope
POST /v1/maengel Neuen Mangel anlegen write
GET /v1/maengel?projectId=…&status=… Liste mit Filter read
GET /v1/maengel/:id Einzelner Mangel mit voller Historie read
PATCH /v1/maengel/:id Bearbeiten (nur beschreibung, kategorie, verantwortlicher) write
POST /v1/maengel/:id/status Statuswechsel mit optional nachher_foto_id write
POST /v1/maengel/:id/kommentare Kommentar hinzufügen write
POST /v1/maengel/:id/fotos Foto uploaden (multipart) write
POST /v1/ai/classify-defect KI-Klassifikation anfordern write
POST /v1/sla/rules SLA-Regel anlegen/ändern admin
GET /v1/sla/overdue?projectId=… Überfällige Mängel (für Dashboard + Cron) read
POST /v1/sla/eskalieren/:mangelId Manuelle Eskalation auslösen bauleiter+
POST /v1/maengel/:id/sla/pause SLA pausieren (Grund Pflicht) bauleiter+
POST /v1/maengel/:id/sla/resume SLA fortsetzen bauleiter+
POST /v1/reports/mangelbericht PDF-Bericht generieren (§12 VOB/B), async + SSE-Progress bauleiter+

Beispiel-Schema POST /v1/maengel:

{
"projectId": "01924f3a-7b2e-7c01-a123-...",
"titel": "Steckdose lose, Wackelkontakt",
"beschreibung": "Steckdose neben Waschbecken sitzt nicht bündig.",
"kategorie": "elektrik",
"kategorieSource": "user-overridden",
"kiConfidence": 0.87,
"kiModel": "claude-3-5-haiku-20241022",
"prioritaet": "hoch",
"floorplanPdfId": "01924f3a-…",
"floorplanPage": 1,
"pinXPct": 0.342,
"pinYPct": 0.617,
"raumBezeichnung": "1.01 Bad",
"verantwortlicherId": "01924f3a-…",
"vorherFotoId": "01924f3a-…",
"lvPositionId": null,
"idempotencyKey": "01924f3a-7b2e-7c01-a123-pin-1713513300"
}

Beispiel-Response POST /v1/ai/classify-defect:

{
"category": "elektrik",
"confidence": 0.87,
"reasoning": "Schlüsselwörter 'Steckdose', 'Wackelkontakt' deuten stark auf Elektrik-Kategorie.",
"model": "claude-3-5-haiku-20241022",
"latencyMs": 842,
"auditId": "01924f3a-…"
}

7.1 Happy Path: Mangel erfassen → beheben → Bericht

Abschnitt betitelt „7.1 Happy Path: Mangel erfassen → beheben → Bericht“
Feature: Mangel-Ticket mit Grundriss-Pin und SLA
Scenario: Bauleiter erfasst Mangel, Monteur behebt, Bericht wird erzeugt
Given Thomas ist angemeldet als Bauleiter im Projekt "Kita Bremen-Nord"
And Mehmet ist angemeldet als Monteur und im Projektteam
And das Grundriss-PDF "EG_Revision_3.pdf" ist hochgeladen
And die SLA-Regel "Elektrik/hoch" lautet "Reaktion 4h, Behebung 48h"
When Thomas öffnet den Grundriss, Long-Presst auf Raum 1.01 Bad, Pin (0.342, 0.617)
And Thomas gibt ein: Titel "Steckdose lose, Wackelkontakt", Beschreibung "…"
And Thomas erhält KI-Vorschlag "elektrik" mit confidence 0.87
And Thomas bestätigt "elektrik" unverändert
And Thomas nimmt ein Foto der Steckdose auf, weist Mehmet zu, speichert
Then wird ein Ticket #2026-042-17 angelegt mit Status "offen"
And SLA-Reaktions-Frist ist auf jetzt+4h (unter Berücksichtigung Feiertage 07-17 Uhr) gesetzt
And Mehmet erhält eine Push-Benachrichtigung mit Deeplink
When Mehmet öffnet den Deeplink und klickt "Annehmen"
Then Status wechselt auf "angenommen", Reaktions-SLA ist erfüllt, Audit-Eintrag
When Mehmet tauscht die Steckdose, klickt "Behoben melden"
Then erscheint Dialog "Nachher-Foto erforderlich", Button "Jetzt fotografieren"
And Mehmet nimmt Nachher-Foto auf, klickt "Fertig"
Then wechselt Status auf "zur_pruefung", Thomas erhält Push
When Thomas akzeptiert die Behebung
Then wechselt Status auf "behoben", behobenAt wird gesetzt
When Thomas exportiert Mangelbericht §12 VOB/B
Then erhält er ein PDF/A-3 mit einem Abschnitt für #2026-042-17
And die Seite enthält Pin-Screenshot, Side-by-Side Vorher/Nachher, Metadaten, Hash-Fußzeile

7.2 Edge Case 1 — Foto-Pin auf gelöschtem Grundriss

Abschnitt betitelt „7.2 Edge Case 1 — Foto-Pin auf gelöschtem Grundriss“
Scenario: Grundriss-PDF wurde durch Revision ersetzt, Pin verweist auf Vorversion
Given Mangel #2026-042-17 zeigt auf floorplan_pdf_id = "EG_Rev3.pdf"
And der Architekt lädt "EG_Rev4.pdf" hoch mit neuer floorplan_pdf_id
And das System markiert Rev3 als "archiviert" (kein Hard-Delete)
When Thomas öffnet den Mangel #2026-042-17 im Web
Then wird im Pin-Viewer ein gelber Info-Banner angezeigt:
"""
Dieser Mangel verweist auf Grundriss-Revision 3 (archiviert am 19.04.2026).
Aktuelle Revision: 4. Pin wurde NICHT automatisch übertragen — räumliche
Zuordnung würde verfälscht.
"""
And ein Button "Auf Revision 4 umpinnen" erlaubt manuelles Remapping mit Audit-Eintrag
And der Bericht-Export zeigt beide Revisionen, wenn Mängel beider Revisionen enthalten sind
Scenario: Hard-Delete des Grundriss-PDF durch Admin
Given das Grundriss-PDF wurde versehentlich hard-deleted (Admin-Override)
When Thomas öffnet Mangel #2026-042-17
Then erscheint roter Error-Banner "Grundriss nicht mehr verfügbar — räumliche Zuordnung verloren. Mangel-Daten (Text, Foto, Raum-Bezeichnung) bleiben erhalten."
And im Bericht-Export wird statt Pin-Screenshot der Text "Grundriss nicht mehr verfügbar" eingebettet

7.3 Edge Case 2 — KI-Klassifikation falsch, Nutzer überschreibt

Abschnitt betitelt „7.3 Edge Case 2 — KI-Klassifikation falsch, Nutzer überschreibt“
Scenario: KI schlägt falsche Kategorie vor, Nutzer korrigiert, Audit greift
Given Thomas erfasst Mangel "Boden wackelt beim Treten"
And die KI schlägt Kategorie "bodenbelag" mit confidence 0.62 vor
When Thomas öffnet das Kategorie-Dropdown und wählt "estrich"
Then wird kategorieSource = "user-overridden" gesetzt
And kiConfidence = 0.62 bleibt gespeichert
And im Audit-Log ki_audit_log ist der Request dokumentiert mit responseKategorie="bodenbelag"
When Thomas speichert
Then ist im Mangel-Detail ein kleiner Info-Hinweis sichtbar:
"""
KI-Vorschlag war "bodenbelag" (62 % Konfidenz) — überschrieben durch Thomas Schmidt
am 19.04.2026 um 14:23.
"""
Scenario: Monteur wechselt Kategorie nachträglich (z.B. von 'putz' auf 'sanitaer')
Given Mangel #2026-042-17 hat Kategorie "putz" mit SLA "Standard 48h/14d"
When Mehmet öffnet den Mangel, erkennt Wasserschaden dahinter, wechselt Kategorie auf "sanitaer"
Then wird ein neuer SLA "Sanitär 4h/48h" berechnet — aber NICHT rückwirkend
And die alte Frist bleibt als historischer Datensatz in mangel_status_history
And Thomas erhält Push "Kategorie geändert von 'putz' auf 'sanitaer', SLA neu berechnet"

7.4 Edge Case 3 — SLA überschritten + Verantwortlicher krank

Abschnitt betitelt „7.4 Edge Case 3 — SLA überschritten + Verantwortlicher krank“
Scenario: Verantwortlicher ist krankgemeldet, Eskalation greift Vertretungsregel
Given Mangel #2026-042-22 ist zugewiesen an Uwe M. (Sub Sanitär)
And SLA-Behebungs-Frist ist 19.04.2026 12:00 Uhr
And Uwe M. ist ab 18.04.2026 krankgemeldet (Status im §4 Abwesenheitsmodul)
And Uwe M. hat Vertreter Heinz K. hinterlegt
When die Uhr 12:00 erreicht und der Cron-Job läuft 12:05
Then wechselt Status auf "eskaliert"
And Stufe-1-Eskalation benachrichtigt: Heinz K. (Vertreter), Thomas (Bauleiter)
And Push + E-Mail gehen raus mit Betreff "SLA-Überschreitung: Mangel #2026-042-22"
And ein Eintrag in sla_eskalationen wird erzeugt
When 24h später die Frist+24h erreicht wird und kein Statuswechsel erfolgte
Then Stufe-2-Eskalation: Jürgen K. (Polier), Projektleiter
When 48h später immer noch kein Statuswechsel
Then Stufe-3-Eskalation: Geschäftsführung
And im Kanban erscheint der Mangel in der roten "ESKALIERT"-Spalte mit Eskalations-Badge
Scenario: Vertreter nicht gesetzt, Fallback auf Polier
Given Uwe M. ist krank, kein Vertreter gesetzt
When SLA-Frist überschritten wird
Then Eskalation geht an Polier Jürgen K. (Fallback-Konfiguration)
And Thomas erhält zusätzliche Notification "Vertreter für Uwe M. fehlt — Fallback Polier"
Scenario: SLA-Berechnung berücksichtigt Feiertag
Given Mangel erfasst am 30.04.2026 (Mittwoch) 16:00 Uhr, SLA "Reaktion 4h Arbeitszeit 07-17 Uhr"
And 01.05.2026 ist Feiertag (Tag der Arbeit, NRW)
When SLA-Frist berechnet wird
Then ist sla_reaktions_frist = 04.05.2026 09:00 Uhr
(1h am 30.04. genutzt von 16:00-17:00, Rest 3h am nächsten Werktag ab 07:00)
And NICHT 30.04.2026 20:00 oder 01.05.2026 11:00

# Fall
E1 Pin wird offline gesetzt, Grundriss-PDF wurde inzwischen online durch Revision ersetzt → Konflikt-Ticket “Pin auf veralteter Revision”, Bauleiter entscheidet Remapping
E2 Foto-Upload schlägt fehl wegen großer Dateigröße > 10 MB → Client komprimiert automatisch auf 1920×1440, Qualität 80, EXIF-erhaltend
E3 KI-Provider liefert Kategorie, die nicht im Enum ist (“fenster-abdichtung”) → Mapping auf nächstliegende (“fenster”), im Audit dokumentiert
E4 Mangel wird versehentlich doppelt erfasst durch zwei Personen gleichzeitig (z.B. Thomas + Sandra) → Idempotency-Key {user_id}-{project_id}-{pin_x}-{pin_y}-{date_hour} dedupliziert
E5 SLA-Frist-Berechnung Zeitzone: Projekt in DE, Nutzer reist nach NYC, Gerät UTC-4 → Server rechnet immer in Projekt-Timezone (Europa/Berlin)
E6 Verantwortlicher wird aus Projektteam entfernt, bevor Mangel behoben ist → Mangel bleibt zugewiesen (Audit), aber Push scheitert → Bauleiter bekommt “Verantwortlicher nicht mehr im Team”-Hinweis
E7 Kommentar enthält DSGVO-relevanten Namen eines Dritten (“Nachbar Müller beschwert sich”) → Kein Auto-Redact, aber Manuell-Löschen via Kommentar-Überschreibung (neuer Kommentar “redacted”)
E8 Grundriss-PDF ist 50 MB+ (Plotter-Export) → Server rendert serverseitig zu WebP-Tiles (Tile-Pyramid), Mobile lädt nur sichtbare Tiles
E9 Nutzer fotografiert Vorher-Foto mit Kamera-Blitz nachts, Nachher-Foto bei Tag → kein Auto-Rejection, aber Bericht-Export enthält EXIF-Zeitstempel pro Foto
E10 Push-Benachrichtigung kommt nicht an (z.B. Android-Battery-Saver) → Fallback E-Mail nach 30 min ohne Lese-Bestätigung der Push
E11 Mangelbericht-PDF > 200 Seiten → Server-Render async mit SSE-Progress, Timeout 10 min, Fallback-Download per E-Mail-Link
E12 Nutzer versucht Statuswechsel auf “behoben” ohne Nachher-Foto (Frontend-Bug) → Server-seitige Validierung blockiert mit 422 “Nachher-Foto fehlt”
E13 Objektüberwachung versucht Mangel zu bearbeiten → RLS-Policy blockt mit 403 “Rolle objektueberwachung hat keine Schreibrechte auf Mängel”
E14 Sprachnotiz-Transkription schlägt fehl (Hintergrundlärm) → Transkript leer, User bearbeitet manuell
E15 Bauleiter löscht Mangel “versehentlich erfasst” → Soft-Delete mit Grund-Pflicht, Audit, aber KEIN physisches Löschen (GoBD)

Anforderung Umsetzung
Unveränderbarkeit Tabelle mangel_status_history append-only, RLS-Policy blockt UPDATE/DELETE
Nachvollziehbarkeit Hash-Kette hashPrev/hashSelf (SHA-256) über alle Status-Änderungen
Vollständigkeit Jeder Statuswechsel dokumentiert, auch „Abgelehnt“ mit Begründung
Zeitgerechte Erfassung createdAt = Server-Zeit (UTC), Mobile-Offline-Erfassung preserves client_created_at mit sync_offset_seconds
Aufbewahrungsfrist 10 Jahre S3 Object Lock Compliance-Mode (legal-hold), AWS Backup → Glacier Deep Archive nach 1 Jahr
Artikel Umsetzung
Art. 5 (Zweckbindung) Mängeldaten nur für Baudokumentation/VOB-Abnahme verwendet, keine Zweckfremdnutzung
Art. 6 (Rechtsgrundlage) Vertrag (AG-AN), berechtigtes Interesse (Gewährleistung)
Art. 9 (besondere Kat.) Mängelfotos dürfen Personen zeigen (z.B. Monteur in Aktion) → Foto-Policy: keine Gesichter auf Bericht-Fotos, Auto-Blur optional
Art. 17 (Löschung) Nach Ablauf Gewährleistung (5 J. §634a BGB) + GoBD-Frist (10 J.) → Soft-Delete + Anonymisierung (Nutzer-Bezug entfernen)
Art. 20 (Portabilität) Export Mängel als JSON + ZIP-Fotos pro Projekt
Art. 30 (VVT) Eintrag “Mängel-Verwaltung” im Verzeichnis, Empfänger: Subunternehmer (Auftragsverarbeitung)
Art. 35 (DSFA) DSFA erforderlich für KI-Klassifikation → siehe §14
Anforderung Umsetzung
Transparenz-Pflicht gegenüber Nutzer Badge “KI-Vorschlag — bitte prüfen” immer sichtbar, kategorieSource im Datenmodell
Kennzeichnung in Ausgabe Mangelbericht-PDF kennzeichnet Kategorie mit “(KI-Vorschlag akzeptiert)” / “(manuell gewählt)” in Fußnote
Nutzerentscheidung dokumentiert kategorieSource enum persistiert Entscheidungsweg
Logging (Art. 12) ki_audit_log dokumentiert jeden Request (nur Hashes, DSGVO-konform)

9.4 BGB §634 (Mängelrechte) / §640 (Abnahme) / VOB/B §13-§14

Abschnitt betitelt „9.4 BGB §634 (Mängelrechte) / §640 (Abnahme) / VOB/B §13-§14“
Norm Umsetzung
§634 BGB Mangel als Anspruchsgrundlage für Nacherfüllung, Rücktritt, Minderung, SE → Mangel-Ticket ist Beweismittel
§640 BGB Abnahme Mangelbericht-PDF kann als Grundlage für Abnahmeprotokoll dienen (Votum “Mit Vorbehalt”)
§13 Abs. 5 VOB/B Mangelrüge durch Status “offen” mit Zeitstempel = Beginn Gewährleistungsuhr, Audit-Log revisionssicher
§14 VOB/B Abrechnung (später in §4 Finanzen) kann Mängel-Rückstellungen berücksichtigen via lvPositionId-Verknüpfung
§12 VOB/B Abnahme Mangelbericht-PDF als Anhang zum Abnahmeprotokoll, Unterschriftenseite enthält Abnahme-Votum-Checkboxen
Anforderung Umsetzung
Sicherheitsrelevante Mängel Kategorie “elektrik” + Priorität “kritisch” → automatischer Stop-Work-Workflow in §4.5 HSSE
Dokumentation SiGeKo-relevant Export in SiGeKo-PDF möglich (separat in §4.5)

Code-Bereich Test-Szenario
slaEngine.calculateDeadline Feiertag NRW 01.05., Arbeitszeit 07-17 Uhr, Frist 4h startend 30.04.2026 16:00 → 04.05.2026 09:00 Uhr
hashChain.verifyStatusChain Sequenz von 5 Status-Änderungen, Manipulation von Eintrag 3 erkannt
pinCoordinateTransform PDF-Page-Space 0.342/0.617 → SVG-Pixel bei Viewport 1920×1080, zoom 1.5
kiFallback LLM-Timeout 3s → kein Vorschlag, manueller Workflow, Audit-Log enthält fehlerCode='timeout'
mangelNrGenerator Format YYYY-{projectShortId}-{seq} pro Projekt, Race-Condition-frei (DB-Sequence + Locking)
Szenario
Mobile: PDF laden → Pin setzen → Ticket anlegen → offline gehen → Status ändern → online gehen → Sync erfolgt
Web: Kanban-Drag&Drop von “offen” nach “behoben” löst Nachher-Foto-Modal aus, Ablehnung → Status unverändert
E2E: Bauleiter erstellt 20 Mängel, alle mit KI-Vorschlag, davon 5 überschrieben → DB-Zählung kategorie_source = “user-overridden” = 5
Eskalations-Test: SLA-Frist manuell auf vergangenen Zeitpunkt gesetzt, Cron läuft → Eskalation Stufe 1 + Push + E-Mail erfolgt
Bericht-Export: 50 Mängel, davon 30 behoben → PDF/A-3 hat 30 Seiten + Deckblatt + Unterschriftenseite = 32 Seiten, alle mit vorher/nachher
Metrik Zielwert
Mangel-Anlage (POST) p95 ≤ 400 ms (ohne Foto-Upload)
KI-Klassifikation p95 ≤ 2.5 s (Timeout 3 s, Fallback manuell)
Grundriss-PDF-Load im Mobile Cache ≤ 1 s (5 MB PDF, lokal)
Mangelbericht-PDF-Render (100 Mängel) ≤ 45 s async mit SSE-Progress
SLA-Cron pro 10.000 aktive Mängel ≤ 8 s
Kanban-Board-Load im Web (500 Mängel) ≤ 1.5 s p95
  • DoR §1.1: Akzeptanzkriterien aus US-01 bis US-12 Gherkin-verifiziert
  • DoR §1.2: Compliance-Matrix vollständig (§9) — GoBD, DSGVO, KI-VO, VOB/B
  • DoR §1.3: UI-Wireframes + Design-Token-Mapping (wz-pill, wz-btn, wz-card)
  • DoD §2.1: Unit-Test-Coverage ≥ 80 % auf Modul maengel
  • DoD §2.4: RLS-Policies getestet (5 Rollen × 4 Operationen = 20 Fälle)
  • DoD §2.7: DSFA-Dokument zu KI-Klassifikation abgeschlossen (siehe §14)

  1. BIM-/3D-Koordinaten: V4+. 2D-Grundriss-PDF ist V2, ausreichend für 95 % der Baustellen.
  2. Automatische Mangelbehebungs-Kalkulation: Keine automatische Rückstellungs-Buchung in §4 Finanzen. Manuelle Ableitung durch Buchhaltung aus offenen Mängeln.
  3. Gutachter-Workflow: Externe Gutachter-Einbindung (mit eigener Rechnung) → V3, §4.13.
  4. OCR auf Plänen: Raumerkennung aus PDF-Plan per OCR → V3. In V2 manuelle Raum-Eingabe oder Auswahl aus Raumbuch.
  5. Integration Bau-Software (iTWO, RIB, Nevaris): V3, über Standard-API oder IFC-Export.
  6. Mängel-Statistik-Benchmark Inter-Tenant: Nicht geplant (DSGVO-Bedenken).
  7. Automatische Verjährungs-Berechnung §634a BGB: V3, erfordert fundiertes Werkleistungs-Modell.
  8. Unterstützung fremder Grundriss-Formate (DWG, DXF): V3, erfordert CAD-Parser.

# Risiko Eintr.-Wahrscheinlichkeit Impact Gegenmaßnahme
R1 KI-Klassifikation halluziniert Kategorie, Nutzer übernimmt blind → falscher SLA Mittel Hoch Confidence-Threshold 0.7, unter 0.7 keine Vorauswahl, Transparenz-Hinweis, Audit
R2 Grundriss-PDF-Revision ohne Pin-Remapping → Mangel räumlich nicht mehr zuordenbar Mittel Mittel Banner + manuelles Remapping + Bericht zeigt beide Revisionen
R3 Push-Benachrichtigung kommt nicht an (iOS Background-Policy) → SLA verletzt, Mangel unbehandelt Mittel Hoch Fallback E-Mail nach 30 min, SMS bei Kritikalität=kritisch (optional)
R4 LLM-Provider-Ausfall (Anthropic, OpenAI) → KI-Klassifikation unverfügbar, UX-Degradation Niedrig Mittel Zwei Provider konfigurierbar (Fallback), bei beiden Ausfall: manuell mit Info-Banner
R5 DSGVO-Verstoß: Mitarbeiter-Foto auf Nachher-Foto, im Bericht exportiert Mittel Hoch Foto-Policy, Auto-Blur-Option (Cloudflare AI Face-Blur), Schulung
R6 Server-side PDF-Render OOM bei 500+ Mängel Niedrig Mittel Streaming-Render (pdfkit-stream), Chunk pro 50 Mängel, Timeout + Retry
R7 Offline-Outbox: Pins werden 5 Tage offline gehortet, dann Massen-Sync bricht wegen Rate-Limit Niedrig Mittel Rate-Limit pro Client-ID progressiv, Retry mit Backoff, Bauleiter-Benachrichtigung bei > 100 queued
R8 Hash-Chain-Bruch durch Datenbank-Restore von Backup vor Manipulation Sehr niedrig Hoch Zweiter Hash-Anker in S3 Object Lock (Hash pro Tag), dreifach abgesichert
R9 Rechtsstreit AG vs. AN: Mangel vorhanden, aber Zeitstempel strittig (“Rückdatiert?”) Niedrig Sehr hoch Hash-Kette + S3 Object Lock + optionale qualifizierte Zeitstempel via DATEV-TSA
R10 KI-VO-Verstoß: Transparenz-Hinweis nicht sichtbar genug → Bußgeld bis 15 Mio € / 3 % Umsatz Sehr niedrig Sehr hoch DSFA + UX-Test mit Anwälten (Kanzlei Waldeck), Screenshot-Archiv als Beweis

  • §4.1 Bautagebuch: Mangel-Eintrag wird automatisch als Tages-Notiz in Tagesbericht aufgenommen (Bidirektional-Link).
  • §4.3 GAEB-LV: Mangel kann an LV-Position verknüpft werden → Kalkulations-Abweichung bei Nacharbeit.
  • §4.5 Foto-Doku/HSSE: Mängel-Fotos werden im Foto-Archiv mit Tag mangel-#2026-042-17 gespeichert.
  • §4.6 Mobiles Lager: Bei Mangel “Material defekt/fehlerhaft” kann Rück-Bestellung via OCI-Punchout ausgelöst werden.
  • §4.8 Subunternehmer: Verantwortlicher-Zuweisung greift auf Sub-User-Liste zu.
  • Stammdaten/04 Ansprechpartner: Verantwortlicher kann auch externer Ansprechpartner (ohne Login) sein — dann nur E-Mail-Notification, kein Push.

14. DSFA (Datenschutz-Folgenabschätzung) — Kurzfassung

Abschnitt betitelt „14. DSFA (Datenschutz-Folgenabschätzung) — Kurzfassung“

Verarbeitung: Mangelfotos, Grundrisse, KI-Klassifikation (LLM-Cloud).

Risiko-Klassifikation: Mittel (Art. 35 DSGVO).

Betroffene Personen: Mitarbeiter (Monteure auf Fotos), AG-Vertreter, Dritte (z.B. Nachbarn in Kommentaren).

Schutzmaßnahmen:

  1. Fotos werden in S3 eu-central-1 gespeichert, verschlüsselt SSE-S3
  2. LLM-Anfragen: Anthropic Claude API mit “no-training”-Flag, AVV vorliegend
  3. KI-Audit-Log speichert nur SHA-256-Hashes des Inputs, kein Klartext
  4. RLS blockt unautorisierten Zugriff
  5. Löschkonzept: 10 Jahre GoBD, danach Anonymisierung
  6. Auto-Blur-Option für Gesichter in Export-Fotos (optional, Default an)

Restrisiko: Akzeptabel. Vollständige DSFA siehe docs/werkszeit/dsfa/07-maengel-tickets-dsfa.md (separates Dokument).


Bauleitung Gebrüder Schmidt GmbH, Bremen — 42 Mitarbeiter, 4-6 parallele Bauvorhaben (Wohnbau, Kita, Gewerbe) — Pilot ab Q3/2026 geplant, 3 Projekte als Referenz.

Nutzungs-Benchmark (Baseline):

  • 42 Mängel/Woche pro Bauleiter
  • 1100 m² BGF ⌀ Projektgröße
  • 7 Subunternehmer ⌀
  • Derzeitige Tool-Landschaft: WhatsApp, Bautagebuch-Papier, Excel, Dropbox-Fotos

Erwarteter Business-Case (ROI):

  • 8h/Woche Ersparnis pro Bauleiter × 42 €/h × 46 Wochen = 15.456 € pro Bauleiter/Jahr
  • Reduktion streitiger Abnahmen 1,8 → 0,3 = 1,5 Rechtsstreitigkeiten weniger/Jahr × 12.000 € ⌀ Kanzlei/Streit = 18.000 €/Jahr
  • Gesamt: ~33.000 €/Jahr pro Bauleiter, ROI <6 Monate bei Werkszeit-Lizenz 49 €/User/Monat

  • Greenfield in V2, keine Bestandsdaten-Migration
  • Alt-App hatte Mangel-Excel-Import → optional CSV-Import-Endpoint (V2.1)
  • Import-Format: projekt_nr;mangel_nr;titel;beschreibung;kategorie;raum;status;erfasst_am;erfasst_durch
  1. Q2/2026 (Alpha): Internes Test-Projekt, 2 Bauleiter, 1 Sub
  2. Q3/2026 (Beta): Referenz-Kunde Gebr. Schmidt, 3 Projekte, 20 User
  3. Q4/2026 (GA): Öffentliche Freigabe nach KI-VO-Compliance-Review
  4. Q1/2027 (V2.1): CSV-Import, SLA-Kalender mit regionalen Feiertagen, SMS-Fallback

Unsere Empfehlung für die Umsetzungsreihenfolge (in 12 Wochen bis GA):

Sprint 1-2 (Wochen 1-3): Datenmodell + RLS

  • Drizzle-Schema + Migrations + RLS-Policies
  • Hash-Chain mit Unit-Tests
  • SLA-Engine Core (ohne UI)

Sprint 3-4 (Wochen 4-6): Mobile Mangel-Erfassung

  • Grundriss-Viewer mit Pin-Setzen
  • Foto-Pflicht-Workflow (vorher/nachher)
  • Offline-Cache + Outbox-Sync

Sprint 5-6 (Wochen 7-9): Web-Kanban + SLA-Config

  • Kanban-Board mit Drag&Drop
  • Grundriss-Viewer mit PDF.js + Canvas
  • SLA-Konfigurator
  • Eskalations-Cron

Sprint 7 (Woche 10): KI-Klassifikation

  • LLM-Integration mit Fallback
  • KI-Audit-Log
  • DSFA abschließen

Sprint 8-9 (Wochen 11-12): Mangelbericht-PDF + Abnahme

  • PDF-Render mit vorher/nachher
  • §12 VOB/B Abnahme-Checkboxen
  • Unterschriftenseite
  • E2E-Tests mit Referenz-Kunde

Kritische Pfade: PDF.js + Flutter-PDF-Interop (Wochen 4-5), SLA-Feiertagsberechnung (Woche 7), KI-VO-Compliance-Review (Wochen 10-11).

Nicht-verhandelbar: (1) Nachher-Foto-Pflicht als Server-Side-Validation, (2) Transparenz-Hinweis bei KI-Vorschlag in allen Views, (3) Hash-Kette ab Tag 1.


  1. Q: Soll das Grundriss-PDF serverseitig auf Tile-Pyramid vorgerendert werden (für Plotter-Exports > 50 MB)? — Antwort vorgeschlagen: Ja, ab V2.1.
  2. Q: Welches LLM-Modell ist Default (Claude 3.5 Haiku vs. GPT-4o-mini)? — Antwort vorgeschlagen: Claude 3.5 Haiku primär (DSGVO-AVV vorhanden, EU-Rechenzentrum über AWS Bedrock eu-central-1 verfügbar).
  3. Q: Ist die Kategorie-Enum-Liste final oder tenant-erweiterbar? — Antwort vorgeschlagen: Tenant-erweiterbar via mangel_kategorien Lookup-Table (V2.1), Enum in V2.0.
  4. Q: Wie restriktiv ist der Nachher-Foto-Enforcement bei Ablehnung? — Antwort vorgeschlagen: Bei Status “abgelehnt” KEIN Foto-Zwang, bei “behoben” Pflicht.
  5. Q: Soll SMS-Fallback bei kritischen Mängeln in V2 enthalten sein (Twilio-Kosten, AVV)? — Offen, Business-Entscheidung.
  6. Q: Wie werden Mängel bei Projektabschluss archiviert — aktiv verschiebbar oder automatisch nach Abnahme? — Antwort vorgeschlagen: Automatisch 30 Tage nach finaler Abnahme, dann Read-Only.

Die R1-Scheibe liefert PDF-Mängelprotokoll (§4.7.6) und SLA-Eskalations-Timer (§4.7.7) in Migration 0032_handwerk_07_maengel_sla.sql. Die Tabellen-Namen weichen pragmatisch vom ursprünglichen Entwurf (§11) ab — funktional identisch, aber kürzer und konsistent mit dem Feature-Präfix maengel_*:

Feinkonzept §11 R1-Migration 0032 Zweck
sla_rules sla_konfig Pro tenant × prioritaet SLA-Defaults
sla_eskalationen maengel_eskalationen Append-only Audit mit Hash-Chain
maengel.reaktion_faellig_bis ALTER maengel (Trigger-berechnet)
maengel.loesung_faellig_bis ALTER maengel (Trigger-berechnet)
maengel.eskalation_faellig_bis ALTER maengel (Trigger-berechnet)

Die Seed-Zeilen pro Tenant (via SELECT id FROM tenants beim INSERT ... ON CONFLICT DO NOTHING):

Priorität Reaktion Lösung Eskalation
kritisch 1 h 4 h 2 h
hoch 4 h 24 h 12 h
mittel 24 h 72 h 48 h
niedrig 72 h 168 h 120 h

UNIQUE-Index (mangel_id, eskalationsstufe, ueberfaellig_typ) auf maengel_eskalationen verhindert doppelte Einträge beim Cron-Rerun. Der Insert läuft in einem SAVEPOINT (SAVEPOINT me_insert); Konflikt auf der UNIQUE-Constraint (errcode 23505) → ROLLBACK TO SAVEPOINT und stilles Überspringen. Damit ist der Cron beliebig oft aufrufbar.

  • Einzel-PDF: GET /v1/handwerk/maengel/{id}/protokoll.pdf
  • Batch-PDF: POST /v1/handwerk/maengel/batch-protokoll.pdf (1..200 IDs)
  • Content-Type: application/pdf
  • Content-Disposition: attachment; filename="mangelprotokoll-{id}.pdf"
  • Transport: Readable.toWeb() → Hono c.body(ReadableStream) (pdfkit flusht Seiten-weise, RSS bleibt konstant auch bei 200er-Batch).

Erhalten bleiben: KI-Klassifikation, Grundriss-Pin, sla_kalender (Arbeitszeit-aware Fristen), Web-Kanban, Sprachnotiz. Die SLA-Engine R1 rechnet mit kalendarischen Stunden — die Feiertagsberechnung (§17 Sprint 5-6) wandert mit sla_kalender in R2.


Ende des Feinkonzepts §4.7 — Mängel- & Ticket-Management.


Die zugehörigen Web-Admin-Screens sind in §3.4 (SLA-Engine), §4.5 (Mängel-Kanban) und §4.6 (Mangelbericht-PDF) als Anforderungen bzw. Wireframes ausgearbeitet. Diese Sektion fasst die F-A-* Feature-IDs konsolidiert zusammen — Admin-Modul-Stub-URL: /admin/handwerk/maengel-tickets/.

  • F-A-01Mangel-Kategorien-Katalog. Hierarchische Kategorisierung (Gewerk → Untergewerk → Schweregrad), Default-SLA pro Knoten, Default-Verantwortlicher (Rolle oder konkreter User), Sichtbarkeit pro Projekt-Typ.
  • F-A-02SLA-Regeln. Antwortzeit (Acknowledge), Lösungszeit (Resolve), Eskalations-Pfad (n Stufen, je Stufe Empfänger + Auslöse-Schwelle). Werktage vs. Kalendertage konfigurierbar, Feiertagskalender pro Bundesland.
  • F-A-03KI-Klassifikator-Schwelle. Confidence-Threshold ab dem die Klassifikation auto-übernommen wird, untere Schwelle ab der Mensch entscheiden muss. Override-Pflicht-Modus für Phase-1-Rollout (immer Mensch dran). Klassifikator-Modell-Version anzeigen + manuelles Re-Training auslösen.
  • F-A-04Mangelbericht-PDF-Vorlage. Logo-Upload, Briefkopf-Felder, Pflicht-Felder (BV-Nummer, Bauleiter-Unterschrift, §12-VOB/B-Hinweise), Sprachen (DE/EN/PL), Vorschau-Button mit Test-Daten.
  • F-A-05Push-Empfänger-Routing. Gewerke-Routing (welche Kategorie an welchen Polier/Subunternehmer), Bereitschafts-Plan (Wochentag/Uhrzeit), Wochenend-/Urlaubsvertretung mit automatischer Re-Routing-Logik.
  • F-A-06Foto-Pflicht-Konfiguration. Pro Status-Übergang konfigurierbar (Erfassung: 1 Foto Pflicht; Behoben: Nachher-Foto Pflicht; Abgenommen: Bauleiter-Foto optional). Mindest-Auflösung in Pixel, EXIF-Pflicht (Geo + Zeit) zur Beweissicherung.
  • F-A-07Aufbewahrungs-Policy. Lösch-Frist nach Abnahme + Garantie-Ablauf konfigurierbar pro Projekt-Typ (Default 5 Jahre VOB/B-Gewährleistung), GoBD-Pflichtfristen für rechnungs-relevante Tickets (10 Jahre), DSGVO-Personenbezug-Anonymisierung nach Frist X.
  • F-A-08Status-Workflow-Editor. Definition zusätzlicher Status-Übergänge (z.B. „Wartet auf Material“, „Wartet auf Subunternehmer“) und deren SLA-Pause-Verhalten (zählt SLA weiter ja/nein).
  • F-A-09Kanban-Spalten-Konfiguration. Welche Statuse als eigene Spalte, Reihenfolge, Farbe, WIP-Limit pro Spalte (Lean-Ansatz für Polier-Übersicht).
Für Entwickler — API-Endpoints12
MethodePfadAuthZweck
GET/v1/handwerk/maengelbearerAuthMängel-Liste (Cursor-Pagination)
POST/v1/handwerk/maengelbearerAuthMangel-Ticket anlegen
PUT/v1/handwerk/maengel-sla-konfig/{prioritaet}bearerAuthMängel-SLA-Konfiguration einer Priorität setzen (admin)
GET/v1/handwerk/maengel/{id}bearerAuthMangel-Ticket Detail
POST/v1/handwerk/maengel/{id}/commentsbearerAuthKommentar hinzufügen (append-only)
POST/v1/handwerk/maengel/{id}/photosbearerAuthFoto-Metadaten registrieren
GET/v1/handwerk/maengel/{id}/protokoll.pdfbearerAuthMangel-Protokoll als PDF
PATCH/v1/handwerk/maengel/{id}/statusbearerAuthStatus-Übergang auslösen
POST/v1/handwerk/maengel/batch-protokoll.pdfbearerAuthSammel-Mangel-Protokoll als PDF
POST/v1/handwerk/maengel/sla-checkbearerAuthSLA-Check ausführen (Eskalationen erzeugen)
GET/v1/handwerk/maengel/sla-konfigbearerAuthMängel-SLA-Konfiguration
GET/v1/handwerk/maengel/ueberfaelligbearerAuthÜberfällige Mängel (SLA-Verletzungen)