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
|
||||||
|
$$;
|
||||||
@@ -55,6 +55,7 @@ declare
|
|||||||
v_offen int;
|
v_offen int;
|
||||||
v_meldung text;
|
v_meldung text;
|
||||||
v_ging boolean;
|
v_ging boolean;
|
||||||
|
v_titel text;
|
||||||
begin
|
begin
|
||||||
select id into v_wurzel from org_units where parent_id is null;
|
select id into v_wurzel from org_units where parent_id is null;
|
||||||
select id into v_ort from locations order by name limit 1;
|
select id into v_ort from locations order by name limit 1;
|
||||||
@@ -112,7 +113,34 @@ begin
|
|||||||
if (select paygrade::text from employees where id = v_a) <> 'HG15' then
|
if (select paygrade::text from employees where id = v_a) <> 'HG15' then
|
||||||
raise exception 'Die Beförderung hat den Hay-Grade nicht gesetzt.';
|
raise exception 'Die Beförderung hat den Hay-Grade nicht gesetzt.';
|
||||||
end if;
|
end if;
|
||||||
raise notice '4/8 Beförderung am selben Tag, Hay-Grade gesetzt';
|
|
||||||
|
-- Die Bezeichnung gehört zur Planstelle: dort lesen sie Akte, Organigramm
|
||||||
|
-- und die Exporte. Vorher stand der neue Titel allein an der Person und war
|
||||||
|
-- nirgends zu sehen.
|
||||||
|
select j.title into v_titel
|
||||||
|
from om_positions p join jobs j on j.id = p.job_id where p.id = v_p3;
|
||||||
|
if v_titel <> 'Rauchtest Leitung' then
|
||||||
|
raise exception 'Die Zielplanstelle heisst nach der Beförderung „%", erwartet „Rauchtest Leitung".', v_titel;
|
||||||
|
end if;
|
||||||
|
raise notice '4/8 Beförderung am selben Tag: Hay-Grade und Planstellenbezeichnung';
|
||||||
|
|
||||||
|
-- Dieselbe Planstelle, nur ein neuer Name — der Fall aus dem Testtag.
|
||||||
|
perform promote_employee(jsonb_build_object(
|
||||||
|
'employee_id', v_a, 'effective_date', current_date::text,
|
||||||
|
'new_title', 'Rauchtest Leitung Senior', 'new_paygrade', 'HG16'));
|
||||||
|
|
||||||
|
select j.title into v_titel
|
||||||
|
from om_positions p join jobs j on j.id = p.job_id where p.id = v_p3;
|
||||||
|
if v_titel <> 'Rauchtest Leitung Senior' then
|
||||||
|
raise exception 'Ohne Stellenwechsel heisst die Planstelle „%", erwartet „Rauchtest Leitung Senior".', v_titel;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Der Katalogeintrag der alten Bezeichnung bleibt stehen: an ihm können
|
||||||
|
-- andere Planstellen hängen. Umbenannt wird die Stelle, nicht der Katalog.
|
||||||
|
if not exists (select 1 from jobs where title = 'Rauchtest Leitung') then
|
||||||
|
raise exception 'Der alte Katalogeintrag wurde umbenannt statt die Planstelle umgehängt.';
|
||||||
|
end if;
|
||||||
|
raise notice '4b/8 Beförderung ohne Stellenwechsel benennt dieselbe Planstelle um';
|
||||||
|
|
||||||
-- ── Vorgemerkte Planstelle (20260929200000) ─────────────────────
|
-- ── Vorgemerkte Planstelle (20260929200000) ─────────────────────
|
||||||
-- Eine Versetzung in die Zukunft belegt die Zielstelle, obwohl dort noch
|
-- Eine Versetzung in die Zukunft belegt die Zielstelle, obwohl dort noch
|
||||||
|
|||||||
Reference in New Issue
Block a user