Zum Inhalt springen

Bautagebuch & Vor-Ort-Dokumentation

Live in Produktion

Feinkonzept · Bautagebuch & Vor-Ort-Dokumentation (§4.1)

Abschnitt betitelt „Feinkonzept · Bautagebuch & Vor-Ort-Dokumentation (§4.1)“

Feature-ID: handwerk/01-bautagebuch · Stand: 2026-04-19 · Autor: Senior-Berater-Rolle Handwerks-/Bau-SaaS Verwandte Feinkonzepte: 02-nachtrag (ein Bautagebuch-Eintrag ist fast immer der Auslöser eines Nachtrags), 10-arbeitssicherheit (PSA-Check und Erst-Unterweisung sind eigene Eintrags-Typen im Bautagebuch).


feature_id: handwerk/01-bautagebuch
title: Bautagebuch & Vor-Ort-Dokumentation
funktionsumfang_ref: §4.1
roadmap_horizont: MVP # Basis-Bautagebuch ist MVP (FU §6); Werkzeug-QR & DWD-Cache V1
plattformen:
mobile: vollständig # Primär — der Eintrag entsteht immer auf der Baustelle
web: mit-Bulk # Wochenberichte, PDF-Export, Stammdaten, ERP-Integration
desktop: aus # V1.5 nur auf Nachfrage (FU §6)
owner_rolle: Bauleitung # Eigentümer des Eintrags
modul_gate_flag: module.handwerk.bautagebuch
compliance_flags:
gobd: true # Bautagebuch ist rechnungs- und streitrelevant → §147 AO, 10 Jahre
arbzg: false # Zeit wird in §3.1 erfasst, nicht hier
vob: true # §3 Abs. 3, §6 (Behinderungsanzeige), §15 (Regiebericht)
dsgvo: true # Fotos enthalten ggf. Personen, Geo-EXIF ist personenbezogen
bfsg: false # Kein ESS-Pflichtflow
betrvg: true # Geo-EXIF an Fotos = mittelbare Ortsdatenerfassung → BR-Freigabe
stvg: false
weitere: [DWD-Nutzungsbedingungen, KI-VO Art. 50 (nur bei KI-Zusammenfassung)]
referenzkunde:
status: TBD
name: "zu klären mit Sales — Kandidat: SHK-Gebrüder-Schmidt (NRW, 80 MA), aktueller Prozess: Excel + WhatsApp + Foto-Galerie"
quelle: Sales-Call 2026-03
estimate_eng_tage: 38 # 14 Mobile + 10 Backend + 6 Web + 4 DWD-Adapter + 4 Test/Compliance
abhängigkeiten:
- kern/03-projekte-baustellen # Baustellen-Stammdaten müssen existieren
- kern/08-audit-log # Jeder Eintrag schreibt ins Audit-Log
- kern/09-foto-upload-pipeline # S3-Multipart mit presigned URL
- handwerk/10-arbeitssicherheit # PSA-Check-Eintrag als Bautagebuch-Typ

Marktrealität (DE-Handwerk). Das Bautagebuch ist im Bauvertrag nach VOB/B keine freie Fleißarbeit, sondern die Beweisgrundlage. Bei Behinderung (§6 Abs. 1 VOB/B) muss der Auftragnehmer die Ursache und ihre Wirkung unverzüglich und schriftlich anzeigen — wer drei Tage wartet, verliert seinen Anspruch auf Bauzeitverlängerung. Bei Regiearbeiten schreibt §15 Abs. 3 VOB/B vor, dass der Stundenlohn-Zettel werktäglich, spätestens am übernächsten Werktag zur Bestätigung vorzulegen ist. Ohne Unterschrift des Bauherren gilt die Leistung als nicht erbracht — der Handwerker trägt die Beweislast (BGH VII ZR 314/13).

Heute läuft das in der Zielbranche typischerweise so: Der Polier schreibt abends im Auto ein paar Zeilen in ein liniertes A4-Bautagebuch, klebt drei Polaroids oder Smartphone-Ausdrucke ein, holt dem Bauleiter morgens auf dem Hof die Unterschrift (oder eben nicht), scannt das Ganze irgendwann im Büro ein. Das Wetter wird geschätzt („trocken, ca. 12°C“), nicht gemessen. Werkzeug-Übergaben laufen über einen Zettel im Werkzeug-Container, der nach sechs Monaten aussieht wie ein Biotop. WhatsApp-Fotos haben kein Geo-Tag, keinen verlässlichen Zeitstempel (iOS strippt EXIF beim Teilen) und lassen sich vor Gericht nur mit Aufwand zuordnen.

Der rechtliche Rahmen erzwingt die digitale Lösung: GoBD (§147 AO) verlangt 10 Jahre Aufbewahrung unveränderlich, DSGVO Art. 5 Datenminimierung verbietet „Foto-Dumping auf Gruppen-Chats“, und §15 Abs. 3 VOB/B setzt eine Unterschrift auf dem Regiebericht als Anspruchsvoraussetzung. Ein PDF mit eingebettetem Foto und Touch-Signatur ist heute die rechtssicherste und gleichzeitig schnellste Form.

Schmerzpunkt der Alt-App. Die Alt-App (zeitapp) hatte bereits einen BautagebuchScreen. Er war aber (a) sichtbar für jeden Tenant unabhängig von der Branche (Feature-Wildwuchs, LESSONS-LEARNED §2), (b) ohne Wetter-API angebunden (der Nutzer musste die Temperatur tippen), (c) mit PWA-Foto-Upload gelöst, der auf iOS bei längerer Baustellen-Abwesenheit regelmäßig Daten verloren hat (Safari-Storage-Cleanup, LESSONS-LEARNED §4). Der Regiebericht war nicht vom „freien Eintrag“ getrennt — also gab es auch keine Signatur-Pflicht.

Was wir explizit anders machen:

  1. DWD-Open-Data-Wetter automatisch aus Baustellen-Geo → Temperatur, Niederschlag, Windgeschwindigkeit, Frost-Flag. Kein Tippen. Offline-Fallback mit Cache der letzten 48 h.
  2. Foto-EXIF mit kryptografischem Geo-Zeitstempel (SHA-256 über Foto-Bytes + GPS + UTC) — beweisfest, nicht nur hübsch.
  3. Werkzeug-Ausgabe über QR/NFC statt Klemmbrett, mit DGUV-Prüfdaten-Anzeige direkt im Scan-Flow.
  4. Regiebericht als eigener, signaturpflichtiger Eintragstyp — ohne Bauherr-Unterschrift lässt sich der Eintrag nicht als „Regieleistung“ markieren.
  5. Append-only + Hash-Chain statt „Edit-Button für den Polier“ — Einträge sind nach Submit eingefroren, Korrektur = neuer Eintrag mit Verweis auf Vorgänger.

Erwarteter Outcome. Messbar am Design-Partner (SHK-Gebrüder-Schmidt): Erstellung eines vollständigen Tageseintrags von heute 18 min (Excel + Foto-Galerie + Signatur nachträglich) auf 3 min auf der Baustelle. Quote signierter Regieberichte am selben Tag von ~40 % auf >85 % (BGH-relevante Beweis-Quote). Dauer eines VOB-§6-Behinderungs-Streits von „14 Tage E-Mail-Ping-Pong“ auf „30 Minuten, alle Belege in einem PDF“.


Rolle Aktion Scope Plattform
Monteur Eintrag erfassen (eigene Arbeit), Werkzeug ausbuchen, Foto hochladen own 📱
Vorarbeiter / Polier Regiebericht führen, Bauherr-Signatur einholen, Behinderungsanzeige team 📱
Bauleitung Wochenbericht prüfen, Eintrag korrigieren (append-only), PDF-Export team 📱🌐
Manager Tenant-übergreifender Überblick, Bulk-PDF-Export all 🌐
Admin Werkzeug-Stammdaten, DGUV-Prüfdaten, ERP-Integration all 🌐
Bauherr (externer Gast) Regiebericht signieren (Link-Token, ohne Login) extern 📱🌐

Persona-Skizzen:

  • Mehmet Yılmaz (Monteur, 32, SHK, Tenant shk-gebruder-schmidt) — arbeitet auf der „Sanierung Heizungsanlage Bgm.-Müller-Schule, Schulstraße 12, 50667 Köln“. Kein LTE im Kellergeschoss. Trägt Handschuhe. Tippt ungern. Spricht deutsch und türkisch — die App ist deutsch, aber Voice-Notiz auf Türkisch soll transkribiert werden können (V1.5).
  • Thomas Schmidt (Bauleitung, 45, Tenant shk-gebruder-schmidt) — koordiniert 4 parallele Baustellen, fährt zwischen Köln und Bonn, dokumentiert abends am Tablet im Auto. Will Wochenbericht auf Knopfdruck.
  • Hr. Müller-Bgm. (Bauherr-Vertreter Schulträger, nicht in Werkszeit-System) — bekommt E-Mail mit Link, unterschreibt per Touch auf dem Smartphone. Kein App-Download, keine Registrierung.
  • Sabine Maier (Manager Tenant musterbetrieb-maler, 25-MA-Maler Bayern) — nutzt das Bautagebuch für Malerarbeiten, braucht den DWD-Wetter-Nachweis für den Winterausfallgeld-Antrag (siehe §4.9 SOKA-Bau, V2).

