10 KiB
Holiday Calendar Implementation Plan
Ziel
CapaKraken soll standortabhaengige Feiertage fachlich korrekt berechnen koennen, sodass zwei Personen im selben Land, aber in unterschiedlichen Regionen oder Staedten, unterschiedliche SAH und damit unterschiedliche Chargeability erhalten koennen.
Die Feiertagsaufloesung soll kuenftig diese Prioritaet haben:
metroCityfederalState/ Regioncountry
Manuelle, ressourcenspezifische PUBLIC_HOLIDAY-Eintraege bleiben weiterhin moeglich und ueberschreiben bzw. ergaenzen den Kalender.
Ist-Zustand
Aktuell existieren drei getrennte Mechanismen:
- Statisch codierte Feiertage in
packages/shared/src/constants/publicHolidays.ts - Batch-/Auto-Import von
PUBLIC_HOLIDAY-Vacations - Laufzeitberechnung von
SAHbzw. Chargeability aus Land/Bundesland
Die zentralen Luecken:
- Es gibt kein Holiday-Stammdatenmodell in der Datenbank.
- Es gibt keinen Editor fuer Feiertagskalender.
metroCitywird fuer Feiertage nicht ausgewertet.- Die aktuelle Logik ist faktisch auf Deutschland plus
federalStatezugeschnitten. - Feiertagswissen ist doppelt vorhanden: statische Kalenderlogik plus importierte
Vacation-Datensaetze.
Zielarchitektur
1. Holiday Calendar als Stammdatenmodell
Neue Stammdatenobjekte:
HolidayCalendarHolidayCalendarEntry
HolidayCalendar beschreibt den Gueltigkeitsbereich eines Kalenders:
scopeType:COUNTRY | STATE | CITYcountryId- optional
stateCode - optional
metroCityId nameisActive- optional
priority
HolidayCalendarEntry beschreibt den einzelnen Feiertag:
holidayCalendarIddatename- optional
isRecurringAnnual - optional
source
Fachregel:
- Pro Scope soll es genau einen aktiven Kalender geben.
- Die effektiven Feiertage eines Mitarbeiters ergeben sich aus Merge mit Prioritaet
country < state < city. - Gleiche Daten auf engerem Scope ueberschreiben denselben Tag vom breiteren Scope.
2. Laufzeit-Resolver statt statischer Sonderlogik
Neue gemeinsame Backend-Komponente:
resolveResourceHolidayCalendar(...)
Aufgaben:
- liest Kalenderdaten fuer
countryId,federalState,metroCityId - ermittelt die effektiven Feiertage fuer einen Zeitraum
- merged diese mit expliziten
Vacation-Eintraegen vom TypPUBLIC_HOLIDAY - liefert:
publicHolidayStringsabsenceDays- optional Debug-Metadaten zur Herkunft eines Feiertags
Diese Komponente wird die einzige Quelle fuer Feiertagslogik in:
- Chargeability Report
- Chargeability Alerts
- Computation Graph
- ggf. weitere SAH-/Allocation-Pfade
3. Import und Editor werden auf Stammdaten umgestellt
Der heutige Batch-/Auto-Import darf nicht die Primarlogik fuer Feiertage bleiben.
Zielbild:
- Stammdatenkalender sind die Quelle der Wahrheit.
- Optionaler Import in
Vacationbleibt nur als Kompatibilitaets- oder Exportfunktion. - Bestehende
PUBLIC_HOLIDAY-Vacations werden fuer Uebergangszeit weiter beruecksichtigt.
Datenmodell
Prisma-Erweiterungen
Neue Modelle:
model HolidayCalendar {
id String @id @default(cuid())
name String
scopeType HolidayCalendarScope
countryId String
stateCode String?
metroCityId String?
isActive Boolean @default(true)
priority Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
country Country @relation(fields: [countryId], references: [id])
metroCity MetroCity? @relation(fields: [metroCityId], references: [id])
entries HolidayCalendarEntry[]
@@index([countryId, scopeType])
@@index([metroCityId])
}
model HolidayCalendarEntry {
id String @id @default(cuid())
holidayCalendarId String
date DateTime @db.Date
name String
isRecurringAnnual Boolean @default(false)
source String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
holidayCalendar HolidayCalendar @relation(fields: [holidayCalendarId], references: [id], onDelete: Cascade)
@@unique([holidayCalendarId, date, name])
@@index([date])
}
Neues Enum:
enum HolidayCalendarScope {
COUNTRY
STATE
CITY
}
Integritaetsregeln
STATEverlangtstateCode.CITYverlangtmetroCityId.CITYundSTATEmuessen zum selbencountryIdpassen.- Ein
CITY-Kalender darf nur fuer eineMetroCitydes angegebenen Landes existieren.
Diese Regeln werden teils im Schema, teils in Router-Validierung erzwungen.
API- und Backend-Pakete
Paket A: Schema und Datenzugriff
Dateien:
packages/db/prisma/schema.prisma- neue Migration
- ggf.
packages/shared/src/types/* - ggf.
packages/shared/src/schemas/*
Ergebnis:
- Holiday-Calendar-Datenmodell ist vorhanden
- Zod-/Shared-Typen fuer CRUD sind definiert
Paket B: Holiday Calendar Router
Neue oder erweiterte API:
packages/api/src/router/holiday-calendar.ts- Router in
packages/api/src/index.ts
Operationen:
listCalendarsgetCalendarByIdcreateCalendarupdateCalendardeleteCalendarcreateEntryupdateEntrydeleteEntry- optional
previewResolvedHolidays
Paket C: Gemeinsamer Resolver
Dateien:
packages/api/src/lib/holiday-resolver.ts- bestehende Hilfen in
packages/api/src/lib/holiday-availability.tsrefactoren oder ersetzen
Ergebnis:
- einheitliche Feiertagsaufloesung fuer alle Backend-Pfade
- keine neue statische Sonderlogik in Routern
Paket D: Integration in Berechnungen
Betroffene Stellen:
packages/api/src/router/chargeability-report.tspackages/api/src/lib/chargeability-alerts.tspackages/api/src/router/computation-graph.ts- weitere
calculateSAH-Aufrufer mit Feiertagsbezug
Abnahme:
- dieselbe Ressource liefert je nach
metroCity/federalStateunterschiedlicheSAH - gleiche Eingaben erzeugen in allen Reports denselben Feiertagseffekt
Paket E: UI / Admin
Betroffene Stellen:
- neue Admin-Seite oder Erweiterung im Country-Admin
- Wiederverwendung moeglicher Muster aus:
apps/web/src/components/admin/CountriesClient.tsxapps/web/src/components/vacations/PublicHolidayBatch.tsx- vorhandene Modal-/Table-Komponenten
Ziel:
- Kalender pro Land / Bundesland / Stadt anlegen und bearbeiten
- Eintraege pro Jahr pflegen
- Aufloesung fuer eine Beispiel-Ressource optional vorschauen
Paket F: Kompatibilitaet / Migration
Uebergangsstrategie:
- Bestehende
PUBLIC_HOLIDAY-Vacations bleiben gueltig. - Neuer Resolver nutzt zuerst Stammdatenkalender plus manuelle Overrides.
- Batch-/Auto-Import wird als Legacy-Funktion markiert.
- Spaeter kann entschieden werden, ob Import nur noch Materialisierung fuer Sonderfaelle ist.
Fachliche Aufloesungsregeln
Prioritaet
- Manuelle ressourcenspezifische
PUBLIC_HOLIDAY-Vacation CITY-KalenderSTATE-KalenderCOUNTRY-Kalender
Merge-Regeln
- Gleiches Datum mehrfach:
- engster Scope gewinnt fuer Anzeige/Quelle
- fuer
SAHzaehlt der Tag genau einmal
- Feiertag auf Wochenende:
- erscheint im Kalender
- reduziert
SAHnur, wenn der Tag laut Verfuegbarkeit ein Arbeitstag ist
- Halbtag-Feiertage:
- aktuell nicht erforderlich
- nur aufnehmen, wenn fachlich explizit benoetigt
Umsetzung in parallelen Workern
Worker 1: Schema + Shared Contracts
Verantwortung:
- Prisma-Modelle
- Migration
- Shared Types / Zod Schemas
Write Scope:
packages/db/prisma/schema.prismapackages/shared/src/types/*packages/shared/src/schemas/*
Worker 2: Backend Router + Validation
Verantwortung:
- CRUD-API fuer Holiday Calendars
- Validierung von Scope-Regeln
- Audit-Logging
Write Scope:
packages/api/src/router/holiday-calendar.tspackages/api/src/index.ts- eng verbundene Tests
Worker 3: Resolver + Berechnungsintegration
Verantwortung:
- gemeinsamer Holiday Resolver
- Integration in Report, Alerts, Computation Graph
- Entfernung duplizierter Feiertagslogik
Write Scope:
packages/api/src/lib/holiday-resolver.tspackages/api/src/lib/holiday-availability.tspackages/api/src/router/chargeability-report.tspackages/api/src/lib/chargeability-alerts.tspackages/api/src/router/computation-graph.ts- eng verbundene Tests
Worker 4: Admin UI
Verantwortung:
- neue Holiday-Calendar-Admin-Oberflaeche
- Calendar-Entry-Editing
- optional Preview fuer aufgeloeste Feiertage
Write Scope:
apps/web/src/components/admin/*- relevante App-Routen
- eng verbundene UI-Tests falls vorhanden
Worker 5: Migration / Legacy Behavior / Verify
Verantwortung:
- Legacy Import klar einhaengen oder abgrenzen
- Verify-/Smoke-Pfade
- End-to-End-Pruefung der fachlichen Szenarien
Write Scope:
packages/api/src/lib/holiday-auto-import.tspackages/api/src/router/vacation.ts- Verify-Skripte und Tests
Teststrategie
Unit
- Resolver merged
country + state + citykorrekt CITYueberschreibtSTATE,STATEergaenztCOUNTRY- manuelle
PUBLIC_HOLIDAY-Vacation wird beruecksichtigt - identisches Datum wird nur einmal auf
SAHangerechnet
Integration
- Chargeability Report: zwei Ressourcen, gleiches Land, unterschiedliche Stadt, unterschiedliche
SAH - Chargeability Alerts: derselbe Feiertagseffekt wie im Report
- Computation Graph: dieselbe Feiertagsanzahl wie Resolver
UI
- Kalender anlegen fuer
COUNTRY,STATE,CITY - Eintrag anlegen/aendern/loeschen
- Scope-Validierung verhindert ungueltige Kombinationen
Datenmigration / Regression
- bestehende
PUBLIC_HOLIDAY-Vacations bleiben wirksam - alte Batch-Funktion erzeugt keine Konflikte
- Repo-weit:
pnpm testpnpm typecheck- relevanter E2E-Smoke fuer Admin-Pfad, falls vorhanden
Abnahme-Kriterien
- Feiertage sind nicht mehr hart an Deutschland/Bundesland im Laufzeitpfad gekoppelt.
metroCitykannSAHfachlich beeinflussen.- Es gibt eine Admin-faehige Pflege fuer Feiertagskalender.
- Report, Alerts und Computation Graph verwenden denselben Resolver.
- Bestehende manuelle Feiertagsabwesenheiten bleiben kompatibel.
Empfohlene Reihenfolge
- Schema + Shared Contracts
- Backend Router
- Resolver + Integration
- UI
- Migration/Legacy und Gesamttests
Offene Produktentscheidungen
- Sollen Feiertage kuenftig nur manuell gepflegt werden oder auch per externem Provider importierbar sein?
- Brauchen wir Halbtag-Feiertage?
- Reicht
metroCityals lokaler Scope oder brauchen wir spaeter feinere Geo-Einheiten? - Soll Legacy-Batch-Import langfristig entfernt oder als Materialisierung behalten werden?