394 lines
10 KiB
Markdown
394 lines
10 KiB
Markdown
# Holiday Calendar Implementation Plan
|
|
|
|
## Ziel
|
|
|
|
Planarchy 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:
|
|
|
|
```prisma
|
|
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:
|
|
|
|
```prisma
|
|
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?
|