Files
CapaKraken/docs/holiday-calendar-implementation-plan.md

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:

  1. metroCity
  2. federalState / Region
  3. country

Manuelle, ressourcenspezifische PUBLIC_HOLIDAY-Eintraege bleiben weiterhin moeglich und ueberschreiben bzw. ergaenzen den Kalender.

Ist-Zustand

Aktuell existieren drei getrennte Mechanismen:

  1. Statisch codierte Feiertage in packages/shared/src/constants/publicHolidays.ts
  2. Batch-/Auto-Import von PUBLIC_HOLIDAY-Vacations
  3. Laufzeitberechnung von SAH bzw. Chargeability aus Land/Bundesland

Die zentralen Luecken:

  • Es gibt kein Holiday-Stammdatenmodell in der Datenbank.
  • Es gibt keinen Editor fuer Feiertagskalender.
  • metroCity wird fuer Feiertage nicht ausgewertet.
  • Die aktuelle Logik ist faktisch auf Deutschland plus federalState zugeschnitten.
  • Feiertagswissen ist doppelt vorhanden: statische Kalenderlogik plus importierte Vacation-Datensaetze.

Zielarchitektur

1. Holiday Calendar als Stammdatenmodell

Neue Stammdatenobjekte:

  • HolidayCalendar
  • HolidayCalendarEntry

HolidayCalendar beschreibt den Gueltigkeitsbereich eines Kalenders:

  • scopeType: COUNTRY | STATE | CITY
  • countryId
  • optional stateCode
  • optional metroCityId
  • name
  • isActive
  • optional priority

HolidayCalendarEntry beschreibt den einzelnen Feiertag:

  • holidayCalendarId
  • date
  • name
  • 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 Typ PUBLIC_HOLIDAY
  • liefert:
    • publicHolidayStrings
    • absenceDays
    • 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 Vacation bleibt 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

  • STATE verlangt stateCode.
  • CITY verlangt metroCityId.
  • CITY und STATE muessen zum selben countryId passen.
  • Ein CITY-Kalender darf nur fuer eine MetroCity des 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:

  • listCalendars
  • getCalendarById
  • createCalendar
  • updateCalendar
  • deleteCalendar
  • createEntry
  • updateEntry
  • deleteEntry
  • optional previewResolvedHolidays

Paket C: Gemeinsamer Resolver

Dateien:

  • packages/api/src/lib/holiday-resolver.ts
  • bestehende Hilfen in packages/api/src/lib/holiday-availability.ts refactoren 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.ts
  • packages/api/src/lib/chargeability-alerts.ts
  • packages/api/src/router/computation-graph.ts
  • weitere calculateSAH-Aufrufer mit Feiertagsbezug

Abnahme:

  • dieselbe Ressource liefert je nach metroCity / federalState unterschiedliche SAH
  • 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.tsx
    • apps/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:

  1. Bestehende PUBLIC_HOLIDAY-Vacations bleiben gueltig.
  2. Neuer Resolver nutzt zuerst Stammdatenkalender plus manuelle Overrides.
  3. Batch-/Auto-Import wird als Legacy-Funktion markiert.
  4. Spaeter kann entschieden werden, ob Import nur noch Materialisierung fuer Sonderfaelle ist.

Fachliche Aufloesungsregeln

Prioritaet

  1. Manuelle ressourcenspezifische PUBLIC_HOLIDAY-Vacation
  2. CITY-Kalender
  3. STATE-Kalender
  4. COUNTRY-Kalender

Merge-Regeln

  • Gleiches Datum mehrfach:
    • engster Scope gewinnt fuer Anzeige/Quelle
    • fuer SAH zaehlt der Tag genau einmal
  • Feiertag auf Wochenende:
    • erscheint im Kalender
    • reduziert SAH nur, 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.prisma
  • packages/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.ts
  • packages/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.ts
  • packages/api/src/lib/holiday-availability.ts
  • packages/api/src/router/chargeability-report.ts
  • packages/api/src/lib/chargeability-alerts.ts
  • packages/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.ts
  • packages/api/src/router/vacation.ts
  • Verify-Skripte und Tests

Teststrategie

Unit

  • Resolver merged country + state + city korrekt
  • CITY ueberschreibt STATE, STATE ergaenzt COUNTRY
  • manuelle PUBLIC_HOLIDAY-Vacation wird beruecksichtigt
  • identisches Datum wird nur einmal auf SAH angerechnet

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 test
    • pnpm typecheck
    • relevanter E2E-Smoke fuer Admin-Pfad, falls vorhanden

Abnahme-Kriterien

  • Feiertage sind nicht mehr hart an Deutschland/Bundesland im Laufzeitpfad gekoppelt.
  • metroCity kann SAH fachlich beeinflussen.
  • Es gibt eine Admin-faehige Pflege fuer Feiertagskalender.
  • Report, Alerts und Computation Graph verwenden denselben Resolver.
  • Bestehende manuelle Feiertagsabwesenheiten bleiben kompatibel.

Empfohlene Reihenfolge

  1. Schema + Shared Contracts
  2. Backend Router
  3. Resolver + Integration
  4. UI
  5. 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 metroCity als lokaler Scope oder brauchen wir spaeter feinere Geo-Einheiten?
  • Soll Legacy-Batch-Import langfristig entfernt oder als Materialisierung behalten werden?