-- Der Hay-Grade in „Daten ändern" -- -- Die Beförderung konnte ihn schon setzen, „Daten ändern" nicht: weder das -- Formular noch change_employee_data kannten das Feld. Damit war eine -- Einstufung nur über den Weg „Beförderung" zu ändern — und eine Korrektur -- („da stand von Anfang an die falsche Stufe") ist keine Beförderung, sondern -- eine Richtigstellung, die auch keine Planstelle wechseln soll. -- -- Das Feld gehört zur Gruppe `contract` und ist damit **datiert**: eine -- Umstufung gilt ab einem Tag, nicht rückwirkend seit immer. Das heisst, sie -- kann in pending_org_changes landen — und deshalb steht sie unten auch im -- Nachtlauf. Genau diese zweite Stelle ist in diesem Projekt schon einmal -- vergessen worden: die Gruppe `role` fehlte dort monatelang, und eine -- datierte Änderung an Betriebsrat oder Kollektivvertrag wurde als `applied` -- vermerkt, ohne etwas zu tun. -- -- Drittens app_feld_karte: ohne Eintrag dort liesse sich der Eintrag in der -- Historie hinterher nicht richtigstellen. -- -- Alle drei Funktionen werden **nicht** aus einer Datei kopiert, sondern aus -- der laufenden Definition gelesen und an genau einem Anker ergänzt. Jeder -- Anker muss genau einmal vorkommen; sonst bricht die Migration ab, statt an -- einer Stelle zu schreiben, die hier niemand angesehen hat. -- ═══ 1. change_employee_data ══════════════════════════════════════ do $migration$ declare -- Der Eintrag im Änderungsprotokoll … v_alt_log constant text := $anker$if v_contract ? 'contract_end_date' then v_contract_changes := app_aenderung(v_contract_changes, 'Befristet bis', v_old.contract_end_date::text, (nullif(v_contract->>'contract_end_date','')::date)::text); end if;$anker$; v_neu_log constant text := $anker$if v_contract ? 'contract_end_date' then v_contract_changes := app_aenderung(v_contract_changes, 'Befristet bis', v_old.contract_end_date::text, (nullif(v_contract->>'contract_end_date','')::date)::text); end if; if v_contract ? 'paygrade' then v_contract_changes := app_aenderung(v_contract_changes, 'Hay-Grade', v_old.paygrade::text, v_contract->>'paygrade'); end if;$anker$; -- … und die Spalte selbst. coalesce und nicht nullif: die Stufe ist NOT NULL, -- sie lässt sich nicht leeren, nur wechseln. v_alt_upd constant text := $anker$contract_type = coalesce((v_contract->>'contract_type')::contract_type, contract_type),$anker$; v_neu_upd constant text := $anker$contract_type = coalesce((v_contract->>'contract_type')::contract_type, contract_type), paygrade = coalesce((v_contract->>'paygrade')::paygrade_type, paygrade),$anker$; v_def text; begin select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'change_employee_data' and p.prokind = 'f'; if v_def is null then raise exception 'change_employee_data ist nicht vorhanden.'; end if; if (length(v_def) - length(replace(v_def, v_alt_log, ''))) / length(v_alt_log) <> 1 then raise exception 'Der Anker für das Protokoll kommt nicht genau einmal vor.'; end if; if (length(v_def) - length(replace(v_def, v_alt_upd, ''))) / length(v_alt_upd) <> 1 then raise exception 'Der Anker für die Spalte kommt nicht genau einmal vor.'; end if; if v_def like '%paygrade%' then raise exception 'change_employee_data kennt den Hay-Grade bereits.'; end if; execute replace(replace(v_def, v_alt_log, v_neu_log), v_alt_upd, v_neu_upd); end $migration$; -- ═══ 2. apply_due_pending_changes ═════════════════════════════════ do $migration$ declare v_alt constant text := $anker$contract_type = coalesce((v_rec.payload->'contract'->>'contract_type')::contract_type, contract_type),$anker$; v_neu constant text := $anker$contract_type = coalesce((v_rec.payload->'contract'->>'contract_type')::contract_type, contract_type), paygrade = coalesce((v_rec.payload->'contract'->>'paygrade')::paygrade_type, paygrade),$anker$; v_def text; begin select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'apply_due_pending_changes' and p.prokind = 'f'; if v_def is null then raise exception 'apply_due_pending_changes ist nicht vorhanden.'; end if; if (length(v_def) - length(replace(v_def, v_alt, ''))) / length(v_alt) <> 1 then raise exception 'Der Anker im Nachtlauf kommt nicht genau einmal vor.'; end if; execute replace(v_def, v_alt, v_neu); end $migration$; -- ═══ 3. app_feld_karte ════════════════════════════════════════════ do $migration$ declare -- Der zweite Eintrag ist der Typ, den update_history_entry unmittelbar als -- Umwandlung ins SQL setzt. paygrade_type ist seit 20260928140000 eine -- Domain über text; die Prüfung der Stufe passiert damit dort, wo sie -- hingehört, und nicht an einer zweiten Liste in dieser Funktion. v_alt constant text := $anker$jsonb_build_array('contract_end_date', 'date', 'contract'),$anker$; v_neu constant text := $anker$jsonb_build_array('contract_end_date', 'date', 'contract'), 'Hay-Grade', jsonb_build_array('paygrade', 'paygrade_type', 'contract'),$anker$; v_def text; begin select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'app_feld_karte' and p.prokind = 'f'; if v_def is null then raise exception 'app_feld_karte ist nicht vorhanden.'; end if; if (length(v_def) - length(replace(v_def, v_alt, ''))) / length(v_alt) <> 1 then raise exception 'Der Anker in der Feldkarte kommt nicht genau einmal vor.'; end if; execute replace(v_def, v_alt, v_neu); end $migration$; -- ═══ 4. Selbstprüfung ═════════════════════════════════════════════ -- -- Geprüft wird nicht nur das Neue, sondern auch das, was bei früheren -- Neuerzeugungen in diesem Projekt schon einmal still verschwunden ist. do $$ declare v_def text; v_anz int; begin select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'change_employee_data' and p.prokind = 'f'; if v_def not like '%''Hay-Grade''%' then raise exception 'change_employee_data protokolliert den Hay-Grade nicht.'; end if; if v_def not like '%v_contract->>''paygrade''%' then raise exception 'change_employee_data schreibt den Hay-Grade nicht.'; end if; if v_def not like '%require_hr_admin()%' then raise exception 'change_employee_data hat die Rechtepruefung verloren.'; end if; if v_def not like '%search_path%' then raise exception 'change_employee_data hat den search_path verloren.'; end if; if v_def not like '%cornerstone_id%' then raise exception 'change_employee_data hat die Cornerstone-ID verloren.'; end if; -- Die Form, die eine leere Angabe als null schreibt statt als '' (siehe -- 20260923160000). Ginge sie verloren, scheiterte die zweite Person ohne -- E-Mail am eindeutigen Index. if v_def not like '%nullif(v_person->>''email'', '''')%' then raise exception 'change_employee_data hat die Leerstring-Behandlung verloren.'; end if; select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'apply_due_pending_changes' and p.prokind = 'f'; if v_def not like '%''contract''->>''paygrade''%' then raise exception 'Der Nachtlauf wendet den Hay-Grade nicht an.'; end if; if v_def not like '%v_rec.payload->''role''%' then raise exception 'Der Nachtlauf hat die Gruppe role verloren.'; end if; -- Zwei Zweige hängen eine Besetzung um: die Versetzung und die Beförderung -- auf eine andere Planstelle. Gezählt und nicht bloss auf Vorhandensein -- geprüft — einer der beiden ist hier schon einmal verschwunden. v_anz := (length(v_def) - length(replace(v_def, 'insert into position_assignments', ''))) / length('insert into position_assignments'); if v_anz <> 2 then raise exception 'Der Nachtlauf haengt die Besetzung % mal um, erwartet zweimal.', v_anz; end if; -- Und was zuletzt hier verlorenging, bleibt drin. if v_def not like '%emergency_contact_name%' then raise exception 'Der Nachtlauf hat den Notfallkontakt wieder verloren.'; end if; if v_def not like '%target_position_id%' then raise exception 'Der Nachtlauf hat die Zielplanstelle wieder verloren.'; end if; -- Der Lauf hat keine angemeldete Person: er arbeitet als SECURITY DEFINER -- und ruft deshalb **nicht** require_hr_admin(). Ginge das Kennzeichen bei -- einer Neuerzeugung verloren, liefe er nachts in die Zeilenschutz-Regeln -- und täte nichts mehr. if not exists ( select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'apply_due_pending_changes' and p.prosecdef ) then raise exception 'Der Nachtlauf ist nicht mehr SECURITY DEFINER.'; end if; select pg_get_functiondef(p.oid) into v_def from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'app_feld_karte' and p.prokind = 'f'; if v_def not like '%''Hay-Grade''%' then raise exception 'Die Feldkarte kennt den Hay-Grade nicht.'; end if; if v_def not like '%cornerstone_id%' then raise exception 'Die Feldkarte hat die Cornerstone-ID verloren.'; end if; raise notice 'Hay-Grade steht in Daten aendern, im Nachtlauf und in der Feldkarte.'; end $$;