Eine Befoerderung 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 Exporte zeigen alle die Taetigkeit der Planstelle. Haltbar war er auch nicht, denn hire_employee, transfer_employee und change_employee_data setzen dieselbe Spalte aus der Planstelle; die naechste Adressaenderung ueberschrieb ihn wieder. Gemeldet von Lara und im Testprotokoll als H.10. Max hat entschieden: die Befoerderung benennt die Planstelle um. Umbenannt wird die Stelle, auf der die Person danach sitzt -- die Zielstelle, sonst die bisherige. Im Formular ist das ohne Ueberraschung, weil es beim Waehlen einer Zielplanstelle deren Taetigkeit als Vorschlag eintraegt: wer nichts aendert, benennt auch nichts um. Und umbenannt wird nicht der Katalogeintrag, sondern die Planstelle zeigt auf einen anderen -- den Eintrag selbst umzubenennen traefe jede Planstelle mit derselben Taetigkeit. Die Regel "gleiche Taetigkeit, ein Katalogeintrag" stand nur in create_position und wandert nach job_fuer_titel(); zwei Fassungen derselben Regel laufen hier erfahrungsgemaess auseinander. Die Nummernvergabe nimmt dabei die erste freie statt count(*) + 1 -- gezaehlt wurde bisher, und das vergibt eine belegte Nummer, sobald ein Eintrag geloescht wurde oder Codes aus einer fremden Quelle danebenstehen, wie bei der Uebernahme der Manner-Daten. Der Nachtlauf tut dasselbe, sonst benennt eine datierte Befoerderung am Stichtag nichts um. Der Rauchtest prueft beide Wege und dass der alte Katalogeintrag stehen bleibt.
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
-- 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
|
||||
$$;
|
||||
Reference in New Issue
Block a user