US-01 [MVP] Als Monteur möchte ich vor Ort in 90 Sekunden einen Tageseintrag
anlegen können (Foto, 2 Sätze Tätigkeit, Wetter automatisch),
auch wenn mein LTE gerade nicht stabil ist.
US-02 [MVP] Als Polier möchte ich einen Regiebericht mit Bauherr-Unterschrift
abschließen, bevor der Bauherr die Baustelle verlässt, damit ich
§15 Abs. 3 VOB/B einhalte.
US-03 [MVP] Als Bauleitung möchte ich den Tagesbericht aller Baustellen am
Abend in einer Liste sehen (Anwesende, Wetter, Besonderheiten)
und per Swipe zwischen den Tagen blättern.
US-04 [V1] Als Polier möchte ich eine Werkzeug-Ausgabe per QR-Scan oder
NFC-Siegel dokumentieren, ohne die DGUV-Prüfplakette zu suchen.
US-05 [V1] Als Bauleitung möchte ich einen Wochenbericht als PDF mit
Hash-Signatur erzeugen, der vor Gericht als Anscheinsbeweis taugt
(§371 ZPO Augenscheinsbeweis).
US-06 [V1] Als Polier möchte ich eine VOB-§6-Behinderungsanzeige in der App
erfassen, die automatisch mit DWD-Wetterdaten, Foto und
Zeitstempel versehen per E-Mail an den Bauherrn rausgeht.
US-07 [V1.5] Als Manager möchte ich das Bautagebuch in RIB iTWO / Nevaris / pds
einspielen können (XML-Export nach deren Schema).
US-08 [V1.5] Als Monteur möchte ich eine Voice-Notiz aufnehmen, die on-device
zu Text transkribiert und an den Eintrag geheftet wird.

  • F-M-01 Eintragstyp wählen. Fünf Typen: Tageseintrag, Regiebericht, Behinderungsanzeige (VOB §6), Werkzeug-Übergabe, PSA-Check (aus handwerk/10-arbeitssicherheit). Standard beim Öffnen ist Tageseintrag. Jeder Typ ist eine eigene RLS-isolierte Variante mit eigenen Pflichtfeldern.
  • F-M-02 Baustelle auswählen. Default: nächstgelegene aktive Baustelle per GPS (Haversine-Distanz <300 m → auto-ausgewählt mit grünem Badge „GPS-Treffer“). Manuelle Auswahl immer möglich. Kein GPS → letzte verwendete Baustelle.
  • F-M-03 Wetter automatisch. DWD-Open-Data (opendata.dwd.de/weather/weather_reports/poi/) wird auf Basis der Baustellen-Koordinaten im nächstgelegenen DWD-MOSMIX-Punkt angefragt. Ergebnis: Temperatur (°C), Niederschlag (mm/h), Windgeschwindigkeit (km/h), Frost-Flag (T<0°C). Offline: Cache der letzten 48 h pro Baustelle, Fallback auf manuelle Eingabe mit Banner „Wetter manuell — DWD nicht erreichbar“.
  • F-M-04 Foto-Upload mit Geo-EXIF. Bis 12 Fotos pro Eintrag. Pro Foto wird beim Shot lokal ein Geo-Stempel-Artefakt erzeugt: JSON mit {foto_sha256, lat, lon, utc_iso, device_id, heading_deg?}, anschließend SHA-256 über diesen JSON + Foto-Hash. Beides bleibt dauerhaft als Nachweis-Blob gespeichert. Komprimierung auf Longest-Edge 2048 px, JPEG q=82, wenn Original >10 MB (LESSONS-LEARNED §4: Offline-Fotos bis 500 MB lokal cachen, dann Sync).
  • F-M-05 Tätigkeits-Freitext. Bis 2000 Zeichen. Voice-Dictation per iOS Speech / Android SpeechRecognizer als Alternative zur Tastatur (Handschuh-Tauglichkeit).
  • F-M-06 Anwesende Gewerke. Multi-Select aus Baustellen-Konfiguration (Subunternehmer-Liste). Neues Gewerk: Quick-Create mit Mindestfeldern (Name, Kontakt-Telefon).
  • F-M-07 Fortschritt-Schätzer. Drei Varianten: Im Plan / Vorsprung ~X Tage / Verzug ~X Tage, Grund: <Auswahl>. Bei Verzug mit Ursache „Witterung“ wird automatisch DWD-Wetter des betroffenen Zeitraums angeheftet (unterstützt spätere Behinderungsanzeige).
  • F-M-08 Werkzeug-Ausgabe per Scan. mobile_scanner für QR/Code-128 oder nfc_manager für NFC-Siegel (ISO 14443). Scan → Werkzeug-Stammsatz geladen → DGUV-Prüfdatum sichtbar, bei abgelaufener Prüfung Warn-Banner „DGUV-Prüfung fällig seit X Tagen — Werkzeug darf nicht übergeben werden“ mit Hart-Block für handwerk/10-arbeitssicherheit.
  • F-M-09 Regiebericht — Pflichtfelder. Stunden (aus Zeiterfassung vorausgefüllt, editierbar mit Begründungszwang), Material (freitext + optional aus Autolager-Stammsatz), Bauherr-Name, Touch-Signatur Pflicht. Ohne Signatur kein Status „Bestätigt“ — bleibt als „Entwurf“ liegen, Push-Erinnerung an Polier und Bauleitung.
  • F-M-10 Offline-Erfassung. Voll-offline. Outbox-Einträge mit Idempotency-Key (UUID v7). Fotos landen zuerst in lokaler SQLite als Thumbnail-Referenz, Originale in App-Sandbox-Storage. Bei Netz: S3-Multipart-Upload über presigned URL, danach Eintrag-POST. Sync-Priorität: Regieberichte > Behinderungsanzeigen > Tageseinträge > Fotos.
  • F-M-11 Signature-Pad für Bauherr. signature Flutter-Package, Canvas 340×180 px, Vector-Strokes gespeichert (kein Raster). Signatur-Metadaten: Name (Pflicht), Funktion (Bauherr / Bauleiter / Vertreter), Datum/Uhrzeit (System), Geräte-Fingerprint. Alternative bei ablehnendem Bauherr: PIN-Code-Signatur (optional, 6-stellig, aus separatem E-Mail-Link) — niedrigere Beweiskraft, explizit als solche gekennzeichnet (EES statt eigene Schrift-Signatur).
  • F-M-12 Bauleiter-Mit-Signatur (optional). Zweiter Signatur-Slot für Bauleitung. Wenn Bauleiter abwesend: Regiebericht bleibt trotzdem wirksam bei Bauherr-Signatur (§15 VOB/B — Bauherr-Bestätigung genügt).
  • F-W-01 Wochenbericht-Liste. Tabelle mit Filter nach Baustelle / Woche / Gewerk. Sortierung nach Datum absteigend. Bulk-Export: bis 50 Einträge als kombiniertes PDF.
  • F-W-02 Eintrag-Detail mit Foto-Lightbox und Audit-Trail. Alle Mutationen des Eintrags werden als versionierte Hash-Chain dargestellt (Zeit, User, Diff-Feldliste).
  • F-W-03 Werkzeug-Stammdaten. CRUD für Werkzeuge mit DGUV-Prüfintervall, Letzter-Prüfer, Zuweisungs-Status, Fahrzeug-Bindung. Massen-Import aus CSV (EAN, DGUV-Prüfdatum).
  • F-W-04 DWD-Cache-Verwaltung. Admin sieht Cache-Zustand pro Baustelle (letzter Abruf, Cache-Hits vs. Misses). Manueller Refresh-Trigger.
  • F-W-05 ERP-Export (V1.5). XML-Export nach Schema von RIB iTWO (.x83-ähnlich), Nevaris (.XML), pds Baulogbuch. Konfiguration pro Kunden-Integration.
  • F-W-06 Regiebericht-Freigabe / Abrechnung. Freigegebene Regieberichte landen als Positionen im Rechnungs-Entwurf (siehe kern/05-rechnung).
  • F-X-01 Append-only mit Hash-Chain. Jeder Eintrag hat hash_prev (SHA-256 des Vorgängers in derselben Baustelle) und hash_self (SHA-256 über kanonisierte Zeilen-Repräsentation incl. aller Anhänge-Hashes). Korrektur = neuer Eintrag mit corrects_entry_id + Pflicht-Begründung ≥ 20 Zeichen.
  • F-X-02 Status-Maschine. entwurf → offen → bestätigt (bei Regiebericht mit Signatur), → exportiert → archiviert. Kein Rückwärts-Übergang — nur neue Version.
  • F-X-03 DSGVO Art. 20 Export. User kann eigene Einträge als ZIP (JSON + Original-Fotos) herunterladen.
  • F-A-01 Bautagebuch-Pflichtfeld-Profil. Pro Tenant konfigurierbar: welche Felder sind bei Tageseintrag Pflicht (z. B. „Gewerke anwesend“ Pflicht in Tenant A, optional in Tenant B).
  • F-A-02 Regiebericht-Vorlage. Logo, Anrede-Textbaustein, Mini-Verzicht-auf-Einwendungen-Klausel (juristisch vom Kunden selbst zu stellen — Werkszeit liefert einen leeren Slot mit Hinweis-Link auf DE-Anwaltsportal).
  • F-A-03 BR-Freigabe Geo-EXIF. Geo-Stempel ist BetrVG §87(1)6-relevant. Default: aus. Aktivierung nur mit dokumentierter BR-Zustimmung, Datum und BR-Beschluss-ID sind Pflichtfelder (siehe §10.6).
