Abwesenheiten (Absences)
Zweck
Absence modelliert Abwesenheiten von Mitarbeitern — Urlaub, Krankheit, Schulung, Berufsschule oder Sonstiges. Pro Eintrag werden type, subtype (kontextabhängig), Zeitraum und ggf. Halbtags-Flags gepflegt. Der Genehmigungs-Workflow läuft über status (requested → approved/rejected/cancelled); Absences mit Status approved reduzieren das Urlaubskonto und werden in Zeiterfassungen und Arbeitszeit-Modellen berücksichtigt.
Das Modul stellt zwei Sichten bereit:
/absences— Self-Service-Sicht des angemeldeten Mitarbeiters (eigene Anträge, Jahres-Übersicht mit Urlaubskonto). Standardansicht: Kalender./absences/management— HR/Vorgesetzten-Sicht mit Filter-Panel (Status, Mitarbeiter, Abteilung, Datum), Listen-/Kalender-Toggle und Auto-Expand offener Anträge.
Voraussetzungen
Berechtigungen (CASL)
| Action | Subject | Wirkung |
|---|---|---|
view | FE_Absence | Self-Service-Route /absences aufrufbar |
view | FE_AbsenceManagement | Management-Route /absences/management aufrufbar |
view | Absence | Eigene/unterstellte Anträge sehen |
create | Absence | Neuen Antrag anlegen |
update | Absence | Antrag stornieren (cancel) |
do | ApproveAbsence | Anträge genehmigen oder ablehnen |
do | ViewAllAbsences | Alle Anträge sehen (Admin-Bypass) |
do | BulkImportAbsences | KI-Bulk-Import ausführen |
do | BypassAbsenceApproval | HR kann Antrag direkt mit Status approved anlegen |
Self-Service-Sicht (/absences)
Die Sicht des angemeldeten Mitarbeiters auf seine eigenen Abwesenheiten. Standardansicht ist der Kalender (in localStorage unter absence_yearView_mode persistiert, Default calendar). Wechsel zur Listen-Sicht oben rechts.
Antrag stellen
- Abwesenheiten (
/absences) → + Neu. typewählen:vacation— Urlaub mit Subtypenerholung,sonderurlaub,bildungsurlaub,unbezahlt,mutterschutz,elternzeitMitGeld,elternzeitOhneGeld,pflegezeit,freizeitausgleich.sick— Krankheit mit Subtypennormal,kindKrank,kur,verletztengeld,krankBeiEintritt.training— Fortbildung (kein Subtyp).vocationalSchool— Berufsschule (kein Subtyp).other— Sonstiges mit Subtypenunentschuldigt,beschaeftigungsverbot,dienstreise.
subtypeergänzen, falls für dentypedefiniert.startDateundendDate(Datumsbereich).- Bei
type = vacation:halfDayStartund/oderhalfDayEndsetzen, falls halbe Tage am Rand des Zeitraums. - Bei
type = sick:sickNoteConfirmed-Switch — bestätigt das Vorliegen einer AU. reason(Freitext) ergänzen.
Kollisions-Vorabcheck im Formular
Sobald type = vacation und der Antragsteller den eigenen Mitarbeiter-Account verwendet (Self-Service), läuft beim Tippen ein vorab-Check gegen bestehende Anträge:
- 400 ms Debounce auf
startDate/endDate. - Sucht eigene Vacation-Anträge mit Status
requestedoderapproved, deren Zeitraum sich überlappt. - Bei Treffer erscheint eine Warning-Alert mit Zeitraum und Status des bestehenden Antrags.
- Speichern-Button wird disabled, solange die Kollision besteht — kein BE-Roundtrip auf den Happy-Path nötig.
Der Check läuft nicht im HR-Pfad (mehrere employeeIds im Multi-Employee-Antrag) — dort übernimmt das Backend die Prüfung (siehe unten).
Urlaubskonto mit Jahresclipping
Die VacationBalanceCard oben in der Jahres-Übersicht zeigt:
- Jahresanspruch (aus aktivem Vertrag + Vacation-Type-Stammdaten).
- Übertrag aus Vorjahr.
- Bereits genommen (Anträge
approvedmitendDate < heute). - Zukünftig genehmigt (Anträge
approvedmitendDate >= heute). - Beantragt aber nicht entschieden (Status
requested). - Verbleibend.
Jahresclipping: Jahresübergreifende Anträge (z. B. 29.12.2025–05.01.2026) werden an der Jahresgrenze geteilt — 3 Tage zählen in 2025, die übrigen Tage in 2026. Die Logik läuft in EmployeeAbsenceYearView clientseitig und überschreibt die Backend-Werte für die jeweils gewählte Jahres-Sicht.

