Zum Hauptinhalt springen

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 (requestedapproved/rejected/cancelled); Absences mit Status approved reduzieren das Urlaubskonto und werden in Zeiterfassungen und Arbeitszeit-Modellen berücksichtigt.

Das Modul stellt zwei Sichten bereit:

  • /absencesSelf-Service-Sicht des angemeldeten Mitarbeiters (eigene Anträge, Jahres-Übersicht mit Urlaubskonto). Standardansicht: Kalender.
  • /absences/managementHR/Vorgesetzten-Sicht mit Filter-Panel (Status, Mitarbeiter, Abteilung, Datum), Listen-/Kalender-Toggle und Auto-Expand offener Anträge.

Voraussetzungen

- Berechtigung `view:FE_Absence` für die Self-Service-Sicht, `view:FE_AbsenceManagement` für die Management-Sicht. - `create:Absence` zur Anlage, `do:ApproveAbsence` für Genehmigung/Ablehnung. - `do:ViewAllAbsences` für Admin-Sicht ohne Bereichs-Filter, `do:BulkImportAbsences` für den KI-Bulk-Import. - Bestehender [Mitarbeiter](/employees) als Antragsteller (`requestedById`).

Berechtigungen (CASL)

ActionSubjectWirkung
viewFE_AbsenceSelf-Service-Route /absences aufrufbar
viewFE_AbsenceManagementManagement-Route /absences/management aufrufbar
viewAbsenceEigene/unterstellte Anträge sehen
createAbsenceNeuen Antrag anlegen
updateAbsenceAntrag stornieren (cancel)
doApproveAbsenceAnträge genehmigen oder ablehnen
doViewAllAbsencesAlle Anträge sehen (Admin-Bypass)
doBulkImportAbsencesKI-Bulk-Import ausführen
doBypassAbsenceApprovalHR 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

  1. Abwesenheiten (/absences) → + Neu.
  2. type wählen:
    • vacation — Urlaub mit Subtypen erholung, sonderurlaub, bildungsurlaub, unbezahlt, mutterschutz, elternzeitMitGeld, elternzeitOhneGeld, pflegezeit, freizeitausgleich.
    • sick — Krankheit mit Subtypen normal, kindKrank, kur, verletztengeld, krankBeiEintritt.
    • training — Fortbildung (kein Subtyp).
    • vocationalSchool — Berufsschule (kein Subtyp).
    • other — Sonstiges mit Subtypen unentschuldigt, beschaeftigungsverbot, dienstreise.
  3. subtype ergänzen, falls für den type definiert.
  4. startDate und endDate (Datumsbereich).
  5. Bei type = vacation: halfDayStart und/oder halfDayEnd setzen, falls halbe Tage am Rand des Zeitraums.
  6. Bei type = sick: sickNoteConfirmed-Switch — bestätigt das Vorliegen einer AU.
  7. 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 requested oder approved, 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 approved mit endDate < heute).
  • Zukünftig genehmigt (Anträge approved mit endDate >= 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.

Self-Service Jahres-Übersicht mit Urlaubskonto-Karte und Kalender.

Management-Sicht (/absences/management)

HR/Vorgesetzten-Übersicht über alle Anträge im Berechtigungs-Scope.

Filter-Panel

FeldVerhaltenDefault
statusMulti-Select über alle Status-Werte["requested"] — zeigt zunächst nur offene Anträge
employeeIdAsync-Select (Sichtbar bei view:Employee). Lädt nur status=active UND employeeType != apileer
departmentIdAsync-Select über Abteilungenleer
dateRangevon (matched auf endDate) und bis (matched auf startDate) — Overlap-Semantikleer

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 requested werden 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:

  1. Datei oder Text hochladen → POST /api/absences/bulk-import/preview parst und zeigt die geplanten Datensätze.
  2. Bestätigung → POST /api/absences/bulk-import/execute legt die Anträge an.

Management-Sicht mit Filter-Panel, Listen-/Kalender-Toggle und ausgeklappten offenen Anträgen.

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 in absence_employees (zentrale Sammelanträge).
  • Fehler-Response: 400 Bad Request mit 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:

IconAktion (aria-label)CASLWirkung
ZurückgehenZurück zur Liste.
🏠Zur Startseite gehenSpringt auf das Dashboard / /.
⏮/◀/▶/⏭PaginationNavigation durch die gefilterte Liste — Massen-Bearbeitung ohne Listen-Sprung.

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

FeldnamePflichtDatentypWirkung beim AusfüllenVoraussetzung
typejaENUM (s. oben)Bestimmt verfügbare Subtypen und Conditional-Felder.
subtypebedingtString (s. oben)Detaillierter Sub-Typ. Sichtbar nur wenn für type Subtypen definiert sind.
startDatejaDateBeginn der Abwesenheit.
endDatejaDateEnde der Abwesenheit.endDate >= startDate
halfDayStartneinBooleanHalber Tag am Anfang.type = vacation
halfDayEndneinBooleanHalber Tag am Ende.type = vacation
sickNoteConfirmedneinBooleanAU-Bescheinigung liegt vor.type = sick
reasonneinTEXTFreitext-Begründung.
statusnein (Workflow)ENUM (requested, approved, rejected, cancelled)Genehmigungs-Status. Default beim Create: requested.
requestedByIdja (auto)UUIDMitarbeiter, der antraegt. Aus Session.
approvedByIdnein (auto)UUIDGenehmigender. Bei Approval gesetzt.do:ApproveAbsence
approvedAtnein (auto)DateZeitpunkt der Genehmigung/Ablehnung.
rejectionReasonneinTEXTBegründung bei Ablehnung.
notesneinTEXTHR-Eskalations- und Approver-Notizen.

Workflows und Zustände

Wiederverwendbare Konzepte

Verknüpfungen zu anderen Modulen

Häufige Fehler und Lösungen

FehlerLösung
Subtyp-Select leertype hat keine Subtypen (z. B. training, vocationalSchool).
Halbtags-Switch nicht sichtbartype ist nicht vacation.
AU-Switch nicht sichtbartype ist nicht sick.
Speichern-Button disabled mit WarningKollisions-Vorabcheck hat einen überlappenden Urlaubsantrag gefunden — bestehenden Antrag stornieren oder Zeitraum anpassen.
Genehmigung nicht möglichBerechtigung do:ApproveAbsence fehlt oder Status ist nicht mehr requested.
Mitarbeiter im Management-Kalender fehltNur interne Mitarbeiter werden gezeigt (employeeType = internal). Externe Accounts sind ausgeblendet.

API/Schnittstellen

MethodeEndpointZweckCASL
GET/api/absencesListe mit Filter, Search, Pagination und Sortview Absence
GET/api/absences/:idDetailview Absence
GET/api/absences/balanceUrlaubskonto (Jahresanspruch + Übertrag + Genommen + Pending)view Absence
GET/api/absences/:id/collisionsSoft-Warning für Approver: andere Anträge im selben Zeitraum von Unterstelltendo ApproveAbsence
POST/api/absencesAnlegen — prüft Vacation-Overlap automatischcreate Absence
PATCH/api/absences/:idÄndernupdate Absence
DELETE/api/absences/:idSoft-Deleteupdate Absence
POST/api/absences/:id/approveFreigebendo ApproveAbsence
POST/api/absences/:id/rejectAblehnendo ApproveAbsence
POST/api/absences/:id/cancelAntragsteller storniertupdate Absence
POST/api/absences/bulk-import/previewKI-Bulk-Import parsen und vorschauen
POST/api/absences/bulk-import/executeKI-Bulk-Import ausführendo 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-Subjects FE_AbsenceManagement, ViewAllAbsences, BypassAbsenceApproval. Status-Werte sind requested | approved | rejected | cancelled (zuvor irrtümlich pending dokumentiert).