Anforderung-ID MVP V1 V1.5 V2
F-M-01 Tageseintrag
F-M-02 GPS-Auswahl
F-M-03 DWD-Wetter
F-M-03 DWD-Cache 48h
F-M-04 Foto + Geo-EXIF
F-M-08 Werkzeug-Scan
F-M-09 Regiebericht
F-M-11 PIN-Signatur
F-W-05 ERP-Export
Voice-Transkription
Drohnen-Orthofoto ❔ (nur auf Referenzkunde)

HTML-Hero-Mockup: 01-bautagebuch.html — Mobile + Web Side-by-Side. Mobile-Persona: Mehmet Yılmaz (Monteur) auf Baustelle Schulstraße 12. Web-Persona: Thomas Schmidt (Bauleitung) im Büro shk-gebruder-schmidt.

┌────────────────────────────────┐
│ ← Bautagebuch 📍 GPS│
├────────────────────────────────┤
│ 🛡 GoBD · VOB · DSGVO │ ← Compliance-Banner
├────────────────────────────────┤
│ Schulstr. 12, 50667 Köln │
│ Mo 19.04.2026 · KW 16 │
│ 🌧 6°C · 1,2 mm/h · Wind 18 │ ← DWD auto
│ │
│ Welcher Eintrag? │
│ ┌──────────────────────────┐ │
│ │ 📋 Tageseintrag │ │
│ │ Foto + 2 Sätze │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ ✍ Regiebericht 🔴 │ │ ← Signatur-Pflicht
│ │ Unterschrift Bauherr │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ ⚠ Behinderung (VOB §6) │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ 🔧 Werkzeug-Übergabe │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ 🦺 PSA-Check (→ §4.10) │ │
│ └──────────────────────────┘ │
│ │
├────────────────────────────────┤
│ [Zeit] [Plan] [Doku] [Mehr] │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ← Neuer Tageseintrag ✓ │
├────────────────────────────────┤
│ 🛡 GoBD · VOB · DSGVO │
├────────────────────────────────┤
│ BAUSTELLE 🔴 │
│ ┌─────────────────────────┐ │
│ │ Schulstraße 12, Köln ▼ │ │
│ │ 🟢 GPS-Treffer (12 m) │ │
│ └─────────────────────────┘ │
│ │
│ WETTER (DWD 09:42) │
│ 🌧 6°C · 1,2 mm/h · W 18km/h │
│ [bearbeiten] │
│ │
│ TÄTIGKEIT 🔴 │
│ ┌─────────────────────────┐ │
│ │ Heizungsdemontage │ │
│ │ Keller, Altkessel aus. │ │
│ │ Asbest-Prüfung negativ. │ │
│ │ 🎤 │ │ ← Voice-Dictation
│ └─────────────────────────┘ │
│ │
│ FOTOS (3/12) │
│ ┌──┐┌──┐┌──┐┌──┐ │
│ │📷││📷││📷││ +│ │
│ └──┘└──┘└──┘└──┘ │
│ 🟢 Geo-Stempel aktiv │
│ │
│ ANWESENDE GEWERKE │
│ [ ✓ SHK-Schmidt ] [ ✓ Elektro] │
│ [ + Gewerk hinzufügen ] │
│ │
│ FORTSCHRITT │
│ ( ) Im Plan │
│ (•) Verzug ~1 Tag │
│ Grund: ▼ Witterung │
│ │
│ ┌────────────────────────────┐ │
│ │ EINTRAG SPEICHERN │ │ ← Primary (orange)
│ └────────────────────────────┘ │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ← Regiebericht ⋯ │
├────────────────────────────────┤
│ 🛡 GoBD · VOB §15 · DSGVO │
├────────────────────────────────┤
│ Schulstr. 12 · 19.04.2026 │
│ │
│ LEISTUNG 🔴 │
│ ┌─────────────────────────┐ │
│ │ Mehr-Stunden Keller │ │
│ │ Asbest-Sanierung Decke │ │
│ └─────────────────────────┘ │
│ │
│ STUNDEN │
│ ┌─────────────────────────┐ │
│ │ Yılmaz, M. 4:15 h │ │
│ │ Demir, F. 4:15 h │ │
│ │ ────────────────────── │ │
│ │ Summe: 8:30 h │ │
│ └─────────────────────────┘ │
│ │
│ MATERIAL (optional) │
│ PE-Folie 4m² 12,40 € │
│ Entsorgungs-Big-Bag 48,00 € │
│ [ + aus Lager ] [ + frei ] │
│ │
│ BAUHERR-UNTERSCHRIFT 🔴 │
│ Name: Müller-Bgm. │
│ Funktion: ▼ Bauherr │
│ ╭───────────────────────────╮ │
│ │ │ │
│ │ ⎯⎯⎯ Hier unterschreiben │ │
│ │ │ │
│ ╰───────────────────────────╯ │
│ ✕ leeren │
│ Alternative: [ 📧 PIN per Mail]│
│ │
│ ┌────────────────────────────┐ │
│ │ REGIEBERICHT BESTÄTIGEN │ │ ← nur aktiv bei Signatur
│ └────────────────────────────┘ │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ← Werkzeug-Übergabe 📡 │
├────────────────────────────────┤
│ Scan bereit │
│ │
│ ╭──────────────╮ │
│ │ ▓▓▓ ▓▓ ▓▓▓ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ │ ▓▓ ▓▓▓▓▓ ▓▓ │ │
│ │ ▓▓▓▓ ▓▓ ▓▓▓▓ │ │
│ ╰──────────────╯ │
│ │
│ [ QR / Code-128 ] [ NFC 📡 ]│
│ │
│ Ausgabe an: │
│ Mehmet Yılmaz ▼ │
│ │
├────────────────────────────────┤
│ Zuletzt gescannt: │
│ HILTI TE 30-AVR WZ-00142 │
│ DGUV-Prüfung: 14.01.2026 ✅ │
│ [ Ausbuchen ] │
└────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Werkszeit · shk-gebruder-schmidt Thomas Schmidt · Bauleitung [▾] │
├────────────────────────────────────────────────────────────────────────────────┤
│ Sidebar │ Bautagebuch · KW 16 · 12.–18.04.2026 [⤓ Wochen-PDF] │
│ ───────── │ Baustelle: Schulstraße 12, Köln ▼ │
│ Zeit │ ───────────────────────────────────────────────────────────── │
│ Plan │ KPI: 5 Tage · 18 Fotos · 2 Regieberichte · 1 Behinderung │
│ ▶ Bautage │ │
│ Aufmaß │ ┌──────┬──────────────┬────────┬──────────┬─────────┬─────────┐ │
│ Mängel │ │ Tag │ Wetter DWD │ Typ │ Ersteller│ Status │ Signatur│ │
│ Projekte │ ├──────┼──────────────┼────────┼──────────┼─────────┼─────────┤ │
│ Rechng. │ │ Mo19 │ 🌧 6°, 1,2mm │ Tag │ Yılmaz │ Offen │ — │ │
│ Audit │ │ Mo19 │ 🌧 6° │ Regie │ Schmidt │ 🟡 war- │ ✍ 1/2 │ │
│ │ │ │ │ │ │ tend │ │ │
│ │ │ Fr16 │ ☁ 12° │ VOB §6 │ Schmidt │ 🟢 GS │ ✍ 1/1 │ │
│ │ │ Do15 │ ☀ 14° │ Regie │ Yılmaz │ 🟢 GS │ ✍ 2/2 │ │
│ │ │ Mi14 │ ☀ 13° │ Werkz. │ Yılmaz │ 🟢 │ — │ │
│ │ └──────┴──────────────┴────────┴──────────┴─────────┴─────────┘ │
│ │ │
│ │ Geo-Karte aller Fotos: │
│ │ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ Köln · Schulstraße 12 │ │
│ │ │ 📍 📍 📍 │ │
│ │ │ 📍 📍 📍 📍 │ │
│ │ │ 📍 │ │
│ │ └────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────┐
│ ← Bautagebuch 🚫LTE │
├────────────────────────────────┤
│ 🟡 Offline · 3 Einträge + │
│ 14 Fotos warten auf Sync │
│ │
│ Sync-Queue │
│ ┌────────────────────────────┐ │
│ │ 📋 Mo 19.04. · Tageseintr. │ │
│ │ ⏳ wartet (2 Fotos) │ │
│ ├────────────────────────────┤ │
│ │ ✍ Mo 19.04. · Regie │ │
│ │ 🟢 bereit (signiert) │ │
│ ├────────────────────────────┤ │
│ │ 📷 Mo 19.04. · 9 Fotos │ │
│ │ ⏳ 4,2 MB (wird kompr.) │ │
│ └────────────────────────────┘ │
│ │
│ DWD-Cache: letzter Abruf │
│ Mo 19.04. 09:42 · Köln │
│ 🟢 Gültig bis Di 21.04. 09:42 │
│ │
│ [ Jetzt synchronisieren ] │
└────────────────────────────────┘

