Consolidation pass: HR-only access, effective-dated mutations, data integrity guards, test suite

Reworks the app from a two-role (hr_admin/manager) model to a single
HR-only role gated by profiles.is_active, fixes transfer/promote/karenz/
reorg RPCs to actually defer future-dated changes via a new
pending_org_changes table instead of writing them immediately (applied
by a daily Vercel Cron route), makes reorg undo append-only instead of
deleting history, adds Karenz-return and history-date integrity guards,
deprecates the salary column, and adds explicit schema grants + perf
indexes needed to run against a fresh (non-hosted) Postgres instance.

Adds vitest unit + integration test suites (the latter against a real
local Supabase instance) covering all of the above, plus lint/typecheck/
build wiring (`npm run check`).
This commit is contained in:
2026-07-14 20:32:20 +02:00
parent 4299277af0
commit 901c5c426e
67 changed files with 4765 additions and 286 deletions

View File

@@ -0,0 +1,33 @@
-- Performance indexes (spec §15).
--
-- Existing indexes already cover employees(team_id/division_id/manager_id/
-- status), employee_history(employee_id, event_date desc), positions(team_id/
-- status), audit_log(occurred_at desc), and the new pending_org_changes
-- table's own indexes (see its migration). This adds the gaps: location-based
-- filtering, entry/exit date range queries (dashboard "upcoming" widget +
-- Eintritte/Austritte reports), personnel-number lookup (also enforces the
-- uniqueness a personnel number should have, which the identity column
-- alone did not), reorg/audit foreign-key lookups, and trigram search
-- support for the employee list's free-text search box.
create unique index if not exists idx_employees_personnel_number on employees (personnel_number);
create index if not exists idx_employees_location_id on employees (location_id);
create index if not exists idx_employees_entry_date on employees (entry_date);
create index if not exists idx_employees_exit_date on employees (exit_date) where exit_date is not null;
create index if not exists idx_employees_karenz_return_date on employees (karenz_return_date) where karenz_return_date is not null;
create index if not exists idx_audit_log_target_employee_id on audit_log (target_employee_id) where target_employee_id is not null;
create index if not exists idx_employee_history_reorg_scenario_id on employee_history (reorg_scenario_id) where reorg_scenario_id is not null;
create index if not exists idx_employee_history_event_type on employee_history (event_type);
create index if not exists idx_reorg_scenarios_applied on reorg_scenarios (applied, applied_at desc);
create index if not exists idx_positions_division_id on positions (division_id);
-- Free-text search over name/title (Mitarbeiter:innen list search box) —
-- trigram index so ILIKE '%term%' queries can use an index instead of a
-- sequential scan.
create extension if not exists pg_trgm;
create index if not exists idx_employees_name_trgm
on employees using gin ((first_name || ' ' || last_name) gin_trgm_ops);
create index if not exists idx_employees_job_title_trgm
on employees using gin (job_title gin_trgm_ops);