Org assignment history, mobile support, and a correctness pass
Data model - employee_assignments records org placement over time (valid_from/valid_to), written by a trigger on `employees` rather than inside each RPC: ~70 `update employees` statements spread over fifteen migrations mean per-call bookkeeping would miss paths today and again with every future RPC. A partial unique index enforces the one-open-interval invariant the trigger relies on when closing the current row. - The Organigramm gains a Stichtag (default today). Membership comes from entry/exit/karenz, past placement from the new history, future placement projected from pending_org_changes. Placements predating the migration are backfilled with today's values and flagged as such in the UI, since employee_history only ever stored free text and cannot be reconstructed. Correctness - Reports and exports silently truncated at PostgREST's 1000-row cap (db.max_rows); employee_history is already past it at ~800 staff. Every whole-table read now pages explicitly. - XLSX date cells were a day early: ExcelJS converts a Date to an Excel serial straight off getTime(), so a Date built at local midnight lands on the previous day's serial in any positive-offset zone. - Date handling is pinned to Europe/Vienna throughout, and date-only strings are formatted without a Date round-trip. The dashboard's YTD window was built by round-tripping a local Date through toISOString(), which shifted it a day early and dropped 31 December entirely. - Export routes parsed measure/group/split/eventType with unchecked `as` casts, so an unknown value reached column headers as `undefined` and the Content-Disposition filename. Parsed against the label maps now, with the filename slugged as a backstop. - toXlsx keyed columns by header text, silently dropping the second of any two columns sharing a name — split columns take their header from data. - The org chart tree walks had no cycle guard; nothing in the schema forbids a manager_id cycle, and one would hang the tab rather than misreport. - The login page reflected ?error= verbatim, letting anyone put arbitrary text on the real sign-in screen; messages are looked up by code now. - React Flow needs elementsSelectable on, or it sets pointer-events:none on the whole node and the expand control stops responding. UI - Mobile: the shell was unusable below lg — a fixed 236px margin pushed content off-screen with no mobile navigation at all. The sidebar is now a drawer, dvh replaces vh, safe-area insets are honoured, inputs are 16px so iOS stops zooming on focus, and form grids stack. - Org chart nodes redesigned: per-kind accent stripes and icons, vacant roles called out, expand control moved to the bottom edge carrying the child count. - Pagination is windowed; it previously rendered one link per page (54 for the employee list, unbounded for the audit log). - Positions page reduced to open positions with a single "Besetzen" action. - The employee Organisation tab links into the org chart focused on that person, reusing the chart's existing search-match highlighting. Also included, uncommitted until now - Dependants, HR notes, academic titles, split address fields, position validity and role/employment fields, with their migrations and UI. - Docker/compose deployment setup, data-model and security-review docs.
This commit is contained in:
154
README.md
154
README.md
@@ -1,36 +1,150 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# Manner HR Master
|
||||
|
||||
## Getting Started
|
||||
Interne HR-Stammdatenverwaltung: Mitarbeiter:innen, Organisationsstruktur
|
||||
(Bereich/Abteilung/Team), Planstellen, Neueinstellungen, Versetzungen/
|
||||
Beförderungen/Karenz, Reorganisationen und der zugehörige Audit-Trail.
|
||||
|
||||
First, run the development server:
|
||||
## 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
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
Für lokale Supabase-Entwicklung (statt gegen ein Cloud-Projekt):
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
```bash
|
||||
supabase start # startet lokalen Postgres/Auth/Studio-Stack
|
||||
```
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
`supabase/config.toml` und `.env.test.local` sind bereits auf die
|
||||
Standard-Ports der lokalen Supabase-CLI abgestimmt.
|
||||
|
||||
## Learn More
|
||||
## Umgebungsvariablen
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
Siehe [`.env.example`](.env.example) für die vollständige, kommentierte
|
||||
Liste. Kurzfassung:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
| 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` |
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
`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).
|
||||
|
||||
## Deploy on Vercel
|
||||
## Scripts
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
| 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 |
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
## 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 <CRON_SECRET>` 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.
|
||||
|
||||
Reference in New Issue
Block a user