Management-Sicht (/absences/management)
HR/Vorgesetzten-Übersicht über alle Anträge im Berechtigungs-Scope.
Filter-Panel
| Feld | Verhalten | Default |
|---|---|---|
status | Multi-Select über alle Status-Werte | ["requested"] — zeigt zunächst nur offene Anträge |
employeeId | Async-Select (Sichtbar bei view:Employee). Lädt nur status=active UND employeeType != api | leer |
departmentId | Async-Select über Abteilungen | leer |
dateRange | von (matched auf endDate) und bis (matched auf startDate) — Overlap-Semantik | leer |
Die Datum-Filterung verwendet Overlap-Semantik: endDate >= von UND startDate <= bis. Damit erscheinen auch jahresübergreifende Anträge im Filter für ein einzelnes Jahr.
Listen-/Kalender-Toggle
Buttons oben rechts (Listen-Icon und Kalender-Icon). Der gewählte Modus wird in localStorage unter absenceManagement_viewMode persistiert.
- Listen-Modus — paginierte Tabelle mit Filter-Anwendung. Anträge mit Status
requestedwerden automatisch ausgeklappt, sobald der User noch nicht interagiert hat. - Kalender-Modus — zeigt Anträge im Monats-/Wochen-/Tages-/Jahres-Raster. Im Management-Kalender werden nur interne Mitarbeiter angezeigt (
employeeType = internal); externe/API/Contractor-Accounts sind bewusst ausgeblendet.
Bulk-Import
Bei vorhandener Berechtigung do:BulkImportAbsences steht ein + Bulk-Import-Button bereit. Workflow:
- Datei oder Text hochladen →
POST /api/absences/bulk-import/previewparst und zeigt die geplanten Datensätze. - Bestätigung →
POST /api/absences/bulk-import/executelegt die Anträge an.

