Zeit-Übersicht (TimeOverview)
Zweck
/time-overview ist das zentrale Arbeitszeit-Dashboard: Es führt pro Mitarbeiter Zeiterfassungen, Abwesenheiten, das Arbeitszeit-Modell und die §3-ArbZG-Mehrarbeit-Genehmigungen zu einem einheitlichen Saldo zusammen.
Die Seite hat zwei Tabs (Übersicht / Report) mit gemeinsamem Filter-Panel und einen Druck-Dialog für Abwesenheits-Auszüge (z. B. für die Lohnbuchhaltung).
Sub-Routen:
/time-overview— Tab Übersicht (Dashboard mit KPI-Karten und Mitarbeiter-Tabelle)./time-overview/report— Tab Report (TIV-ähnliche Detail-Auswertung pro Mitarbeiter)./time-overview/employee/:employeeId— Mitarbeiter-Detail mit Drill-Down auf einzelne Tage.
Voraussetzungen
Berechtigungen (CASL)
| Action | Subject | Wirkung |
|---|---|---|
view | FE_TimeOverview | Seite aufrufbar |
view | EmployeeTimeTracking, WorkTimeModel, Absence, Employee | Datenquellen |
view | WorkTimeOvertimeApproval | §3-ArbZG-Approvals lesen |
create | WorkTimeOvertimeApproval | Backfill-Endpoint für historische Tage |
Toolbar
Oben rechts auf der Dashboard-Seite:
| Element | Wirkung |
|---|---|
| Übersicht / Report | View-Switch (ButtonGroup). Steuert den aktiven Tab. |
Drucken (TbPrinter) | Öffnet den Abwesenheits-Druck-Dialog. Druckt den Zeitraum aus dem Filter-Panel. |
Filter (TbFilter) | Klappt das Filter-Panel auf/zu. Der Zustand wird pro Browser in localStorage (timeOverview_filterOpen) gemerkt. |
Filter-Panel
Das Filter-Panel ist global für beide Tabs — Übersicht und Report lesen aus derselben State.
| Feld | Verhalten |
|---|---|
| Zeitraum-Preset | Eines aus thisWeek, lastWeek, thisMonth (Default), lastMonth, thisQuarter, thisYear, last4Weeks, custom. |
| Von / Bis | Datums-Inputs. Maximal heute 23:59:59 — Zukunfts-Tage werden gekappt. |
| Mitarbeiter | Async-Select (paginiert). Filtert beide Tabs auf einen einzelnen Mitarbeiter. |
| Abteilung | Async-Select. Filtert auf Mitarbeiter mit aktiver Rolle in der gewählten Abteilung. |
| Niederlassung (seit Welle 132) | Async-Select. Filtert auf Mitarbeiter, die laut aktivem EmployeeContract.branchId der gewählten Niederlassung zugeordnet sind. Backend-seitig als virtueller Query-Param branchId umgesetzt — der branchResolver.service löst zur Vertragsliste auf und übergibt eine id IN (...)-Bedingung an GET /api/employees. |
Cap-Logik der Presets: thisWeek, thisMonth, thisQuarter, thisYear, last4Weeks enden bei heute. lastWeek und lastMonth enden am echten Ende der Vorperiode (für abgeschlossene Auswertungen).
URL-Sync: Die Filter werden direkt in die URL geschrieben (preset, from, to, employeeId, departmentId, branchId). Lesezeichen und Links auf eine bestimmte Sicht sind damit möglich.
Tab Übersicht
KPI-Karten
Sechs feste Stat-Cards plus eine optionale §3-Karte:
| KPI | Berechnung | Ampel |
|---|---|---|
| Mitarbeiter mit Einträgen | Anzahl Mitarbeiter mit recordedWorkHours > 0,001 | grün (100 %), gelb (Teilmenge), rot (0) |
| Erfasste Zeit | workIstSum / workSollSum in % | grün ≥ 95 %, gelb 80–95 %, rot < 80 %, blau > 110 % |
| Saldo | sum(saldoHoursAfterIncluded) über alle Mitarbeiter | neutral (≈ 0), grün (> 0), gelb (kleines Minus), rot (großes Minus) |
| Pflichtpause-Compliance | recordedBreak / breakSoll in %, wenn Soll > 0 | grün (≥ 100 %), gelb (50–100 %), rot (< 50 %), neutral (kein Soll) |
| Tage gesamt | sum(workdaysInPeriod), Sub-Zeile: Fehltage-Quote | konstant lila |
| §3 ausstehend (nur wenn > 0) | sum(pendingOvertimeHours) | gelb (kleine Menge), orange (große Menge) |
Mitarbeiter-Tabelle
Unter den KPIs steht eine MUI-DataGrid-Premium-Tabelle mit einer Zeile pro Mitarbeiter im Filter-Scope. Spalten u. a. (über Spalten-Selector ein-/ausblendbar): Mitarbeiter-Nr., Name, Soll-Stunden, Erfasste Arbeit, Brutto-Anwesenheit, Netto-Ist, Pflichtpause-Soll/Ist, Pausen-Defizit, Saldo, Saldo nach inkludierten Überstunden, ausstehende/genehmigte/abgelehnte Mehrarbeit, Abwesenheits-Tage, Werktage im Zeitraum.
Klick auf eine Zeile öffnet /time-overview/employee/:id mit den aktuellen Filter-Parametern (preset, from, to).
Toolbar der Tabelle: Spalten-Selector, Density-Selector, CSV-Export, Quick-Filter.
Tab Report
Der Report-Tab zeigt eine TIV-ähnliche Detail-Auswertung für einen Mitarbeiter — wahlweise den im Filter ausgewählten oder einen über den eingebetteten Employee-Picker gewählten. Sektionen:
- Evaluation-Details — tagesgenaue Auflistung von Soll/Ist, Pausen, Saldo.
- Mehrarbeit-Approvals — §3-Genehmigungen mit Status und Begründung.
- Tagesübersicht — pro Tag: Zeit-Einträge, Abwesenheiten, Soll-/Ist-Bilanz.
- Pausen-Analyse — Pflicht-Erfüllung gegen ArbZG-Schwellen.
- Plausibilität — Auffälligkeiten (Überlappungen, fehlende Pausen, unrealistische Netto-Zeiten).
Oben rechts im Report-Tab: Print-Button (Browser-Druck), Hilfe-Modal mit Felder-Erklärung, Reload-Button (invalidiert alle Queries).
Default-Zeitraum bei Direktaufruf von /time-overview/report: Montag der Vorwoche bis heute (23:59:59). Vor Welle 113 endete der Default am Sonntag der Vorwoche — die laufende Woche fehlte.
Abwesenheits-Druck (AbrechnungPrintModal)
Der Druck-Dialog erzeugt eine HTML-Druckansicht mit allen Abwesenheiten im Filter-Zeitraum — Mitarbeiter- und Abteilungsfilter werden bewusst ignoriert, damit nichts vergessen wird.
Datenquelle
GET /api/absences
?from=<dateRange.startDate (YYYY-MM-DD)>
&to=<dateRange.endDate (YYYY-MM-DD)>
&filter=status,in,approved|requested
&size=-1
Spalten
| Spalte | Quelle |
|---|---|
| Mitarbeiter-Nr. | employee.employeeNumber |
| Vorname / Nachname | employee.firstName/lastName (Fallback: Parsing aus fullName) |
| Abwesenheit | Lokalisierte type + subtype |
| Anzahl | Berechnet — siehe nächster Abschnitt |
| Start | absence.startDate (DD.MM.YYYY) |
| Ende | absence.endDate (DD.MM.YYYY) |
Anzahl-Berechnung
| Subtyp | Berechnung |
|---|---|
elternzeitMitGeld, elternzeitOhneGeld, mutterschutz, pflegezeit | Kalendertage (Start bis Ende inklusive, inkl. Wochenende) |
| Alle anderen Subtypen | Werktage Mo–Fr inklusive, abzüglich 0,5 bei halfDayStart und/oder halfDayEnd |
Wichtig: Feiertage werden nicht abgezogen — die Liste ist eine Vorschau für die Lohnbuchhaltung, die Feiertage selbst kennt.
Farbcodierung
| Farbe | Hex | Bedingung |
|---|---|---|
| Rot | #c0392b | type === "sick" |
| Blau | #1f3a8a | type === "vacation" (außer den lila Subtypen) |
| Lila | #7c2d8a | Subtyp aus elternzeit*, mutterschutz, pflegezeit |
| Grau | #4a5568 | Default (Training, Berufsschule, Sonstiges) |
Druck-Ablauf
- Modal öffnet sich, zeigt eine sortierte Tabelle (Nachname aufsteigend) zur Kontrolle.
- Drucken-Button erzeugt eine eigene HTML-Seite, öffnet ein neues Browser-Fenster (
1100×800 px), schreibt das HTML hinein und ruft nach kurzemsetTimeoutwindow.print()auf. - Der Druck verzichtet bewusst auf
@media printim Original-DOM — so vermeidet er Stacking-Probleme mit Chakra-UI-Portalen.

