Zeiterfassung
Zweck
Zeiterfassung dokumentiert geleistete Arbeitszeit pro Mitarbeiter — entweder global (Anwesenheit) oder im Kontext eines Auftrags (Leistungs-Zeit). Pro Eintrag pflegen Sie Start, Ende, optionalen Service (Was wurde erbracht?), Billable-Flag (Verrechenbar?) und Kommentar.
Voraussetzungen
Berechtigungen (CASL)
| Action | Subject | Wirkung | Keycloak-Rolle |
|---|---|---|---|
view | FE_EmployeeTimeTracking, EmployeeTimeTracking | Liste/Details aufrufbar | — |
create | FullEmployeeTimeTracking | Neue Zeit erfassen | APP_SPEAMCORE_CREATE_FULL_EMPLOYEE_TIME_TRACKING |
update/delete | EmployeeTimeTracking | Eigene oder fremde Zeiten ändern | APP_SPEAMCORE_UPDATE/DELETE_EMPLOYEE_TIME_TRACKING |
Schritt-für-Schritt-Anleitung
Zeit erfassen
- Zeiterfassung (
/employee-time-trackings) → + Neu. - Im Modal
FullTimeTrackingForm:- Mitarbeiter wählen.
- Parent-Kontext wählen:
Employee(Anwesenheits-Zeit) oderWorkorder(Leistungs-Zeit). Bei Workorder-Auswahl Auftrag mit auswählen. - Start-Zeit (Pflicht).
- End-Zeit (optional — leer lassen für „laufende" Zeitmessung).
- Service (optional, für Leistungs-Klassifikation).
- Verrechenbar (Switch).
- Kommentar (optional).
- Speichern. System prueft Doppelbuchungen (offene Records) und Workorder-Lock.
Zeit bearbeiten
Eintrag in der Liste öffnen → Modal mit denselben Feldern.
![]()
Überlauf-Schutz „Noch da?" (über 10 Stunden)
Läuft eine Zeiterfassung ohne Ende sehr lange, greift seit Juni 2026 ein Überlauf-Schutz — er verhindert vergessene laufende Zeiten und unterstützt die Arbeitszeit-Dokumentation (ArbZG).
- Ein globaler Watcher im Frontend ruft alle 60 Sekunden die laufende Zeit ab (
GET /employees/:id/active-time-tracking). - Überschreitet die offene Zeit 10 Stunden und wurde sie nicht innerhalb der letzten Stunde bestätigt, erscheint ein „Noch da?"-Hinweis mit 5-Minuten-Countdown und (einmalig je Sitzung) eine Browser-Benachrichtigung.
- Zwei Aktionen:
- Ja, noch da →
POST …/confirm-overrun— setztoverrunConfirmedAt; danach 1 Stunde Ruhe. - Stoppen →
POST …/stop-overrun— beendet die Zeit.
- Ja, noch da →
- Failsafe im Backend: Ein Minuten-Cron erkennt überlange offene Zeiten unabhängig vom Browser und sendet eine Push-Benachrichtigung an die Geräte des Mitarbeiters.
Toolbar (Detail-Seite)
Schlanke Toolbar oben rechts:
| Icon | Aktion (aria-label) | CASL | Wirkung |
|---|---|---|---|
| ← | Zurückgehen | — | Zurück zur Liste. |
| 🏠 | Zur Startseite gehen | — | Springt auf das Dashboard / /. |
| ⏮/◀/▶/⏭ | Pagination | — | Navigation durch die gefilterte Liste — Massen-Bearbeitung ohne Liste-Sprung. |
Globale Floating-Drawer (links)
Wie auf jeder Detail-Seite verfuegbar — siehe Floating-Quickbar:
- KAL. (Mini-Kalender)
- ZEIT (Persoenliche Wochen-Arbeitszeit)
- ARBEIT (Eigene bevorstehende Aufträge)
Felder und Eingaben
| Feldname | Pflicht | Datentyp | Beschreibung | Wirkung beim Ausfuellen | Voraussetzung |
|---|---|---|---|---|---|
employeeId | ja | UUID (Lookup) | Mitarbeiter, dem die Zeit zugeordnet wird. | Wird auf Anwesenheits- und Auftrags-Reports angerechnet. | view:Employee |
parentType | ja | Enum | Employee (Anwesenheit) oder Workorder (Leistung). | Steuert, gegen welchen Datensatz die Zeit verbucht wird. | — |
parentId | ja | UUID | ID des Parent-Datensatzes. | — | Wenn Workorder: view:Workorder und Auftrag nicht gesperrt. |
startTime | ja | DateTime | Beginn der Zeit. | Anker für Sortierung und Tages-/Monats-Aggregation. | — |
endTime | nein | DateTime | Ende der Zeit. Leer = laufende Zeit. | Wenn leer, gilt der Eintrag als „offen" — Doppelbuchungen pro Mitarbeiter werden geblockt. | — |
serviceId | nein | UUID (Lookup) | Service-Zuordnung für Leistungs-Auswertung. | Beeinflusst Reports und Auswertungen pro Service. | view:Service |
billable | nein | Boolean (Switch) | Verrechenbar gegenueber Kunde. | Wenn true: kann auf einen Verkaufsbeleg übernommen werden. | — |
comment | nein | TEXT | Optionaler manueller Kommentar. | Erscheint in Reports und auf der Workorder-Detail-Seite. | — |
autoComment | — | String (read-only) | Automatisch befüllter Kommentar (Mai 2026), getrennt vom manuellen comment. Quelle: In-App-Aktivität (nur bei Einwilligung) bzw. Speam-intern GitLab-Commits. | Überschreibt comment nie; reine Lese-Hilfe. | Employee.activityTrackingConsent für den Activity-Provider. |
employeeTimeTrackingTypeId | nein | UUID | Klassifizierungs-Stammdatum (z. B. „Reise", „Pause"). | Wird in Reports gruppiert. | — |
overrunPromptedAt | — | DateTime (read-only) | Zeitpunkt der letzten „Noch da?"-Aufforderung. | gesetzt vom Überlauf-Schutz | — |
overrunConfirmedAt | — | DateTime (read-only) | Zeitpunkt der letzten Bestätigung durch den Mitarbeiter (danach 1 h Ruhe). | gesetzt bei „Ja, noch da" | — |
Wiederverwendbare Konzepte
- Polymorpher Parent-Pattern —
parentType ∈ {Employee, Workorder}. - Berechtigungen verstehen (CASL)
Verknuepfungen zu anderen Modulen
- Mitarbeiter — Tab
Zeiterfassungzeigt eigene Zeiten. - Aufträge — Tab
Arbeitszeitzeigt alle Trackings dieses Auftrags. - WorkorderEmployee — wird automatisch erzeugt/reaktiviert, wenn eine Zeit zu einem Auftrag erfasst wird.
- Verkaufsbelege —
billable-Trackings koennen in Belegpositionen übernommen werden.
Häufige Fehler und Lösungen
| Fehler | Lösung |
|---|---|
| „Doppelbuchung" beim Speichern | Bestehender offener Eintrag (endTime = null) — vorherige Zeit erst beenden. |
| Workorder-Tracking blockiert | Auftrag ist gesperrt (locked = true) — Auftrag entsperren oder andere Workorder wählen. |
endTime vor startTime | Validierung lehnt das ab. Werte korrigieren. |
API/Schnittstellen
| Methode | Endpoint | Zweck | CASL |
|---|---|---|---|
GET | /api/employees/time-trackings | Liste | view EmployeeTimeTracking |
POST | /api/employees/time-tracking/:parentType/:parentId/:employeeId | Anlegen mit Auto-Ensure WorkorderEmployee | create FullEmployeeTimeTracking |
PATCH | /api/employees/time-tracking/:id | Ändern (mit Lock-Check) | update EmployeeTimeTracking |
DELETE | /api/employees/time-tracking/:id | Soft-Delete | delete EmployeeTimeTracking |
GET | /api/employees/:employeeId/active-time-tracking | laufende Zeit für den „Noch da?"-Watcher | view EmployeeTimeTracking |
POST | /api/employees/:employeeId/time-tracking/:id/confirm-overrun | Überlauf bestätigen | update EmployeeTimeTracking |
POST | /api/employees/:employeeId/time-tracking/:id/stop-overrun | Überlauf-Zeit stoppen | update EmployeeTimeTracking |
UI — RefLink-Spalte (Welle 132)
Jede Zeile der Liste hat in der Actions-Spalte ein Sprung-zum-Kontext-Icon (FiExternalLink):
parentType = Workorder→ Klick öffnet/workorders/:idim Detail (CASL-Voraussetzungview:Workorder)parentType = Employee→ Klick öffnet/employees/:id(view:Employee)
Label entsprechend „Gehe zu Workorder" oder „Gehe zu Mitarbeiter" (i18n). Die Spalte ist 140 px breit und steht vor den Edit/Delete-Actions. Backend-Query nutzt include=workorder.NumberCircleAssignment, damit die Workorder-Nummer in der Tabelle korrekt rendert.
Versionshinweise
-
2026-06-22: Überlauf-Schutz „Noch da?" dokumentiert — globaler Watcher (60 s), 10-Stunden-Schwelle, 5-Minuten-Countdown + Browser-Benachrichtigung, Backend-Failsafe-Cron; neue read-only-Felder
overrunPromptedAt/overrunConfirmedAtund Endpointsactive-time-tracking,confirm-overrun,stop-overrun. Verifiziert anTimeTrackingOverrunWatcher.tsx,timeTrackingOverrun.service.ts,employeeTimeTracking.router.ts,employeeTimeTracking.model.ts. -
2026-05-31: Read-only-Feld
autoCommentergänzt — automatisch befüllter Kommentar (Provider-basiert: Activity bei Einwilligung / GitLab Speam-intern), getrennt vom manuellencomment. Quelle: BEautoComment.service.ts, Migrationadd-auto-comment-to-employee-time-trackings. -
2026-05-20 (Welle 132): Neue Actions-Spalte mit RefLink-Icon zum verknüpften Parent (Workorder oder Employee), basierend auf
parentType. Quelle: FE-Commit3da01a80. -
2026-04-29: Initiale Veroeffentlichung mit FE-Tiefen-Standard.