Pseudo-Drizzle-Schema (Postgres 17 + RLS + pgvector).

apps/api/src/db/schema/bautagebuch.ts
export const bautagebuchEntriesTable = pgTable('bautagebuch_entries', {
id: uuid('id').primaryKey().defaultRandom(), // UUID v7
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
baustelleId: uuid('baustelle_id').notNull().references(() => baustellenTable.id),
entryType: bautagebuchEntryType('entry_type').notNull(),
// 'tageseintrag' | 'regiebericht' | 'behinderung_vob6'
// | 'werkzeug_uebergabe' | 'psa_check'
entryDate: date('entry_date').notNull(), // Tag auf der Baustelle (nicht createdAt)
// Wetter (DWD)
weatherSource: wetterSource('weather_source').notNull().default('dwd_mosmix'),
// 'dwd_mosmix' | 'dwd_cache' | 'manual_fallback'
weatherTempC: real('weather_temp_c'),
weatherPrecMm: real('weather_precipitation_mm_h'),
weatherWindKmh: real('weather_wind_kmh'),
weatherFrost: boolean('weather_frost').notNull().default(false),
weatherFetchedAt: timestamp('weather_fetched_at', { withTimezone: true }),
// Inhaltliche Felder
description: text('description').notNull(), // Pflicht bei tageseintrag
progressEnum: progressEnum('progress'), // 'on_track' | 'ahead' | 'delayed'
progressDelayDays: integer('progress_delay_days'),
progressReason: delayReasonEnum('progress_reason'), // 'weather' | 'material' | 'sub' | 'other'
// Signatur (bei regiebericht pflicht)
signedByName: text('signed_by_name'),
signedByRole: signedByRole('signed_by_role'), // 'bauherr' | 'bauleiter' | 'vertreter'
signedAt: timestamp('signed_at', { withTimezone: true }),
signatureBlobId:uuid('signature_blob_id').references(() => blobsTable.id),
signatureKind: signatureKind('signature_kind'), // 'touch_vector' | 'pin_token'
// Status-Maschine
status: entryStatus('status').notNull().default('entwurf'),
// 'entwurf' | 'offen' | 'bestaetigt' | 'exportiert' | 'archiviert'
correctsEntryId:uuid('corrects_entry_id').references(() => bautagebuchEntriesTable.id),
correctionReason: text('correction_reason'), // Pflicht wenn correctsEntryId
// GoBD (Flag: true)
hashPrev: bytea('hash_prev'), // 32 Byte, NULL nur für ersten Eintrag je Baustelle
hashSelf: bytea('hash_self').notNull(), // 32 Byte
geoExifEnabled: boolean('geo_exif_enabled').notNull(), // aus Tenant-Config, Snapshot zum Entry
// Audit
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
createdBy: uuid('created_by').notNull().references(() => usersTable.id),
idempotencyKey: uuid('idempotency_key').notNull().unique(),
}, (t) => ({
tenantBaustelleIdx: index('bautagebuch_tenant_baustelle_idx')
.on(t.tenantId, t.baustelleId, t.entryDate.desc()),
statusIdx: index('bautagebuch_status_idx').on(t.tenantId, t.status),
hashChainIdx: uniqueIndex('bautagebuch_hash_self_uq').on(t.tenantId, t.hashSelf),
}));
export const bautagebuchPhotosTable = pgTable('bautagebuch_photos', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
entryId: uuid('entry_id').notNull().references(() => bautagebuchEntriesTable.id),
s3Key: text('s3_key').notNull(), // S3 Object Lock Compliance-Mode
contentSha256: bytea('content_sha256').notNull(),
widthPx: integer('width_px').notNull(),
heightPx: integer('height_px').notNull(),
bytesOriginal: bigint('bytes_original', { mode: 'number' }).notNull(),
bytesStored: bigint('bytes_stored', { mode: 'number' }).notNull(),
gpsLat: decimal('gps_lat', { precision: 9, scale: 6 }),
gpsLon: decimal('gps_lon', { precision: 9, scale: 6 }),
capturedAtUtc: timestamp('captured_at_utc', { withTimezone: true }).notNull(),
headingDeg: real('heading_deg'),
geoStampSha256: bytea('geo_stamp_sha256').notNull(), // SHA-256 über {foto_sha, lat, lon, utc, device}
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});
export const bautagebuchGewerkeTable = pgTable('bautagebuch_gewerke', {
entryId: uuid('entry_id').notNull().references(() => bautagebuchEntriesTable.id),
subunternehmerId: uuid('subunternehmer_id').references(() => subunternehmerTable.id),
freitextName: text('freitext_name'), // wenn kein Stammsatz existiert
}, (t) => ({ pk: primaryKey({ columns: [t.entryId, t.subunternehmerId, t.freitextName] }) }));
export const werkzeugeTable = pgTable('werkzeuge', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
inventarNr: text('inventar_nr').notNull(), // z. B. "WZ-00142"
bezeichnung: text('bezeichnung').notNull(), // "HILTI TE 30-AVR Bohrhammer"
qrCode: text('qr_code'), // EAN-13 / Code-128 / QR-Payload
nfcUid: text('nfc_uid'), // ISO 14443 UID hex
dguvPruefDatum: date('dguv_pruef_datum'),
dguvPruefIntervallMonate: integer('dguv_pruef_intervall_monate').notNull().default(12),
dguvLetzterPruefer: text('dguv_letzter_pruefer'),
fahrzeugId: uuid('fahrzeug_id').references(() => fahrzeugeTable.id),
ausgegebenAnUserId: uuid('ausgegeben_an_user_id').references(() => usersTable.id),
ausgegebenSeit: timestamp('ausgegeben_seit', { withTimezone: true }),
}, (t) => ({
tenantQrIdx: uniqueIndex('werkzeuge_tenant_qr_uq').on(t.tenantId, t.qrCode),
tenantNfcIdx: uniqueIndex('werkzeuge_tenant_nfc_uq').on(t.tenantId, t.nfcUid),
}));
export const werkzeugUebergabenTable = pgTable('werkzeug_uebergaben', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
werkzeugId: uuid('werkzeug_id').notNull().references(() => werkzeugeTable.id),
entryId: uuid('entry_id').notNull().references(() => bautagebuchEntriesTable.id),
richtung: uebergabeRichtung('richtung').notNull(), // 'ausgabe' | 'ruecknahme'
vonUserId: uuid('von_user_id').references(() => usersTable.id),
anUserId: uuid('an_user_id').references(() => usersTable.id),
scanMethod: scanMethod('scan_method').notNull(), // 'qr' | 'nfc' | 'manual'
scannedAt: timestamp('scanned_at', { withTimezone: true }).notNull().defaultNow(),
dguvValidAtScan:boolean('dguv_valid_at_scan').notNull(), // Snapshot — blockt Übergabe in 10.x
});
export const dwdCacheTable = pgTable('dwd_cache', {
cacheKey: text('cache_key').primaryKey(), // "{baustelleId}:{dateIso}"
baustelleId: uuid('baustelle_id').notNull().references(() => baustellenTable.id),
dateIso: date('date_iso').notNull(),
mosmixStation: text('mosmix_station').notNull(), // DWD-MOSMIX-Kennung
payload: jsonb('payload').notNull(), // Vollständige DWD-Antwort
fetchedAt: timestamp('fetched_at', { withTimezone: true }).notNull(),
validUntil: timestamp('valid_until', { withTimezone: true }).notNull(),
});

RLS-Policy-Sketch:

ALTER TABLE bautagebuch_entries ENABLE ROW LEVEL SECURITY;
CREATE POLICY btb_entries_tenant_isolation ON bautagebuch_entries
USING (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY btb_entries_scope_own ON bautagebuch_entries FOR SELECT
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND (
current_setting('app.scope') = 'all'
OR (current_setting('app.scope') = 'team'
AND baustelle_id IN (SELECT bs_id FROM user_baustellen_membership
WHERE user_id = current_setting('app.user_id')::uuid))
OR (current_setting('app.scope') = 'own' AND created_by = current_setting('app.user_id')::uuid)
)
);
-- Append-only: UPDATE nur auf status, signature, korrektur-Felder. Inhaltliche Felder eingefroren.
CREATE POLICY btb_entries_no_content_update ON bautagebuch_entries FOR UPDATE
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (
-- Inhaltliche Felder dürfen nach INSERT nicht geändert werden
description = (SELECT description FROM bautagebuch_entries WHERE id = bautagebuch_entries.id)
AND entry_date = (SELECT entry_date FROM bautagebuch_entries WHERE id = bautagebuch_entries.id)
-- Weather-Snapshot ebenfalls eingefroren
AND weather_temp_c IS NOT DISTINCT FROM
(SELECT weather_temp_c FROM bautagebuch_entries WHERE id = bautagebuch_entries.id)
);
-- DELETE strikt verboten (GoBD):
REVOKE DELETE ON bautagebuch_entries FROM PUBLIC;

ER-Bezug. Referenzierte Tabellen: tenants, users, baustellen (Kern), subunternehmer (Kern), fahrzeuge (V2-Lager), blobs (zentraler Signatur-/Asset-Store), audit_log (Append-only Zentraltabelle).


