One script for the Supabase SQL editor. It transforms rather than wipes: divisions/departments/teams become org_units, every employee gets a position and an assignment, so the org chart is populated the moment it finishes. The Abteilungsleitung positions are created *vacant*. Nobody holds them, and inventing holders would be worse than a visible gap — the upward rule skips an unfilled chief, so the reporting line stays unbroken either way. Mutations are rewritten onto the model. The reporting line is derived now, which removes manager bookkeeping from all of them: terminate_employee no longer reassigns direct reports at all, because they roll up on their own. Transfer becomes what it is in OM — end one assignment, begin another. apply_reorg, undo_reorg, create_position, delete_position and staff_position_internally are dropped rather than rewritten: they need the UI to move to org units first, so rewriting them now would be guesswork. Those screens are out until the port. Written by inspection, not by running it — Docker is not up and the project is not linked, so this is unverified SQL. Re-reading the first draft caught five defects that would each have aborted it: a window function inside a JOIN condition, a jobs insert placed after the positions referencing it, row_number() computed twice for a mapping that has to agree, a DROP VIEW naming a view that does not exist while the real one (employees_directory) depends on the columns being dropped, and exit_date = entry_date violating the assignment range check. There may be more.
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).
Tech-Stack
- Next.js 16 (App Router) — Achtung: Next.js 16 hat Breaking Changes
gegenüber älteren Versionen (u. a.
proxy.tsstattmiddleware.ts). Vor Änderungen an Framework-nahen Dateien die lokalen Docs unternode_modules/next/dist/docs/konsultieren; sie sind maßgeblich, ältere Anleitungen im Netz beschreiben teils überholte APIs. - React 19, TypeScript
- Supabase (Postgres, Auth, RLS) — Datenhaltung liegt vollständig in Supabase, nicht im Next.js-Prozess.
- Tailwind CSS v4
- Vitest — Unit- (Node), Komponenten- (jsdom) und Integrationstests (gegen ein lokales Supabase)
Setup
npm install
cp .env.example .env.local # Werte eintragen, siehe unten
npm run dev
Für lokale Supabase-Entwicklung (statt gegen ein Cloud-Projekt):
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 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-Funktionis_hr_user(). Kein Sub-Rollensystem — siehedocs/data-model.md. - Service-Role-Key ist server-only. Einzige Verwendung:
lib/supabase/admin.ts, geschützt durchimport "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 siehedocs/security-review.md. - Historie ist append-only (
employee_history,audit_log) — RLS erlaubt keinupdate/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.jsondefiniert den täglichen Schedule; Vercel Cron sendetAuthorization: Bearer <CRON_SECRET>automatisch, wennCRON_SECRETin den Projekt-Env-Vars gesetzt ist. - Außerhalb von Vercel (Docker): kein Vercel Cron verfügbar — siehe
DEPLOYMENT.mdfür den Cron-Sidecar-Container, der denselben Endpoint mit demselben Schema aufruft. - Fehlt
CRON_SECREToder stimmt der Header nicht, antwortet die Route mit401(nicht500— bewusst, siehetests/unit/security.test.ts).
Supabase-Hinweise
- Schema-Quelle der Wahrheit:
supabase/migrations/. Menschlich lesbare Zusammenfassung:docs/data-model.md. - Migrationen einspielen:
supabase db push(gegen das verlinkte Projekt) bzw.supabase start+ automatische Anwendung für lokale Entwicklung. supabase/seed.tsund.env.test.localsind 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 (siehetests/integration/authorization.test.tsfür das HR-Only-Zugriffs- modell).npm run test:e2e— Playwright gegen einen laufenden Dev-/Preview-Server.
Deployment
Siehe 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.tssetzt 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.gitignoreausgeschlossen; 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.