Versetzung und Befoerderung am Tag, an dem die Besetzung begann
Wer heute schon einmal versetzt wurde, liess sich heute nicht noch einmal versetzen oder befoerdern: die laufende Besetzung beginnt dann heute, und der Code schliesst sie auf denselben Tag. chk_assignment_range verlangt aber valid_to > valid_from -- ein Intervall von null Tagen ist verboten, und der Vorgang brach mit der rohen Datenbankmeldung ab. Das Verbot ist richtig, und der Fall ist im Haus schon einmal entschieden worden: terminate_employee loescht die Besetzung beim Grund "No Show", statt sie auf [Eintritt, Eintritt) zu schliessen -- niemand hatte sie je inne. Dieselbe Begruendung gilt hier. Die zweite Versetzung am selben Tag abzuweisen waere genau der haeufigste Fall gewesen: jemand hat die falsche Planstelle erwischt und will es sofort richtigstellen. Beide Ereignisse bleiben in der Historie stehen. Vier Stellen mit demselben Paar aus Schliessen und Einfuegen: transfer_employee, promote_employee und im Nachtlauf die Zweige transfer und promotion. Alle vier aus der laufenden Definition gelesen und an einem Anker ergaenzt; die Selbstpruefung zaehlt die beiden Stellen im Nachtlauf, statt nur ihr Vorhandensein zu pruefen -- der Zweig transfer ist dort schon einmal spurlos verschwunden.
This commit is contained in:
177
db/migrations/20260929180000_besetzung_am_selben_tag.sql
Normal file
177
db/migrations/20260929180000_besetzung_am_selben_tag.sql
Normal file
@@ -0,0 +1,177 @@
|
||||
-- Versetzung und Beförderung am selben Tag, an dem die Besetzung begann
|
||||
--
|
||||
-- Gemeldet beim Nachtest am 29.09.: eine Beförderung auf eine andere
|
||||
-- Planstelle scheiterte mit
|
||||
--
|
||||
-- new row for relation "position_assignments" violates check constraint
|
||||
-- "chk_assignment_range"
|
||||
--
|
||||
-- Die Person war am selben Tag schon einmal versetzt worden. Ihre laufende
|
||||
-- Besetzung begann also heute, und der Code schliesst sie auf denselben Tag:
|
||||
--
|
||||
-- update position_assignments set valid_to = <wirksam ab> where valid_to is null;
|
||||
-- insert into position_assignments (…) values (…, <wirksam ab>);
|
||||
--
|
||||
-- `chk_assignment_range` verlangt `valid_to > valid_from`; ein Intervall von
|
||||
-- null Tagen ist verboten. Das ist richtig so — und der Fall ist im Haus auch
|
||||
-- schon einmal gelöst worden: terminate_employee **löscht** die Besetzung beim
|
||||
-- Grund „No Show", statt sie auf [Eintritt, Eintritt) zu schliessen, mit der
|
||||
-- Begründung, dass niemand sie je innehatte. Dieselbe Begründung gilt hier:
|
||||
-- eine Besetzung, die am Tag ihres Beginns wieder endet, hat nicht
|
||||
-- stattgefunden.
|
||||
--
|
||||
-- Die Alternative — die zweite Versetzung am selben Tag abzuweisen — wäre
|
||||
-- genau der häufigste Fall: jemand hat die falsche Planstelle erwischt und
|
||||
-- will es sofort richtigstellen. Die Ereignisse in employee_history bleiben
|
||||
-- beide stehen; sichtbar ist also weiterhin, dass zweimal verschoben wurde.
|
||||
--
|
||||
-- Betroffen sind vier Stellen mit demselben Paar aus Schliessen und Einfügen:
|
||||
-- transfer_employee, promote_employee und im Nachtlauf die Zweige `transfer`
|
||||
-- und `promotion`. Alle vier werden aus der laufenden Definition gelesen und
|
||||
-- an einem Anker ergänzt.
|
||||
|
||||
-- ═══ 1. transfer_employee ═════════════════════════════════════════
|
||||
do $migration$
|
||||
declare
|
||||
v_alt constant text := $anker$update position_assignments set valid_to = v_effective$anker$;
|
||||
v_neu constant text := $anker$delete from position_assignments
|
||||
where employee_id = v_employee_id and valid_to is null and valid_from >= v_effective;
|
||||
update position_assignments set valid_to = v_effective$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 = 'transfer_employee' and p.prokind = 'f';
|
||||
|
||||
if v_def is null then
|
||||
raise exception 'transfer_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 transfer_employee kommt % mal vor, erwartet einmal.', v_anzahl;
|
||||
end if;
|
||||
|
||||
execute replace(v_def, v_alt, v_neu);
|
||||
end
|
||||
$migration$;
|
||||
|
||||
-- ═══ 2. promote_employee ══════════════════════════════════════════
|
||||
do $migration$
|
||||
declare
|
||||
v_alt constant text := $anker$update position_assignments set valid_to = v_effective_date$anker$;
|
||||
v_neu constant text := $anker$delete from position_assignments
|
||||
where employee_id = v_employee_id and valid_to is null and valid_from >= v_effective_date;
|
||||
update position_assignments set valid_to = v_effective_date$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, beide Zweige ═══════════════════════════════
|
||||
--
|
||||
-- Hier steht dasselbe zweimal — bei `transfer` und bei `promotion` mit
|
||||
-- Zielplanstelle. Beide werden ersetzt, und die Zahl wird geprüft: der Zweig
|
||||
-- `transfer` ist in diesem Projekt schon einmal spurlos verschwunden.
|
||||
do $migration$
|
||||
declare
|
||||
v_alt constant text := $anker$update position_assignments set valid_to = v_rec.effective_date$anker$;
|
||||
v_neu constant text := $anker$delete from position_assignments
|
||||
where employee_id = v_rec.employee_id and valid_to is null and valid_from >= v_rec.effective_date;
|
||||
update position_assignments set valid_to = v_rec.effective_date$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 <> 2 then
|
||||
raise exception 'Der Anker im Nachtlauf kommt % mal vor, erwartet zweimal.', v_anzahl;
|
||||
end if;
|
||||
|
||||
execute replace(v_def, v_alt, v_neu);
|
||||
end
|
||||
$migration$;
|
||||
|
||||
|
||||
-- ═══ 4. 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 = 'transfer_employee' and p.prokind = 'f';
|
||||
if v_def not like '%valid_from >= v_effective%' then
|
||||
raise exception 'transfer_employee raeumt die Besetzung vom selben Tag nicht ab.';
|
||||
end if;
|
||||
if v_def not like '%require_hr_admin()%' then
|
||||
raise exception 'transfer_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 = 'promote_employee' and p.prokind = 'f';
|
||||
if v_def not like '%valid_from >= v_effective_date%' then
|
||||
raise exception 'promote_employee raeumt die Besetzung vom selben Tag nicht ab.';
|
||||
end if;
|
||||
if v_def not like '%require_hr_admin()%' then
|
||||
raise exception 'promote_employee prueft die Rechte nicht.';
|
||||
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;
|
||||
|
||||
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';
|
||||
|
||||
v_anzahl := (length(v_def) - length(replace(v_def, 'valid_from >= v_rec.effective_date', '')))
|
||||
/ length('valid_from >= v_rec.effective_date');
|
||||
if v_anzahl <> 2 then
|
||||
raise exception 'Der Nachtlauf raeumt an % von 2 Stellen ab.', v_anzahl;
|
||||
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;
|
||||
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;
|
||||
|
||||
raise notice 'Besetzung am selben Tag: alle vier Stellen abgeraeumt.';
|
||||
end
|
||||
$$;
|
||||
Reference in New Issue
Block a user