Pfad-Konvention: /v1/handwerk/bautagebuch/.... Alle hinter Hono-Modul-Gate module.handwerk.bautagebuch.

Methode Pfad Auth-Scope Rate-Limit Idempotenz Beschreibung
POST /v1/handwerk/bautagebuch/entries bautagebuch:write Standard Idempotency-Key Pflicht Eintrag anlegen
GET /v1/handwerk/bautagebuch/entries bautagebuch:read:<scope> Standard Liste, Filter baustelleId, from, to, type
GET /v1/handwerk/bautagebuch/entries/{id} bautagebuch:read:<scope> Standard Detail
POST /v1/handwerk/bautagebuch/entries/{id}/signature bautagebuch:write Privileged Pflicht Signatur anhängen, Status → bestaetigt
POST /v1/handwerk/bautagebuch/entries/{id}/correction bautagebuch:write Standard Pflicht Korrektur-Eintrag (neuer Datensatz mit correctsEntryId)
GET /v1/handwerk/bautagebuch/entries/{id}/pdf bautagebuch:read:<scope> Heavy Typst-PDF, Hash-Signatur im Footer
POST /v1/handwerk/bautagebuch/photos/presign bautagebuch:write Standard Pflicht Pre-Signed URL für S3-Multipart
POST /v1/handwerk/bautagebuch/photos bautagebuch:write Standard Pflicht Metadaten-Post nach S3-Upload, inkl. Geo-Stempel-Hash
GET /v1/handwerk/bautagebuch/weather bautagebuch:read:own Heavy (DWD-Rate-Limit) DWD-Proxy mit Cache, Query baustelleId, date
POST /v1/handwerk/werkzeuge/{id}/uebergabe werkzeug:write Standard Pflicht Ausgabe/Rücknahme per Scan
GET /v1/handwerk/werkzeuge werkzeug:read:<scope> Standard Stammdaten-Liste, Filter nach DGUV-Fälligkeit
POST /v1/handwerk/bautagebuch/weekly-pdf bautagebuch:read:team Heavy Pflicht Wochenbericht-PDF-Bulk

OpenAPI-Schema-Skizze:

paths:
/v1/handwerk/bautagebuch/entries:
post:
operationId: createBautagebuchEntry
x-werkszeit-scope: bautagebuch:write
x-werkszeit-module-gate: module.handwerk.bautagebuch
x-werkszeit-rate-limit: standard
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BautagebuchEntryCreateInput'
responses:
'201': { description: 'Created', content: { application/json: { schema: { $ref: '#/components/schemas/BautagebuchEntry' } } } }
'409': { description: 'Idempotency conflict' }
'422': { description: 'Validation error (Valibot)' }
components:
schemas:
BautagebuchEntryCreateInput:
type: object
required: [idempotencyKey, baustelleId, entryType, entryDate, description]
properties:
idempotencyKey: { type: string, format: uuid }
baustelleId: { type: string, format: uuid }
entryType: { type: string, enum: [tageseintrag, regiebericht, behinderung_vob6, werkzeug_uebergabe, psa_check] }
entryDate: { type: string, format: date }
description: { type: string, maxLength: 2000 }
weatherOverride:
type: object
properties:
tempC: { type: number }
precMm: { type: number }
windKmh: { type: number }
progress: { type: string, enum: [on_track, ahead, delayed] }
progressDelayDays: { type: integer, minimum: 0, maximum: 90 }

Webhook-Events (AsyncAPI 3.0, via BullMQ an registrierte Partner-Endpoints):

  • bautagebuch.entry.created
  • bautagebuch.entry.signed (Regiebericht — Trigger für Rechnungs-Vorgang)
  • bautagebuch.entry.corrected
  • bautagebuch.behinderung.declared (VOB §6 — Trigger für Fristüberwachung)
  • werkzeug.uebergeben
  • werkzeug.dguv_abgelaufen (täglicher Scheduler, Push an Bauleitung)

Profil Begründung Mechanik
Voll-offline Monteur-Alltag im Heizungskeller, SHK-Schacht, Stahlbetonbau — oft stundenlang kein LTE. Regiebericht muss trotzdem signierbar sein, bevor der Bauherr die Baustelle verlässt. Outbox-Pattern mit UUID-v7-Idempotency-Key. Drift-Tabelle bautagebuch_outbox mit entry_json, photo_refs[], status (pending / uploading / failed). Foto-Originale im App-Sandbox-Filesystem, nur Thumbnail in SQLite. Bei Netz: Photos zuerst (S3 presigned Multipart), dann Entry-POST mit Photo-IDs.

Konflikt-Strategie. Keine — Einträge sind append-only. „Konflikt“ heißt hier nicht „gleicher Datensatz wurde von zwei Seiten geändert“, sondern „der Idempotency-Key wurde zweimal zugestellt“ → Server antwortet 409 + bestehender Eintrag, Client entfernt aus Outbox.

Wetter-Cache. DWD-Antworten werden pro Baustelle für 48 h gecacht. Bei Offline-Submit wird der letzte gültige Cache-Eintrag angeheftet und als weather_source='dwd_cache' markiert. Beweis-Wert reicht für §6-VOB-Nachweis; Nachtrag-Berechnung von Winterausfallgeld (§4.9 SOKA-Bau, V2) fordert zusätzlich Server-seitigen DWD-Nachabgleich.

Foto-Sync. S3-Multipart mit presigned URL. Bei Foto >10 MB (Originale iPhone 16 Pro Apple ProRAW) wird clientseitig auf 2048 px longest edge + JPEG q=82 komprimiert; Original wird optional als _orig.heic mitgeschickt, wenn der Tenant das aktiviert hat (Default: aus — spart S3-Kosten). Geo-EXIF-Hash wird vor der Komprimierung gerechnet, damit er den Original-Zustand beweist.


  • Append-only-Tabelle. bautagebuch_entries mit hash_prev / hash_self. Hash ist SHA-256 über kanonisierte Feld-Reihenfolge (id, tenantId, baustelleId, entryType, entryDate, description, weatherTempC, weatherPrecMm, signedByName, signedAt, hashPrev) — stabile Ordnung, keine Insertion-Order-Abhängigkeit.
  • Aufbewahrung. Fotos in S3 mit Object Lock (Compliance-Mode, 10 Jahre) — Lifecycle transition to Glacier Instant Retrieval after 1 year. Entry-Daten in Postgres mit PITR (7 Tage) + täglichem pgBackRest-Snapshot + Hot-Standby in eu-central-1b.
  • Nachvollziehbarkeit. Jede Mutation erzeugt einen audit_log-Eintrag (bautagebuch.entry.created, ...signed, ...corrected). Audit-Log ist selbst Hash-verkettet (siehe kern/08-audit-log).
  • Verfahrensdokumentation. Textbaustein in gobd-verfahrensdoku.md mit Modul-Abschnitt „Bautagebuch — Erfassung, Unveränderbarkeit, Archivierung“.

— nicht zutreffend, weil Arbeitszeit-Erfassung in kern/01-zeiterfassung läuft. Ein Bautagebuch-Eintrag ist kein Stempel-Ereignis. Regiebericht kann aber auf existierende Zeit-Einträge verweisen (Foreign Key zeit_eintraege[], read-only).

  • §3 Abs. 3 (Ausführungsunterlagen): Bautagebuch als „gleichwertiger Nachweis“ der Ausführung nach übergebener Unterlage.
  • §6 (Behinderung): Separater Eintragstyp behinderung_vob6 mit Pflichtfeldern Ursache, Zeitraum, erwarteter Einfluss auf Bauzeit. Nach Signatur wird automatisch ein E-Mail-Versand an den bei der Baustelle hinterlegten Bauherr-Kontakt ausgelöst (bautagebuch.behinderung.declared → Mailer-Worker). Zeitstempel des E-Mail-Versands wird als „Unverzüglichkeits-Nachweis“ mitprotokolliert.
  • §14 (Abrechnung): Regiebericht wird als Position in Teil-/Schlussrechnung übernommen (siehe handwerk/04-rechnung-vob, V1).
  • §15 Abs. 3 (Stundenlohnarbeiten): Regiebericht = Stundenlohn-Zettel. Pflicht-Signatur des Auftraggebers im Status-Übergang offen → bestaetigt. Ohne Signatur bleibt Eintrag entwurf — keine Abrechnung möglich. Push-Reminder um 17:00 an Polier + um 18:00 an Bauleitung bei ungesigniertem Regiebericht vom selben Tag.
  • Art. 5 Datenminimierung. Fotos werden standardmäßig ohne Geo-EXIF abgelegt — der Geo-Stempel ist ein separater Nachweis-Hash, nicht EXIF. Geo-EXIF im Foto selbst bleibt nur, wenn der Tenant geoExifInExif explizit aktiviert (BR-Workflow, siehe §10.6). Gesichter auf Fotos werden nicht automatisch verpixelt (kein KI-Feature-Zwang), der Kunde trägt die DSFA-Verantwortung für Baustellen-Fotos.
  • Art. 6 Rechtsgrundlage. Vertrag (Art. 6 Abs. 1 lit. b DSGVO) für Bauherr-Daten und Sub-Vertreter-Daten, berechtigtes Interesse (Art. 6 Abs. 1 lit. f) für Werkzeug-Übergabe-Logs an eigene Mitarbeiter (BetrVG-konform, siehe §10.6).
  • Art. 17 Löschung. Eintrags-Inhalt in Postgres kann nach Ablauf der GoBD-10-Jahres-Frist gelöscht werden. Vorher: logische Maskierung (personenbezogene Namen → [geloescht_2028-03], Fotos → schwarze Kachel mit Hash-Nachweis). Keine physische Löschung vor Ablauf — DSGVO-vs-GoBD-Konflikt wird zugunsten GoBD entschieden, wie in Art. 17 Abs. 3 lit. b vorgesehen.
  • Art. 20 Datenexport. User bekommt ZIP mit eigenen Einträgen (JSON + Foto-Originale + Signatur-Bytes), nicht die komplette Baustelle.
  • Art. 30 Verarbeitungsverzeichnis. Eintrag „Bautagebuch“ mit Zweck: Dokumentationspflicht VOB/B, Rechtsgrundlage: Vertrag, Empfänger: keine (Bauherr ist Vertragspartner), Drittland: keines (AWS Frankfurt).
  • DSFA. Nicht pflichtig im Regelfall (kein systematisches Monitoring, keine sensiblen Kategorien nach Art. 9). Pflicht wird Pflicht, wenn der Tenant zusätzlich Gesichts-/Nummernschild-KI-Schwärzung aktiviert — dann DSFA-Template aus kern/11-dsgvo-toolkit.

