Zum Inhalt springen

Zeiterfassung — BAG-konforme Stempeluhr mit Offline, NFC und Voice

Live in Produktion

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.


feature_id: kern/01-zeiterfassung
title: Zeiterfassung — BAG-konforme Stempeluhr mit Offline, NFC und Voice
funktionsumfang_ref: §3.1
roadmap_horizont: MVP
plattformen:
mobile: vollständig
web: vollständig
desktop: ab V1.5 bei Bedarf
owner_rolle: Mitarbeiter
modul_gate_flag: module.kern.zeiterfassung
compliance_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 Schichtplan

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.

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).

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.

  • F-M-01Stempel-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-02Quick-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-03Home-Screen-Widget 2×2 mit Start/Stop + aktuellem Saldo. Android Glance + iOS WidgetKit.
  • F-M-04NFC-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-05Voice-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-06Offline-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-07Zeitart-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-08Auto-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-09Baustelle-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-10Manuelle Korrektur einer Buchung (eigene, ≤ 7 Tage alt, vor Monatsabschluss): Begründungs-Pflichtfeld. Erzeugt neue versionierte Buchung, die Vorgänger als superseded_by markiert. Alte Zeile bleibt lesbar (GoBD).
  • F-W-01Team-Dashboard mit Live-Status (WebSocket + SSE-Fallback). Je Mitarbeiter: Status (stempelt / Pause / abwesend / geplant), Baustelle, Saldo heute, Saldo Woche.
  • F-W-02Tabellen-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-03Kalender-Monatssicht mit Farbkodierung (grün = gestempelt, gelb = ArbZG-Warnung, rot = Abwesenheit, grau = Wochenende / Feiertag).
  • F-W-04Detail-Ansicht Buchung zeigt Audit-Trail: ursprünglich angelegt, korrigiert durch X mit Grund Y, freigegeben durch Z.
  • F-W-05CSV-Export (MVP) mit ArbZG-Flags. DATEV-LODAS-Export (V1) siehe §3.10 Funktionsumfang.
  • F-W-06Monatsabschluss-Workflow: Entwurf → Korrektur → Freigabe → Sperre. Ab Sperre: nur noch Admin + Audit-Eintrag.
  • F-X-01Zeitarten-Auswahl aus Tenant-Stammdaten (kein hardcoded Enum).
  • F-X-02Projekt-/Baustellen-Zuordnung via Autocomplete, sortiert nach letzter Nutzung + GPS-Nähe.
  • F-X-03Tätigkeitsbeschreibung Freitext (160 Zeichen), optional Voice-Transkript.
  • F-X-04Rundungsregel tenant-konfigurierbar: keine / 1-min / 5-min / 15-min (Standard: keine, um BAG-Vorgabe „vollständig erfassen“ nicht zu unterlaufen).
  • F-X-05Feiertags-Warnung Bundesland-abhängig (Bayern, NRW, … — intl-Locale + eigene Feiertags-Tabelle inkl. regional Fronleichnam u.a.).
  • F-A-01Zeitarten anlegen/deaktivieren/sortieren. Default-Zeitart pro Rolle konfigurierbar.
  • F-A-02NFC-Ausweise registrieren (UID → User-ID Mapping), sperren, ersetzen.
  • F-A-03Rundungsregel auswählen (+ Warnhinweis bei >5 min: potenziell BAG-kritisch).
  • F-A-04Monatsabschluss für Zeitraum durchführen (nur Rolle Buchhaltung oder Admin).
  • F-A-05BetrVG-Freischaltung: GPS-Vorschlag + Voice + Anomalie-KI sind default aus und benötigen eine dokumentierte Betriebsrat-Zustimmung (siehe §10.6).
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

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] │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ✕ 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 ] │
└────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ 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 │ │
└────────────┴───────────────────────────────────────────────────────────────────┘
┌────────────────────────────────┐
│ 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 [ ____ ] │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 🛜 Kein Empfang — Buchungen │
│ werden synchronisiert, sobald│
│ die Verbindung zurück ist. │
│ Warteschlange: 2 Einträge │
│ [Details] │
└────────────────────────────────┘

apps/api/src/db/schema/time-entries.ts
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 supersede

ER-Bezug. Verwendet users, tenants, projects, sites, team_memberships, audit_log. Feiertagskalender liegt in holidays (tenant-scoped + Bundesland).


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.created
  • time.entry.corrected
  • time.month_closed
  • time.arbzg_warning (Broadcast an Manager-Abonnenten)

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.


  • Append-only-Tabelle time_entries mit hash_prev + hash_self = SHA-256 über kanonisierte Zeile (Felder sortiert, UTC-normalisiert). Beim Insert wird hash_prev auf den hash_self des 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 Felder activityText und geoHint werden 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-Eintrag time.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.
  • §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), mit arbzg_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“.

— 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.

  • 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 Job dsgvo.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.
  • 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 #FF6B1A auf Anthrazit #1A1F2B = 6.4:1. Warnungen #C77A0A auf #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.

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.

