-- Eine Beförderung benennt die Planstelle um -- -- Bisher schrieb promote_employee den neuen Titel nach employees.job_title — -- und **nur** dorthin. Gesehen hat ihn niemand: Akte, Organigramm und die -- Exportdateien zeigen alle die Tätigkeit der Planstelle. Schlimmer noch, der -- Wert war nicht einmal haltbar: hire_employee, transfer_employee und -- change_employee_data setzen dieselbe Spalte aus der Planstelle, also -- überschrieb die nächste Adressänderung den Titel wieder. -- -- Gemeldet von Lara („in der zusammenfassung der person ist jedoch noch -- Marketingtätigkeit angeführt") und im Testprotokoll als H.10. Max hat am -- 30.09. entschieden: die Beförderung benennt die Planstelle um. -- -- ═══ Welche Planstelle ════════════════════════════════════════════ -- -- Die, auf der die Person **danach** sitzt: die Zielplanstelle, wenn eine -- gewählt wurde, sonst die bisherige. Ein Titel, der zu keiner Stelle gehört, -- war ja gerade das Problem. -- -- Im Formular ist das ohne Überraschung, weil es beim Wählen einer -- Zielplanstelle deren Tätigkeit als Vorschlag einträgt: wer nichts ändert, -- benennt auch nichts um. -- -- ═══ Der Katalog ═════════════════════════════════════════════════ -- -- Umbenannt wird nicht der Katalogeintrag, sondern die Planstelle zeigt auf -- einen anderen. Den Eintrag selbst umzubenennen träfe **jede** Planstelle mit -- derselben Tätigkeit — bei Manner hängen an einem Eintrag bis zu fünf. -- -- Die Regel „gleiche Tätigkeit, ein Katalogeintrag" stand bisher nur in -- create_position. Sie wandert in job_fuer_titel() und wird von dort aus -- benutzt; zwei Fassungen derselben Regel laufen in diesem Projekt -- erfahrungsgemäss auseinander. create or replace function job_fuer_titel(p_titel text) returns uuid language plpgsql set search_path = public, pg_temp as $$ declare v_titel text := nullif(btrim(p_titel), ''); v_id uuid; begin if v_titel is null then raise exception 'Es muss eine Tätigkeit angegeben werden.'; end if; -- Ohne Rücksicht auf Gross- und Kleinschreibung: sonst stehen -- „Schlosser:in" und „schlosser:in" nebeneinander und jede Auswertung nach -- Tätigkeit ist wertlos. select id into v_id from jobs where lower(title) = lower(v_titel); if v_id is not null then return v_id; end if; -- Die erste freie Nummer statt count(*) + 1. Gezählt wurde bisher, und das -- vergibt eine bereits belegte Nummer, sobald ein Eintrag gelöscht wurde -- oder — wie bei der Übernahme der Manner-Daten — Codes aus einer fremden -- Quelle danebenstehen. insert into jobs (code, title) select 'J' || lpad(n::text, 4, '0'), v_titel from generate_series(1, 100000) n where not exists (select 1 from jobs j where j.code = 'J' || lpad(n::text, 4, '0')) order by n limit 1 returning id into v_id; if v_id is null then raise exception 'Es ist keine freie Jobnummer mehr frei.'; end if; return v_id; end; $$; comment on function job_fuer_titel(text) is 'Der Katalogeintrag zu einer Tätigkeit — vorhandener oder neuer. Eine Tätigkeit, ein Eintrag, ohne Rücksicht auf Gross- und Kleinschreibung.'; grant execute on function job_fuer_titel(text) to alpenwerk_app; -- ═══ 1. create_position benutzt die Auskunft statt einer zweiten Fassung ══ do $migration$ declare v_alt constant text := $anker$ select id into v_job_id from jobs where lower(title) = lower(v_job_title); if v_job_id is null then insert into jobs (code, title) values ('J' || lpad((select count(*) + 1 from jobs)::text, 4, '0'), v_job_title) returning id into v_job_id; end if;$anker$; v_neu constant text := $anker$ v_job_id := job_fuer_titel(v_job_title);$anker$; v_def text; v_anzahl 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 = 'create_position' and p.prokind = 'f'; if v_def is null then raise exception 'create_position ist nicht vorhanden.'; end if; v_anzahl := (length(v_def) - length(replace(v_def, v_alt, ''))) / length(v_alt); if v_anzahl <> 1 then raise exception 'Der Anker in create_position kommt % mal vor, erwartet einmal.', v_anzahl; end if; execute replace(v_def, v_alt, v_neu); end $migration$; -- ═══ 2. promote_employee benennt die Planstelle um ════════════════ do $migration$ declare v_alt constant text := $anker$ update employees set job_title = payload->>'new_title', paygrade = coalesce((payload->>'new_paygrade')::paygrade_type, paygrade) where id = v_employee_id;$anker$; v_neu constant text := $anker$ update employees set job_title = payload->>'new_title', paygrade = coalesce((payload->>'new_paygrade')::paygrade_type, paygrade) where id = v_employee_id; -- Die Bezeichnung gehört zur Planstelle, nicht zur Person: dort lesen sie -- Akte, Organigramm und die Exporte. Betroffen ist die Stelle, auf der die -- Person danach sitzt — die Zielstelle, sonst die bisherige. declare v_umbenennen uuid := coalesce(v_position, ( select pa.position_id from position_assignments pa where pa.employee_id = v_employee_id and pa.valid_to is null)); begin if v_umbenennen is not null then update om_positions set job_id = job_fuer_titel(payload->>'new_title') where id = v_umbenennen; end if; end;$anker$; v_def text; v_anzahl 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 = 'promote_employee' and p.prokind = 'f'; if v_def is null then raise exception 'promote_employee ist nicht vorhanden.'; end if; v_anzahl := (length(v_def) - length(replace(v_def, v_alt, ''))) / length(v_alt); if v_anzahl <> 1 then raise exception 'Der Anker in promote_employee kommt % mal vor, erwartet einmal.', v_anzahl; end if; execute replace(v_def, v_alt, v_neu); end $migration$; -- ═══ 3. Der Nachtlauf, Zweig `promotion` ══════════════════════════ -- -- Eine auf später datierte Beförderung muss dasselbe tun, sonst benennt sie -- am Stichtag nichts um und der Titel wäre wieder nur an der Person — genau -- der Zustand, den diese Migration beendet. do $migration$ declare v_alt constant text := $anker$ update employees set job_title = coalesce(v_rec.payload->>'new_title', job_title), paygrade = coalesce((v_rec.payload->>'new_paygrade')::paygrade_type, paygrade) where id = v_rec.employee_id;$anker$; v_neu constant text := $anker$ update employees set job_title = coalesce(v_rec.payload->>'new_title', job_title), paygrade = coalesce((v_rec.payload->>'new_paygrade')::paygrade_type, paygrade) where id = v_rec.employee_id; if nullif(v_rec.payload->>'new_title', '') is not null then declare v_umbenennen uuid := coalesce( nullif(v_rec.payload->>'target_position_id', '')::uuid, ( select pa.position_id from position_assignments pa where pa.employee_id = v_rec.employee_id and pa.valid_to is null)); begin if v_umbenennen is not null then update om_positions set job_id = job_fuer_titel(v_rec.payload->>'new_title') where id = v_umbenennen; end if; end; end if;$anker$; v_def text; v_anzahl 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 = '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; v_anzahl := (length(v_def) - length(replace(v_def, v_alt, ''))) / length(v_alt); if v_anzahl <> 1 then raise exception 'Der Anker im Nachtlauf kommt % mal vor, erwartet einmal.', v_anzahl; end if; execute replace(v_def, v_alt, v_neu); end $migration$; -- ═══ Selbstprüfung ════════════════════════════════════════════════ do $$ declare v_def text; v_anzahl 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 = 'create_position' and p.prokind = 'f'; if v_def not like '%job_fuer_titel(%' then raise exception 'create_position benutzt die gemeinsame Auskunft nicht.'; end if; if v_def like '%count(*) + 1 from jobs%' then raise exception 'create_position traegt noch die zweite Fassung der Katalogregel.'; end if; if v_def not like '%require_hr_admin()%' then raise exception 'create_position prueft die Rechte nicht.'; 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 = 'promote_employee' and p.prokind = 'f'; if v_def not like '%update om_positions set job_id = job_fuer_titel%' then raise exception 'promote_employee benennt die Planstelle nicht um.'; end if; -- Was an dieser Funktion schon einmal verlorenging. if v_def not like '%target_position_id%' then raise exception 'promote_employee hat die Zielplanstelle verloren.'; end if; if v_def not like '%valid_from >= v_effective_date%' then raise exception 'promote_employee raeumt die Besetzung vom selben Tag nicht mehr ab.'; end if; if v_def not like '%planstelle_vorgemerkt(%' then raise exception 'promote_employee prueft die vorgemerkten Planstellen nicht mehr.'; end if; if v_def not like '%require_hr_admin()%' then raise exception 'promote_employee prueft die Rechte nicht.'; 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 '%job_fuer_titel(v_rec.payload%' then raise exception 'Der Nachtlauf benennt die Planstelle nicht um.'; end if; v_anzahl := (length(v_def) - length(replace(v_def, 'insert into position_assignments', ''))) / length('insert into position_assignments'); if v_anzahl <> 2 then raise exception 'Der Nachtlauf haengt die Besetzung % mal um, erwartet zweimal.', v_anzahl; end if; if v_def not like '%v_rec.payload->''role''%' then raise exception 'Der Nachtlauf hat die Gruppe role verloren.'; end if; raise notice 'Befoerderung benennt die Planstelle um — sofort und im Nachtlauf.'; end $$;