Saldo-Modell (Backend)
Seit Welle 113 trennt der Backend-Service workTimeBalance strikt zwischen reiner Arbeitszeit und Brutto-Anwesenheit:
| Feld | Bedeutung |
|---|---|
recordedWorkMinutes | Erfasste Arbeit (roh, vor Korrekturen) |
recordedBreakMinutes | Erfasste Pausen |
bruttoIstMinutes | recordedWorkMinutes + recordedBreakMinutes (Anwesenheit) |
actualMinutes | Reine Arbeitszeit nach §3-ArbZG-Cap (work-only) — Basis für Saldo |
absenceCreditMinutes | Gutschrift für genehmigte Abwesenheits-Tage |
breakDeficitMinutes | Pausen-Soll-Unterschreitung |
balanceMinutes | actualMinutes + absenceCreditMinutes − targetMinutes |
balanceMinutesAfterIncluded | Saldo nach Abzug der im Vertrag inkludierten Mehrarbeit |
Formel:
bruttoIst = recordedWork + recordedBreak
nettoIst = bruttoIst − duplicateDeduction − overlapDeduction
− unrealisticNetDeduction − breakDeficitDeduction
istCorrected = nettoIst + absenceCredit
sollHours = arbeitsrelevanteTage × (workHoursPerDay + breakHoursPerDay)
balance = istCorrected − sollHours
Diese Aufschlüsselung sorgt für eine konsistente SSoT zwischen Dashboard, Report-Tab und Mitarbeiter-Detail — alle drei Sichten rufen denselben Endpoint auf.
Backfill für Mehrarbeit-Genehmigungen
Wenn ein historischer Tag noch keine §3-ArbZG-Approval hat (z. B. weil die Erfassung nachträglich korrigiert wurde), kann ein Approval-Record manuell erzeugt werden:
POST /api/work-time-overtime-approvals/backfill
{
"employeeId": "<uuid>",
"date": "2026-04-15",
"decision": "approved", // optional
"reason": "Notfall-Einsatz Brandanlage Pflegeheim" // bei "rejected" Pflicht
}
- Der Service prüft, ob am genannten Tag ein §3-Überlauf (> 10 h reine Arbeit) vorliegt. Wenn nicht, schlägt der Aufruf mit einer entsprechenden Fehlermeldung fehl.
- Wird
decisionmitgegeben, setzt der Service Status und Metadaten direkt — andernfalls wird der Approval-Record im Statuspendingangelegt und läuft durch den normalen Workflow.
Details und der nutzerseitige Workflow stehen im Tutorial Mehrarbeit genehmigen und im Modul Mehrarbeit-Genehmigungen.
Schritt-für-Schritt — Routine-Nutzung
Wöchentliche HR-Auswertung
/time-overviewöffnen — Default istthisMonth, ggf. auflastWeekumschalten.- KPI-Karten anschauen: gibt es Mitarbeiter mit ungewöhnlichem Saldo oder Pausen-Defizit?
- §3 ausstehend prüfen — wenn die Karte erscheint, in den Report-Tab oder direkt zu Mehrarbeit-Genehmigungen wechseln.
- Auffällige Mitarbeiter anklicken → Detail-Sicht öffnet sich mit denselben Filter-Parametern.
Monatsabschluss
- Preset auf
lastMonthsetzen. - Drucken klicken → Abwesenheits-Auszug für die Lohnbuchhaltung. Im Druck-Dialog die Tabelle prüfen, dann Druckfenster öffnen.
- CSV-Export der Mitarbeiter-Tabelle (Toolbar der DataGrid) als Anlage zum Monatsabschluss.
Wiederverwendbare Konzepte
Verknüpfungen zu anderen Modulen
- Zeiterfassungen — Datenquelle für
recordedWorkMinutes. - Abwesenheiten — werden im Druck-Dialog und als
absenceCreditim Saldo berücksichtigt. - Arbeitszeit-Modelle — definieren Soll-Stunden pro Tag und die Brutto-Pausenzeit.
- Mehrarbeit-Genehmigungen — §3-Überlauf-Workflow, mit Backfill-Endpoint.
- Mitarbeiter — Liste der ausgewerteten Personen.
Häufige Fehler und Lösungen
| Fehler | Lösung |
|---|---|
| Mitarbeiter fehlt in Liste | Kein Arbeitszeit-Modell zugeordnet, status != active oder employeeType != internal (Dashboard filtert auf interne Mitarbeiter). |
| KPI „Erfasste Zeit" steht auf 0 % | Zeitraum überschneidet keine Buchungen, oder alle Buchungen sind im Status, der vom Saldo nicht berücksichtigt wird. |
| Karte „§3 ausstehend" erscheint nicht | Es gibt keine offenen Mehrarbeit-Genehmigungen — Karte wird nur eingeblendet, wenn sum(pendingOvertimeHours) > 0,001 h. |
| Druck-Fenster bleibt leer | Pop-up-Blocker des Browsers erlaubt das neue Fenster nicht; in den Browser-Einstellungen Pop-ups für die Seite freigeben. |
| Backfill-Aufruf schlägt mit „kein §3-Überlauf" fehl | Am genannten Tag liegt die reine Arbeitszeit ≤ 10 h. Backfill ist nur für tatsächliche Überschreitungen vorgesehen. |
| Zukunftstage werden mit 0 ausgewertet | Erwartet — Presets cappen auf heute 23:59:59. Im custom-Preset kann manuell weiter in die Zukunft gewählt werden, hat aber keine Aussagekraft. |
API/Schnittstellen
| Methode | Endpoint | Zweck | CASL |
|---|---|---|---|
GET | /api/employees/:id/time-balance?from=&to= | Saldo + Detail-Felder pro Mitarbeiter im Zeitraum (SSoT) | view EmployeeTimeTracking |
GET | /api/employees?status=active&employeeType=internal | Mitarbeiter-Pool für das Dashboard | view Employee |
GET | /api/absences | Quelle für Abwesenheits-Druck und absenceCredit | view Absence |
GET | /api/work-time-models | Soll-Stunden-Modelle (Detail-Sicht) | view WorkTimeModel |
GET | /api/work-time-overtime-approvals | §3-Approvals für KPI „§3 ausstehend" | view WorkTimeOvertimeApproval |
POST | /api/work-time-overtime-approvals/backfill | Manueller Backfill historischer Tage | create WorkTimeOvertimeApproval |
Versionshinweise
- 2026-04-30: Initiale Veröffentlichung.
- 2026-05-12 (Welle 113): Zentrales Arbeitszeit-Dashboard mit Two-Tab-Layout (Übersicht/Report), Toolbar + collapsible Filter-Panel mit Presets, sechs feste KPI-Karten und optionale §3-Karte, MUI-DataGrid-Premium mit CSV-Export, neuer Druck-Dialog
AbrechnungPrintModalmit Subtyp-spezifischer Anzahl-Logik, Default-Zeitraum für Report-Tab auf „Montag Vorwoche bis heute" geändert, EmployeePicker-Sort-Format-Fix (employeeNumber,asc),useTimeOverviewDataals gemeinsame Daten-SSoT (GET /employees/:id/time-balancepro MA). Backend-seitig:actualMinutesist jetzt reine Arbeitszeit nach §3-Cap,bruttoIstMinutesals separate Brutto-Anwesenheit, neuer EndpointPOST /work-time-overtime-approvals/backfillfür historische Tage. - 2026-05-20 (Welle 132): Niederlassungs-Filter im Filter-Panel ergänzt — Async-Select über
BranchService.listBranches, im URL-Sync mitbranchId. Backend: virtuellerbranchId-Query-Param aufGET /api/employees, aufgelöst überbranchResolver.service(zieht alleEmployeeContract.branchId-Treffer, dedupliziert zuid IN (...)-Filter). Damit ist die Branch-Auswertung im Dashboard verfügbar, ohne dassEmployeeselbst ein Branch-Feld trägt. Quelle: FE923c6a7d, BEdd366422+ neuer ServicebranchResolver.service.ts.