— nicht zutreffend, weil das Bautagebuch kein ESS-Flow ist. Trotzdem gilt: Web-Oberfläche erfüllt WCAG 2.2 AA (Screenreader-Labels, Tastatur-Navigation, Kontrast ≥4.5:1) als Haus-Standard aus kern/11-bfsg-baseline.

  • Geo-EXIF / Geo-Stempel als Ortsdatenerfassung. Der Geo-Stempel (Baustellen-Position pro Foto) ist mittelbare Leistungs-/Verhaltenskontrolle nach BetrVG §87(1)6. Default: deaktiviert auf Tenant-Ebene. Aktivierung nur über Admin-UI → Einstellungen → Betriebsrat-Freischaltungen mit Pflichtfeldern: BR-Beschluss-Datum, Protokoll-ID (Freitext), Uploaded-Beschluss-PDF.
  • Werkzeug-Übergabe-Log. Das Log speichert von_user_id, an_user_id → potenziell Leistungsdaten. Rechtsgrundlage: berechtigtes Interesse (Haftung §823 BGB, DGUV-Prüfpflicht). Kein BR-Gate erforderlich, aber Transparenz-Pflicht: Mitarbeiter sieht im eigenen Profil alle eigenen Übergaben.
  • Audit-Eintrag bei Aktivierung Geo-EXIF mit betrvg.feature.activated und briefErstattDatum → Compliance-Suite.
  • DWD-Nutzungsbedingungen. Open-Data-Lizenz DL-DE→BY-2.0 mit Quellenangabe „Datenbasis: Deutscher Wetterdienst, eigene Darstellung“ im PDF-Footer.
  • KI-VO Art. 50 (EU AI Act) — Transparenzpflicht. Relevant nur, wenn der Kunde die Voice-Transkription (V1.5) oder eine spätere Tages-Zusammenfassung per LLM aktiviert. Dann: sichtbares Label „Vorschlag — bitte prüfen“ und Audit-Eintrag ai.summary.shown mit Modell-ID, Version, Provider (AWS Bedrock Frankfurt / Mistral Paris). Kein KI-Output wird unkommentiert als verbindlicher Text übernommen.

# Szenario Erwartetes Verhalten
EC-01 DWD-API offline (DWD-Wartung, nachts zwischen 02:00 und 04:00, regulär) Client fällt auf Cache zurück (weather_source = 'dwd_cache'), angezeigt mit gelbem Badge „Wetter aus Cache (Mo 19.04. 09:42)“. Bei Cache älter 48 h: manuelle Eingabe mit explizitem weather_source = 'manual_fallback' und Audit-Eintrag. Eintrag wird trotzdem gespeichert.
EC-02 Foto >10 MB (iPhone 16 Pro Apple ProRAW DNG, ~28 MB) Client komprimiert vor S3-Upload auf Longest-Edge 2048 px + JPEG q=82. Geo-Stempel-Hash wird über Original-Bytes berechnet, nicht über komprimierte Version. Ergebnis: beweisfeste Kette vom Original-Hash zum gespeicherten Foto (Komprimierungs-Metadaten als Sidecar-JSON).
EC-03 Bauleiter lehnt Signatur ab Regiebericht-Status bleibt entwurf, kein Rechts-/Abrechnungseffekt. Polier kann den Eintrag mit entryType='behinderung_vob6' kopieren und als Behinderungsanzeige umwidmen („Verweigerung Regie-Bestätigung trotz angeordneter Leistung“). Bauleitung bekommt Push, Abrechnung pausiert.
EC-04 Zwei Monteure erfassen gleichen Tag offline Beide dürfen separate Tageseinträge anlegen (entryType='tageseintrag' ist kein Singleton je Tag/Baustelle). Die Aggregation im Wochenbericht zeigt beide mit Creator-Zuordnung. Nur regiebericht ist pro Tag/Baustelle/Gewerk unique (DB-Constraint).
EC-05 Baustelle wird während Eintrag-Erfassung deaktiviert (Admin deaktiviert im Web) Client-seitige Cache der Baustelle bleibt bis Sync gültig. Server-Reaktion auf Eintrag-POST: 201 Created mit Warn-Header X-Werkszeit-Baustelle-Inactive: true. Eintrag wird in Web mit Pill „Baustelle inaktiv seit …“ markiert, ist aber voll gültig.
EC-06 Werkzeug-DGUV-Prüfung abgelaufen Scan zeigt rote Warnung. Übergabe ist trotzdem möglich, aber der Eintrag bekommt dguv_valid_at_scan = false, Audit-Eintrag werkzeug.ausgegeben_trotz_abgelaufen, E-Mail an HSSE-Rolle und Bauleitung. Für handwerk/10-arbeitssicherheit ist dies ein Hart-Block (Schicht startet nicht), siehe dort. Hier im Bautagebuch nur Warnung.
EC-07 Bauherr-Unterschrift per PIN statt Touch Signatur-Kind pin_token. E-Mail an Bauherr mit 6-stelliger PIN, Eintrag im Bautagebuch-PDF als „EES-Textform-Signatur (PIN)“ gekennzeichnet — schwächere Beweiskraft als eigene Schrift, aber rechtsgültig nach §126b BGB. Im Web-Audit-Trail mit anderer Farbe.
EC-08 Tenant wechselt zwischen musterbetrieb-maler und shk-gebruder-schmidt (Berater mit zwei Tenants) Aktiver Tenant ist pro Session festgelegt (Header X-Werkszeit-Tenant-Id). RLS blockt Query auf fremden Tenant, API antwortet 404 (nicht 403, um Existenz nicht zu offenbaren). Outbox wird pro Tenant getrennt gehalten (Composite-Key {tenantId, idempotencyKey}).
EC-09 Eintrag-Korrektur: Monteur will Tippfehler fixen nach Submit POST /entries/{id}/correction mit Begründung ≥ 20 Zeichen. Neuer Eintrag mit correctsEntryId=<original>, Status offen, originaler Eintrag bekommt Flag superseded_by (non-destructive, nur View-Layer). PDF-Export zeigt beide, Original ausgegraut mit Stempel „Korrigiert am …“
EC-10 S3-Multipart-Upload schlägt bei 6. von 8 Fotos fehl (Netz-Abbruch) Outbox-Worker retried die einzelnen Multipart-Parts (exponential backoff, max 5 Versuche). Bei dauerhaftem Fehler: Foto bleibt in Outbox mit Status failed, Eintrag wird trotzdem committet (Fotos nachträglich anhängbar). Nutzer sieht Banner „2 Fotos warten noch auf Upload“.

