-- 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 $$;