Backend-seitige Kollisionsprüfung
Beim POST /api/absences prüft der Service bei type = vacation automatisch, ob bereits ein Antrag mit Status requested oder approved im gleichen Zeitraum für denselben Antragsteller (oder im Multi-Employee-Junction) existiert.
- Overlap-Bedingung:
startDate <= endDate_neu UND endDate >= startDate_neu. - Scope: nur
type = vacation— Krankmeldungen, Schulungen oder Sonstiges dürfen legitim parallel laufen. - Berücksichtigt: direkte Antragsteller (
requestedById) und Junction-Treffer inabsence_employees(zentrale Sammelanträge). - Fehler-Response:
400 Bad Requestmit Zeitraum und Status des bestehenden Antrags, z. B.:
Für diesen Zeitraum existiert bereits ein Urlaubsantrag (06.01.2026 – 10.01.2026,
Status: genehmigt). Bitte den bestehenden Antrag stornieren oder bearbeiten,
bevor ein neuer für den gleichen Zeitraum gestellt wird.
Auch wer den Frontend-Vorabcheck umgeht (Direkt-API-Aufrufe, Bulk-Import), läuft in diese Prüfung.
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 Listen-Sprung. |
Globale Floating-Drawer (links)
Wie auf jeder Detail-Seite verfügbar — siehe Floating-Quickbar:
- KAL. (Mini-Kalender)
- ZEIT (Persönliche Wochen-Arbeitszeit)
- ARBEIT (Eigene bevorstehende Aufträge)
Felder und Eingaben
| Feldname | Pflicht | Datentyp | Wirkung beim Ausfüllen | Voraussetzung |
|---|---|---|---|---|
type | ja | ENUM (s. oben) | Bestimmt verfügbare Subtypen und Conditional-Felder. | — |
subtype | bedingt | String (s. oben) | Detaillierter Sub-Typ. Sichtbar nur wenn für type Subtypen definiert sind. | — |
startDate | ja | Date | Beginn der Abwesenheit. | — |
endDate | ja | Date | Ende der Abwesenheit. | endDate >= startDate |
halfDayStart | nein | Boolean | Halber Tag am Anfang. | type = vacation |
halfDayEnd | nein | Boolean | Halber Tag am Ende. | type = vacation |
sickNoteConfirmed | nein | Boolean | AU-Bescheinigung liegt vor. | type = sick |
reason | nein | TEXT | Freitext-Begründung. | — |
status | nein (Workflow) | ENUM (requested, approved, rejected, cancelled) | Genehmigungs-Status. Default beim Create: requested. | — |
requestedById | ja (auto) | UUID | Mitarbeiter, der antraegt. Aus Session. | — |
approvedById | nein (auto) | UUID | Genehmigender. Bei Approval gesetzt. | do:ApproveAbsence |
approvedAt | nein (auto) | Date | Zeitpunkt der Genehmigung/Ablehnung. | — |
rejectionReason | nein | TEXT | Begründung bei Ablehnung. | — |
notes | nein | TEXT | HR-Eskalations- und Approver-Notizen. | — |
Workflows und Zustände
Wiederverwendbare Konzepte
Verknüpfungen zu anderen Modulen
- Mitarbeiter — Antragsteller (
requestedById) und Sub-Liste/employees/:id/absences. - Zeit-Übersicht — visualisiert Abwesenheiten gegen Soll-Stunden und stellt einen Abwesenheits-Druck-Dialog bereit.
- Arbeitszeit-Modelle — bestimmen, wie Abwesenheiten auf Soll-Stunden wirken.
- Mitarbeiter-Zeitarten — Abwesenheits-Typen können mit Zeitarten verknüpft werden.
- Vacation-Types und Vertrags-Urlaubsansprüche — Quelle für Jahresanspruch und Expiry-Regeln im Urlaubskonto.
Häufige Fehler und Lösungen
| Fehler | Lösung |
|---|---|
| Subtyp-Select leer | type hat keine Subtypen (z. B. training, vocationalSchool). |
| Halbtags-Switch nicht sichtbar | type ist nicht vacation. |
| AU-Switch nicht sichtbar | type ist nicht sick. |
| Speichern-Button disabled mit Warning | Kollisions-Vorabcheck hat einen überlappenden Urlaubsantrag gefunden — bestehenden Antrag stornieren oder Zeitraum anpassen. |
| Genehmigung nicht möglich | Berechtigung do:ApproveAbsence fehlt oder Status ist nicht mehr requested. |
| Mitarbeiter im Management-Kalender fehlt | Nur interne Mitarbeiter werden gezeigt (employeeType = internal). Externe Accounts sind ausgeblendet. |
API/Schnittstellen
| Methode | Endpoint | Zweck | CASL |
|---|---|---|---|
GET | /api/absences | Liste mit Filter, Search, Pagination und Sort | view Absence |
GET | /api/absences/:id | Detail | view Absence |
GET | /api/absences/balance | Urlaubskonto (Jahresanspruch + Übertrag + Genommen + Pending) | view Absence |
GET | /api/absences/:id/collisions | Soft-Warning für Approver: andere Anträge im selben Zeitraum von Unterstellten | do ApproveAbsence |
POST | /api/absences | Anlegen — prüft Vacation-Overlap automatisch | create Absence |
PATCH | /api/absences/:id | Ändern | update Absence |
DELETE | /api/absences/:id | Soft-Delete | update Absence |
POST | /api/absences/:id/approve | Freigeben | do ApproveAbsence |
POST | /api/absences/:id/reject | Ablehnen | do ApproveAbsence |
POST | /api/absences/:id/cancel | Antragsteller storniert | update Absence |
POST | /api/absences/bulk-import/preview | KI-Bulk-Import parsen und vorschauen | — |
POST | /api/absences/bulk-import/execute | KI-Bulk-Import ausführen | do BulkImportAbsences |
Wichtige Query-Parameter für GET /api/absences:
?page=1&size=50
&type=vacation&status=requested
&from=2026-01-01&to=2026-12-31
&employeeId=<uuid>&departmentId=<uuid>
&searchTerm=<name oder reason>
&filter=<field,operator,value>
&sort=createdAt,DESC;startDate,ASC
searchTerm durchsucht Employee.firstName, lastName, email, employeeNumber und das reason-Feld der Abwesenheit. departmentId joint über die aktive Rolle des Mitarbeiters auf die zugehörige Abteilung.
Versionshinweise
- 2026-04-30: Initiale Veröffentlichung.
- 2026-05-12 (Welle 112): Management-Page mit Filter-Panel + Listen-/Kalender-Toggle, Self-Service-Kalender als Default in
/absences, Vorab-Kollisionsprüfung im Antragsformular (Vacation, Self-Service), Backend-Overlap-Check beim Create, Urlaubskonto-Jahresclipping, Filter „nur interne Mitarbeiter" im Management-Kalender,Reflect.ownKeys()-Fix für jahresübergreifende Date-Filter, neue CASL-SubjectsFE_AbsenceManagement,ViewAllAbsences,BypassAbsenceApproval. Status-Werte sindrequested | approved | rejected | cancelled(zuvor irrtümlichpendingdokumentiert).