Funktionalität: Bautagebuch-Eintrag auf Baustelle Schulstraße 12, Köln
Hintergrund:
Angenommen ein Tenant "shk-gebruder-schmidt" mit aktivem Modul-Gate "module.handwerk.bautagebuch"
Und die Baustelle "Sanierung Heizungsanlage Bgm.-Müller-Schule, Schulstraße 12, 50667 Köln"
Und der Monteur "Mehmet Yılmaz" mit Rolle "Mitarbeiter" und Scope "own"
Und der Bauleiter "Thomas Schmidt" mit Rolle "Bauleitung" und Scope "team"
Szenario: Happy Path — Tageseintrag mit DWD-Wetter und Foto
Wenn Mehmet am 19.04.2026 um 16:42 Uhr die App auf der Baustelle öffnet
Und auf "Neuer Eintrag" tippt
Und "Tageseintrag" auswählt
Dann zeigt die App die Baustelle "Schulstraße 12" als GPS-Treffer (12 m Distanz)
Und das Wetter "6°C, 1,2 mm/h, 18 km/h" mit Quelle "DWD MOSMIX K5701" ist automatisch befüllt
Wenn Mehmet 3 Fotos mit Kamera aufnimmt
Und "Heizungsdemontage Keller, Altkessel ausgebaut" als Tätigkeit tippt
Und auf "Eintrag speichern" tippt
Dann wird der Eintrag mit Status "offen" erstellt
Und der hash_self ist berechnet und chainet an den letzten Eintrag der Baustelle
Und ein Audit-Log-Eintrag "bautagebuch.entry.created" mit hashSelf ist vorhanden
Und die 3 Fotos liegen in S3 mit Object Lock (Compliance-Mode, 10 Jahre)
Und jedes Foto hat einen Geo-Stempel-SHA256 mit {lat, lon, utc, device_id}
Szenario: Grenzfall — DWD-API offline, Cache-Fallback
Angenommen die DWD-API "opendata.dwd.de" ist nicht erreichbar (Timeout nach 3 s)
Und der letzte Cache für die Baustelle ist 12 h alt
Wenn Mehmet einen Tageseintrag anlegen will
Dann zeigt die App ein gelbes Banner "Wetter aus Cache (19.04.2026 03:00 Uhr)"
Und das Wetter-Feld ist mit den Cache-Werten vorbelegt
Und `weather_source` wird als "dwd_cache" gespeichert
Und der Eintrag wird normal erstellt
Und im Audit-Log steht "bautagebuch.weather.cache_fallback" mit Cache-Alter
Szenario: Grenzfall — Foto >10 MB wird komprimiert, Original-Hash bleibt beweisfest
Angenommen Mehmet nimmt ein Foto im Apple ProRAW-Format mit 28,4 MB auf
Wenn das Foto in die Outbox gelegt wird
Dann wird der Geo-Stempel-SHA256 über die Original-Bytes berechnet
Und das Foto wird auf Longest-Edge 2048 px, JPEG q=82 komprimiert (Ergebnis ~1,8 MB)
Und die Sidecar-JSON speichert {original_sha256, compressed_sha256, compression_params}
Und der S3-Upload erfolgt mit dem komprimierten Foto
Und im PDF-Export ist die Kette "Original-SHA256 → Komprimierungs-Params → Stored-SHA256" sichtbar
Szenario: Grenzfall — Bauleiter lehnt Regiebericht-Signatur ab
Angenommen Mehmet hat einen Regiebericht über 8:30 h und 60,40 € Material erfasst
Wenn der Bauleiter "Hr. Müller-Bgm." die Unterschrift verweigert
Und Mehmet "Signatur verweigert" im Signatur-Pad-Menü auswählt und die Situation beschreibt
Dann bleibt der Regiebericht im Status "entwurf"
Und es entsteht keine Rechnungs-Position
Und Mehmet kann den Eintrag mit einem Tipp als "Behinderungsanzeige (VOB §6)" umwidmen
Und die Bauleitung Thomas Schmidt bekommt eine Push-Nachricht "Regie-Bestätigung verweigert: Schulstraße 12, 19.04.2026"
Szenario: Grenzfall — Multi-Tenant-Isolation beim Eintrags-Detail
Angenommen ein zweiter Tenant "musterbetrieb-maler" mit einem eigenen Eintrag E_MM
Wenn Mehmet (Tenant shk-gebruder-schmidt) versucht, E_MM per ID abzurufen
Dann antwortet die API mit HTTP 404
Und es entsteht KEIN Audit-Log-Eintrag in "musterbetrieb-maler"
Und die RLS-Policy protokolliert den Zugriffsversuch im Security-Log (anonymisiert)

  • U-01 DWD-Response-Parser: Mock-Payload → {tempC, precMm, windKmh, frost} korrekt
  • U-02 Geo-Stempel-Hash: gleicher Input → gleicher Hash (deterministisch); Byte-Änderung am Foto → anderer Hash
  • U-03 Hash-Chain-Canonicalization: Feld-Reihenfolge invariant gegenüber JSON-Key-Order
  • U-04 Property-based: für 10.000 zufällige Eintrags-Payloads ist hashSelf kollisionsfrei
  • U-05 Signatur-Vector → SVG-Serialisierung → Re-Parse ist identisch (Roundtrip)
  • W-01 Stempel-Button-State: connectivity == none → Upload-Button disabled, Outbox-Counter sichtbar
  • W-02 Golden-Test für de-DE + Dark Theme auf Regiebericht-Screen
  • W-03 Signature-Pad: Canvas empty → “Bestätigen” disabled; ≥ 5 Punkte → enabled
  • W-04 DWD-Widget: Offline + Cache-Alter 12 h → gelbes Banner sichtbar

13.3 Integrations-Tests (Backend, Drizzle In-Memory)

Abschnitt betitelt „13.3 Integrations-Tests (Backend, Drizzle In-Memory)“
  • I-01 Multi-Tenant-Isolation: Tenant A legt Eintrag an, Tenant B liest Liste → leer. Tenant B fragt Eintrags-UUID direkt ab → 404. (DOD §2.2 Pflicht)
  • I-02 RLS own-Scope: User U1 legt Eintrag an, User U2 (gleicher Tenant, gleiche Baustelle, Scope own) sieht ihn nicht in GET /entries
  • I-03 Outbox-Idempotenz: doppelter POST mit gleichem Idempotency-Key → 409 + bestehender Entry, kein zweiter DB-Row
  • I-04 Hash-Chain: Mutation eines Eintrags über direkten DB-Update (Test-Bypass) → Chain-Verify-Job meldet Bruch
  • I-05 Append-only RLS: UPDATE description wird von RLS-Policy abgelehnt (kein SQL-Error, stattdessen 0 Rows affected)
  • I-06 DWD-Cache-Hit: zweiter Weather-Call innerhalb 48 h triggert keinen Upstream-DWD-Request (Mock-Counter = 1)
  • E-01 Happy Path aus §12: Patrol iOS + Android; Playwright Chromium/Firefox/WebKit für Web
  • E-02 Offline-Erfassung: Patrol simuliert network off, Nutzer legt 3 Einträge + 9 Fotos an, network on → Sync komplett, Golden-Image aller 3 Detail-Views
  • E-03 Visuelle Regression: Golden pro Plattform × (Light/Dark) × de-DE; Abweichung >0.1 % blockt Merge
  • E-04 axe-core ohne Critical/Serious-Findings auf Web-Wochenbericht
  • E-05 DWD-Mock-Offline-Szenario: Chaos-Test mit 3-s-Timeout auf opendata.dwd.de → Banner erscheint in <4 s
  • C-01 Hash-Chain-Integrität: Simulierter Tamper auf description via direktem UPDATE → verifyBautagebuchChain(tenantId, baustelleId) liefert erste gebrochene Position korrekt
  • C-02 DWD-Lizenz: jeder PDF-Export enthält im Footer „Datenbasis: Deutscher Wetterdienst, eigene Darstellung“ (Regex-Test auf generiertem Typst-Output)
  • C-03 DSGVO Art. 20: Export-ZIP enthält alle Einträge des Users, alle Foto-Originale, Signatur-Blobs; keine fremden User-Daten
  • C-04 DSGVO Art. 17 + GoBD-Konflikt: DELETE /v1/me/data auf User mit 18-monatigen Einträgen → logische Maskierung statt Delete; erneuter Export zeigt [geloescht_2026-10]
  • C-05 VOB §6-Pflicht-Felder Property-Test: 100 zufällige behinderung_vob6-Payloads — Ursache + Zeitraum + Einfluss sind Pflicht, Valibot failt korrekt
  • C-06 BetrVG-Gate: Aktivierung geoExifInExif ohne hinterlegten BR-Beschluss → 422 „BR-Beschluss-Nachweis fehlt“

Lokaler 20× Re-Run der neuen E2E-Tests (Offline-Sync ist Flakiness-Kandidat Nummer 1) grün in CI, kein Test landet in der Quarantäne-Liste. Auto-Retry in Patrol auf max. 1 (nicht 3 — wir wollen das echte Signal, siehe TECH-STACK §8.2).


Nicht-Ziel Begründung
Drohnen-Orthofoto-Integration Kein Phase-1-Kunde. Spezialtool (Pix4D, DroneDeploy) bleibt bei Kunde. V2+ nur mit zahlendem Design-Partner.
Automatische Gesichts-/Nummernschild-Verpixelung DSFA-Overhead + KI-Modell-Haftung. Kunde regelt DSGVO im Baustellen-Vertrag mit Bauherrn; wir bieten Foto-Lösch-Tool im Nachgang.
3D-Scan-Integration (LiDAR, Matterport) Nische, kein KMU-Bedarf im Zielsegment. Liegt bei Spezialtools.
BIM-Modell-Verknüpfung (IFC, BCF) Liegt bei RIB iTWO, Solibri. Wir exportieren Bautagebuch zu denen, nicht umgekehrt.
KI-Fortschritts-Erkennung aus Foto Technisch reizvoll, rechtlich heikel (Leistungskontrolle §87(1)6), ROI unklar. Frühestens V3.
Wetterdaten anderer Provider (OpenWeatherMap etc.) DWD ist amtlich und kostenlos. Kein zweiter Provider, solange kein Kunde „DWD-Ausfall >1×/Monat“ reklamiert.
Unterschrift nach QES-Niveau EES (Touch) + PIN reicht für §15 VOB/B. QES-Massensignatur ist V3+, nur wenn Enterprise-Kunde es fordert (FU §5).
Live-Video-Stream von Baustelle Datenschutz-Alarm (BR + DSGVO), operativ wenig Nutzen gegenüber Foto + Zeitstempel. Nein.

