# Manner HR Master Interne HR-Stammdatenverwaltung: Mitarbeiter:innen, Organisationsstruktur (Bereich/Abteilung/Team), Planstellen, Neueinstellungen, Versetzungen/ Beförderungen/Karenz, Reorganisationen und der zugehörige Audit-Trail. ## Zweck Die App ersetzt Excel-basierte HR-Stammdatenpflege durch ein Werkzeug mit verbindlichen Regeln (z. B. wirksame Daten statt sofortiger Änderungen, eindeutige Positions-/Org-Nummern, verpflichtende Historie) und einem lückenlosen Audit-Trail für jede Änderung. **Alle Mitarbeiterdaten in diesem System sind vertraulich** — Stammdaten, Verträge, Sozialversicherungsnummern, Angehörige und Audit-Daten. Zugriff ist auf explizit aktivierte HR-Benutzer:innen beschränkt (siehe [Sicherheitsprinzipien](#sicherheitsprinzipien)). ## Tech-Stack - Next.js 16 (App Router) — **Achtung:** Next.js 16 hat Breaking Changes gegenüber älteren Versionen (u. a. `proxy.ts` statt `middleware.ts`, siehe `AGENTS.md`). Vor Änderungen an Framework-nahen Dateien die lokalen Docs unter `node_modules/next/dist/docs/` konsultieren. - React 19, TypeScript - Supabase (Postgres, Auth, RLS) — Datenhaltung liegt vollständig in Supabase, nicht im Next.js-Prozess. - Tailwind CSS v4 - Vitest (Unit + Integrationstests), Playwright (E2E) ## Setup ```bash npm install cp .env.example .env.local # Werte eintragen, siehe unten npm run dev ``` Für lokale Supabase-Entwicklung (statt gegen ein Cloud-Projekt): ```bash supabase start # startet lokalen Postgres/Auth/Studio-Stack ``` `supabase/config.toml` und `.env.test.local` sind bereits auf die Standard-Ports der lokalen Supabase-CLI abgestimmt. ## Umgebungsvariablen Siehe [`.env.example`](.env.example) für die vollständige, kommentierte Liste. Kurzfassung: | Variable | Sichtbarkeit | Zweck | |---|---|---| | `NEXT_PUBLIC_SUPABASE_URL` | Browser + Server | Supabase-Projekt-URL | | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Browser + Server | Anon-Key, RLS-gebunden | | `SUPABASE_SERVICE_ROLE_KEY` | **Nur Server** | Umgeht RLS vollständig — niemals im Browser-Bundle, niemals loggen | | `CRON_SECRET` | Nur Server | Schützt `/api/cron/apply-pending-changes` | `NEXT_PUBLIC_*`-Werte werden beim Build in das Client-Bundle eingebacken — eine Änderung erfordert einen Rebuild, nicht nur einen Neustart (relevant für Docker-Deployments, siehe unten). ## Scripts | Befehl | Zweck | |---|---| | `npm run dev` | Lokaler Dev-Server | | `npm run build` | Produktions-Build | | `npm run start` | Produktions-Server (nach `build`) | | `npm run lint` | ESLint (`eslint-config-next`, Flat Config) | | `npm run typecheck` | `tsc --noEmit` | | `npm run test` | Vitest, Unit-Tests (`tests/unit/**`) | | `npm run test:integration` | Vitest gegen eine echte (lokale) Supabase-Instanz — braucht `supabase start` und `.env.test.local` | | `npm run test:e2e` | Playwright | | `npm run check` | lint + typecheck + test + build in Folge | ## Sicherheitsprinzipien - **RLS ist die eigentliche Schranke, nicht die UI.** Jede Tabelle hat Row Level Security aktiv; `proxy.ts` (App-Ebene) ist Defense-in-Depth, keine Ersatzkontrolle. - **Ein Rollenmodell:** `profiles.role = 'hr'` + `profiles.is_active = true`, geprüft über die SQL-Funktion `is_hr_user()`. Kein Sub-Rollensystem — siehe [`docs/data-model.md`](docs/data-model.md#zugriffsmodell). - **Service-Role-Key ist server-only.** Einzige Verwendung: `lib/supabase/admin.ts`, geschützt durch `import "server-only"` (macht einen versehentlichen Client-Import zu einem Build-Fehler statt einem Laufzeitproblem). - **Audit-Log ist transaktional in der Datenbank**, nicht im App-Code: jede mutierende SQL-Funktion schreibt ihren `audit_log`-Eintrag in derselben Transaktion wie die Änderung selbst. Details und Prüfung siehe [`docs/security-review.md`](docs/security-review.md). - **Historie ist append-only** (`employee_history`, `audit_log`) — RLS erlaubt kein `update`/`delete`. Korrekturen sind kompensierende Einträge. ## Cron-Konfiguration `/api/cron/apply-pending-changes` wendet wirksam gewordene, zukunftsdatierte Änderungen an (`pending_org_changes` → `apply_due_pending_changes()`). - **Auf Vercel:** `vercel.json` definiert den täglichen Schedule; Vercel Cron sendet `Authorization: Bearer ` automatisch, wenn `CRON_SECRET` in den Projekt-Env-Vars gesetzt ist. - **Außerhalb von Vercel (Docker):** kein Vercel Cron verfügbar — siehe [`DEPLOYMENT.md`](DEPLOYMENT.md) für den Cron-Sidecar-Container, der denselben Endpoint mit demselben Schema aufruft. - Fehlt `CRON_SECRET` oder stimmt der Header nicht, antwortet die Route mit `401` (nicht `500` — bewusst, siehe `tests/unit/security.test.ts`). ## Supabase-Hinweise - Schema-Quelle der Wahrheit: `supabase/migrations/`. Menschlich lesbare Zusammenfassung: [`docs/data-model.md`](docs/data-model.md). - Migrationen einspielen: `supabase db push` (gegen das verlinkte Projekt) bzw. `supabase start` + automatische Anwendung für lokale Entwicklung. - `supabase/seed.ts` und `.env.test.local` sind nur für lokale Entwicklung/Tests gedacht, nie für ein Produktivprojekt verwenden. ## Testing - `npm run test` — schnell, keine externen Abhängigkeiten, läuft in CI. - `npm run test:integration` — braucht eine laufende lokale Supabase-Instanz (`supabase start`) und `.env.test.local`; prüft RLS-Verhalten end-to-end (siehe `tests/integration/authorization.test.ts` für das HR-Only-Zugriffs- modell). - `npm run test:e2e` — Playwright gegen einen laufenden Dev-/Preview-Server. ## Deployment Siehe [`DEPLOYMENT.md`](DEPLOYMENT.md) für Docker-basiertes Deployment (Dockerfile, docker-compose.yml, Reverse-Proxy/TLS, Cron-Ersatz, Updates). Für Vercel: `vercel.json` ist bereits vorhanden; Env-Vars im Vercel-Projekt setzen (siehe oben). ## Known TODOs vor Produktivbetrieb - **Content-Security-Policy fehlt noch** (`next.config.ts` setzt bewusst keine CSP — Skript-/Style-/Connect-Quellen sind noch nicht vollständig inventarisiert; ungeprüft geraten zu setzen riskiert, Hydration oder den Supabase-Client stillschweigend zu brechen). - **Lokale Scratch-Artefakte** (`.scratch_*`, `.scratch_shots/`) enthalten Screenshots/Hilfsskripte aus einer früheren manuellen Verifikation und liegen noch im Arbeitsverzeichnis. Sie sind jetzt über `.gitignore` ausgeschlossen; vor einem Produktiv-Handover sollten sie durchgesehen und bei Bedarf gelöscht werden. - **Kein granulareres Rollenmodell** — aktuell HR-only (alles-oder-nichts). Falls z. B. eine reine Lese-Rolle künftig gebraucht wird, gehört die Erweiterung in eine neue Migration (`is_hr_user()`/RLS-Policies), nicht in App-seitigen Code.