— nicht zutreffend (Zeiterfassung betrifft weder §13b UStG, §48 EStG, SOKA-Bau, DGUV noch StVG; diese kommen in anderen Feinkonzepten zum Tragen).


# 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.

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-Pflichtfeld

  • U-01ArbzgChecker.pause(durationHours) — erwartet 0 min < 6h, 30 min ≥ 6h, 45 min ≥ 9h.
  • U-02TimeEntryValidator.rundung(timestamp, rule) — prüft alle 4 Rundungsmodi.
  • U-03 — Property-based (glados): für beliebige Stempel-Ketten start, pause*, stop gilt sumArbeit == stop - start - sumPause.
  • U-04HashChain.verify(entries) — erkennt Manipulation einer Zeile (Hash-Bruch an Position i).
  • U-05IdempotencyStore.insert(key) × 2 — zweiter Call idempotent.
  • W-01 — Stempel-Button disabled wenn connectivity == none && offlineAllowed == false (Kiosk-Modus).
  • W-02 — Golden-Test zeit/stempel-start Light/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.
  • I-01Multi-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=true liefert integrity_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_manager Event).
  • 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.

Lokaler 20×-Lauf E-01, E-02, E-05 grün vor Merge (gem. DOD §2.2).


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.

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.

  • 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; setzt arbzg_flags.
    • kern/03-abwesenheiten — Krank/Urlaub setzt einen „Ganztags-Entry“ der Art abwesenheit, 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 aus time_entries + time_entries_arbzg_flags.
  • Wird konsumiert von: handwerk/03-stundennachweis, kern/07-reporting, handwerk/04-bautagebuch (Zeit-Referenz pro Eintrag).

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:

  1. Wie erfassen Sie heute Arbeitszeit — Papier, Excel, Tool X? Wie viele Minuten Aufwand pro MA pro Woche?
  2. Welche Szenarien brechen das heute — Heizungskeller-Offline, Handschuhe, Monteure ohne Firmen-Smartphone?
  3. Haben Sie Betriebsrat? Wie aktiv ist der bei Geo-/KI-Themen?
  4. Lohnabrechnung über welches DATEV-Produkt (LODAS / L+G / direkt beim Steuerberater)?
  5. Sind Sie bereit, 30 Tage im Beta-Status zu testen gegen 50 % Preis-Lock-in im ersten Jahr?

Build-Sequenz (innerhalb des Features):

  1. Datenmodell + RLS + Hash-Chain + Multi-Tenant-Test. Alles andere steht auf Sand.
  2. Backend POST /entries + Idempotency-Store + Outbox-Replay. Minimal-API, durch E2E Patrol + Curl abgedeckt.
  3. Flutter-Client Stempel-Screen + Drift-Outbox + Sync-Loop. Mobile first, Web teilt denselben Code.
  4. NFC-Kiosk-Flow. Lieferbar als zweiter Screen am Tablet.
  5. Monats-Sperre + DATEV-CSV-Export. Buchhaltungs-Anforderung, ohne die kein Kunde bezahlt.
  6. ArbZG-Flags + Live-Warnungen (Koordination mit kern/02, dort liegt die Policy-Engine).
  7. Compliance-Tests (C-01 Hash, C-05 Property) + Golden-Screenshots.
  8. 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
MethodePfadAuthZweck
GET/v1/kern/zeit/employeesbearerAuthMitarbeiter-Übersicht mit Filter, Pagination, Aggregation
GET/v1/kern/zeit/entriesbearerAuthTime-Entries auflisten (cursor-paginiert)
POST/v1/kern/zeit/entriesbearerAuthStempeln (start/stop/pause)
GET/v1/kern/zeit/entries/{id}bearerAuthTime-Entry-Detail inkl. Korrektur-Kette
POST/v1/kern/zeit/entries/{id}/approvebearerAuthManager/Admin Freigabe eines Time-Entry
POST/v1/kern/zeit/entries/{id}/correctbearerAuthKorrektur eines Time-Entry als neue Version
POST/v1/kern/zeit/entries/{id}/rejectbearerAuthManager/Admin Ablehnung eines Time-Entry mit Begründung
POST/v1/kern/zeit/exports/datev-csvbearerAuthDATEV-CSV-Export (async, BullMQ-Job)
GET/v1/kern/zeit/exports/datev-csv/{jobId}bearerAuthDATEV-CSV-Export-Job-Status (Polling)
GET/v1/kern/zeit/mebearerAuthLive-Timer + Heute/Woche/Monat-Aggregat für eingeloggten User
POST/v1/kern/zeit/month-closebearerAuthMonatsabschluss sperren
POST/v1/kern/zeit/nfc-stempelbearerAuthKiosk-NFC-Stempeln (HMAC-badgeUid)
GET/v1/kern/zeit/status/teambearerAuthSSE-Stream Live-Status des Teams