# 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:

```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?
