Remove Supabase
The database moved to a container of our own; the platform is gone. This takes out what was left of it — and, where the leftovers were load bearing, moves rather than deletes. Moved, not deleted: supabase/migrations/ -> db/migrations/ the schema's source of truth supabase/build-org.ts -> scripts/build-org.ts lib/supabase/types.ts -> lib/types.ts 52 import sites repointed The bookkeeping needed care. It lived in `supabase_migrations.schema_migrations`, and simply renaming the schema would have left the runner facing an empty table: it would have called all 67 migrations pending and replayed them against a database that is long since current. So the runner now creates `migrationen.schema_migrations` and, once, copies the old rows across — guarded so a second run does nothing and a fresh database skips it entirely. Only then does migration 20260907100000 drop the old schema. Deleted: the CLI config, the seed, the historical schema/function dumps (nothing read them), scripts/umzug-von-supabase.sh (the move is done), and both Supabase packages plus the CLI. Nothing in the application imported them — the build now succeeds with no environment variables at all, which is the proof. Integration tests: six of them signed in through Supabase Auth and asserted against the anon key and the service role. That model is gone, so the tests were not portable — they are deleted. session-context and employee-status-filter already ran on pg and are untouched; om-reporting is ported to a direct connection because it guards a real risk (the reporting line rule exists twice, once in SQL and once in TypeScript). CI: the integration job started a Supabase stack. It now runs a postgres service, applies deploy/db-init and every migration to an empty database — that was the valuable part, and it still holds — then checks that a second run is a no-op, which is what proves the bookkeeping works. Docs: security-review.md audited a service-role key, a cookie adapter and auth.users, none of which exist. Restating findings about removed components would suggest today's system had been reviewed; it has not. It now records what was removed and says a fresh review is due. data-model.md was already marked obsolete and described the pre-OM schema; azure-migration.md was a plan for a route not taken. Both deleted. Verified: npm ci, typecheck, lint, 445 tests, build — all clean without the packages. Integration tests skip cleanly with no database. Migration SQL and the runner are reviewed but NOT executed: no Docker here, and the old instance no longer resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
113
db/migrations/20260814100000_no_show.sql
Normal file
113
db/migrations/20260814100000_no_show.sql
Normal file
@@ -0,0 +1,113 @@
|
||||
-- Wer nie angetreten ist: Austrittsgrund „No Show".
|
||||
--
|
||||
-- Der Fall gibt es, und bisher liess er sich nicht erfassen. Ein Austritt am
|
||||
-- Eintrittstag scheiterte an chk_assignment_range: die Besetzung wurde auf
|
||||
-- valid_to = valid_from geschlossen, und ein leeres Intervall ist dort
|
||||
-- verboten. Ausweichen auf den Folgetag hätte bedeutet, einen Tag
|
||||
-- Beschäftigung zu behaupten, den es nie gab — mit allem, was daran hängt:
|
||||
-- Kopfzahl, Zugehörigkeit, Auswertungen zum Stichtag.
|
||||
--
|
||||
-- Drei Dinge macht dieser Grund deshalb anders:
|
||||
--
|
||||
-- * Das Austrittsdatum ist **immer** der Eintrittstag, unabhängig davon,
|
||||
-- was übergeben wurde. Daraus folgt „nie aktiv" von selbst: als
|
||||
-- beschäftigt gilt, wessen exit_date *nach* dem Stichtag liegt, und das
|
||||
-- ist hier an keinem Tag der Fall.
|
||||
-- * Die Planstellenzuordnung wird **entfernt**, nicht geschlossen. Die
|
||||
-- Stelle war nie besetzt und ist wieder frei.
|
||||
-- * Der Status springt sofort auf „Ausgetreten", auch bei einem Eintritt in
|
||||
-- der Zukunft. Sonst bliebe in der Spalte „Geplant" stehen — es gibt
|
||||
-- keinen Lauf, der das später nachzieht.
|
||||
--
|
||||
-- Die Bedingung unten hält das fest, egal auf welchem Weg jemand schreibt —
|
||||
-- auch über den Import.
|
||||
|
||||
alter table employees drop constraint if exists chk_no_show_am_eintritt;
|
||||
alter table employees add constraint chk_no_show_am_eintritt
|
||||
check (exit_reason is distinct from 'No Show' or exit_date = entry_date);
|
||||
|
||||
comment on constraint chk_no_show_am_eintritt on employees is
|
||||
'Ein Nichtantritt endet am Eintrittstag. Sonst gäbe es Tage, an denen die Person als beschäftigt zählte, obwohl sie nie da war. „is distinct from" statt „<>", damit ein leerer Grund nicht zu null auswertet und die Bedingung durchrutschen lässt.';
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.terminate_employee(payload jsonb)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SET search_path TO 'public', 'pg_temp'
|
||||
AS $function$
|
||||
declare
|
||||
v_employee_id uuid := (payload->>'employee_id')::uuid;
|
||||
v_exit date := (payload->>'exit_date')::date;
|
||||
v_name text;
|
||||
-- „No Show" ist kein gewöhnlicher Austritt: die Person hat nie
|
||||
-- angefangen. Deshalb hängt an diesem einen Grund anderes Verhalten.
|
||||
v_no_show boolean := coalesce(payload->>'exit_reason', '') = 'No Show';
|
||||
v_entry date;
|
||||
begin
|
||||
perform require_hr_admin();
|
||||
select first_name || ' ' || last_name, entry_date into v_name, v_entry
|
||||
from employees where id = v_employee_id;
|
||||
|
||||
-- Wer nie angetreten ist, tritt am Tag seines Eintritts wieder aus.
|
||||
-- Damit gibt es keinen einzigen Tag, an dem die Person beschäftigt war:
|
||||
-- die Statusableitung verlangt exit_date > Stichtag, um jemanden als
|
||||
-- beschäftigt zu zählen, und das ist hier nie erfüllt. „Nie aktiv" ist
|
||||
-- damit keine zusätzliche Regel, sondern folgt aus dem Datum.
|
||||
if v_no_show then
|
||||
v_exit := v_entry;
|
||||
end if;
|
||||
|
||||
update employees set
|
||||
-- Bei einem Nichtantritt sofort, auch wenn der Eintritt noch in der
|
||||
-- Zukunft lag: sonst bliebe in der Spalte auf Dauer „Geplant" stehen,
|
||||
-- denn es gibt keinen Lauf, der sie später nachzieht.
|
||||
status = case when v_no_show or v_exit <= current_date then 'Ausgetreten' else status end,
|
||||
exit_date = v_exit,
|
||||
exit_reason = payload->>'exit_reason'
|
||||
where id = v_employee_id;
|
||||
|
||||
-- Die Planstelle wird frei. Direkte Berichte müssen nicht umgehängt
|
||||
-- werden: die Berichtslinie wird abgeleitet und rutscht von selbst auf
|
||||
-- die nächste besetzte Ebene.
|
||||
if v_no_show then
|
||||
-- Die Planstelle war nie besetzt. Sie auf [Eintritt, Eintritt) zu
|
||||
-- schliessen ginge nicht — chk_assignment_range verlangt ein echtes
|
||||
-- Intervall, und genau daran scheiterte ein Austritt am Eintrittstag
|
||||
-- bisher. Die Zuordnung wird deshalb entfernt: die Stelle ist wieder
|
||||
-- frei, und es steht nirgends, jemand hätte sie je innegehabt.
|
||||
delete from position_assignments
|
||||
where employee_id = v_employee_id and valid_to is null;
|
||||
else
|
||||
update position_assignments set valid_to = v_exit
|
||||
where employee_id = v_employee_id and valid_to is null;
|
||||
end if;
|
||||
|
||||
insert into employee_history (employee_id, event_date, event_type, description)
|
||||
values (v_employee_id, v_exit, 'Austritt',
|
||||
case when v_no_show
|
||||
then 'Kein Antritt am ' || v_entry || ' (No Show)'
|
||||
else 'Austritt (' || coalesce(payload->>'exit_reason', '-') || ')' end);
|
||||
|
||||
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
|
||||
values (app_current_user_id(), current_actor_name(), 'Austritt', v_name, v_employee_id, case when v_no_show then 'Kein Antritt am ' || v_entry || ' (No Show)' else 'Austritt am ' || v_exit end);
|
||||
end;
|
||||
$function$;
|
||||
|
||||
|
||||
-- Selbstprüfung.
|
||||
do $$
|
||||
declare
|
||||
v_def text := pg_get_functiondef('public.terminate_employee(jsonb)'::regprocedure);
|
||||
begin
|
||||
if v_def not like '%v_no_show%' then
|
||||
raise exception 'terminate_employee kennt den Nichtantritt nicht';
|
||||
end if;
|
||||
if v_def not like '%delete from position_assignments%' then
|
||||
raise exception 'Die Zuordnung wird bei einem Nichtantritt nicht entfernt';
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_constraint where conname = 'chk_no_show_am_eintritt'
|
||||
) then
|
||||
raise exception 'Die Bedingung fehlt';
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
Reference in New Issue
Block a user