Reporting & Auswertung (Tag/Woche/Monat, Drill-Down, Dashboards, BFSG-konform)
Feinkonzept — Reporting & Auswertung
Abschnitt betitelt „Feinkonzept — Reporting & Auswertung“Gruppe: Kern · Quelle: FUNKTIONSUMFANG §3.7 Autor: Senior-Consulting-Sparte Reporting & Barrierefreiheit · Stand: 2026-04-19 HTML-Hero:
07-reporting-auswertung.htmlCross-Link: Reports sind über die öffentliche API exportierbar — siehe12-api-entwickler-portal.md.
1. Header & Metadaten
Abschnitt betitelt „1. Header & Metadaten“feature_id: kern/07-reporting-auswertungtitle: Reporting & Auswertung (Tag/Woche/Monat, Drill-Down, Dashboards, BFSG-konform)funktionsumfang_ref: §3.7roadmap_horizont: MVP (Basis) · V1 (Drill-Down, Dashboards, XLSX) · V1.5 (Dashboard-Konfigurator)plattformen: mobile: vollständig # Tages-/Wochen-/Monats-Saldo, Team-Status (Manager) web: mit-Bulk # Mitarbeiter-/Projekt-/Kunden-Reports, Drill-Down, Dashboards, Bulk-Export desktop: ab V1.5 bei Bedarfowner_rolle: Manager · HR · Buchhaltung · Bauleitung · Admin · Mitarbeiter (eigene Reports)modul_gate_flag: module.kern.reportingcompliance_flags: gobd: true # Reports lesen GoBD-pflichtige Buchungen, PDF-Export ist revisionssicher zu siegeln arbzg: true # Dashboards zeigen ArbZG-Verletzungen (§3 Höchstarbeitszeit, §5 Ruhezeit) vob: false # VOB liegt bei §4.5 Bau-Abrechnung dsgvo: true # Art. 12 Transparenz, Art. 15 Auskunft, Art. 88 BDSG-Beschäftigtendaten bfsg: true # Reporting ist ESS-/Admin-Oberfläche → WCAG 2.2 AA ab 28.06.2025 betrvg: true # §87(1)6 — jede Form aggregierter Leistungskontrolle braucht BR-Zustimmung stvg: false weitere: ["§147 AO (10 Jahre Aufbewahrung Export-Belege)", "EN 301 549 (WCAG 2.2 AA harmonisiert)"]referenzkunde: status: TBD name: "zu klären mit Sales — Kandidat: Malerbetrieb Musterbetrieb, 25 MA, Bayern (Stundennachweis-PDF), SHK Gebrüder Schmidt, 80 MA, NRW (Drill-Down Projekt→MA→Tag)" quelle: Sales-Call 2026-03 (unverbindlich)estimate_eng_tage: 32 (MVP 12 · V1 14 · V1.5 6)abhängigkeiten: - kern/01-zeiterfassung # Quelldaten: Stempelungen - kern/02-arbeitszeit-compliance # ArbZG-Widgets - kern/03-abwesenheiten # Krankenstand / Urlaubssaldo - kern/11-audit-log # jeder Export erzeugt Audit-Eintrag - kern/12-api-entwickler-portal # Report-Endpunkte sind Teil der öffentlichen API2. Kontext & Problem
Abschnitt betitelt „2. Kontext & Problem“Marktrealität (DE-Handwerk). In 80 % der von uns interviewten Handwerksbetriebe landet die „Stundenauswertung“ am Monatsende als Excel-Tabelle auf dem Schreibtisch der Buchhaltung — kopiert aus Stempelzetteln, Stundenzetteln, WhatsApp-Screenshots des Poliers. Das BAG-Urteil 13.09.2022 (1 ABR 22/21) zwingt den Arbeitgeber zur vollständigen Arbeitszeiterfassung, das Nachweisgesetz zur monatlichen Aushändigung eines Stundennachweises. Beides kollidiert mit dem heute gelebten Excel-Prozess: kein Audit-Trail, keine ArbZG-Prüfung, keine revisionssichere Ablage. Parallel kommt das Barrierefreiheitsstärkungsgesetz (BFSG) ab 28.06.2025 ins Spiel — B2B-Oberflächen, die Arbeitnehmer-Ansprüche abbilden (Lohnzettel, Stundennachweis, Urlaubsantrag), gelten als „Verbraucherprodukt“ im Arbeitsverhältnis und sind WCAG-2.2-AA-pflichtig.
Reports sind damit nicht ein Nice-to-have-Dashboard, sondern die Beweisschicht des Produkts: Was der Monteur gestempelt hat, was der Manager genehmigt hat, was die Buchhaltung an DATEV übergeben hat — ein einziger konsistenter Bericht, zu dem Mitarbeiter, Betriebsrat, Steuerberater und Prüfer Zugriff haben.
Schmerzpunkt der Alt-App. LESSONS-LEARNED §2 und §7 beschreiben zwei relevante Mängel:
- Reports in der Alt-App waren CRUD-Listen („kann man Einträge abrufen?“), keine echten Auswertungen. Kein Drill-Down, kein Ist-vs-Soll, keine ArbZG-Aggregation.
- Export-Formate waren inkonsistent — CSV ohne BOM, ohne Trenner-Deklaration, Datum als ISO-String, den Excel-DE falsch interpretiert. Mehrere Support-Tickets pro Monat wegen „Excel zeigt Zahlen als Text“.
Zusätzlich fiel in der Alt-App die BFSG-Vorprüfung aus — Screenreader-Test an der großen Zeitenübersichts-Tabelle ergab keine Überschriften-Hierarchie, keine Spalten-Header-Assoziation, unlesbare Kontraste im Dark-Theme (Kontrast 3.1:1 statt 4.5:1).
Erwarteter Outcome.
- Stundennachweis-Generierung für 25 MA von heute ≈45 min/Monat Handarbeit auf <5 min (gemessen an Design-Partner).
- ArbZG-Abweichungen erscheinen im selben Report wie die Arbeitszeit — kein separater Prüflauf.
- Drill-Down Monat → Mitarbeiter → Tag → Stempelung in ≤3 Klicks, jeder Pfad bookmark-fähig.
- BFSG-Audit durch externen Prüfer ohne Critical-Findings auf Reporting + Dashboard (Re-Audit-Budget 3–5 k€/Jahr, siehe TECH-STACK §11.2).
- Exporte bestehen DATEV-Validator-Import (CSV) und
libreoffice --headless-Öffnung (XLSX) ohne Warnungen.
3. Personas & Rollen
Abschnitt betitelt „3. Personas & Rollen“| Rolle | Aktion | Scope | Plattform |
|---|---|---|---|
| Mitarbeiter | Eigener Stundennachweis (Monat) ansehen, PDF herunterladen, per E-Mail an sich schicken | own |
📱 🌐 |
| Manager | Team-Report (Tag/Woche/Monat), Drill-Down zu einzelnen MA und Buchungen, Bulk-PDF-Versand | team |
📱 (Read) 🌐 (Bulk) |
| HR | Krankenstand / Urlaubs-Saldo / Abwesenheitskalender (Jahres-Sicht) | all |
🌐 |
| Buchhaltung | Monats-Aggregate für DATEV-Export, XLSX-Pivot-Vorlage, CSV für Lohnabrechnung | all |
🌐 |
| Bauleitung | Projekt-/Baustellen-Report (Ist vs. Budget, abrechenbare vs. interne Zeit, Forecast) | team |
🌐 |
| Admin | Dashboard-Konfiguration, Sichtbarkeit von Widgets, BR-Freischaltung pro Widget | all |
🌐 |
Betriebsrat (Rolle br_viewer) |
Aggregierte Auswertungen ohne Personenbezug einsehen (BetrVG §80) | all (aggregiert) |
🌐 |
Persona-Skizzen.
- Hannes Krüger (Monteur, 38, SHK). Lädt am Ende des Monats „seinen Wisch“ als PDF runter, schickt ihn an den Kundendienst seiner Hausbank für die Kreditantrag-Gehaltsnachweis-Anlage. Muss den Nachweis mit einem Klick in der Mobile-App finden, ohne durch Menüs zu klicken. Kennt den Begriff „Drill-Down“ nicht.
- Sabine Maier (Manager / Polier, 45, Hochbau). Sitzt abends 15 Minuten im Büro, will sehen: „Wer von meinen 12 Leuten hat diese Woche Überstunden, wer hat noch Kapazität für Samstag?“ Scrollen verboten — will eine einzige Übersicht.
- Frau Müller (Buchhaltung, 56, Musterbetrieb-Maler). Öffnet Excel, hat Pivot-Tabellen seit 1998 im Schlaf. Will XLSX mit Pivot-Vorlage, nicht CSV-Import-Assistent durchklicken. Screenreader braucht sie nicht, aber Kontrast muss stimmen (Augenbehinderung, neue Brille).
- Michael Bauer (Betriebsrat, 52, SHK Gebrüder Schmidt). Hat laut BR-Vereinbarung das Recht auf aggregierte Auswertungen — Krankenstand pro Abteilung, Überstunden-Gesamtvolumen — aber nicht auf Einzeldaten. Eine versehentliche Personen-Anzeige wäre ein BetrVG-Verstoß.
4. User-Stories
Abschnitt betitelt „4. User-Stories“US-01 [MVP] Als Mitarbeiter möchte ich meinen Monats-Stundennachweis als PDF herunterladen, um ihn meiner Bank für den Kreditantrag zu geben.
US-02 [MVP] Als Manager möchte ich am Smartphone den Team-Status sehen (wer stempelt, wer Pause, wer krank), damit ich abends in 30 Sekunden weiß, wo ich stehe.
US-03 [MVP] Als Buchhaltung möchte ich am Monatsende eine CSV mit allen Zeiten der Kostenstelle exportieren, die sich in Excel-DE ohne Klick-Stress öffnet.
US-04 [V1] Als Manager möchte ich vom Monats-Übersichts-Widget auf einen Mitarbeiter, dann auf einen Tag, dann auf eine einzelne Stempelung klicken können, ohne die URL zu verlieren (bookmark-fähig).
US-05 [V1] Als HR möchte ich den Krankenstand des letzten Quartals pro Abteilung sehen, damit ich in der Jahresplanung Rückmeldung geben kann.
US-06 [V1] Als Buchhaltung möchte ich eine XLSX mit vorbereiteter Pivot-Tabelle herunterladen, damit ich die Daten direkt dem Steuerberater senden kann.
US-07 [V1] Als Bauleiter möchte ich einen Projekt-Report mit Ist-Budget vs. Plan-Budget, abrechenbare vs. interne Zeit, Forecast bis Projektende, damit ich im wöchentlichen Jour-Fixe eine harte Zahl habe.
US-08 [V1.5] Als Admin möchte ich ein Dashboard aus den 9 Kern-Widgets pro Rolle zusammenstellen, damit jede Rolle nur die für sie relevanten Zahlen sieht.
US-09 [V1.5] Als Betriebsrat-Viewer möchte ich nur die aggregierten Kennzahlen sehen, und die UI muss mich explizit darauf hinweisen, dass Personenbezug ausgeblendet ist (DSGVO Art. 12 Transparenzgebot).
US-10 [V2] Als Entwickler (Drittanbieter-Integration) möchte ich denselben Monats-Report über die öffentliche API abrufen, den ich im Web sehe — als JSON mit Links auf PDF/CSV/XLSX-Varianten.5. Funktionale Anforderungen
Abschnitt betitelt „5. Funktionale Anforderungen“5.1 Mobile App (📱) — iOS + Android
Abschnitt betitelt „5.1 Mobile App (📱) — iOS + Android“- F-M-01 — Tagesübersicht (Mitarbeiter). Heutige Stempelungen, Tagesbrutto, Pause, Nettoarbeitszeit, Saldo-Delta zum Sollwert. Offline-fähig aus Drift-Cache.
- F-M-02 — Wochen-/Monats-Saldo. Kompakte Karte „Woche 16: 32:15 / 40:00 h — +6:42 auf Konto“. Tap öffnet die Detail-Ansicht der Woche.
- F-M-03 — Stundennachweis-PDF abrufen. Dropdown Monat → Vormonat → Vorvormonat (max. 13 Monate rückwirkend auf Mobile — ältere nur Web, um Bundle-Größe zu begrenzen). Tippen auf „PDF“ öffnet native Share-Sheet (iOS
UIActivityViewController, AndroidIntent.ACTION_SEND). - F-M-04 — Team-Status (Manager). Live-Liste der Team-Mitglieder mit Pill-Status (🟢 stempelt / 🟡 Pause / 🔴 abwesend), aktuelle Baustelle, Tagessaldo. Pull-to-refresh, SSE-Live-Update falls online.
- F-M-05 — ArbZG-Kacheln. Heutige ArbZG-Risiko-Karte: „1 Mitarbeiter > 9 h“ (tap → Drill-Down zur Person).
- F-M-06 — Offline-Banner. Wenn Daten aus Drift stammen (>5 min alt), ist oben ein Banner sichtbar: „Daten vom 19.04.2026 09:12, offline gecached“.
- F-M-07 — Keine Excel-/XLSX-Generierung auf Mobile. Bewusste Einschränkung: Bulk-Exporte laufen am Server, Mobile zeigt nur Thumbnail-PDF an, nicht XLSX/CSV-Writing.
5.2 Web-App (🌐)
Abschnitt betitelt „5.2 Web-App (🌐)“- F-W-01 — Report-Matrix-Startseite. Kartengrid mit den 5 Haupt-Reports: Mitarbeiter / Projekt / Kunde / Kostenstelle / Abwesenheit. Jede Karte zeigt den zuletzt ausgeführten Report.
- F-W-02 — Drill-Down-Hierarchie. Pfad
Monat (Aggregat) → Mitarbeiter → Tag → Stempelung. Jede Ebene ist ein eigener URL-State:/reports/monthly/2026-04/reports/monthly/2026-04/employees/hannes-krueger/reports/monthly/2026-04/employees/hannes-krueger/days/2026-04-19/reports/monthly/2026-04/employees/hannes-krueger/days/2026-04-19/entries/01J8K...Alle URLs sind bookmark-fähig, Browser-Back funktioniert semantisch.
- F-W-03 — Filter-Leiste mit Persistenz. Filter (Zeitraum, Mitarbeiter-Auswahl, Projekt, Kostenstelle, Abwesenheitsart) werden pro
(user_id, report_type)inreport_filter_statespersistiert. Default beim nächsten Öffnen: letzter verwendeter Filter. Filter-Pills mit „ד zum Einzelentfernen, „Alle zurücksetzen“ als Ghost-Button. - F-W-04 — KPI-Kopfleiste. Oberhalb der Tabelle: 4 KPI-Karten (Ist-Stunden, Soll-Stunden, Krankenstand, ArbZG-Verletzungen). Karten sind klickbar → filtert die Tabelle auf die jeweilige Dimension.
- F-W-05 — Datentabelle mit Bulk-Action-Bar. Sortierung pro Spalte (ARIA
aria-sort), Checkbox-Column, „Alle auswählen“-Mehrstufig (checkbox--mixed-State). Bei n≥1 ausgewählten Zeilen erscheint die Bulk-Action-Bar (Design-System.wz-bulkbar) mit: Genehmigen / Ablehnen / Als PDF versenden / Als CSV exportieren. Bulk-Aktionen werden serverseitig als Batch-Job ausgeführt, Fortschritt im Toast. - F-W-06 — Export-Profile. Dropdown „Export“ mit drei festen Profilen plus
Profil verwalten…:- PDF — Gestempelter Stundennachweis (DIN A4, Kopf mit Firmenlogo + Mitarbeiter-Stammdaten, Fuß mit Signatur-Zeile und Hash-Chain-Referenz, QR-Code zur Online-Verifikation).
- CSV — Excel-DE-kompatibel (UTF-8 + BOM
\uFEFF, Trennzeichen;, DatumTT.MM.JJJJ, Dezimal-Komma, Tausender-Punkt, erste Zeilesep=;für Excel-Legacy). - XLSX — mit Pivot-Vorlage (Blatt 1 „Daten“ als ListObject, Blatt 2 „Pivot“ mit vorkonfigurierter PivotTable: Zeilen = Mitarbeiter, Spalten = Tag, Wert = Summe Nettoarbeitszeit; Blatt 3 „Sollstunden-Abgleich“ mit Formel-Spalten).
- F-W-07 — Bulk-PDF-Versand. „Monats-Nachweise an alle 25 MA per E-Mail versenden“ — erzeugt n PDFs im Batch, BullMQ-Job, SES-Versand an
user.email, Audit-Log-Eintragreport.bulk.sentpro Empfänger. - F-W-08 — Dashboard-Widgets (V1). 9 vordefinierte Widgets, rollen-sichtbar per default, admin-konfigurierbar:
- Sollstunden-Erfüllung (Donut: Ist/Soll-% pro Mitarbeiter, farbig: grün >95 %, gelb 80–95 %, rot <80 %)
- Krankenstand letzte 30 Tage (Linie)
- Top-5-Projekte nach Stunden
- ArbZG-Verletzungen (Tages-Balken mit Drill-Down)
- Überstunden-Konto Team (Tabelle mit Saldo-Trend-Pfeilen)
- Urlaubs-Saldo Team (gestapelter Balken)
- Abrechenbare Zeit vs. Intern (Tortendiagramm pro Projekt)
- Forecast Projekt-Budget (Burndown mit Plan-Linie)
- Letzte ArbZG-Warnungen (Event-Log-Feed)
- F-W-09 — Betriebsrat-Sicht (V1.5). Rolle
br_viewersieht nur Widgets 1 (aggregiert ohne MA-Namen), 2, 4 (nur Counts), 6 (Summe). Jede Widget-Karte trägt einen Banner: „Aggregiert — keine personenbezogenen Daten sichtbar (DSGVO Art. 12, BetrVG §80)“. - F-W-10 — Bookmark-fähige URLs + Teilen. „Link teilen“-Knopf kopiert die aktuelle URL inkl. Filter-State in die Zwischenablage. Empfänger öffnet dieselbe Sicht (RLS prüft Zugriff).
- F-W-11 — Print-Stylesheet. CSS
@media printrendert die Tabelle saubergedruckt (keine Sidebar, keine Bulk-Bar, Kopf mit Datum + Tenant, Fußzeile mit Hash-Referenz).
5.3 Cross-Plattform (🔄)
Abschnitt betitelt „5.3 Cross-Plattform (🔄)“- F-X-01 — Deterministische Report-IDs. Jeder generierte Report hat eine ID
rpt_{uuid-v7}, die sowohl als Filename-Suffix (stundennachweis-krueger-2026-04_rpt_01J8K4...pdf) als auch im Audit-Log als auch als QR-Code im PDF erscheint.GET /v1/kern/reports/{id}liefert die Metadaten, die Datei und den Hash zurück. - F-X-02 — Aggregations-Konsistenz. Tagesbrutto = Σ Buchungen, Tagesnetto = Brutto − Pausen. Rundung auf 1 min. Dieselbe Formel in Dart (Client) und TypeScript (Server) — Property-based Test gegen dieselben Fixtures (siehe §13 C-05).
5.4 Admin-Konfiguration
Abschnitt betitelt „5.4 Admin-Konfiguration“- F-A-01 — Export-Profil-Editor. Admin kann benannte Export-Profile anlegen (z. B. „DATEV LODAS Variante Tenant X“) mit eigenem Spalten-Set, Dateinamenschema, Header-Zeile.
- F-A-02 — Dashboard-Zusammenstellung pro Rolle. Admin wählt pro Rolle, welche Widgets default sichtbar sind. Jeder User kann innerhalb des erlaubten Sets selbst umsortieren (drag-and-drop); individuelle Reihenfolge pro User persistiert.
- F-A-03 — Betriebsrat-Widget-Freigabe. Widgets mit Leistungs-/Verhaltenskontroll-Charakter (Widget 1, 4, 5) zeigen im Admin-Editor einen Banner „BetrVG §87(1)6 — Aktivierung erfordert Betriebsrats-Zustimmung. Zustimmung hochladen…“. Default: deaktiviert.
- F-A-04 — Aufbewahrungs-Config für Exporte. S3 Object Lock Compliance-Mode, 10 Jahre (§147 AO) — für jeden generierten Report-PDF-Beleg. Nicht konfigurierbar runter, konfigurierbar rauf (bis 30 Jahre).
5.5 Roadmap-Schichtung
Abschnitt betitelt „5.5 Roadmap-Schichtung“| Anforderung-ID | MVP | V1 | V1.5 |
|---|---|---|---|
| F-M-01 Tagesübersicht | ✅ | ||
| F-M-02 Wochen-/Monats-Saldo | ✅ | ||
| F-M-03 Stundennachweis-PDF (Mobile Download) | ✅ | ||
| F-M-04 Team-Status (Manager) | ✅ | ||
| F-M-05 ArbZG-Kachel | ✅ | ||
| F-W-01 Report-Matrix | ✅ | ||
| F-W-02 Drill-Down-URLs | ✅ | ||
| F-W-03 Filter-Persistenz | ✅ | ||
| F-W-04 KPI-Kopfleiste | ✅ | ||
| F-W-05 Bulk-Action-Bar | ✅ | ||
| F-W-06 Export-Profile PDF/CSV/XLSX | ✅ (PDF+CSV) | ✅ (XLSX) | |
| F-W-07 Bulk-PDF-Versand | ✅ | ||
| F-W-08 Dashboard 9 Widgets | ✅ | ||
| F-W-09 BR-Sicht | ✅ | ||
| F-W-10 URL-Teilen | ✅ | ||
| F-W-11 Print-Stylesheet | ✅ | ||
| F-A-01 Export-Profil-Editor | ✅ | ||
| F-A-02 Rollen-Dashboard | ✅ | ||
| F-A-03 BR-Widget-Freigabe | ✅ | ||
| F-A-04 Object-Lock-Config | ✅ |
6. Mockups & Flows
Abschnitt betitelt „6. Mockups & Flows“HTML-Hero-Mockup: 07-reporting-auswertung.html — Mobile (Monteur Hannes, Monats-Stundennachweis) + Web (Manager Sabine, Monats-Report mit Drill-Down) side-by-side, Design-Tokens aus werkszeit-design-system.css, Daten aus Seed-Tenants musterbetrieb-maler (Bayern) und shk-gebruder-schmidt (NRW).
6.1 Mobile — Mitarbeiter-Monatsübersicht
Abschnitt betitelt „6.1 Mobile — Mitarbeiter-Monatsübersicht“┌────────────────────────────────┐│ ← Mein Stundennachweis ⚙ │├────────────────────────────────┤│ April 2026 ▼ ││ ││ ┌──────────────────────────┐ ││ │ Summe Netto 167:12 h │ ││ │ Soll 168:00 h │ ││ │ Saldo −0:48 h │ ││ │ ArbZG-Warnungen 0 │ ││ └──────────────────────────┘ ││ ││ Pro Woche: ││ ┌──────────────────────────┐ ││ │ KW 14 40:05 / 40:00 🟢│ ││ │ KW 15 38:20 / 40:00 🟡│ ││ │ KW 16 42:10 / 40:00 🟢│ ││ │ KW 17 46:37 / 48:00 🟢│ ││ └──────────────────────────┘ ││ ││ [ ⬇ PDF-Stundennachweis ] ││ [ ✉ Per E-Mail an mich ] ││ │├────────────────────────────────┤│ [Zeit] [Plan] [Doku] [Mehr] │└────────────────────────────────┘6.2 Mobile — Manager-Team-Status (ArbZG-Risiko sichtbar)
Abschnitt betitelt „6.2 Mobile — Manager-Team-Status (ArbZG-Risiko sichtbar)“┌────────────────────────────────┐│ ← Team-Status Mi19.04│├────────────────────────────────┤│ ┌────────────┬────────────┐ ││ │ Einge- │ In Pause │ ││ │ stempelt │ │ ││ │ 18 │ 3 │ ││ ├────────────┼────────────┤ ││ │ Krank/Url. │ ArbZG⚠ │ ││ │ 4 │ 1 → │ ││ └────────────┴────────────┘ ││ ││ ArbZG-Risiko heute: ││ ┌──────────────────────────┐ ││ │ 🟡 Hannes Krüger │ ││ │ 9:42 h · noch nicht │ ││ │ ausgestempelt │ ││ │ (Limit §3 ArbZG 10 h) │ ││ │ [Jetzt informieren →] │ ││ └──────────────────────────┘ ││ │├────────────────────────────────┤│ [Zeit] [Plan] [Doku] [Mehr] │└────────────────────────────────┘6.3 Web — Monats-Report (Aggregat-Ebene, Drill-Down-Einstieg)
Abschnitt betitelt „6.3 Web — Monats-Report (Aggregat-Ebene, Drill-Down-Einstieg)“┌────────────────────────────────────────────────────────────────────────────────┐│ Werkszeit · shk-gebruder-schmidt Thomas S. · Bauleitung [Profil ▾] │├──────────────┬─────────────────────────────────────────────────────────────────┤│ Sidebar │ Reports / Mitarbeiter / 2026-04 [⌨ Shortcuts]││ │ ─────────────────────────────────────────────────────────────── ││ Zeit │ Pfad: Monat ▸ Mitarbeiter ││ Plan │ ││▶Reports │ Filter: [Zeitraum: April 2026 ×] [Abteilung: SHK-Nord ×] ││ Bautagebuch │ [Nur ArbZG-Verletzungen ☐] [Filter zurücksetzen] ││ … │ ││ │ ┌──────────┬──────────┬──────────┬──────────┐ ││ │ │Ist h │Soll h │Krankstand│ArbZG-Verl│ ← KPI-Kacheln ││ │ │ 3.247:12 │ 3.360:00 │ 4,1 % │ 3 │ ││ │ └──────────┴──────────┴──────────┴──────────┘ ││ │ ││ │ ┌──[Bulk-Bar]── 3 ausgewählt ─ [PDF-Versand] [CSV] [Genehmigen]┐││ │ │ ☑ │ Mitarbeiter │Abt. │Ist h │Soll h│Saldo│ArbZG│ ▸ │││ │ │───┼──────────────────┼─────┼──────┼──────┼─────┼─────┼───────│││ │ │ ☑ │ Hannes Krüger │Nord │167:12│168:00│−0:48│ 0 │ [>] ◀─┼─ Drill-Down│ │ │ ☑ │ Mehmet Yılmaz │Nord │172:40│168:00│+4:40│ 1 │ [>] │││ │ │ ☑ │ Frank Demir │Süd │ 88:15│168:00│Krank│ 0 │ [>] │││ │ │ ☐ │ Sabine Maier │Büro │152:00│168:00│−16:0│ 0 │ [>] │││ │ └─────────────────────────────────────────────────────────────┘││ │ ││ │ [⬇ PDF] [⬇ CSV (Excel-DE)] [⬇ XLSX mit Pivot] [🔗 Link teilen]│└──────────────┴─────────────────────────────────────────────────────────────────┘6.4 Web — Drill-Down-Ebene 2: Mitarbeiter Hannes Krüger, April 2026 (Tagesliste)
Abschnitt betitelt „6.4 Web — Drill-Down-Ebene 2: Mitarbeiter Hannes Krüger, April 2026 (Tagesliste)“┌────────────────────────────────────────────────────────────────────────────────┐│ Reports / Mitarbeiter / 2026-04 / Hannes Krüger [⌨ Shortcuts] ││ ───────────────────────────────────────────────────────────────────────────── ││ Pfad: Monat ▸ Mitarbeiter ▸ Hannes Krüger ││ ││ Hannes Krüger · Personalnr. 0042 · SHK-Nord ││ Ist 167:12 h / Soll 168:00 h · Saldo −0:48 h · 0 ArbZG-Warnungen ││ ││ ┌────┬──────────┬───────┬────────┬─────┬──────────────────────────────┐ ││ │ Tag│ Datum │Brutto │Pausen │Netto│ Projekt │ ││ │ Mo │ 01.04. │ 8:30 │ 0:45 │7:45 │ Bürogebäude Lehrer Allee 7 │ [>] ◀─┼─ Drill-Down│ │ Di │ 02.04. │ 8:15 │ 0:45 │7:30 │ Bürogebäude Lehrer Allee 7 │ [>] ││ │ Mi │ 03.04. │ 8:45 │ 0:45 │8:00 │ Villa Waldheim │ [>] ││ │ … │ … │ … │ … │ … │ … │ ││ │ Fr │ 30.04. │ 8:00 │ 0:30 │7:30 │ Bürogebäude Lehrer Allee 7 │ [>] ││ └────┴──────────┴───────┴────────┴─────┴──────────────────────────────┘ ││ ││ [⬇ PDF dieses Mitarbeiters] [⬇ CSV nur Hannes K.] [🔗 Link teilen] │└────────────────────────────────────────────────────────────────────────────────┘6.5 Web — Drill-Down-Ebene 4: Einzel-Stempelung (Audit-Sicht)
Abschnitt betitelt „6.5 Web — Drill-Down-Ebene 4: Einzel-Stempelung (Audit-Sicht)“┌────────────────────────────────────────────────────────────────────────────────┐│ Reports / … / 2026-04-19 / Stempelung 01J8K4NJ… ││ ───────────────────────────────────────────────────────────────────────────── ││ ││ 📌 Stempelung-ID 01J8K4NJAR9TQ5KXFN8C7V0YW1 ││ 👤 Mitarbeiter Hannes Krüger (Personalnr. 0042) ││ ⏱ Start Mo 19.04.2026 07:02:18 MESZ ││ ⏱ Ende Mo 19.04.2026 16:42:05 MESZ ││ ⏸ Pause 12:00–12:45 (automatisch, ArbZG §4) ││ 🏗 Projekt Bürogebäude Lehrer Allee 7, München ││ 📍 Geo (Start) 48.1351, 11.5820 (± 12 m) ││ 📱 Erfasst via iOS 18.4, NFC-Siegel (Seriennr. LAP-WZ-442) ││ ││ Hash-Chain: ││ prev fd3c…ae71 ││ self 8f02…91bc ✓ validiert ││ ││ Änderungshistorie: ││ ┌─────────────┬─────────────────┬──────────────────────────────────┐ ││ │ 19.04 07:02 │ created │ Hannes Krüger (offline gestempelt)│ ││ │ 19.04 12:00 │ pause.started │ System (ArbZG-Regel §4) │ ││ │ 19.04 16:42 │ stopped │ Hannes Krüger │ ││ └─────────────┴─────────────────┴──────────────────────────────────┘ ││ ││ [🔗 Audit-Log öffnen] [🔒 Als Beleg zur Akte ablegen] │└────────────────────────────────────────────────────────────────────────────────┘6.6 Web — Dashboard (Bauleitung-Rolle) mit 4 sichtbaren Widgets
Abschnitt betitelt „6.6 Web — Dashboard (Bauleitung-Rolle) mit 4 sichtbaren Widgets“┌────────────────────────────────────────────────────────────────────────────────┐│ Dashboard · Bauleitung [⚙ Layout] │├────────────────────────────────────────────────────────────────────────────────┤│ ┌─────────────────────┐ ┌─────────────────────┐ ││ │ Sollstunden-Erf. │ │ Krankenstand 30 Tage│ ││ │ ○ 94 % │ │ ▁▂▃▂▃▄▃▂▁ │ ││ │ 12 MA >95% · 4 MA… │ │ Ø 4,1 % (Vj 3,8 %) │ ││ └─────────────────────┘ └─────────────────────┘ ││ ┌─────────────────────┐ ┌─────────────────────┐ ││ │ Top-5 Projekte h │ │ Forecast Projekt X │ ││ │ 1. Lehrer Allee 234 │ │ Ist 78 % · Plan 82 %│ ││ │ 2. Villa Waldh. 156 │ │ Prognose +3 Tage │ ││ │ 3. Schule Poing 112 │ │ │ ││ │ 4. Kita Haar 89 │ │ │ ││ │ 5. KH Ramers. 72 │ │ │ ││ └─────────────────────┘ └─────────────────────┘ │└────────────────────────────────────────────────────────────────────────────────┘Symbol-Konvention wie TEMPLATE.md §6.
7. Datenmodell-Skizze
Abschnitt betitelt „7. Datenmodell-Skizze“Reports lesen überwiegend aus bestehenden Tabellen (time_entries, absences, projects, employees). Neu sind drei Tabellen zur Persistenz der Report-Konfiguration und der generierten Export-Belege (GoBD-Pflicht).
import { pgTable, uuid, text, jsonb, timestamp, bytea, index, pgEnum } from 'drizzle-orm/pg-core';
export const reportFormatEnum = pgEnum('report_format', ['pdf', 'csv', 'xlsx', 'json']);export const reportKindEnum = pgEnum('report_kind', ['monthly', 'weekly', 'daily', 'project', 'customer', 'absence']);
// 1) Persistierte Filter pro User und Report-Typ (F-W-03)export const reportFilterStates = pgTable('report_filter_states', { id: uuid('id').primaryKey().defaultRandom(), tenantId: uuid('tenant_id').notNull().references(() => tenants.id), userId: uuid('user_id').notNull().references(() => users.id), reportKind: reportKindEnum('report_kind').notNull(), filter: jsonb('filter').notNull(), // { from, to, employees[], projects[], ... } updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),}, (t) => ({ uq: index('rfs_user_kind_uq').on(t.tenantId, t.userId, t.reportKind),}));
// 2) Generierte Report-Dokumente — GoBD-Pflichtbeleg, append-only, Object-Lock in S3export const reportDocuments = pgTable('report_documents', { id: uuid('id').primaryKey().defaultRandom(), // UUID v7 = rpt_... tenantId: uuid('tenant_id').notNull().references(() => tenants.id), kind: reportKindEnum('kind').notNull(), format: reportFormatEnum('format').notNull(), params: jsonb('params').notNull(), // exakt die Filter, mit denen generiert wurde s3Key: text('s3_key').notNull(), // s3://wz-reports-eu/{tenant}/{yyyy}/{mm}/rpt_... sha256: bytea('sha256').notNull(), // Datei-Hash sizeBytes: text('size_bytes').notNull(), rowCount: text('row_count'), // bei CSV/XLSX createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), createdBy: uuid('created_by').notNull().references(() => users.id), // GoBD Hash-Chain: hashPrev: bytea('hash_prev'), hashSelf: bytea('hash_self').notNull(), objectLockRetainUntil: timestamp('object_lock_retain_until', { withTimezone: true }).notNull(),}, (t) => ({ byTenantCreated: index('rd_tenant_created_idx').on(t.tenantId, t.createdAt.desc()), byKindParams: index('rd_kind_params_idx').on(t.tenantId, t.kind, t.createdAt.desc()),}));
// 3) Dashboard-Widget-Konfiguration pro Tenant+Rolle (F-A-02)export const dashboardLayouts = pgTable('dashboard_layouts', { id: uuid('id').primaryKey().defaultRandom(), tenantId: uuid('tenant_id').notNull().references(() => tenants.id), role: text('role').notNull(), // 'manager' | 'bauleitung' | 'hr' | ... widgets: jsonb('widgets').notNull(), // [{ id, order, enabled, brApproved }] updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), updatedBy: uuid('updated_by').notNull().references(() => users.id),}, (t) => ({ uq: index('dl_tenant_role_uq').on(t.tenantId, t.role),}));RLS-Policies (Ausschnitt für report_documents — Prinzip gilt für alle drei Tabellen):
ALTER TABLE report_documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY rd_tenant_isolation ON report_documents USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- Scope-Regel: Mitarbeiter sehen nur Reports, die sie selbst erzeugt haben ODER die sie als Empfänger benennenCREATE POLICY rd_scope_own ON report_documents FOR SELECT USING ( tenant_id = current_setting('app.tenant_id')::uuid AND ( current_setting('app.scope') = 'all' OR (current_setting('app.scope') = 'team' AND created_by IN ( SELECT user_id FROM team_members WHERE team_id = ANY(current_setting('app.team_ids')::uuid[]) )) OR (current_setting('app.scope') = 'own' AND created_by = current_setting('app.user_id')::uuid) ) );
-- Append-only: kein UPDATE, kein DELETECREATE POLICY rd_no_update ON report_documents FOR UPDATE USING (false);CREATE POLICY rd_no_delete ON report_documents FOR DELETE USING (false);ER-Bezug. Referenziert: time_entries (Faktentabelle), absences, projects, customers, cost_centers, employees, users, tenants, audit_log. Neue Tabellen: drei oben. Keine Denormalisierung — Aggregate werden zur Laufzeit berechnet (pgvector nicht benötigt; HNSW-Index nicht nötig).
Performance-Leitplanke. Für Monats-Aggregate über >80 MA + >90 Tage: Materialized View mv_daily_employee_totals mit REFRESH … CONCURRENTLY nachts. Alt-Suche fragt ausschließlich auf MV. p95 für Monats-Report bei 80 MA: Ziel <500 ms (siehe §13 E-05).
8. API-Endpunkte
Abschnitt betitelt „8. API-Endpunkte“Pfad-Konvention: /v1/kern/reports/…, alle hinter Modul-Gate module.kern.reporting.
| Methode | Pfad | Auth-Scope | Rate-Limit-Klasse | Idempotenz | Beschreibung |
|---|---|---|---|---|---|
GET |
/v1/kern/reports/monthly |
reports:read:<scope> |
Standard | — | Monats-Aggregat, Query-Params month, employees[], projects[] |
GET |
/v1/kern/reports/monthly/{month}/employees/{employeeId} |
reports:read:<scope> |
Standard | — | Drill-Down Mitarbeiter |
GET |
/v1/kern/reports/monthly/{month}/employees/{employeeId}/days/{date} |
reports:read:<scope> |
Standard | — | Drill-Down Tag |
GET |
/v1/kern/reports/entries/{entryId} |
reports:read:<scope> |
Standard | — | Drill-Down einzelne Stempelung mit Hash-Chain |
POST |
/v1/kern/reports/exports |
reports:export |
Privileged (10/min) | Idempotency-Key Pflicht |
Exportjob anlegen (format, params) |
GET |
/v1/kern/reports/exports/{id} |
reports:read:<scope> |
Standard | — | Status abfragen, `state: queued |
GET |
/v1/kern/reports/exports/{id}/download |
reports:read:<scope> |
Privileged | — | Presigned S3 URL, 10 min gültig |
POST |
/v1/kern/reports/exports/bulk-email |
reports:export + reports:send_email |
Privileged (5/min) | Idempotency-Key Pflicht |
Bulk-PDF-Versand an n Empfänger |
GET |
/v1/kern/reports/filter-states/{kind} |
reports:read:own |
Standard | — | Persistierten Filter-State des Users laden |
PUT |
/v1/kern/reports/filter-states/{kind} |
reports:write:own |
Standard | — | Filter-State speichern |
GET |
/v1/kern/dashboards/{role} |
reports:read:<scope> |
Standard | — | Dashboard-Layout für Rolle |
PUT |
/v1/kern/dashboards/{role} |
dashboards:admin |
Standard | — | Dashboard-Layout speichern (Admin) |
OpenAPI-Schema-Skizze:
paths: /v1/kern/reports/exports: post: operationId: createReportExport x-werkszeit-scope: reports:export x-werkszeit-rate-limit: privileged parameters: - in: header name: Idempotency-Key required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ReportExportCreate' } responses: '202': description: Export-Job angenommen headers: Location: { schema: { type: string }, description: "/v1/kern/reports/exports/{id}" } content: application/json: schema: { $ref: '#/components/schemas/ReportExport' } '409': description: Idempotency-Konfliktcomponents: schemas: ReportExportCreate: type: object required: [kind, format, params] properties: kind: { type: string, enum: [monthly, weekly, daily, project, customer, absence] } format: { type: string, enum: [pdf, csv, xlsx, json] } params: { type: object, additionalProperties: true } deliverEmailTo: { type: array, items: { type: string, format: email } }Webhook-Events (vgl. §3.12 / Feinkonzept 12):
report.export.requestedreport.export.ready— Payload enthältdownloadUrl(10 min gültig) undsha256report.export.failedreport.bulk.sent— pro Empfänger ein Event
9. Offline-Profil
Abschnitt betitelt „9. Offline-Profil“| Profil | Begründung | Mechanik |
|---|---|---|
| Best-effort-offline (Mobile, Ansicht) | Mitarbeiter und Manager wollen auch im Keller sehen, wie viel sie heute schon gestempelt haben. Wir blockieren die Ansicht nicht, aber kennzeichnen Datenstand. | Drift-Cache der letzten 13 Monate eigener Daten; Team-Daten werden bei Online-Sync aktualisiert. Offline-Banner bei Stand >5 min. |
| Online-only (Web, Exporte, Drill-Down) | Exports brauchen Server (Typst, Excel-Writer, BullMQ). Drill-Down auf einzelne Stempelungen braucht Live-Hash-Check. | UI-Block bei connectivity == none, explizite Fehlermeldung „Keine Verbindung zum Werkszeit-Server“. |
Konflikt-Strategie. Reports sind lesend — es gibt keinen Konflikt. Die Konfiguration (Filter-States, Dashboard-Layout) wird per If-Match/ETag optimistic-locked: bei Konkurrenz-Schreib-Situation erhält der zweite Client 412 Precondition Failed und kann reload anbieten.
Foto-/Datei-Sync. Keine Fotos in Reports; PDFs werden serverseitig erzeugt, Client lädt nur via presigned URL. S3-Multipart nicht erforderlich (PDFs typisch <2 MB).
10. Compliance-Mapping
Abschnitt betitelt „10. Compliance-Mapping“10.1 GoBD
Abschnitt betitelt „10.1 GoBD“- Unveränderbarkeit Report-Belege: Jeder generierte Export (PDF, CSV, XLSX) wird in S3 mit Object Lock Compliance-Mode, 10 Jahre (§147 AO) gespeichert. Selbst Tenant-Admin kann das Retention nicht verkürzen. Bucket-Policy verbietet
s3:PutObjectRetentionmitMode=Governance. - Hash-Chain:
report_documents.hashSelf = SHA-256(canonicalize(id, tenant_id, kind, format, params, s3_key, sha256, size_bytes, row_count, created_at, created_by, hash_prev)). Hash-Chain-Validierungs-Test inapps/api/test/compliance/reports-hashchain.test.ts(§13 C-01). - Quelle der Aggregate: Reports lesen nur aus
time_entries(append-only), nicht aus einer Zwischen-Aggregat-Tabelle, die mutiert werden könnte. Materialized View ist explizit als „derived“ markiert und ausschließlich für Read-Performance; jeder MV-Refresh läuft mit SECURITY DEFINER-Funktion, die inaudit_logprotokolliert. - PDF-Stundennachweis trägt einen QR-Code mit URL
https://app.werkszeit.de/verify/{rpt_id}?h={sha256_prefix}. Die öffentliche Verifikationsseite prüft die Hash-Chain im Backend und gibt grün/rot zurück — ohne eingeloggten User (Prüfungs-Szenario durch Finanzamt).
10.2 ArbZG
Abschnitt betitelt „10.2 ArbZG“- §3 Höchstarbeitszeit (10 h/Tag): Tages-Widget markiert rot ab 10 h, gelb ab 9 h. Aggregat zählt Tage pro MA, an denen >10 h erreicht wurden.
- §4 Pausenregelung: Im Tages-Detail wird die automatische Pausen-Einfügung (ArbZG-konforme 30/45 min) als Systemeintrag angezeigt und nicht als Mitarbeiter-Stempelung kaschiert.
- §5 Ruhezeit (11 h): Dashboard-Widget „ArbZG-Verletzungen“ zählt Tage mit Ruhezeitverletzung <11 h. Detail zeigt konkret: „Zwischen Stempel-Out 22:10 und Stempel-In 07:02 nur 8:52 h“.
- §7 Ausnahmen (Tarifvertrag): Als Ausnahme markierte Einträge erscheinen gesondert mit Hinweis „§7 ArbZG — tarifliche Ausnahme“.
10.3 VOB/B
Abschnitt betitelt „10.3 VOB/B“— nicht zutreffend, weil VOB-Abrechnung in §4.5 Bau-Modul liegt. Reports konsumieren nur Zeit-/Abwesenheits-/Projekt-Daten.
10.4 DSGVO
Abschnitt betitelt „10.4 DSGVO“- Art. 5 Datenminimierung: Exporte enthalten nur die vom User explizit über Filter ausgewählten Felder. „Alle Felder“ ist kein Default-Profil. Geo-Koordinaten nur, wenn Rolle
bauleitung+ Widget-Feature-Flaggeo_in_reportsvom BR freigegeben ist. - Art. 6 Rechtsgrundlage: Vertrag (Art. 6 Abs. 1 b, Arbeitsverhältnis) für Mitarbeiter-Sichten; berechtigtes Interesse (Art. 6 Abs. 1 f, Unternehmenssteuerung) für Manager-/Bauleitungs-Aggregate; rechtliche Verpflichtung (Art. 6 Abs. 1 c, §147 AO) für die Aufbewahrung der Exporte.
- Art. 12 Transparenz: Jede Report-Seite enthält einen Fuß-Disclaimer: „Diese Auswertung nutzt personenbezogene Arbeitszeit-Daten gemäß Art. 6 Abs. 1 b/f DSGVO. Einsichtsrecht nach Art. 15 DSGVO beim Datenschutzbeauftragten.“ Link auf Tenant-spezifische DSB-Mail.
- Art. 15 Auskunft: Mitarbeiter sehen in der Mobile-App ihren vollständigen eigenen Report — das erfüllt de facto das Auskunftsrecht ohne separaten Antrag.
- Art. 17 Löschung: Zeitbuchungen sind GoBD-pflichtig nicht löschbar. Bei DSGVO-Löschantrag wird der Name des Mitarbeiters in den generierten Exporten auf
Mitarbeiter #0042 (anonymisiert 2029-06-01)maskiert; der Export-Beleg bleibt als Hash-gekettete Datei erhalten (Konflikt DSGVO ↔ GoBD, gelöst durch Maskierung). Dokumentiert ingobd-verfahrensdoku.md§4.2. - Art. 20 Datenexport: Mitarbeiter erhalten ihre eigenen Reports im JSON-Format über
GET /v1/kern/ess/data-export(Feinkonzept 11). - Art. 30 Verarbeitungsverzeichnis: Eintrag „Arbeitszeit-Reporting“, Rechtsgrundlage Vertrag, Empfänger Steuerberater (via DATEV-Export), Speicherdauer 10 Jahre (§147 AO), technisch-organisatorische Maßnahmen: RLS, S3 Object Lock, TLS 1.3, Audit-Log.
- DSFA: Pflicht-DSFA, weil systematische Überwachung im Sinne Art. 35 Abs. 3 b (Beschäftigtendaten-Verarbeitung mit Leistungs-/Verhaltensbezug). DSFA liegt als
docs/werkszeit/dsfa/reporting.mdvor — nicht Scope dieses Feinkonzepts, aber Voraussetzung vor Go-Live.
10.5 BFSG / WCAG 2.2 AA
Abschnitt betitelt „10.5 BFSG / WCAG 2.2 AA“- Screenreader-Labels (VoiceOver / TalkBack / NVDA):
- KPI-Karten:
aria-label="Soll-Stunden 168, Ist-Stunden 167 Stunden 12 Minuten, Abweichung minus 48 Minuten". - Tabelle:
<caption>mit Report-Titel,scope="col"auf<th>,scope="row"auf Mitarbeiternamen,aria-sortmit Live-Update. - Filter-Pills:
role="listitem"mit Remove-Buttonaria-label="Filter 'Abteilung SHK-Nord' entfernen". - Drill-Down-Links:
aria-label="Details von Hannes Krüger öffnen, 167 Stunden 12 Minuten".
- KPI-Karten:
- Tastatur-Navigation: Tab-Reihenfolge Sidebar → Hauptinhalt → Bulk-Bar → Tabelle → Pagination → Export. Skip-Link
<a href="#report-content">Zum Report-Inhalt</a>am Seitenanfang.Enterauf Tabellenzeile = Drill-Down;Shift+Enter= öffnet in neuem Tab.Ctrl+E= Export-Dropdown öffnen. Bulk-Bar mit Shift+Klick-Range-Selection; Leertaste auf Checkbox toggelt. - Focus-Visible:
:focus-visiblemit 3 px Outline in--wz-stahl(Design-System Token), nicht:focuspauschal — sonst Maus-Klicks zeigen den Focus-Ring. - Kontrast (WCAG 2.2 AA ≥4.5:1):
- Primärtext
#14181Fauf#FAFAF7→ 18.3:1 ✓ - Orange
#FF6B1Aals CTA auf Anthrazit#1A1F2B→ 4.89:1 ✓ (Normal-Text AA 4.5:1, Large AAA) - Status-Pill Warning
#C77A0Aauf#FBF1DA→ 4.52:1 ✓ (grenzwertig, validiert) - KPI-Delta-Rot
#B5391Aauf#FFFFFF→ 6.28:1 ✓ - Drill-Down-Link Stahl
#2C5F8Dauf#FFFFFF→ 6.24:1 ✓
- Primärtext
- Kein Farbe-einziges-Indiz: ArbZG-Verletzungen sind rot UND tragen das Symbol ⚠ und Text „ArbZG-Verletzung“. Sort-Pfeile sind Pfeile UND
aria-sort. - Media-Queries:
prefers-reduced-motiondeaktiviert Diagramm-Animationen;prefers-contrast: morewechselt in High-Contrast-Theme (Token-Set--wz-ink-900wird auf#000, Background auf#FFF, Border 2 px). - Touch-Targets Mobile: 48 × 48 dp (Design-System
--wz-touch), Handschuh-tauglich. - Test-Harness:
axe-corein Playwright-E2E läuft auf jeder Report-Seite, Critical/Serious = Build fail. Flutter-Semantics-Test überSemanticsHarnessdeckt die Mobile-Views. - Externer Audit: Pre-Launch-Audit (≈5 k€, TECH-STACK §11.2) durch BFSG-zertifizierten Prüfer. Re-Audit bei jedem Major-UI-Release.
10.6 BetrVG §87(1)6
Abschnitt betitelt „10.6 BetrVG §87(1)6“- Widget-Freischaltung: Widgets mit Leistungs-/Verhaltenskontroll-Charakter (Sollstunden-Erfüllung pro MA, ArbZG-Verletzungen pro MA, Überstunden-Saldo) sind per Default deaktiviert. Admin sieht in
Einstellungen → Dashboards → Widgetseinen Banner mit dem Gesetzestext, einen Upload-Slot für die BR-Zustimmung (PDF) und ein Datum-Feld „Beschluss vom“. Erst nach Upload + Bestätigung wird das Widget für die Rollen sichtbar. Audit-Log-Eintragdashboard.widget.br_approved. - BR-Sicht (
br_viewer): Separate Rolle mit Scope „aggregiert ohne Personenbezug“. InF-W-09definiert. - Mengengerüst-Grenze: Bei Teams <5 MA wird Aggregat nicht angezeigt („zu kleine Gruppengröße — Re-Identifikation möglich“). Empirisch abgeleitet aus k-Anonymität k=5.
10.7 Weitere Spezial-Compliance
Abschnitt betitelt „10.7 Weitere Spezial-Compliance“- §147 AO — 10 Jahre Aufbewahrung: S3 Object Lock Compliance-Mode, siehe 10.1.
- EN 301 549 / WCAG 2.2 AA: harmonisiert, Audit-Basis.
- BDSG §26 (Beschäftigtendatenschutz): dokumentiert in DSFA.
11. Edge-Cases
Abschnitt betitelt „11. Edge-Cases“| # | Szenario | Erwartetes Verhalten |
|---|---|---|
| EC-01 | Zeitzonenwechsel — Monteur reist 18.04. nach Österreich (UTC+2), stempelt dort aus, kehrt zurück | Alle Zeiten werden als UTC gespeichert; Report-Rendering nutzt Europe/Berlin als Tenant-Default. Grenzwert: Stempelung landet am korrekten Kalendertag, selbst wenn lokal die Datumsgrenze überschritten wurde. Visualisierung zeigt kleinen Globus-Indikator „Erfasst in UTC+2“ neben der Zeile. |
| EC-02 | Tagesgrenze 00:00 — Nachtschicht 22:00–06:00 | Buchung wird an beide Tage in der Tagesansicht angezeigt (Verweis-Text „beginnt Vortag“ bzw. „endet Folgetag“). Im Monats-Aggregat wird die gesamte Schicht dem Datum des Stempel-In zugerechnet (ArbZG §3 Arbeitstag-Definition). Property-based Test C-05. |
| EC-03 | Multi-Tenant-Bleed-Versuch — User gehört zu Tenant A, hat zufällig einen Link auf report_documents.id aus Tenant B geraten |
RLS-Policy rd_tenant_isolation liefert 0 rows. HTTP-Response: 404 Not Found (nicht 403 — Existenz nicht offenbaren, vgl. TEMPLATE.md §12 Grenzfall 3). Kein Audit-Log in Tenant B. |
| EC-04 | Sync-Konflikt Filter-State — User öffnet Report auf Laptop + Handy, speichert auf beiden unterschiedliche Filter | Optimistic-Locking via ETag: zweiter Request erhält 412 Precondition Failed; UI zeigt Dialog „Filter wurde auf anderem Gerät geändert — überschreiben oder neu laden?“. |
| EC-05 | Großmenge — Bauleitung fordert CSV mit 250.000 Zeilen (3 Jahre, 80 MA) | Synchroner Response wäre Timeout-Risiko. Endpunkt antwortet 202 Accepted + Job-ID; BullMQ-Worker streamt Postgres-Cursor direkt in S3 (kein Memory-Peak), report.export.ready-Webhook + Email-Benachrichtigung mit Download-Link. p95-Ziel: <60 s für 250k Zeilen. |
| EC-06 | Abgelaufener Referenzkunde-Zugriff — Ex-Mitarbeiter versucht nach Austritt einen Monats-PDF-Link zu öffnen | Presigned URL läuft nach 10 min ab; Benutzer-Account ist deaktiviert → RLS users.active = true-Check greift. Response 404, Audit-Log report.access.denied im Tenant. |
| EC-07 | Plan-Limit erreicht — Starter-Plan (10 MA), Admin fordert XLSX-Pivot-Export, aber Feature ist Business+ | Hono-Route-Gate module.reporting.xlsx_pivot prüft Plan-Limit, antwortet 402 Payment Required mit RFC 7807 Problem Details, Link auf Upgrade-Seite. UI zeigt Upsell-Banner statt Fehler. |
| EC-08 | Rollen-Demotion mid-action — Manager wird während laufendem Bulk-Export zu Mitarbeiter demoted | Batch-Job verifiziert vor jeder Mail-Zustellung die Scope-Berechtigung erneut (Postgres SELECT ... FOR SHARE auf users.role). Nach Demotion: verbleibende Mails werden nicht versendet, report.bulk.aborted mit Grund „scope_revoked“. |
| EC-09 | Druckansicht bei dunklem System-Theme | Print-Stylesheet erzwingt color-scheme: light + schwarze Schrift auf Weiß, unabhängig vom User-Theme. Golden-Test C-06. |
| EC-10 | ReDoS-Versuch im Filter-Textfeld (Freitext-Suche über Projekt-Name) | Valibot validiert Input auf maxLength: 128, verwirft Regex-Meta-Chars oder escapt sie; Server-Query nutzt Postgres plainto_tsquery, keine raw-Input-Regexe. |
12. Akzeptanzkriterien (Gherkin)
Abschnitt betitelt „12. Akzeptanzkriterien (Gherkin)“Funktionalität: Reporting & Auswertung (Werkszeit Kern §3.7)
Hintergrund: Angenommen ein Tenant "musterbetrieb-maler" mit aktivem Modul-Gate "module.kern.reporting" Und ein Mitarbeiter "Hannes Krüger" mit Rolle "Mitarbeiter" und Scope "own" Und ein Manager "Sabine Maier" mit Rolle "Manager" und Scope "team" Und Seed-Daten aus dem Zeitraum 2026-04-01 bis 2026-04-30
Szenario: Happy Path — Monats-PDF-Stundennachweis auf Mobile Wenn Hannes die App öffnet Und im Tab "Mehr" auf "Mein Stundennachweis" tippt Und den Monat "April 2026" auswählt Und auf "PDF" tippt Dann wird eine PDF-Datei "stundennachweis-krueger-2026-04_rpt_<uuid>.pdf" erzeugt Und die PDF enthält einen QR-Code mit URL "https://app.werkszeit.de/verify/rpt_<uuid>" Und ein Audit-Log-Eintrag "report.export.downloaded" wird mit Hannes' user_id erzeugt Und das Dokument wird in S3 mit Object-Lock-Retention 10 Jahre abgelegt
Szenario: Grenzfall — Drill-Down mit Bookmark-URL Angenommen Sabine hat den URL "/reports/monthly/2026-04/employees/hannes-krueger/days/2026-04-19" gebookmarkt Wenn Sabine den Bookmark am nächsten Tag öffnet Dann wird genau die Tagesansicht von Hannes am 19.04.2026 geladen Und der Filter-State (falls in report_filter_states persistiert) wird angewendet Und die KPI-Karten zeigen den Tag: Brutto 9:40, Netto 8:55, 1 ArbZG-Warnung
Szenario: Grenzfall — CSV-Export ist Excel-DE-kompatibel Wenn Frau Müller auf "CSV-Export" klickt Dann startet der Download einer Datei "export-2026-04_rpt_<uuid>.csv" Und die Datei beginnt mit den Bytes "0xEF 0xBB 0xBF" (UTF-8 BOM) Und die erste Datenzeile ist "sep=;" Und Datumsfelder haben das Format "TT.MM.JJJJ" Und Dezimalzahlen nutzen Komma als Trenner Und Excel 2021 DE öffnet die Datei ohne Import-Assistent Und LibreOffice Calc Headless öffnet die Datei ohne Warnung
Szenario: Grenzfall — BetrVG-gesperrtes Widget Angenommen das Widget "Sollstunden-Erfüllung pro MA" ist im Tenant nicht BR-freigegeben Wenn der Manager Sabine das Dashboard öffnet Dann ist das Widget nicht sichtbar Und im Admin-Panel erscheint ein Banner "BetrVG §87(1)6 — Zustimmung erforderlich" Und ein Versuch, das Widget über die API direkt abzufragen, antwortet 403 mit Code "br_approval_missing"
Szenario: Grenzfall — Multi-Tenant-Isolation (Negativ-Test) Angenommen ein zweiter Tenant "shk-gebruder-schmidt" Und eine report_document-ID "rpt_01J8K…" gehört zu shk-gebruder-schmidt Wenn Sabine (Tenant musterbetrieb-maler) GET /v1/kern/reports/exports/rpt_01J8K… aufruft Dann antwortet die API mit 404 Und im Audit-Log von shk-gebruder-schmidt wird KEIN Eintrag erzeugt Und im Audit-Log von musterbetrieb-maler wird "report.access.denied" mit Grund "not_found" erzeugt
Szenario: Grenzfall — Screenreader-Navigation über Tabelle Angenommen Sabine nutzt NVDA mit Firefox auf Windows Wenn Sabine Tab drückt, um in die Datentabelle zu springen Dann liest NVDA "Tabelle Mitarbeiter-Monatsreport, 25 Zeilen, 8 Spalten" Und beim Pfeiltasten-Navigieren liest NVDA für jede Zelle "Mitarbeiter Hannes Krüger, Abteilung Nord, Ist 167 Stunden 12 Minuten, Saldo minus 48 Minuten" Und beim Drücken von Enter auf einer Zeile wird auf die Drill-Down-Seite navigiert Und das axe-core-Playwright-Script meldet 0 Critical- und 0 Serious-Findings13. Test-Cases
Abschnitt betitelt „13. Test-Cases“13.1 Unit-Tests (Dart + TypeScript)
Abschnitt betitelt „13.1 Unit-Tests (Dart + TypeScript)“- U-01 — CSV-BOM +
sep=;inapps/api/src/export/csv.ts(Property-based: beliebige Spaltennamen, Bytes 0..2 müssen0xEF 0xBB 0xBFsein). - U-02 — Aggregations-Formel
tagesnetto = brutto − Σ pausen(Dart + TS identische Fixtures). - U-03 — Hash-Chain-Serializer
canonicalize(reportDocument)— deterministisch, Reihenfolge-invariant bei JSON, Unicode-NFC-normalisiert. - U-04 — Kontrast-Checker in Design-Token-Test (
packages/design-tokens/test/contrast.test.tsvalidiert alle inwerkszeit-design-system.cssdokumentierten Farbpaare ≥4.5:1). - U-05 — ArbZG-Aggregat: Property-based Test für §3 (10 h), §5 (11 h Ruhezeit), §7 (Ausnahme-Markierung). Gleiche Fixtures in TypeScript (Server-Compute) und Dart (Client-Preview).
13.2 Widget- / Component-Tests (Flutter)
Abschnitt betitelt „13.2 Widget- / Component-Tests (Flutter)“- W-01 — Stundennachweis-Mobile-Screen zeigt bei
connectivity == none && cacheAge > 5minBanner „Daten offline gecached“. - W-02 — Golden-Test Monatskarte
de-DELight + Dark Theme + High-Contrast. - W-03 — Semantics-Harness: VoiceOver-Label für KPI-Karten enthält Einheit („Stunden“, „Minuten“) ausgeschrieben.
13.3 Integrations-Tests (Backend, Drizzle In-Memory)
Abschnitt betitelt „13.3 Integrations-Tests (Backend, Drizzle In-Memory)“- I-01 — Multi-Tenant-Isolation (pflicht): zwei Tenants, Cross-Read auf
report_documentsliefert 404; RLS-Policy-Test. - I-02 — Scope
ownsieht nur eigene Exports;teamsieht Exports der Team-Mitglieder;allsieht alle. - I-03 — Outbox-Idempotenz: zweimal
POST /exportsmit gleichem Idempotency-Key → 1 Datenbank-Eintrag, zweite Response 409. - I-04 — ETag auf
filter_states— konkurrierender Write → 412. - I-05 — S3 Object Lock Retention ist gesetzt (mock S3 mit
x-amz-object-lock-retain-until-date).
13.4 E2E-Tests (Patrol iOS/Android, Playwright Web)
Abschnitt betitelt „13.4 E2E-Tests (Patrol iOS/Android, Playwright Web)“- E-01 — Happy Path (Gherkin §12) auf iOS-Simulator, Android-Emulator, Chromium, Firefox, WebKit.
- E-02 — Offline-PDF-Anzeige: Patrol simuliert
network off, öffnet Stundennachweis (aus Cache). - E-03 — Visuelle Regression: Golden-Image des Monats-Reports + Drill-Down auf jeder Plattform/Theme-Kombination. Abweichung >0.1 % blockt Merge.
- E-04 — Accessibility: axe-core im Playwright-Report-Test, 0 Critical/Serious; Flutter-Semantics-Harness grün.
- E-05 — Performance: Monats-Report 80 MA × 30 Tage, p95 <500 ms (lokal), <800 ms (Staging-RDS). Test markiert
@perfläuft nightly, nicht PR-blocking. - E-06 — Bulk-Export 250k Zeilen: Job-Lifecycle, Download via Presigned URL, SHA-256 stimmt.
- E-07 — Keyboard-only: Playwright-Test navigiert Report + Drill-Down + Export ausschließlich mit Tab/Enter/Arrow-Keys, alle interaktiven Elemente erreichbar.
13.5 Compliance-Tests (apps/api/test/compliance/)
Abschnitt betitelt „13.5 Compliance-Tests (apps/api/test/compliance/)“- C-01 — Hash-Chain-Integrität: Report-Dokument generieren, manuelle DB-Manipulation eines einzelnen Bytes → Validator schlägt Alarm.
- C-02 — CSV-Validator: DATEV-Testimport der exportierten CSV (Fixture aus DATEV-Partner-Toolkit) — keine Fehler, keine Warnungen.
- C-03 — XLSX-Validator:
libxlsxwriter-basierter Reader öffnet die Datei, findet genau eine PivotTable, Pivot-Feld-Liste stimmt mit Spezifikation überein. - C-04 — PDF-Signatur-Check: QR-Code-Inhalt stimmt mit
rpt_id;/verify/{id}-Endpunkt gibt grün zurück; nach Hash-Manipulation rot. - C-05 — ArbZG Property-based: §3/§4/§5/§7 Grenzfälle (9:59, 10:00, 10:01 h; 10:59, 11:00, 11:01 h Ruhezeit; Nachtschicht über 00:00 Uhr).
- C-06 — BFSG-Snapshot: Pre-Launch-Audit-Skript führt eine Smoke-Reihe über Report + Dashboard, vergleicht
axe-core-Output gegen Referenz-Manifest; neue Findings = Build fail.
13.6 Flakiness-Schutz
Abschnitt betitelt „13.6 Flakiness-Schutz“- Lokaler 20× Re-Run der neuen E2E-Tests grün (DOD §2.2). Report-Tests sind tendenziell flaky wegen Chart-Render-Timing → Playwright wartet auf
data-testid="chart-rendered"stattnetworkidle.
14. Nicht-Ziele
Abschnitt betitelt „14. Nicht-Ziele“| Nicht-Ziel | Begründung |
|---|---|
| Free-Form-Report-Builder mit beliebiger Pivot-/Filter-Kombination | Scope-Explosions-Risiko. Wir liefern 5 feste Report-Typen + 9 Dashboard-Widgets. „Eigener Report aus beliebigen Feldern“ ist ein V3-Thema, wenn BI-Integratoren anfragen. FUNKTIONSUMFANG §10 „Vollständige CRM-Pipeline“ analog ausgeschlossen. |
| Eigene Formeln in Widgets (wie Excel-Formeln) | Sicherheitsrisiko (Code-Execution) und UX-Komplexität. Stattdessen: XLSX-Export öffnet die Daten in Excel, dort macht die Buchhaltung ihre Formeln. |
| Echtzeit-Streaming-Dashboard (<1 s Latenz) | Stempelungen kommen per SSE, Dashboard-Widgets aktualisieren sich alle 60 s — mehr braucht kein Handwerks-Monitoring. Live-Stream-Dashboards sind Consumer-Feature. |
| KI-generierte Report-Narrative („Diese Woche haben eure Leute 4 % mehr gearbeitet als letzte Woche, weil…“) | Halluzinations-Risiko bei Lohn-relevanten Aussagen. Opt-in-KI-Paket (FUNKTIONSUMFANG §7) ist für OCR und Nachtrag-Textvorschläge — nicht für Reporting. |
Eigene Marketing-Subdomain reports.werkszeit.de |
Wir haben app.werkszeit.de/reports. Eine weitere Subdomain würde Auth-/Session-Komplexität erzeugen. |
| Jasper/BIRT-artige druckbare Layouts mit Designer | Scope-Creep. Typst-Templates in Git sind versionierbar, nachvollziehbar, Tenant-brandbar über 3 Token (Logo, Primärfarbe, Firmenname). |
| Excel-AddIn mit Live-Daten | Liegt bei Drittanbietern, die unsere öffentliche API nutzen (§3.12). |
| Reports auf Desktop-App (V1.5) | Web-App im Browser reicht. Desktop-App ist bewusst auf Offline-Feld-Arbeit fokussiert, nicht auf Büro-Reporting. |
15. Risiken & offene Annahmen
Abschnitt betitelt „15. Risiken & offene Annahmen“| Risiko / Annahme | Impact | Wahrs. | Gegenmaßnahme |
|---|---|---|---|
| BFSG-Audit findet Critical-Finding (z. B. Flutter-Web-Charts nicht screenreader-lesbar) | hoch | mittel | fl_chart wird durch syncfusion_flutter_charts ersetzt oder Chart-Daten als HTML-Fallback-Tabelle (aria-hidden=false) zusätzlich gerendert. Entscheidungspunkt im Sprint 4 nach erstem Audit-Vorbericht. |
| DATEV-CSV-Format hat Tenant-spezifische Abweichungen (Lohnarten-Mapping, Spalten-Reihenfolge) | mittel | hoch | Export-Profil-Editor (F-A-01) erlaubt Tenant-spezifische Konfiguration; Default-Profil ist DATEV LODAS Standard 2026. |
| Performance bei 80 MA × 90 Tage auf RDS t4g.medium | mittel | mittel | Materialized View + HNSW-Index (nicht pgvector, sondern B-Tree). Last-Test Phase 2 mit Seed-Tenant B. Upscale auf t4g.large ist Plan B (Kosten +30 €/Monat). |
| BetrVG-Freigabe-Workflow zu restriktiv — Kunde ohne BR will trotzdem Widgets nutzen | niedrig | hoch | Bei Tenants ohne Betriebsrat (Feldwert has_works_council = false) wird der Workflow durch einen einfacheren Disclaimer ersetzt: „Als Arbeitgeber bestätige ich, dass keine Mitbestimmung erforderlich ist.“ Audit-Log dokumentiert den Self-Approval. |
Hash-Chain-Break in Produktion (z. B. durch Timezone-Bug bei created_at) |
hoch | niedrig | Canonicalisation-Funktion hat Unit-Test U-03 + Property-based Fuzz. Hotfix-Pfad: dedizierte Detection-Suite läuft alle 6 h, Alarmierung via Sentry + PagerDuty. |
| XLSX-Pivot in alten Excel-Versionen (2013) nicht lesbar | niedrig | niedrig | XLSX-Format Office-2013-kompatibel erzeugen (xlsx-lib-Option compatibility: 'excel2013'). Dokumentierte Einschränkung: Excel <2010 nicht unterstützt. |
| Druckansicht bei Firmen-Netzwerk-Druckern (ältere GDI-Drucker mit Encoding-Problemen) | niedrig | niedrig | PDF-Download bevorzugen, Print-Stylesheet ist Zweitweg. Handwerks-Kunden drucken selten direkt aus dem Browser. |
16. Abhängigkeiten
Abschnitt betitelt „16. Abhängigkeiten“- Vorbedingung:
feinkonzepte/kern/01-zeiterfassung(Datenbasistime_entries) undfeinkonzepte/kern/11-audit-log(Audit-Infrastruktur) müssen umgesetzt sein. - Schnittstelle zu: Auth (Scope-Token-Header), DATEV-Export (
datev.csv&datev.extf), S3 Object Lock, BullMQ, Typst-Service. - Wird konsumiert von:
feinkonzepte/kern/12-api-entwickler-portal— Report-Endpunkte sind Teil der öffentlichen API, Report-Webhooks (report.export.ready) sind im AsyncAPI-Katalog.feinkonzepte/kern/10-datev-integration— Monatsabschluss-Workflow löst automatisch Monats-Report-Generierung aus.feinkonzepte/handwerk/4.5-bau-abrechnung(Zukunft V1) — Rechnung referenziert den Ist-Stunden-Stand aus dem Monats-Report.
Cross-Link zur Gruppe: Siehe 12-api-entwickler-portal.md §5 — Report-Export ist einer der primären Use-Cases für den OAuth-Client der Steuerberater-Tools (DATEV-Connector) und prägt das SLA-Versprechen für Monats-Abschluss-APIs.
17. Referenzkunde-Slot
Abschnitt betitelt „17. Referenzkunde-Slot“Status: TBD — zu klären mit Sales (Q2 2026).
DOR §1.1.1 verlangt einen zahlenden Design-Partner.
Kandidaten-Profile:
- Malermeister mit 20–35 MA in Bayern/BaWü — pain: Monatsabschluss 2 Tage Handarbeit, braucht PDF-Stundennachweis für Mitarbeiter + CSV für Steuerberater.
- SHK-Betrieb mit 60–100 MA in NRW — pain: Bauleitung verliert Überblick über Ist-vs-Budget auf 20 parallelen Baustellen, braucht Dashboard-Widgets.
- Gebäudeservice mit aktivem Betriebsrat (~80 MA) — pain: BR verlangt monatliche aggregierte Auswertung, heute Excel-Bastelei. Für den BR-Sicht-Validierungs-Use-Case.
Validierungs-Fragen für das Erst-Gespräch:
- Wie erstellen Sie heute den monatlichen Stundennachweis für jeden Mitarbeiter? Wie lange dauert es?
- Welche Excel-Pivot-Tabelle nutzt Ihr Steuerberater? Können Sie uns eine Beispieldatei geben?
- Wenn Sie Ihren Monatsbericht „Ist-vs-Soll pro Abteilung“ machen — welche Spalten brauchen Sie?
- Haben Sie einen Betriebsrat? Welche aggregierten Reports muss er monatlich erhalten?
- Welche Browser nutzen Ihre Admins? Nutzt jemand Screenreader (Brille/Augenbehinderung)?
- Würden Sie das Feature im Beta-Status für 60 Tage kostenlos testen, gegen 3-monatige Preis-Lock-in-Garantie?
18. Senior-Berater-Empfehlung
Abschnitt betitelt „18. Senior-Berater-Empfehlung“Build-Sequenz (innerhalb des Features):
- Datenmodell + RLS + Multi-Tenant-Test.
report_filter_states,report_documents,dashboard_layoutsmit Drizzle-Migration und RLS-Policy. Zwei-Tenant-Integration-Test (I-01) muss grün sein, bevor eine API-Route geschrieben wird. - Backend-API + OpenAPI-Spec.
GET /reports/monthly+ Drill-Down-Endpunkte. Spec wird committed, Dart-Client generiert, Build grün. - CSV-Export (MVP) + Hash-Chain + S3 Object Lock. Typst kommt erst in Schritt 4, CSV ist der einfachste Schnitt und deckt den DATEV-Fall.
- Typst-PDF-Stundennachweis mit Logo, QR-Code, Verifikationsseite.
- Mobile-MVP (eigene Monats-Sicht + Manager-Team-Status) mit Drift-Cache + Offline-Banner.
- Web-Drill-Down + Filter-Persistenz + Bulk-Bar. An dieser Stelle BFSG-Audit Zwischenstand (in-house mit axe-core).
- Dashboard-Widgets + BR-Freigabe-Workflow.
- XLSX mit Pivot-Vorlage (erfordert präzise Excel-XML-Generierung, ist das fehleranfälligste Stück).
- E2E-Suite vervollständigen, inkl. Keyboard-only- und Screenreader-Smoke.
- Externer BFSG-Audit (≈5 k€). Erst nach grünem Audit: GA-Rollout.
- Doku-PR (
docs/features/kern/reporting.md) mit auto-generierten Screenshots aus E2E.
Risiko-Reihenfolge (wenn Zeitnot):
- Nicht verhandelbar: Multi-Tenant-Isolation (I-01), Hash-Chain (C-01), BFSG Critical-Findings = 0, ArbZG-Property-Tests (C-05).
- Erste Streich-Kandidaten: XLSX-Pivot-Vorlage (Business-Plan-Feature, MVP akzeptiert CSV-only), Dashboard-Konfigurator (V1.5), Print-Stylesheet (nice-to-have, PDF reicht).
Stop-the-Bus-Triggers:
- Multi-Tenant-Bleed in einem Report-Test.
- Hash-Chain-Bruch in der Compliance-Suite.
- BFSG-Audit meldet eine Critical-Finding (z. B. Chart nicht bedienbar per Tastatur) — Launch bis zur Behebung gestoppt.
- DSGVO-Löschantrag eines Mitarbeiters kollidiert in Produktion mit GoBD-Aufbewahrung (Maskierungs-Logik ist der dokumentierte Ausweg; wenn die Logik versagt: Stop).
- p95 Monats-Report >2 s in Staging mit Seed-Tenant B (80 MA).
Was uns 2027 dankbar macht:
- Report-Dokumente sind seit Tag 1 in Hash-Chain + S3 Object Lock — wenn das Finanzamt 2029 nach Beleg-Integrität fragt, haben wir eine Antwort, nicht ein Projekt.
- Die URL-als-State-Architektur macht die UI wartbar (kein Modal-Tiefenstapel) und macht die BFSG-Tastaturnavigation natürlich — Back-Button ist Teil der A11y-Strategie, nicht ein Bug-Report.
- Export-Profile als benannte Konfigurationen sind die Vorlage für die DATEV-Partner-Zertifizierung in Jahr 2 — wir liefern die Datei ohne Nachprogrammierung, sondern durch Auswahl eines Profils.
- Die 9 Widgets sind Rollen-gebunden + BR-gated von Anfang an. Das erspart in Jahr 3 den schmerzhaften Rückbau, den die Alt-App nach dem ersten BR-Audit hatte.
Feinkonzept-Autor: Senior-Consulting-Sparte Reporting & Barrierefreiheit · Werkszeit v0.1 · 19.04.2026
Für Entwickler — API-Endpoints7
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
| POST | /v1/kern/reports/exports | bearerAuth | Export-Job anlegen (PDF oder CSV) |
| GET | /v1/kern/reports/exports/{id} | bearerAuth | Export-Status abfragen |
| GET | /v1/kern/reports/exports/{id}/download | bearerAuth | Presigned S3-URL für fertigen Export |
| GET | /v1/kern/reports/filter-states/{kind} | bearerAuth | Filter-State laden |
| PUT | /v1/kern/reports/filter-states/{kind} | bearerAuth | Filter-State speichern |
| GET | /v1/kern/reports/monthly | bearerAuth | Monats-Aggregat laden |
| GET | /v1/kern/reports/monthly/{month}/employees/{employeeId} | bearerAuth | Drill-Down auf Mitarbeiter-Tagesebene |