Risiko / Annahme Impact Wahrscheinlichkeit Gegenmaßnahme
DWD-MOSMIX-Station bis 30 km von Baustelle entfernt → Wetter ungenau mittel mittel MOSMIX ist Deutschland-flächendeckend auf ~5–10 km Raster, Einzelfall-Abweichung dokumentiert im Feld mosmix_distance_km. Fallback auf Nowcast bei Bedarf.
DWD-API-Rate-Limit bei Bulk-Wochenbericht (viele Baustellen) mittel hoch Batched Request pro Station; Server-seitiger Redis-Cache 1 h für Same-Station-Same-Hour.
Geo-Stempel wird als DSGVO-Leistungskontrolle-Fall ausgelegt hoch niedrig BR-Freischaltungs-Workflow als Default-aus, dokumentierter BR-Beschluss-Slot in Admin-UI; Rechtsanwalt-Konsultation eingeplant vor GA.
Foto-Volumen sprengt S3-Budget (80-MA-Betrieb → ~800 Fotos/Tag) mittel mittel Komprimierung ist Default, Kosten-Check in DOD §2.7. Lifecycle auf Glacier nach 1 Jahr. Monatliches Review.
Bauherren verweigern Signatur auf Smartphone-Display (Generation 60+) niedrig hoch PIN-per-E-Mail als Fallback, Papier-Signatur-Scan als zweite Fallback-Option (V1.5, Foto der Unterschrift + OCR).
VOB-§6-Mail geht nicht raus (SES-Fehler) → „Unverzüglich“ verpasst hoch niedrig BullMQ mit Retry + Dead-Letter-Queue, Push an Polier bei Nicht-Zustellung, zweiter Fallback-Mailer (Mailjet).
Append-only UPDATE-Policy blockt legitime Signatur-Nachtrag-Operation mittel niedrig Separate Spalte signature_* ist explizit aus der Append-only-Check-Constraint ausgeklammert; getestet in I-05.

  • Vorbedingung:
    • kern/03-projekte-baustellen — Baustellen-Stammdaten mit GPS-Koordinaten (für DWD-Lookup)
    • kern/08-audit-log — Zentrale Audit-Log-Tabelle mit Hash-Chain
    • kern/09-foto-upload-pipeline — S3-Multipart mit presigned URL, Object Lock
  • Schnittstelle zu:
    • kern/01-zeiterfassung — Regiebericht referenziert Zeit-Einträge (read-only)
    • handwerk/02-nachtrag — Behinderungsanzeige und nicht-signierter Regiebericht sind typische Auslöser; Deep-Link von Bautagebuch-Eintrag zum Nachtrags-Entwurf
    • handwerk/10-arbeitssicherheit — PSA-Check ist eigener Eintragstyp; Werkzeug-DGUV-Abgelaufen-Event triggert Schicht-Block dort
  • Wird konsumiert von:
    • handwerk/04-rechnung-vob (V1) — Regiebericht wird Rechnungs-Position
    • handwerk/09-soka-bau (V2) — DWD-Frost-Tage aggregiert als Winterausfallgeld-Nachweis (§101 SGB III)
    • kern/07-reporting — Bautagebuch-KPIs im Management-Dashboard

Status: TBD — zu klären mit Sales vor Sprint-Start.

DOR §1.1.1 verlangt mindestens einen zahlenden Design-Partner.

Kandidaten-Profile (für Sales-Recherche):

  • SHK-Betrieb NRW, 50–100 MA, 2–5 parallele Baustellen, nutzt heute Excel + WhatsApp; bereits DATEV-Kunde (simplifiziert Onboarding). Primär-Kandidat: SHK-Gebrüder-Schmidt (aus Interview 2026-03).
  • Maler-/Stuck-Betrieb Bayern, 20–40 MA, häufig öffentliche Aufträge (→ VOB-Pflicht), kennt Sander & Doll / Streit-Wettbewerb. Sekundär: musterbetrieb-maler (Seed-Tenant).
  • Tiefbau 30–80 MA, hoher Witterungs-Streit-Anteil (§6-Behinderungen häufig), DWD-Wetter-Automatisierung ist der Kauf-Argument Nr. 1.

Validierungs-Fragen für das Erst-Gespräch:

  1. Wie viele Regieberichte schreiben Sie pro Monat? Wie viele davon werden nach 48 h noch ohne Unterschrift zurückgewiesen?
  2. Welche VOB-§6-Behinderungsanzeigen haben Sie letztes Jahr tatsächlich geschrieben? Wie hoch war Ihre Erfolgsquote bei Bauzeitverlängerung?
  3. Welches Wetter-Datum steht heute in Ihrem Bautagebuch — geschätzt oder gemessen? Mit welcher Quelle würde Ihr Anwalt vor Gericht besser arbeiten können?
  4. Wären Sie bereit, das Feature im Beta-Status für 30 Tage zu testen, gegen Starter→Business-Preis-Lock-In bis V1?

Build-Sequenz (innerhalb des Features):

  1. Datenmodell + RLS + Multi-Tenant-Test zuerst. bautagebuch_entries, bautagebuch_photos, werkzeuge, werkzeug_uebergaben, dwd_cache + alle RLS-Policies + I-01 grün. Alles andere ist auf Sand.
  2. DWD-Adapter-Lambda als eigenständiger Service mit Redis-Cache. Unabhängig testbar, trägt das höchste externe Ausfall-Risiko.
  3. Backend-API (Hono) + OpenAPI-Spec + Dart-Client-Regenerierung → Build grün.
  4. Mobile-MVP-Flow: Tageseintrag offline-fähig, Foto-Upload mit Outbox, DWD-Integration mit Cache.
  5. Regiebericht-Signatur-Flow mit Signature-Pad + PIN-Fallback + PDF-Export (Typst-Template).
  6. Web-Wochenbericht mit Bulk-PDF und Karten-Ansicht.
  7. Compliance-Test-Suite erweitern: Hash-Chain-Verify, Append-only-RLS, DSGVO-Export-Struktur.
  8. E2E-Suite vervollständigen (Patrol + Playwright), Flakiness-20×-Check.
  9. Werkzeug-Scan-Flow als eigener Sub-Epic (Scope V1 laut Roadmap).
  10. Doku-PR unter docs/features/handwerk/bautagebuch.md mit auto-generierten Screenshots aus E2E.

Risiko-Reihenfolge (was zuerst absichern, falls Zeitnot):

  • Multi-Tenant-Isolation (I-01), Append-only (I-05), Hash-Chain (C-01) sind nicht verhandelbar.
  • Werkzeug-Scan, ERP-Export, Voice-Transkription sind die ersten Streich-Kandidaten.
  • Bei harter Zeitnot: Regiebericht ohne PIN-Fallback ausliefern (nur Touch-Signatur), PIN in V1 nachziehen.

Stop-the-Bus-Triggers (Build sofort anhalten, Eskalation an Tech-Lead + PO):

  • Multi-Tenant-Bleed in I-01.
  • Hash-Chain-Bruch in C-01.
  • DSGVO-Löschung überschreibt GoBD-relevante Felder (C-04 fail).
  • DWD-Adapter-Latenz p95 > 3 s (Mobile-UX kippt bei Offline-Warte-Zeit).
  • Foto-Upload-Pipeline verliert Original-Hash-Kette bei Komprimierung.

Was uns 2027 dankbar macht:

  • Hash-Chain ab Tag 1 — wenn wir später einen Tamper-Audit machen müssen (BGH-relevanter Baustreit, Staatsanwaltschaft), haben wir die Nachweiskette für 10 Jahre.
  • Append-only-RLS statt „Soft Delete + Update-API“ — spart uns die GoBD-Retrofit-Baustelle, die wir in der Alt-App hatten.
  • DWD-Adapter sauber isoliert — wenn DWD die API ändert (passiert alle paar Jahre), ist es eine Service-Deployment, keine Mobile-Release.
  • Geo-Stempel statt EXIF-Geo — BetrVG-Diskussion wird niemals über Foto-Metadaten geführt, sondern über einen expliziten, abschaltbaren Feature-Flag. Saubere Trennung spart 2027 eine DSGVO-Diskussion mit dem Betriebsrat.

Letzte Aktualisierung: 2026-04-19. Änderungen an diesem Feinkonzept erfordern PR-Review durch Tech-Lead + Product-Owner und Refactor der abhängigen Feinkonzepte (02-nachtrag, 10-arbeitssicherheit).

Für Entwickler — API-Endpoints4
MethodePfadAuthZweck
GET/v1/handwerk/bautagebuch/entriesbearerAuthEinträge einer Baustelle im Zeitraum auflisten.
POST/v1/handwerk/bautagebuch/entriesbearerAuthBautagebuch-Eintrag anlegen.
GET/v1/handwerk/bautagebuch/entries/{id}bearerAuthEintrags-Detail.
POST/v1/handwerk/bautagebuch/entries/{id}/correctionbearerAuthKorrektur-Eintrag anlegen (neuer Row, Original bleibt stehen).