Zeiterfassung — BAG-konforme Stempeluhr mit Offline, NFC und Voice
Feinkonzept — Zeiterfassung (BAG-konform)
Abschnitt betitelt „Feinkonzept — Zeiterfassung (BAG-konform)“Pflicht-Modul. Ohne Stempeln kein Werkszeit. Jedes andere Feinkonzept — von der ArbZG-Prüfung bis zum DATEV-Export — setzt auf dem hier entstehenden Stempel-Primitive auf. Dieses Dokument folgt der Pflichtvorlage (
../TEMPLATE.md) mit allen 18 Sektionen.
1. Header & Metadaten
Abschnitt betitelt „1. Header & Metadaten“feature_id: kern/01-zeiterfassungtitle: Zeiterfassung — BAG-konforme Stempeluhr mit Offline, NFC und Voicefunktionsumfang_ref: §3.1roadmap_horizont: MVPplattformen: mobile: vollständig web: vollständig desktop: ab V1.5 bei Bedarfowner_rolle: Mitarbeitermodul_gate_flag: module.kern.zeiterfassungcompliance_flags: gobd: true arbzg: true vob: false dsgvo: true bfsg: true betrvg: true # Geo-Assistenz ist Leistungskontrolle i.S.v. §87(1)6 stvg: false weitere: [§147 AO, §8 BAG-Urteil 1 ABR 22/21]referenzkunde: status: TBD name: "zu klären mit Sales — Kandidaten-Profil siehe §17" quelle: Sales-Call + Interview-Leitfaden "Stempeln-Alltag"estimate_eng_tage: 62 # MVP-Slice (Stempel, Offline, Korrektur, NFC, Voice)abhängigkeiten: - kern/02-arbeitszeit-compliance # Echtzeit-Prüfung on-the-fly - kern/08-dienstplanung # Soll-Stunden aus Schichtplan2. Kontext & Problem
Abschnitt betitelt „2. Kontext & Problem“Marktrealität (DE-Handwerk). Das BAG-Urteil vom 13.09.2022 (1 ABR 22/21) zwingt jeden Arbeitgeber zur vollständigen elektronischen Erfassung der Arbeitszeit — nicht nur der Überstunden. Der Referentenentwurf zur ArbZG-Novelle präzisiert: „elektronisch und manipulationssicher“. Für einen Malerbetrieb mit 8 Gesellen bedeutet das heute Excel mit Unterschrift oder ein Stundenzettel in Papierform — beides ist weder GoBD-konform noch im Streitfall belastbar. Für den SHK-Betrieb mit 80 Monteuren über fünf Bezirke ist Papier faktisch nicht mehr skalierbar, gleichzeitig fehlt Budget für ein fünfstellig eingeführtes Bau-ERP.
Schmerzpunkt der Alt-App. LESSONS-LEARNED §4 dokumentiert: Die IndexedDB-basierte „Offline-First“-Lösung war bei echtem LTE-Abriss instabil (Safari-iOS löscht Storage, Background-Sync unzuverlässig, Doppel-Stempelungen bei Retry). LESSONS-LEARNED §3 beschreibt das Fehlen von Idempotency-Keys. LESSONS-LEARNED §7 hebt hervor, dass ArbZG-Grenzfälle (Nachtarbeit über 00:00, §7-Ausnahmen) nie getestet wurden. Dieses Feinkonzept macht genau das anders: echte native Apps (Flutter + Drift) mit Idempotenz als Pflicht, harten ArbZG-Property-Tests, NFC als Erstwahl statt PIN.
Erwarteter Outcome.
- Stempel-Zeit < 2 Sek Cold-Start von Lockscreen bis gespeicherte Buchung (Quick-Action / Widget).
- Offline-Fenster 5 Arbeitstage ohne Datenverlust (TECH-STACK §6).
- Stundennachweis-Erstellung von aktuell 45 min/Woche (manuelle Übertragung Excel → DATEV) auf < 5 min/Woche (Freigabe-Klick).
- BAG-konforme Dokumentation ab Tag 1 — kein Nachrüsten, kein Parallelbetrieb.
3. Personas & Rollen
Abschnitt betitelt „3. Personas & Rollen“| Rolle | Aktion | Scope | Plattform |
|---|---|---|---|
| Mitarbeiter | Stempeln Start/Stop, Pause, manuelle Korrektur | own |
📱🌐 |
| Mitarbeiter | Zeitarten wählen (Arbeit / Bereitschaft / Reise) | own |
📱🌐 |
| Manager | Team-Live-Status sehen, Buchungen fremd freigeben | team |
📱🌐 |
| Manager | Manuelle Buchung für Mitarbeiter erfassen | team |
🌐 |
| HR | Monats-Korrekturen (mit Audit-Pflichtbegründung) | all |
🌐 |
| Admin | Zeitarten-Stammdaten, NFC-Ausweise verwalten | all |
🌐 |
| Buchhaltung | Monatsabschluss sperren, DATEV-Export auslösen | all |
🌐 |
Persona-Skizzen.
- Hannes Krüger (38, Maler-Geselle, musterbetrieb-maler, Bayern) — Pendelt auf die Baustelle Lehrer Allee 7, München. Hat iPhone 13 (firmenfinanziert) und Samsung-Tablet im Van. Handschuhe bleiben an beim Stempeln. Tippen hasst er; Voice oder NFC ist Pflicht-UX.
- Sabine Maier (43, Manager, musterbetrieb-maler) — Büro in Augsburg, Freitag nachmittags erzeugt sie 12 Stundennachweise auf Web, schickt sie zur Bauherren-Signatur. Toleriert Excel, bevorzugt aber Tabellen-Modus.
- Thomas Schmidt (51, Bauleiter, shk-gebruder-schmidt, NRW) — Koordiniert 18 Monteure auf 4 Baustellen. Tablet auf dem Beifahrersitz, sieht Team-Status live.
- Frau Müller (56, Buchhaltung, shk-gebruder-schmidt) — Kennt DATEV LODAS seit 15 Jahren, will im MVP mindestens DATEV-CSV ohne Klick-Strecke, später LODAS (V1).
4. User-Stories
Abschnitt betitelt „4. User-Stories“US-01 [MVP] Als Monteur möchte ich mit einem einzigen Tipp auf den Home-Screen-Widget einstempeln können, damit ich mit Handschuhen und kaltem Finger produktiv bin.
US-02 [MVP] Als Monteur möchte ich auch im Heizungskeller ohne Empfang stempeln können, weil ich dort Stunden verbringe und die Buchung danach sauber synct.
US-03 [MVP] Als Mitarbeiter möchte ich eine vergessene Stempelung nachtragen können, wobei das System mich zwingt, einen Grund anzugeben (GoBD-Nachvollziehbarkeit).
US-04 [MVP] Als Manager möchte ich live sehen, wer wo eingestempelt ist, um bei Bauherr-Anrufen sofort Auskunft geben zu können.
US-05 [MVP] Als Buchhaltung möchte ich Zeitdaten nach Monatsabschluss sperren, sodass keine rückwirkende Änderung ohne explizite Admin-Freigabe möglich ist.
US-06 [MVP] Als Monteur möchte ich per NFC-Ausweis an einem Werkstatt-Tablet stempeln, weil nicht jeder Geselle ein Smartphone dabei hat (Azubi, Leiharbeiter).
US-07 [V1] Als Monteur möchte ich per Sprache ("Werkszeit, stempel ein Baustelle Lehrer Allee") stempeln, weil ich oft beide Hände am Werkzeug habe.
US-08 [V1] Als Manager möchte ich mehrere Tage im Tabellen-Modus manuell buchen, z.B. nach einer Baustellen-Woche mit Funkloch.
US-09 [V1.5] Als Admin möchte ich die Zeitarten-Stammdaten (Arbeit, Bereitschaft, Reise…) pro Tenant konfigurieren, weil Tarifverträge branchenspezifisch sind.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 — Stempel-Button XL (200×200 px) auf Home-Screen der App. Ein Tipp = Start. Langer Tipp = Baustelle wechseln. Zweiter Tipp = Stop. Kein Modal, keine Bestätigung — die Buchung ist append-only und korrigierbar, nicht bestätigungspflichtig.
- F-M-02 — Quick-Action im Lockscreen (iOS Shortcuts / Android App-Shortcuts). Öffnet die App im Stempel-Modus und löst Start aus, ohne Face-ID/Touch-ID-Friction (gesteuert über Biometrie-Option „Stempeln vereinfachen“).
- F-M-03 — Home-Screen-Widget 2×2 mit Start/Stop + aktuellem Saldo. Android Glance + iOS WidgetKit.
- F-M-04 — NFC-Stempeln. Mitarbeiter-Ausweis (MIFARE DESFire) auf Smartphone/Tablet → Start oder Stop auf aktive Person. Fallback Kiosk-Modus am Werkstatt-Tablet: User wählt Ausweis auflegen → keine PIN nötig.
- F-M-05 — Voice-Stempelung (V1): On-device STT (
speech_to_text) → Intent-Parser in Dart. Trigger-Phrase „Werkszeit, …“. Keine Cloud, keine Dauer-Zuhörung; Aufzeichnung startet erst nach Button-Tipp. - F-M-06 — Offline-Stempelung vollumfänglich: Drift-Outbox-Tabelle, UUID-v7-Idempotency-Key, Server-Sync bei
connectivity.online. Stempelung funktioniert identisch ohne Empfang — UI zeigt einen dezenten „In Warteschlange (1)“-Badge. - F-M-07 — Zeitart-Wechsel (Arbeit / Pause / Bereitschaft / Rufbereitschaft / Dienstreise / Schulung) als zweiter Tipp-Gesten auf den Stempel-Button („STOP + Zeitart wählen“). Default Zeitart = „Arbeit“ und letzte benutzte.
- F-M-08 — Auto-Pause nach ArbZG (§4): 30 min ab 6 h Arbeitszeit, 45 min ab 9 h. Wird als Vorschlag dargestellt — Mitarbeiter kann Pause früher oder später nehmen, System merkt sich Differenz als „ArbZG-Warnung → Manager-Review“ (siehe
kern/02-arbeitszeit-compliance). - F-M-09 — Baustelle-Vorschlag via GPS (opt-in, BetrVG-Flag Pflicht). Bei Start wird GPS einmalig gelesen (nicht persistiert), Vorschlag aus Stammdaten (nearest ≤ 200 m). Mitarbeiter kann Vorschlag ablehnen. Kein Dauer-Tracking, kein Geofencing.
- F-M-10 — Manuelle Korrektur einer Buchung (eigene, ≤ 7 Tage alt, vor Monatsabschluss): Begründungs-Pflichtfeld. Erzeugt neue versionierte Buchung, die Vorgänger als
superseded_bymarkiert. Alte Zeile bleibt lesbar (GoBD).
5.2 Web-App (🌐)
Abschnitt betitelt „5.2 Web-App (🌐)“- F-W-01 — Team-Dashboard mit Live-Status (WebSocket + SSE-Fallback). Je Mitarbeiter: Status (stempelt / Pause / abwesend / geplant), Baustelle, Saldo heute, Saldo Woche.
- F-W-02 — Tabellen-Bulk-Eingabe für Manager: 5–31 Tage × N Mitarbeiter als editierbare Tabelle. Keyboard-Navigation Pflicht (Tab / Enter / Pfeil). Undo-Buffer bis Abschicken.
- F-W-03 — Kalender-Monatssicht mit Farbkodierung (grün = gestempelt, gelb = ArbZG-Warnung, rot = Abwesenheit, grau = Wochenende / Feiertag).
- F-W-04 — Detail-Ansicht Buchung zeigt Audit-Trail: ursprünglich angelegt, korrigiert durch X mit Grund Y, freigegeben durch Z.
- F-W-05 — CSV-Export (MVP) mit ArbZG-Flags. DATEV-LODAS-Export (V1) siehe §3.10 Funktionsumfang.
- F-W-06 — Monatsabschluss-Workflow: Entwurf → Korrektur → Freigabe → Sperre. Ab Sperre: nur noch Admin + Audit-Eintrag.
5.3 Cross-Plattform (🔄)
Abschnitt betitelt „5.3 Cross-Plattform (🔄)“- F-X-01 — Zeitarten-Auswahl aus Tenant-Stammdaten (kein hardcoded Enum).
- F-X-02 — Projekt-/Baustellen-Zuordnung via Autocomplete, sortiert nach letzter Nutzung + GPS-Nähe.
- F-X-03 — Tätigkeitsbeschreibung Freitext (160 Zeichen), optional Voice-Transkript.
- F-X-04 — Rundungsregel tenant-konfigurierbar: keine / 1-min / 5-min / 15-min (Standard: keine, um BAG-Vorgabe „vollständig erfassen“ nicht zu unterlaufen).
- F-X-05 — Feiertags-Warnung Bundesland-abhängig (Bayern, NRW, … —
intl-Locale + eigene Feiertags-Tabelle inkl. regional Fronleichnam u.a.).
5.4 Admin-Konfiguration
Abschnitt betitelt „5.4 Admin-Konfiguration“- F-A-01 — Zeitarten anlegen/deaktivieren/sortieren. Default-Zeitart pro Rolle konfigurierbar.
- F-A-02 — NFC-Ausweise registrieren (UID → User-ID Mapping), sperren, ersetzen.
- F-A-03 — Rundungsregel auswählen (+ Warnhinweis bei >5 min: potenziell BAG-kritisch).
- F-A-04 — Monatsabschluss für Zeitraum durchführen (nur Rolle
BuchhaltungoderAdmin). - F-A-05 — BetrVG-Freischaltung: GPS-Vorschlag + Voice + Anomalie-KI sind default aus und benötigen eine dokumentierte Betriebsrat-Zustimmung (siehe §10.6).
5.5 Roadmap-Schichtung
Abschnitt betitelt „5.5 Roadmap-Schichtung“| Anforderung-ID | MVP | V1 | V1.5 | V2 |
|---|---|---|---|---|
| F-M-01 Stempel-XL | ✅ | |||
| F-M-02 Lockscreen-QA | ✅ | |||
| F-M-03 Widget | ✅ | |||
| F-M-04 NFC | ✅ | |||
| F-M-05 Voice | ✅ | |||
| F-M-06 Offline | ✅ | |||
| F-M-07 Zeitarten | ✅ | |||
| F-M-08 Auto-Pause | ✅ | |||
| F-M-09 GPS-Vorschlag | ✅ | |||
| F-M-10 Korrektur | ✅ | |||
| F-W-01 Team-Dashboard | ✅ | |||
| F-W-02 Bulk-Tabelle | ✅ | |||
| F-W-03 Monatskalender | ✅ | |||
| F-W-05 DATEV-CSV/LODAS | CSV | LODAS | ||
| F-W-06 Monatsabschluss | ✅ |
6. Mockups & Flows
Abschnitt betitelt „6. Mockups & Flows“HTML-Hero-Mockup: 01-zeiterfassung.html — Side-by-Side Mobile (Monteur Hannes K., eingestempelt seit 07:02 auf Lehrer Allee 7) + Web (Manager Sabine M., Team-Status Mo 19.04.2026).
6.1 Stempel-Startscreen Mobile (vor erstem Stempeln)
Abschnitt betitelt „6.1 Stempel-Startscreen Mobile (vor erstem Stempeln)“┌────────────────────────────────┐│ ← Zeit erfassen ⚙ │├────────────────────────────────┤│ 🛡 GoBD · ArbZG · DSGVO │├────────────────────────────────┤│ Mo 19.04.2026 · 06:58 ││ ││ 00:00 ││ noch nicht ││ eingestempelt ││ ││ ╭──────────╮ ││ │ ▶ │ ││ │ START │ ││ │ tippen │ ││ ╰──────────╯ ││ ││ Letzte Baustelle: ││ 📍 Lehrer Allee 7, München ││ [Baustelle wechseln] ││ ││ Woche: 32:15 h / 40:00 h │├────────────────────────────────┤│ [Zeit] [Plan] [Doku] [Mehr] │└────────────────────────────────┘6.2 Stempel-Screen Mobile (eingestempelt mit ArbZG-Warnung)
Abschnitt betitelt „6.2 Stempel-Screen Mobile (eingestempelt mit ArbZG-Warnung)“┌────────────────────────────────┐│ ← Zeit erfassen ⚙ │├────────────────────────────────┤│ 🛡 GoBD · ArbZG · DSGVO │├────────────────────────────────┤│ Mo 19.04.2026 🟢 Eingestempelt││ ││ 06:40 ││ h von 8:00 · Pause 0:32 ││ ││ ╭──────────╮ ││ │ ■ │ ││ │ STOP │ ││ │ Lehrer A.│ ││ ╰──────────╯ ││ ││ Verlauf heute: ││ 07:02 Start Lehrer A. Auto📍││ 12:00 Pause §4 ArbZG ││ 12:32 Weiter ││ ││ ⚠ ArbZG §3 — 10 h in 3:18 h ││ Bei aktuellem Tempo um 17:18││ Höchstarbeitszeit erreicht. │├────────────────────────────────┤│ [Zeit] [Plan] [Doku] [Mehr] │└────────────────────────────────┘6.3 Korrektur-Dialog Mobile
Abschnitt betitelt „6.3 Korrektur-Dialog Mobile“┌────────────────────────────────┐│ ✕ Stempelung korrigieren │├────────────────────────────────┤│ Ursprüngliche Buchung ││ ▸ 16.04.2026 07:02 → 16:42 ││ Lehrer Allee 7 · 8:40 h ││ ││ Neue Zeiten 🔴 ││ Start [ 06:45 ] ││ Stop [ 16:42 ] ││ ││ Begründung 🔴 (GoBD-Pflicht) ││ ┌──────────────────────────┐ ││ │ Vergessen einzustempeln, │ ││ │ reale Ankunft war 06:45. │ ││ └──────────────────────────┘ ││ ││ ℹ Erzeugt versionierte ││ Korrektur-Buchung. ││ Audit-Log: ja. ││ ││ [ Abbrechen ] [ Korrigieren ] │└────────────────────────────────┘6.4 Team-Dashboard Web (Manager)
Abschnitt betitelt „6.4 Team-Dashboard Web (Manager)“┌────────────────────────────────────────────────────────────────────────────────┐│ Werkszeit · musterbetrieb-maler Sabine M. · Manager [Profil ▾] │├────────────┬───────────────────────────────────────────────────────────────────┤│ ⏱ Zeit 14 │ Team heute · Mo 19.04.2026 ⤓ CSV + Manuell [Nachweis⇨] ││ 📅 Plan ├───────────────────────────────────────────────────────────────────┤│ 📋 Doku │ KPI: 18 eingestempelt · 3 Pause · 4 Krank/Urlaub · 1 ArbZG ││ 📐 Aufmaß ├───────────────────────────────────────────────────────────────────┤│ 🏗 Projekte│ ☐ Mitarbeiter Status Baustelle Start Heute Woche ││ 📄 Rechn. │ ☐ Hannes K. 🟢 Stempelt Lehrer Allee 7 07:02 6:40h +2:15 ││ ⤓ DATEV │ ☐ Mehmet Y. 🟡 Pause Lehrer Allee 7 07:15 4:15h −0:45 ││ 🛡 Audit │ ☐ Frank D. 🔴 eAU 22.04. — — 0:00 −16:00 ││ ⚙ Einstell.│ ☐ Sabine M. ⚪ Büro Augsburg 08:00 5:30h +0:30 ││ │ ☐ Andreas W. 🔵 Geplant 13 Schulstr. 12 — — +1:00 ││ ──────────│ ││ SM │ [◀ Gestern] [▶ Heute] [KW 16 ▾] [Alle Baustellen ▾] ││ Manager │ │└────────────┴───────────────────────────────────────────────────────────────────┘6.5 Kiosk-NFC-Modus (Tablet in der Werkstatt)
Abschnitt betitelt „6.5 Kiosk-NFC-Modus (Tablet in der Werkstatt)“┌────────────────────────────────┐│ Werkszeit · Werkstatt-Kiosk ││ musterbetrieb-maler │├────────────────────────────────┤│ ││ 📡 ││ Ausweis auflegen ││ ││ Stempelt automatisch ││ Ein oder Aus ││ ││ Letzte Stempelungen: ││ 06:58 Hannes K. START ││ 07:00 Mehmet Y. START ││ 07:02 Frank D. START ││ ││ Admin-Logout: Code [ ____ ] │└────────────────────────────────┘6.6 Offline-Status-Inline
Abschnitt betitelt „6.6 Offline-Status-Inline“┌────────────────────────────────┐│ 🛜 Kein Empfang — Buchungen ││ werden synchronisiert, sobald││ die Verbindung zurück ist. ││ Warteschlange: 2 Einträge ││ [Details] │└────────────────────────────────┘7. Datenmodell-Skizze
Abschnitt betitelt „7. Datenmodell-Skizze“export const timeEntriesTable = pgTable('time_entries', { id: uuid('id').primaryKey(), // UUID v7 Idempotenz tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), userId: uuid('user_id').notNull().references(() => usersTable.id), kind: timeKindEnum('kind').notNull(), // start | stop | pause_start | pause_end timeArt: varchar('time_art', { length: 32 }).notNull(), // Arbeit|Bereitschaft|Reise|… occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(), projectId: uuid('project_id').references(() => projectsTable.id), siteId: uuid('site_id').references(() => sitesTable.id), activityText: varchar('activity_text', { length: 160 }), source: sourceEnum('source').notNull(), // mobile | web | nfc_kiosk | voice | manual geoHint: point('geo_hint'), // PostGIS, nur wenn BetrVG-Flag aktiv idempotencyKey: uuid('idempotency_key').notNull().unique(), supersedesId: uuid('supersedes_id').references(() => timeEntriesTable.id), // Korrektur-Chain reasonText: varchar('reason_text', { length: 500 }), // Pflicht bei manual & supersede arbzgFlags: jsonb('arbzg_flags').$type<ArbzgFlagSet>().default({}), lockedAt: timestamp('locked_at', { withTimezone: true }), // Monatsabschluss createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), createdBy: uuid('created_by').notNull().references(() => usersTable.id), hashPrev: bytea('hash_prev'), hashSelf: bytea('hash_self'),}, (t) => ({ tenantUserOccurredIdx: index('te_tenant_user_occurred_idx').on(t.tenantId, t.userId, t.occurredAt.desc()), tenantOccurredIdx: index('te_tenant_occurred_idx').on(t.tenantId, t.occurredAt.desc()), idemUniq: uniqueIndex('te_idem_uniq').on(t.tenantId, t.idempotencyKey),}));
export const nfcBadgesTable = pgTable('nfc_badges', { id: uuid('id').primaryKey().defaultRandom(), tenantId: uuid('tenant_id').notNull(), userId: uuid('user_id').notNull(), badgeUidHash: bytea('badge_uid_hash').notNull(), // HMAC-SHA-256, nicht die rohe UID status: varchar('status', { length: 16 }).notNull().default('active'), activatedAt: timestamp('activated_at', { withTimezone: true }).defaultNow(), revokedAt: timestamp('revoked_at', { withTimezone: true }),});RLS-Policy-Sketch.
ALTER TABLE time_entries ENABLE ROW LEVEL SECURITY;
CREATE POLICY te_tenant_iso ON time_entries USING (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY te_scope_own ON time_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 user_id = ANY( SELECT user_id FROM team_memberships WHERE team_id = ANY(current_setting('app.team_ids')::uuid[]))) OR (current_setting('app.scope') = 'own' AND user_id = current_setting('app.user_id')::uuid) ) );
CREATE POLICY te_no_hard_update ON time_entries FOR UPDATE USING (locked_at IS NULL AND current_setting('app.role') IN ('admin', 'hr')) WITH CHECK (FALSE); -- Updates nur via supersedeER-Bezug. Verwendet users, tenants, projects, sites, team_memberships, audit_log. Feiertagskalender liegt in holidays (tenant-scoped + Bundesland).
8. API-Endpunkte
Abschnitt betitelt „8. API-Endpunkte“Alle Endpunkte unter /v1/kern/zeit/…, hinter Hono-Modul-Gate module.kern.zeiterfassung.
| Methode | Pfad | Auth-Scope | Rate-Limit | Idempotenz | Beschreibung |
|---|---|---|---|---|---|
| POST | /v1/kern/zeit/entries |
time:write |
Standard | Idempotency-Key Pflicht | Stempeln (start/stop/pause) |
| POST | /v1/kern/zeit/entries/{id}/correct |
time:write |
Standard | Idempotency-Key Pflicht | Korrektur als neue Version mit Begründung |
| GET | /v1/kern/zeit/entries |
time:read:<scope> |
Standard | — | Liste, paginiert, ?from=&to=&userId=&siteId= |
| GET | /v1/kern/zeit/entries/{id} |
time:read:<scope> |
Standard | — | Detail inkl. Korrektur-Kette |
| GET | /v1/kern/zeit/status/team |
time:read:team |
Live | — | SSE-Stream Live-Status |
| POST | /v1/kern/zeit/nfc-stempel |
time:write |
Privileged | Idempotency-Key Pflicht | Kiosk-NFC-Stempeln (badge-UID-Hash) |
| POST | /v1/kern/zeit/month-close |
time:close |
Privileged | Idempotency-Key Pflicht | Monatsabschluss sperren |
| POST | /v1/kern/zeit/exports/datev-csv |
time:export |
Privileged | Idempotency-Key Pflicht | CSV-Download (MVP) |
paths: /v1/kern/zeit/entries: post: operationId: createTimeEntry x-werkszeit-scope: time:write x-werkszeit-rate-limit: standard x-werkszeit-idempotency: required requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TimeEntryCreateInput' } responses: '201': { content: { application/json: { schema: { $ref: '#/components/schemas/TimeEntry' } } } } '409': { description: 'Idempotency conflict — client soll ursprüngliche Antwort nutzen' } '422': { description: 'Validation (ArbZG-Blocker mit Begründungs-Aufforderung)' } '423': { description: 'Monat gesperrt — Admin-Freigabe nötig' }Webhook-Events (AsyncAPI, §3.12):
time.entry.createdtime.entry.correctedtime.month_closedtime.arbzg_warning(Broadcast an Manager-Abonnenten)
9. Offline-Profil
Abschnitt betitelt „9. Offline-Profil“| Profil | Begründung | Mechanik |
|---|---|---|
| Voll-offline | Heizungskeller, Tiefgarage, Dachstuhl — Kernszenario für Monteure | Drift + Outbox-Tabelle, UUID-v7 Idempotency-Key, 5-Tage-Lokalhistorie, SSE resumes nach Sync |
Konflikt-Strategie. Append-only. Server ist Autorität. Zwei offline-Stempelungen mit gleicher Semantik (z.B. doppel-Start) → Server akzeptiert beide, aber zweite wird als corrected_duplicate markiert und Manager erhält Ticket. Kein Last-Write-Wins — das würde den Goldstandard „vollständige Erfassung“ verletzen.
Foto-/Datei-Sync. Dieses Feature selbst lädt keine Fotos hoch (nur Bautagebuch/Mängel tun das). Wenn ein Mitarbeiter per Voice eine Sprachnotiz anfügt, bleibt die Audio-Datei on-device und das transkribierte Ergebnis geht als activity_text mit, ggf. per S3-Multipart-Presigned-URL wenn > 500 Zeichen.
10. Compliance-Mapping
Abschnitt betitelt „10. Compliance-Mapping“10.1 GoBD (§147 AO Abs. 1 Nr. 4, 10 Jahre)
Abschnitt betitelt „10.1 GoBD (§147 AO Abs. 1 Nr. 4, 10 Jahre)“- Append-only-Tabelle
time_entriesmithash_prev+hash_self= SHA-256 über kanonisierte Zeile (Felder sortiert, UTC-normalisiert). Beim Insert wirdhash_prevauf denhash_selfdes letzten Eintrags pro(tenant_id, user_id)gesetzt (Merkle-Kette je User, damit Mitarbeiter-Historie ohne Cross-User-Abhängigkeit prüfbar ist). - Korrekturen erzeugen neuen Datensatz mit
supersedes_id. Die ursprüngliche Zeile bleibt unverändert lesbar — Maskierung nur bei DSGVO Art. 17, dann FelderactivityTextundgeoHintwerden durch'[maskiert §17 DSGVO]'überschrieben, aber die Zeile + Hash bleiben. - Aufbewahrung: Export-Artefakte (CSV, später LODAS) werden in S3 Frankfurt mit Object Lock Compliance Mode, 10 Jahre abgelegt. Zusätzlich Signatur ed25519 pro Export-File, Public Key im Audit-Log.
- Nachvollziehbarkeit: Jede Mutation erzeugt
audit_log-Eintragtime.entry.{created|corrected|locked}mit Aktor-User, Idempotency-Key, Geräte-Hash, Vorher/Nachher-Diff. - Verfahrensdokumentation: Textbaustein „Zeiterfassung BAG-konform — Verfahrensdokumentation“ (generiert aus diesem Feinkonzept + Stand der Migration) als Teil der Admin-Doku.
10.2 ArbZG
Abschnitt betitelt „10.2 ArbZG“- §3 Höchstarbeitszeit (8 h, bis 10 h bei Ausgleich). System erzwingt nicht, aber markiert. Echtzeit-Warnung (siehe
kern/02-arbeitszeit-compliance). Eintrag wird trotzdem gespeichert (Doku-Pflicht), mitarbzg_flags.hoechstarbeitszeit_ueberschritten = true. - §4 Pausen. Auto-Pause-Vorschlag (30 min ab 6 h, 45 min ab 9 h). Wird die Pause kürzer genommen oder ausgelassen,
arbzg_flags.pause_verkuerzt = true. - §5 Ruhezeit (11 h). Prüfung bei neuem Start-Eintrag:
occurred_at - max(prev_stop.occurred_at) ≥ 11h. Verletzung →arbzg_flags.ruhezeit_verletzt = true, Mitarbeiter sieht Warnung, Manager-Ticket. - §7 Ausnahmen. Konfigurierbare Tarif-Overrides (z.B. Baugewerbe: 10-h-Ausnahme mit TV-Bezug). Admin hinterlegt den TV, Property-Tests prüfen Bit-Identität zum
ArbzgFlagSet. - BAG-Urteil 1 ABR 22/21. Die „vollständige Erfassung“ ist durch das Drei-Kind-Prinzip (
start,pause_start|pause_end,stop) abgebildet. Jeder Tag jedes Mitarbeiters mit Arbeitsvertrag > 0h/Woche muss eine Tages-Projektion haben; fehlende Tage erscheinen im Manager-Report „offene Tage“.
10.3 VOB/B
Abschnitt betitelt „10.3 VOB/B“— nicht zutreffend, weil Zeiterfassung primär Arbeitsrecht berührt, nicht Werkvertragsrecht. Der Zeitstempel fließt später in VOB-konforme Stundennachweise (siehe handwerk/03-stundennachweis); dort ist VOB/B relevant.
10.4 DSGVO
Abschnitt betitelt „10.4 DSGVO“- Art. 5 Datenminimierung. GPS-Hint wird nur gespeichert bei aktivem BetrVG-Flag und nur als einmaliger Punkt (nicht Track). Audio-Aufnahmen der Voice-Stempelung verlassen das Gerät nicht.
- Art. 6 Rechtsgrundlage. Vertragserfüllung (Art. 6 Abs. 1 b) für Arbeitszeit-Dokumentation. Zusätzlich gesetzliche Pflicht (Art. 6 Abs. 1 c) wegen BAG-Urteil + ArbZG + §147 AO. GPS/Voice: Einwilligung (Art. 6 Abs. 1 a) + BR-Zustimmung.
- Art. 17 Löschung. Bei Mitarbeiter-Austritt: personenbezogene Felder (
activity_text,geo_hint) nach 10 Jahren gelöscht. Der Zeitstempel (GoBD-Pflicht) bleibt bis 10 J nach Geschäftsjahresende. Danach Kaskaden-Löschung via Jobdsgvo.kassation. - Art. 20 Datenexport. JSON-Export liefert alle eigenen Zeitbuchungen incl. Korrektur-Kette, ArbZG-Flags, Baustellen-Referenzen.
- Art. 30 VVZ. Eintrag „Zeiterfassung Mitarbeiter“: Kategorien = Mitarbeiter, Daten = Stempelungen/Zeitarten/Tätigkeits-Freitext/optional Geo, Empfänger = Buchhaltung, Löschfrist = 10 J.
- DSFA Pflicht. Ja — systematische Erfassung Arbeitsverhalten. DSFA liegt in
docs/werkszeit/dsfa/zeiterfassung.md.
10.5 BFSG / WCAG 2.2 AA
Abschnitt betitelt „10.5 BFSG / WCAG 2.2 AA“- Stempel-Button hat Alt-Text „Einstempeln — aktuelle Stempelzeit gestartet um …“ / „Ausstempeln — … h gearbeitet“.
- Focus-Order: Header → Stempel-Button (primär) → Liste Verlauf → Tabbar.
- Kontrast: Sicherheits-Orange
#FF6B1Aauf Anthrazit#1A1F2B= 6.4:1. Warnungen#C77A0Aauf#FBF1DA= 4.8:1. Beide ≥ 4.5. - Screenreader-Live-Region meldet Stempel-Ergebnis binnen 1s („Eingestempelt um 07:02 auf Lehrer Allee 7“).
- Tastatur-Navigation im Web:
S= Start/Stop,P= Pause,K= Korrektur, dokumentiert im Keyboard-Shortcuts-Overlay (?). - VoiceOver / TalkBack / NVDA: Pflicht-Test pro Release.
10.6 BetrVG §87(1)6
Abschnitt betitelt „10.6 BetrVG §87(1)6“Mehrere Mechanismen in diesem Feinkonzept erzeugen Leistungs- und Verhaltenskontrolle:
- GPS-Vorschlag (F-M-09). Default aus. Aktivierung durch Admin erfordert im UI-Workflow: Upload BR-Beschluss (PDF), Freigabe-Datum, Scope (pro Team / Tenant-weit). Audit-Log
betrvg.activation. - Voice-Stempelung (F-M-05). Gleicher Workflow, weil on-device STT Biometrie-nah ist.
- Anomalie-Erkennung (V2). Gleicher Workflow.
- Team-Dashboard (F-W-01). Nicht BR-pflichtig, weil Anzeige nur auf aggregierten Status beschränkt ist (Ampel, nicht Detail-Log). Manager sehen keine GPS-Tracks, keine App-Öffnungen, keine Tastatur-Anschläge.
10.7 Weitere Spezial-Compliance
Abschnitt betitelt „10.7 Weitere Spezial-Compliance“— nicht zutreffend (Zeiterfassung betrifft weder §13b UStG, §48 EStG, SOKA-Bau, DGUV noch StVG; diese kommen in anderen Feinkonzepten zum Tragen).
11. Edge-Cases
Abschnitt betitelt „11. Edge-Cases“| # | Szenario | Erwartetes Verhalten |
|---|---|---|
| EC-01 | Zeitzonenwechsel — Hannes stempelt 07:02 in Deutschland ein, fliegt Mittag nach Zürich. | Speicherung in UTC. Anzeige in Tenant-Zeitzone (Europe/Berlin). Reise = Zeitart „Dienstreise“, kein zweiter Stempel nötig. |
| EC-02 | Tagesgrenze 00:00 — Nachtschicht SHK-Notdienst 22:00–06:00. | Start-Eintrag 22:00, Stop-Eintrag 06:00 Folgetag. ArbZG-Ruhezeit-Check auf letzten Stop, nicht auf Kalendertag. Manager-Report zeigt beide Kalendertage anteilig (5h / 4h). |
| EC-03 | Multi-Tenant-Bleed — Hannes ist zusätzlich im shk-gebruder-schmidt als Leiharbeiter. | JWT enthält aktiven tenant_id. Switch im Web = neue Session, RLS schneidet Daten hart. Query auf time-entry-ID eines anderen Tenants liefert 404 (nicht 403). |
| EC-04 | Sync-Konflikt — Hannes stempelt auf Smartphone (offline) und zugleich auf Werkstatt-Tablet. | Beide landen mit unterschiedlichen Idempotency-Keys in DB. System erkennt „zwei aktive Starts ohne dazwischenliegenden Stop“ und erzeugt Manager-Ticket. Hannes bekommt Push. |
| EC-05 | Großmenge — Buchhaltung exportiert 3 Jahre × 80 MA × ~220 Tage CSV. | Export via Async-Job + BullMQ. Antwort sofort 202 + Job-ID; Download-Link nach Abschluss (S3 presigned, 24 h). CSV in Chunks (50k Zeilen), signiert. |
| EC-06 | Abgelaufenes Zertifikat (z.B. DGUV-G25) — Mitarbeiter wird für Arbeit eingeplant. | Stempeln bleibt möglich (ArbZG / Entlohnung nicht blockierbar). Warnung im Verlauf + Manager-Ticket. Schicht-Plan blockiert separat — siehe kern/08-dienstplanung. |
| EC-07 | Plan-Limit — Starter-Tenant, 11. User versucht sich einzuloggen. | Auth-Schicht blockiert vor Stempeln; Stempeln unbetroffen. NFC-Ausweis des 11. Users liefert HTTP 403 plan_limit_exceeded. |
| EC-08 | Rollen-Demotion — Admin verliert Admin-Rolle mitten im Bulk-Month-Close. | Hono-Middleware prüft Scope pro Request. Laufende Transaktion wird auf nächstem Batch 401 beantwortet, bereits geschlossene Monate bleiben geschlossen, Audit-Log erfasst Abbruch. |
| EC-09 | Sommerzeit-Umstellung 29.03.2026 02:00 → 03:00. | UTC-Speicherung → keine Doppel- oder Fehlstunde. Anzeige zeigt tatsächliche Wanduhr-Zeiten. Property-Test C-05 prüft Bit-Identität. |
| EC-10 | NFC-Ausweis geklaut. Admin sperrt über Web. | nfc_badges.status = revoked → Kiosk-Endpoint liefert 403, Fallback-Flow „Ausweis gesperrt, melde dich bei deinem Vorarbeiter“. Audit-Log. |
| EC-11 | Handy-Uhr manipuliert (User stellt Uhr 2h zurück). | Server-Zeit ist Wahrheit (occurredAt wird serverseitig validiert: ` |
| EC-12 | Sperrung während offenem Stempel. Monat wird freitags geschlossen, Hannes ist noch eingestempelt. | Stop im geschlossenen Monat ist möglich (letzte Aktion des Tages), aber Korrekturen blockiert. Admin kann per „Einmal öffnen“-Workflow (mit Begründung, Audit) freischalten. |
12. Akzeptanzkriterien (Gherkin)
Abschnitt betitelt „12. Akzeptanzkriterien (Gherkin)“Funktionalität: Zeiterfassung — Stempeln, Offline, Korrektur, Sperre
Hintergrund: Angenommen ein Tenant "musterbetrieb-maler" mit aktivem Modul-Gate "module.kern.zeiterfassung" Und ein Mitarbeiter "Hannes Krüger" mit Rolle "Mitarbeiter" und Scope "own" Und eine Baustelle "Lehrer Allee 7, München" Und das Datum ist "Mo 19.04.2026"
Szenario: Happy Path — Einstempeln mit einem Tipp Wenn Hannes die App öffnet und auf den Stempel-Button tippt Dann wird ein Time-Entry "start" mit Zeitart "Arbeit" gespeichert Und die UI zeigt "Eingestempelt · 07:02" Und der Audit-Log enthält "time.entry.created" mit Aktor "Hannes Krüger" und Idempotency-Key
Szenario: Grenzfall — ArbZG §3 Höchstarbeitszeit (BAG-Urteil 1 ABR 22/21) Angenommen Hannes hat heute bereits 9:30 h gearbeitet Wenn er um 16:32 auf Stempel "weiter" tippt und bis 17:30 arbeitet Dann zeigt die UI eine Warnung "ArbZG §3 — Höchstarbeitszeit erreicht" Und die Buchung wird trotzdem gespeichert Und das Feld "arbzg_flags.hoechstarbeitszeit_ueberschritten" ist true Und im Manager-Dashboard erscheint ein Ticket "ArbZG-Warnung Hannes Krüger"
Szenario: Grenzfall — Tageswechsel 00:00 bei Nachtschicht (§5 ArbZG Ruhezeit) Angenommen Hannes hat am 18.04.2026 um 22:00 gestartet Wenn er am 19.04.2026 um 06:00 ausstempelt Dann wird der Stop-Eintrag auf den 19.04.2026 datiert Und die ArbZG-Prüfung zeigt beim nächsten Start an 19.04. um 18:00 eine Ruhezeit von 12 h Und wenn er stattdessen bereits um 16:00 startet, erscheint Warnung "Ruhezeit 10 h statt 11 h"
Szenario: Grenzfall — Offline-Stempelung mit späterem Sync Angenommen Hannes ist offline (Heizungskeller) Wenn er einstempelt und 4 h später ausstempelt, noch offline Dann werden beide Entries lokal in der Drift-Outbox mit UUID-v7 gespeichert Und sobald die App wieder Empfang hat, werden beide mit identischem Idempotency-Key hochgeladen Und der Server antwortet "201 Created" beim ersten und "409 Idempotent Replay" beim zweiten identischen Call Und keine Duplikate entstehen
Szenario: Grenzfall — Multi-Tenant-Isolation (kern/LESSONS-LEARNED §2) Angenommen ein zweiter Tenant "shk-gebruder-schmidt" mit Mitarbeiter "Thomas Schmidt" Wenn Hannes (Tenant A) per ID die Time-Entries von Thomas (Tenant B) abruft Dann antwortet die API mit "404 Not Found" Und es entsteht KEIN Audit-Log-Eintrag im Tenant B Und die RLS-Policy `te_tenant_iso` hat den Zugriff auf DB-Ebene verhindert
Szenario: Grenzfall — Korrektur mit GoBD-Begründung Angenommen Hannes hat am 16.04.2026 vergessen einzustempeln Wenn er am 17.04.2026 die Stempelung für 16.04. um 06:45 nachträgt, ohne Begründung Dann antwortet das System mit "422 — Begründung ist Pflichtfeld (GoBD §145 AO)" Wenn er als Begründung "Vergessen einzustempeln, reale Ankunft 06:45" angibt Dann wird ein neuer Time-Entry mit supersedes_id auf den Original-Eintrag erzeugt Und das Audit-Log enthält "time.entry.corrected" mit Begründung
Szenario: Grenzfall — Sperre nach Monatsabschluss Angenommen Frau Müller (Buchhaltung, shk-gebruder-schmidt) hat März 2026 gesperrt Wenn Thomas Schmidt versucht, eine Zeitbuchung vom 15.03.2026 zu korrigieren Dann antwortet die API mit "423 Locked" Und die UI bietet "Admin-Freigabe anfordern" mit Begründungs-Pflichtfeld13. Test-Cases
Abschnitt betitelt „13. Test-Cases“13.1 Unit-Tests
Abschnitt betitelt „13.1 Unit-Tests“- U-01 —
ArbzgChecker.pause(durationHours)— erwartet 0 min < 6h, 30 min ≥ 6h, 45 min ≥ 9h. - U-02 —
TimeEntryValidator.rundung(timestamp, rule)— prüft alle 4 Rundungsmodi. - U-03 — Property-based (glados): für beliebige Stempel-Ketten
start, pause*, stopgiltsumArbeit == stop - start - sumPause. - U-04 —
HashChain.verify(entries)— erkennt Manipulation einer Zeile (Hash-Bruch an Position i). - U-05 —
IdempotencyStore.insert(key) × 2— zweiter Call idempotent.
13.2 Widget-/Component-Tests (Flutter)
Abschnitt betitelt „13.2 Widget-/Component-Tests (Flutter)“- W-01 — Stempel-Button disabled wenn
connectivity == none && offlineAllowed == false(Kiosk-Modus). - W-02 — Golden-Test
zeit/stempel-startLight/Dark × iOS/Android ×de-DE. - W-03 — Semantics:
SemanticsTester.findByLabel('Einstempeln')muss Treffer liefern. - W-04 — ArbZG-Warnung erscheint bei Saldo > 9 h; Focus-Order korrekt.
13.3 Integrations-Tests
Abschnitt betitelt „13.3 Integrations-Tests“- I-01 — Multi-Tenant-Isolation (Pflicht DOD §2.2): Tenant A schreibt Entry, Tenant B bekommt 0 Zeilen auf
SELECT *. - I-02 — RLS
own: Mitarbeiter A bekommt nur eigene Entries, nicht die des Kollegen B im gleichen Tenant. - I-03 — Outbox-Idempotenz: Doppelter Call mit identischem Key → 1 DB-Eintrag, 2. Antwort = 1. (Replay).
- I-04 — Korrektur ohne Begründung → 422; mit Begründung → 201 + supersedes_id gesetzt.
- I-05 — Monats-Sperre blockiert
POST correct; Admin-Override erlaubt mit Audit. - I-06 — Hash-Chain: manipuliere DB-Zeile direkt →
GET /entries/{id}+?verify=trueliefertintegrity_break.
13.4 E2E-Tests (Patrol + Playwright, Pre-Merge-Matrix)
Abschnitt betitelt „13.4 E2E-Tests (Patrol + Playwright, Pre-Merge-Matrix)“- E-01 — Happy-Path Einstempeln (Mobile iOS + Android + Web Chromium/Firefox/WebKit).
- E-02 — Offline-Pfad (Patrol
network off → stempeln → stempeln → network on → sync). - E-03 — Visuelle Regression: Stempel-Start + ArbZG-Warnung + Korrektur-Dialog.
- E-04 — Accessibility: axe-core Critical/Serious = 0; Flutter Semantics-Test grün.
- E-05 — NFC-Simulation (Patrol mockt
nfc_managerEvent).
13.5 Compliance-Tests
Abschnitt betitelt „13.5 Compliance-Tests“- C-01 — Hash-Chain-Integrität: Manipulation führt zu Detektion.
- C-02 — DATEV-CSV-Export: Valider Import in DATEV-Muster-Tenant (DATEV-Validator CLI).
- C-05 — ArbZG-Property: Random
start/stop/pause-Ketten + Tenant-Overrides → erwartete Flags bitgenau. - C-06 — Sommerzeit-Umstellung (29.03. und 25.10.2026) → UTC- und Anzeige-Konsistenz.
13.6 Flakiness-Schutz
Abschnitt betitelt „13.6 Flakiness-Schutz“Lokaler 20×-Lauf E-01, E-02, E-05 grün vor Merge (gem. DOD §2.2).
14. Nicht-Ziele
Abschnitt betitelt „14. Nicht-Ziele“| Nicht-Ziel | Begründung |
|---|---|
| Geofencing mit Auto-Stempeln auf Betreten/Verlassen der Baustelle | BetrVG §87(1)6 sehr streng; Reputationsrisiko. Frühestens V2 mit BR-Workflow + konkret anfragendem Kunden. |
| Aktives GPS-Tracking während Stempelphase | Datenminimierung (DSGVO Art. 5). Wir speichern nur einen Hint-Punkt bei Start/Stop. |
| Fingerabdruck-/Gesicht-Scan an der Kiosk-Stempeluhr | Biometrische Daten = besondere Kategorie Art. 9 DSGVO, separate DSFA, BR-Pflicht, disproportional für Handwerk. NFC reicht. |
| Automatische Stundenzettel-Unterschrift durch Vorgesetzten | Liegt in handwerk/03-stundennachweis. Zeiterfassung erzeugt nur Rohdaten. |
| SMS-Benachrichtigung bei ArbZG-Warnung | BSI ORP.4.A23 — SMS gilt als unsicher; wir nutzen Push + E-Mail. |
| KI-Zeitbuchung aus Kalender (Meeting → Zeit) | §5 FUNKTIONSUMFANG — V3 Werkszeit Office. Nicht Handwerks-relevant. |
| Excel-Import historischer Zeitdaten | Migrationstool als separates CSV-Import-Feature (V1); nicht im Stempel-MVP. |
| DATEV SmartLogin Direkt-Anbindung | Laut FUNKTIONSUMFANG §3.10 erst V2 nach DATEV-Zertifizierung. Im MVP CSV, V1 LODAS-Datei. |
15. Risiken & offene Annahmen
Abschnitt betitelt „15. Risiken & offene Annahmen“| Risiko / Annahme | Impact | Wahrscheinlichkeit | Gegenmaßnahme |
|---|---|---|---|
Safari-iOS Storage-Löschung (Flutter-Web sqlite3.wasm im Private-Mode) |
mittel | mittel | Persistent-Storage-Prompt + Fallback auf In-Memory + Warnung; Mobile-App als primärer Stempel-Kanal positioniert. |
| NFC-Ausweis-Kompatibilität MIFARE vs. LEGIC unterschiedlich bei Kunden-Altbestand | mittel | mittel | MVP nur MIFARE DESFire (Standard); LEGIC via Fremd-Reader-Import (V1 bei Bedarf). |
| BAG-Urteil-Auslegung „vollständig erfassen“ — reicht Stempelung oder muss Pause inklusive Dauer? | hoch | niedrig | Pause als eigener Entry-Kind; Property-Test deckt alle Kombinationen ab; Legal-Review vor GA. |
| Voice-STT-Erkennungsrate bei bayerischem/niederrheinischem Dialekt | mittel | hoch | On-device Modell + deutliches „Fallback-auf-Touch“-UX; Voice ist V1, nicht MVP. |
| Rundungs-Konflikt Kunde besteht auf 15-min-Rundung, BAG-Vorgabe verbietet es implizit | hoch | mittel | Rundung default aus, Admin-Warnung beim Aktivieren, Legal-Opinion-Link im UI. |
| Performance 80-MA-Tenant mit 2000 Entries/Woche → Dashboard-Query | mittel | mittel | Indizes (tenant, occurred desc) + (tenant, user, occurred); Load-Test gegen Seed-Tenant B; Ziel p95 < 500 ms. |
| Idempotency-Key-Kollision (theoretisch UUID v7) | hoch | sehr niedrig | Unique-Index pro Tenant; Monitoring auf Konflikte; Recovery-Pfad dokumentiert. |
16. Abhängigkeiten
Abschnitt betitelt „16. Abhängigkeiten“- Vorbedingung:
kern/09-authentifizierung(Login, Session, Scope-Token), Drizzle-Migrations-Runner, Audit-Log-Service, Feiertags-Tabelle. - Schnittstelle zu:
kern/02-arbeitszeit-compliance— ArbZG-Live-Prüfung konsumiert Entries als Stream; setztarbzg_flags.kern/03-abwesenheiten— Krank/Urlaub setzt einen „Ganztags-Entry“ der Artabwesenheit, die den Tag als erfasst gilt.kern/08-dienstplanung— Schicht-Vorgabe liefert Soll-Stunden; Live-Dashboard vergleicht Ist vs. Soll.kern/10-datev-integration— CSV/LODAS-Export zieht austime_entries+time_entries_arbzg_flags.
- Wird konsumiert von:
handwerk/03-stundennachweis,kern/07-reporting,handwerk/04-bautagebuch(Zeit-Referenz pro Eintrag).
17. Referenzkunde-Slot
Abschnitt betitelt „17. Referenzkunde-Slot“Status: TBD — zu klären mit Sales bis Sprint-Start.
DOR §1.1.1 verlangt: mindestens ein zahlender Design-Partner, der das Feature konkret fordert.
Kandidaten-Profile:
- Malerbetrieb 15–30 MA, Bayern/BW, aktuell Excel + WhatsApp; Motiv: BAG-Deadline-Angst, Kammer-Empfehlung.
- SHK-Betrieb 50–100 MA, NRW; aktuell Zeit Go oder Clockodo, Motiv: fehlende Offline-Qualität, GAEB-Integration in Aussicht.
- Dachdeckerei 10–20 MA, Niedersachsen; Motiv: NFC am Werkstatt-Tablet, Azubi-Quote hoch, Smartphone-Fremd.
Validierungs-Fragen für das Erst-Gespräch:
- Wie erfassen Sie heute Arbeitszeit — Papier, Excel, Tool X? Wie viele Minuten Aufwand pro MA pro Woche?
- Welche Szenarien brechen das heute — Heizungskeller-Offline, Handschuhe, Monteure ohne Firmen-Smartphone?
- Haben Sie Betriebsrat? Wie aktiv ist der bei Geo-/KI-Themen?
- Lohnabrechnung über welches DATEV-Produkt (LODAS / L+G / direkt beim Steuerberater)?
- Sind Sie bereit, 30 Tage im Beta-Status zu testen gegen 50 % Preis-Lock-in im ersten Jahr?
18. Senior-Berater-Empfehlung
Abschnitt betitelt „18. Senior-Berater-Empfehlung“Build-Sequenz (innerhalb des Features):
- Datenmodell + RLS + Hash-Chain + Multi-Tenant-Test. Alles andere steht auf Sand.
- Backend
POST /entries+ Idempotency-Store + Outbox-Replay. Minimal-API, durch E2E Patrol + Curl abgedeckt. - Flutter-Client Stempel-Screen + Drift-Outbox + Sync-Loop. Mobile first, Web teilt denselben Code.
- NFC-Kiosk-Flow. Lieferbar als zweiter Screen am Tablet.
- Monats-Sperre + DATEV-CSV-Export. Buchhaltungs-Anforderung, ohne die kein Kunde bezahlt.
- ArbZG-Flags + Live-Warnungen (Koordination mit
kern/02, dort liegt die Policy-Engine). - Compliance-Tests (C-01 Hash, C-05 Property) + Golden-Screenshots.
- Voice + GPS-Vorschlag mit BetrVG-Workflow (V1).
Risiko-Reihenfolge (was zuerst absichern bei Zeitnot):
- Multi-Tenant-Isolation und Hash-Chain sind nicht verhandelbar — fallen nie raus.
- Bulk-Tabellen-Eingabe (F-W-02), Voice (F-M-05), Monats-Kalender-Polish sind die ersten Streich-Kandidaten.
Stop-the-Bus-Triggers:
- Multi-Tenant-Bleed in einem Integrationstest → Deploy stoppen.
- Hash-Chain-Bruch in C-01 → Incident, Post-Mortem, Rollback.
- p95 Team-Dashboard > 2 s bei Seed-Tenant B → ECS-Scale / Query-Tuning, Feature-Flag-Rollout anhalten.
- DSGVO Art. 17-Pfad kollidiert mit GoBD-Aufbewahrung ohne Maskierungs-Strategie → Legal + Tech-Lead + PO.
Was uns 2027 dankbar macht:
Jede Zeitbuchung hat ab Tag 1 einen UUID-v7-Idempotency-Key und einen Platz in einer Hash-Chain pro User. Diese beiden Entscheidungen kosten im MVP wenige Zeilen Code und machen uns 2027 bei DATEV-Zertifizierung, GoBD-Audit, BSI-C5 und jedem Disaster-Recovery zu dankbaren Menschen. Der Alt-App-Fehler — „WHERE tenant_id = ?“ als einzige Leitplanke — ist hier durch Postgres-RLS + Multi-Tenant-Test-Invariante + Hash-Chain gleich dreifach abgesichert.
Feinkonzept-Stand: 2026-04-19 · Werkszeit Konzeptions-Team · Greenfield Phase 1 · Version v0.1
Für Entwickler — API-Endpoints13
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
| GET | /v1/kern/zeit/employees | bearerAuth | Mitarbeiter-Übersicht mit Filter, Pagination, Aggregation |
| GET | /v1/kern/zeit/entries | bearerAuth | Time-Entries auflisten (cursor-paginiert) |
| POST | /v1/kern/zeit/entries | bearerAuth | Stempeln (start/stop/pause) |
| GET | /v1/kern/zeit/entries/{id} | bearerAuth | Time-Entry-Detail inkl. Korrektur-Kette |
| POST | /v1/kern/zeit/entries/{id}/approve | bearerAuth | Manager/Admin Freigabe eines Time-Entry |
| POST | /v1/kern/zeit/entries/{id}/correct | bearerAuth | Korrektur eines Time-Entry als neue Version |
| POST | /v1/kern/zeit/entries/{id}/reject | bearerAuth | Manager/Admin Ablehnung eines Time-Entry mit Begründung |
| POST | /v1/kern/zeit/exports/datev-csv | bearerAuth | DATEV-CSV-Export (async, BullMQ-Job) |
| GET | /v1/kern/zeit/exports/datev-csv/{jobId} | bearerAuth | DATEV-CSV-Export-Job-Status (Polling) |
| GET | /v1/kern/zeit/me | bearerAuth | Live-Timer + Heute/Woche/Monat-Aggregat für eingeloggten User |
| POST | /v1/kern/zeit/month-close | bearerAuth | Monatsabschluss sperren |
| POST | /v1/kern/zeit/nfc-stempel | bearerAuth | Kiosk-NFC-Stempeln (HMAC-badgeUid) |
| GET | /v1/kern/zeit/status/team | bearerAuth | SSE-Stream Live-Status des Teams |