The old refusal read: "Diese Abwesenheit ist noch nicht wirksam. Sie muss über den Vorgang selbst abgebrochen werden." There was no such way. The row sat in the file, the scheduled change kept running toward its date, and nothing could stop either one. That is not hypothetical. One person went absent in July, came back in August, and still has a second return booked for the first of September — recorded while they were already working again. The guard added yesterday stops a third from being written; it does not remove the one that exists. Absences are called off whole, not field by field. For a planned contract change the scheduled payload gets the affected fields lifted out of it and runs on with the rest; an absence has no fields in that map, and half an absence is not a thing anyone means. So the whole scheduled change is cancelled, and what it had already noted on the person goes with it: the date they were to be away from, the date they were to come back on. Left behind, the profile would show an absence with no event behind it. If the absence is still running, the return date planned when it began applies again. The link between the row and the scheduled change had to exist first — start_karenz and record_karenz_return now record it. Existing rows get it backfilled, but only where one running change of that kind falls on that person and that day. Where two would match, the row keeps refusing: guessing which process to cancel is worse than refusing to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
431 lines
20 KiB
PL/PgSQL
431 lines
20 KiB
PL/PgSQL
-- Eine geplante Abwesenheit oder Rückkehr zurücknehmen
|
|
--
|
|
-- Bisher wies das Löschen sie ab: „Sie muss über den Vorgang selbst
|
|
-- abgebrochen werden." Nur gab es diesen Weg nirgends — die Zeile stand in
|
|
-- der Akte, der Vorgang lief weiter, und niemand konnte beides aufhalten.
|
|
-- Genau dieser Fall steht in den Daten: eine Person, die im Juli abwesend
|
|
-- wurde, im August zurückkam und für die trotzdem noch eine zweite Rückkehr
|
|
-- zum 1. September vorgemerkt ist.
|
|
--
|
|
-- Drei Teile:
|
|
-- 1. start_karenz und record_karenz_return vermerken den geplanten Vorgang
|
|
-- an der Historienzeile. Ohne diesen Verweis liesse sich nur über Person
|
|
-- und Datum raten, welcher Vorgang gemeint ist.
|
|
-- 2. Die vorhandenen Zeilen bekommen den Verweis nachgetragen, aber nur wo
|
|
-- er eindeutig ist.
|
|
-- 3. delete_history_entry bricht den Vorgang ab, statt abzuweisen.
|
|
|
|
CREATE OR REPLACE FUNCTION public.start_karenz(payload jsonb)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
declare
|
|
v_employee_id uuid := (payload->>'employee_id')::uuid;
|
|
v_start_date date := (payload->>'karenz_start_date')::date;
|
|
v_absence_type text := nullif(payload->>'absence_type', '');
|
|
v_name text;
|
|
v_old employees%rowtype;
|
|
-- Ohne Vorher-Werte liesse sich eine irrtümlich erfasste Abwesenheit
|
|
-- nicht zurücknehmen: es stünde nirgends, was vorher galt.
|
|
v_changes jsonb := '[]'::jsonb;
|
|
-- Der geplante Vorgang, damit sich die Zeile in der Historie auf ihn
|
|
-- beziehen kann: ohne diesen Verweis liesse sich eine irrtümlich
|
|
-- erfasste, noch nicht wirksame Abwesenheit nicht mehr abbrechen.
|
|
v_plan_id uuid;
|
|
begin
|
|
perform require_hr_admin();
|
|
select * into v_old from employees where id = v_employee_id;
|
|
v_name := v_old.first_name || ' ' || v_old.last_name;
|
|
|
|
v_changes := app_aenderung(v_changes, 'Status', v_old.status::text,
|
|
case when v_start_date <= current_date then 'Karenz' else v_old.status::text end);
|
|
v_changes := app_aenderung(v_changes, 'Art der Abwesenheit', v_old.absence_type, v_absence_type);
|
|
v_changes := app_aenderung(v_changes, 'Abwesend ab', v_old.karenz_start_date::text, v_start_date::text);
|
|
v_changes := app_aenderung(v_changes, 'Geplante Rückkehr', v_old.karenz_return_date::text, payload->>'planned_return_date');
|
|
|
|
if v_start_date <= current_date then
|
|
update employees set status = 'Karenz', karenz_start_date = v_start_date,
|
|
karenz_return_date = (payload->>'planned_return_date')::date,
|
|
absence_type = v_absence_type
|
|
where id = v_employee_id;
|
|
else
|
|
update employees set karenz_start_date = v_start_date where id = v_employee_id;
|
|
insert into pending_org_changes (employee_id, change_type, effective_date, payload)
|
|
values (v_employee_id, 'karenz_start', v_start_date,
|
|
jsonb_build_object('planned_return_date', payload->>'planned_return_date', 'absence_type', v_absence_type))
|
|
returning id into v_plan_id;
|
|
end if;
|
|
|
|
insert into employee_history (employee_id, event_date, event_type, description, changes, pending_id)
|
|
values (v_employee_id, v_start_date, 'Karenz',
|
|
coalesce(v_absence_type, 'Langzeitabwesenheit') || ', geplante Rückkehr am ' || (payload->>'planned_return_date') ||
|
|
case when payload->>'note' is not null and payload->>'note' <> '' then ' — ' || (payload->>'note') else '' end,
|
|
v_changes, v_plan_id);
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
|
|
values (app_current_user_id(), current_actor_name(), 'Karenz', v_name, v_employee_id,
|
|
coalesce(v_absence_type, 'Langzeitabwesenheit') || ', geplante Rückkehr ' || (payload->>'planned_return_date'));
|
|
end;
|
|
$function$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.record_karenz_return(payload jsonb)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
declare
|
|
v_employee_id uuid := (payload->>'employee_id')::uuid;
|
|
v_return_date date := (payload->>'return_date')::date;
|
|
v_name text;
|
|
v_employment_type employment_type;
|
|
v_weekly_hours numeric;
|
|
v_karenz_start date;
|
|
v_absence_type text;
|
|
v_old employees%rowtype;
|
|
-- Wie bei der Abwesenheit: ohne Vorher-Werte liesse sich eine
|
|
-- irrtümlich erfasste Rückkehr nicht zurücknehmen.
|
|
v_changes jsonb := '[]'::jsonb;
|
|
-- Warum jemand mit weniger Stunden zurückkommt: Wiedereingliederungs-
|
|
-- oder Elternteilzeit. Nur bedeutsam, wenn überhaupt reduziert wird.
|
|
v_grund text := nullif(payload->>'reduction_reason', '');
|
|
-- Wie bei der Abwesenheit: der Verweis auf den geplanten Vorgang.
|
|
v_plan_id uuid;
|
|
begin
|
|
perform require_hr_admin();
|
|
select * into v_old from employees where id = v_employee_id;
|
|
v_name := v_old.first_name || ' ' || v_old.last_name;
|
|
v_karenz_start := v_old.karenz_start_date;
|
|
v_absence_type := v_old.absence_type;
|
|
|
|
-- Ohne Abwesenheit keine Rückkehr. Die Prüfung fehlte, und in den Daten
|
|
-- steht eine Person mit zwei Rückkehren zu einer Abwesenheit: die zweite
|
|
-- wurde erfasst, als sie längst wieder aktiv war. Der Status wäre danach
|
|
-- aus einem Ereignis abgeleitet, das nie stattgefunden hat.
|
|
if v_old.status <> 'Karenz' and v_old.karenz_start_date is null then
|
|
raise exception 'Diese Person ist nicht abwesend — eine Rückkehr gibt es nur aus einer Abwesenheit.';
|
|
end if;
|
|
|
|
-- Und nur eine: eine zweite geplante Rückkehr würde die erste am
|
|
-- Stichtag stillschweigend überschreiben.
|
|
if exists (
|
|
select 1 from pending_org_changes p
|
|
where p.employee_id = v_employee_id and p.change_type = 'karenz_return' and p.status = 'pending'
|
|
) then
|
|
raise exception 'Für diese Person ist bereits eine Rückkehr geplant. Sie muss zuerst zurückgenommen werden.';
|
|
end if;
|
|
|
|
if v_karenz_start is not null and v_return_date <= v_karenz_start then
|
|
raise exception 'Das Rückkehrdatum muss nach dem Beginn der Langzeitabwesenheit (%) liegen.', v_karenz_start;
|
|
end if;
|
|
|
|
if payload->>'employment_mode' = 'Vollzeit' then
|
|
v_employment_type := 'Vollzeit'; v_weekly_hours := 38.5;
|
|
elsif payload->>'employment_mode' = 'Teilzeit' then
|
|
v_employment_type := 'Teilzeit'; v_weekly_hours := (payload->>'weekly_hours')::numeric;
|
|
end if;
|
|
|
|
if v_return_date <= current_date then
|
|
-- Keine Manager-Nachführung mehr nötig: wer aus der Abwesenheit
|
|
-- zurückkehrt, ist wieder anwesend, und die abgeleitete Berichtslinie
|
|
-- fällt automatisch von der Vertretung auf ihn zurück.
|
|
update employees set
|
|
status = 'Aktiv',
|
|
karenz_return_date = null,
|
|
karenz_start_date = null,
|
|
absence_type = null,
|
|
employment_type = coalesce(v_employment_type, employment_type),
|
|
weekly_hours = coalesce(v_weekly_hours, weekly_hours),
|
|
-- Kehrt jemand reduziert zurück, ist der Grund dafür ein Zustand,
|
|
-- kein Einmalereignis: danach lässt sich auswerten, wer gerade in
|
|
-- Eltern- oder Wiedereingliederungsteilzeit ist.
|
|
teilzeit_art = case when payload->>'employment_mode' = 'Teilzeit' then v_grund else teilzeit_art end,
|
|
teilzeit_bis = case
|
|
when payload->>'employment_mode' = 'Teilzeit' and v_grund is not null
|
|
then nullif(payload->>'teilzeit_bis', '')::date
|
|
when payload->>'employment_mode' = 'Teilzeit' then null
|
|
else teilzeit_bis end
|
|
where id = v_employee_id;
|
|
else
|
|
update employees set karenz_return_date = v_return_date where id = v_employee_id;
|
|
insert into pending_org_changes (employee_id, change_type, effective_date, payload)
|
|
values (v_employee_id, 'karenz_return', v_return_date,
|
|
jsonb_build_object('employment_type', v_employment_type, 'weekly_hours', v_weekly_hours,
|
|
'teilzeit_art', v_grund, 'teilzeit_bis', nullif(payload->>'teilzeit_bis', '')))
|
|
returning id into v_plan_id;
|
|
end if;
|
|
|
|
if v_return_date <= current_date then
|
|
v_changes := app_aenderung(v_changes, 'Status', v_old.status::text, 'Aktiv');
|
|
v_changes := app_aenderung(v_changes, 'Art der Abwesenheit', v_old.absence_type, null);
|
|
v_changes := app_aenderung(v_changes, 'Abwesend ab', v_old.karenz_start_date::text, null);
|
|
v_changes := app_aenderung(v_changes, 'Geplante Rückkehr', v_old.karenz_return_date::text, null);
|
|
if v_employment_type is not null then
|
|
v_changes := app_aenderung(v_changes, 'Beschäftigungsausmaß', v_old.employment_type::text, v_employment_type::text);
|
|
v_changes := app_aenderung(v_changes, 'Wochenstunden', v_old.weekly_hours::text, v_weekly_hours::text);
|
|
end if;
|
|
if payload->>'employment_mode' = 'Teilzeit' then
|
|
v_changes := app_aenderung(v_changes, 'Teilzeitvariante', v_old.teilzeit_art, v_grund);
|
|
v_changes := app_aenderung(v_changes, 'Teilzeit bis', v_old.teilzeit_bis::text,
|
|
case when v_grund is not null then nullif(payload->>'teilzeit_bis', '') else null end);
|
|
end if;
|
|
end if;
|
|
|
|
insert into employee_history (employee_id, event_date, event_type, description, changes, pending_id)
|
|
values (v_employee_id, v_return_date, 'Rückkehr',
|
|
'Rückkehr aus ' || coalesce(v_absence_type, 'Langzeitabwesenheit') || ' am ' || v_return_date
|
|
|| case when payload->>'employment_mode' = 'Teilzeit'
|
|
then ', reduziert auf ' || (payload->>'weekly_hours') || ' h'
|
|
|| coalesce(' (' || v_grund || ')', '')
|
|
else '' end,
|
|
v_changes, v_plan_id);
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
|
|
values (app_current_user_id(), current_actor_name(), 'Rückkehr', v_name, v_employee_id, 'Rückkehr am ' || v_return_date || coalesce(' — ' || v_grund, ''));
|
|
end;
|
|
$function$;
|
|
|
|
-- Nachtrag für die Zeilen, die vor dieser Verknüpfung entstanden sind.
|
|
-- Nur wo genau ein laufender Vorgang derselben Person auf denselben Tag
|
|
-- fällt: bei zweien wäre die Zuordnung geraten, und geraten wird hier nicht.
|
|
update employee_history h
|
|
set pending_id = p.id
|
|
from pending_org_changes p
|
|
where h.pending_id is null
|
|
and h.event_type in ('Karenz', 'Rückkehr')
|
|
and p.employee_id = h.employee_id
|
|
and p.status = 'pending'
|
|
and p.effective_date = h.event_date
|
|
and p.change_type = case h.event_type when 'Karenz' then 'karenz_start' else 'karenz_return' end
|
|
and (select count(*) from pending_org_changes q
|
|
where q.employee_id = h.employee_id
|
|
and q.status = 'pending'
|
|
and q.effective_date = h.event_date
|
|
and q.change_type = p.change_type) = 1;
|
|
|
|
CREATE OR REPLACE FUNCTION public.delete_history_entry(payload jsonb)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
declare
|
|
v_id uuid := (payload->>'history_id')::uuid;
|
|
v_eintrag employee_history%rowtype;
|
|
v_name text;
|
|
v_karte constant jsonb := app_feld_karte();
|
|
v_aenderung jsonb;
|
|
v_feld text;
|
|
v_wert text;
|
|
v_spalte text;
|
|
v_typ text;
|
|
v_gruppe text;
|
|
v_spaeter boolean;
|
|
v_zurueckgesetzt jsonb := '[]'::jsonb;
|
|
v_setz text[] := '{}';
|
|
v_plan pending_org_changes%rowtype;
|
|
v_neuer_payload jsonb;
|
|
v_leer boolean;
|
|
-- Das Rückkehrdatum, das beim Beginn der Abwesenheit vorgesehen war.
|
|
v_geplant date;
|
|
begin
|
|
perform require_hr_admin();
|
|
|
|
select * into v_eintrag from employee_history where id = v_id;
|
|
if not found then
|
|
raise exception 'Historieneintrag nicht gefunden.';
|
|
end if;
|
|
|
|
if v_eintrag.event_type = 'Eintritt' then
|
|
raise exception 'Der Eintritt lässt sich nicht löschen — er ist der Anfang der Zeitleiste.';
|
|
end if;
|
|
|
|
if v_eintrag.event_type not in ('Stammdatenänderung', 'Vertragsänderung', 'Karenz', 'Rückkehr') then
|
|
raise exception 'Dieser Vorgang lässt sich hier nicht zurücknehmen. Für % gibt es den passenden Weg.', v_eintrag.event_type;
|
|
end if;
|
|
|
|
-- Die Reihenfolge zählt: eine Rückkehr setzt eine Abwesenheit voraus.
|
|
-- Bliebe sie stehen, während die Abwesenheit verschwindet, stünde in der
|
|
-- Akte eine Rückkehr aus dem Nichts — und der Status ergäbe sich aus
|
|
-- einem Eintrag, dessen Ausgangslage gelöscht ist.
|
|
if v_eintrag.event_type = 'Karenz' and exists (
|
|
select 1 from employee_history h
|
|
where h.employee_id = v_eintrag.employee_id
|
|
and h.event_type = 'Rückkehr'
|
|
and (h.event_date, h.created_at) > (v_eintrag.event_date, v_eintrag.created_at)
|
|
) then
|
|
raise exception 'Zu dieser Abwesenheit gibt es eine Rückkehr. Sie muss zuerst gelöscht werden.';
|
|
end if;
|
|
|
|
-- ── Geplante Abwesenheit oder Rückkehr: ganz abbrechen ────────────
|
|
--
|
|
-- Für die übrigen Vorgänge wird aus dem geplanten Vorgang Feld für Feld
|
|
-- herausgenommen, was die Zeile beschreibt. Abwesenheit und Rückkehr
|
|
-- gehen so nicht: ihre Felder haben in app_feld_karte keinen Ort, und
|
|
-- eine halbe Abwesenheit gibt es fachlich auch nicht. Hier fällt der
|
|
-- Vorgang deshalb ganz — was genau die Frage ist, die jemand stellt,
|
|
-- der die geplante Zeile löscht.
|
|
--
|
|
-- Was am Stammsatz schon vermerkt war, geht mit: das Datum, ab dem
|
|
-- jemand fehlen sollte, und das Datum, an dem er zurückkommen sollte.
|
|
-- Bliebe es stehen, stünde im Profil eine Abwesenheit ohne Ereignis.
|
|
if v_eintrag.event_type in ('Karenz', 'Rückkehr') and v_eintrag.event_date > current_date then
|
|
if v_eintrag.pending_id is null then
|
|
raise exception 'Zu dieser geplanten Abwesenheit ist kein Vorgang hinterlegt. Sie stammt aus der Zeit vor dieser Verknüpfung und lässt sich hier nicht abbrechen.';
|
|
end if;
|
|
|
|
select * into v_plan from pending_org_changes where id = v_eintrag.pending_id for update;
|
|
if not found or v_plan.status <> 'pending' then
|
|
raise exception 'Der geplante Vorgang läuft nicht mehr — er wurde bereits angewendet oder abgebrochen.';
|
|
end if;
|
|
|
|
select first_name || ' ' || last_name into v_name from employees where id = v_eintrag.employee_id;
|
|
|
|
update pending_org_changes set status = 'cancelled' where id = v_plan.id;
|
|
|
|
if v_plan.change_type = 'karenz_start' then
|
|
-- Die Abwesenheit hat nie begonnen; sie kann es auch nicht mehr.
|
|
update employees set karenz_start_date = null, karenz_return_date = null
|
|
where id = v_eintrag.employee_id and status <> 'Karenz';
|
|
else
|
|
-- Läuft die Abwesenheit noch, gilt wieder das Datum, das bei ihrem
|
|
-- Beginn vorgesehen war. Ist die Person längst zurück, war die
|
|
-- geplante Rückkehr ohnehin gegenstandslos.
|
|
select nullif(a->>'nachher', '')::date into v_geplant
|
|
from employee_history h,
|
|
lateral jsonb_array_elements(coalesce(h.changes, '[]'::jsonb)) a
|
|
where h.employee_id = v_eintrag.employee_id
|
|
and h.event_type = 'Karenz'
|
|
and a->>'feld' = 'Geplante Rückkehr'
|
|
order by h.event_date desc, h.created_at desc
|
|
limit 1;
|
|
|
|
update employees
|
|
set karenz_return_date = case when status = 'Karenz' or karenz_start_date is not null then v_geplant end
|
|
where id = v_eintrag.employee_id;
|
|
end if;
|
|
|
|
delete from employee_history where id = v_id;
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
|
values (app_current_user_id(), current_actor_name(), 'Geplante Änderung abgebrochen', v_name, v_eintrag.employee_id,
|
|
v_eintrag.event_type || ' zum ' || v_eintrag.event_date || ' abgebrochen (der Vorgang entfällt ganz)',
|
|
coalesce(v_eintrag.changes, '[]'::jsonb));
|
|
return;
|
|
end if;
|
|
|
|
if v_eintrag.changes is null or jsonb_array_length(v_eintrag.changes) = 0 then
|
|
raise exception 'Zu diesem Eintrag sind keine Feldwerte erfasst — es gibt nichts, worauf zurückgesetzt werden könnte.';
|
|
end if;
|
|
|
|
select first_name || ' ' || last_name into v_name from employees where id = v_eintrag.employee_id;
|
|
|
|
-- ── Noch nicht wirksam: die geplante Änderung entschärfen ──────────
|
|
if v_eintrag.event_date > current_date then
|
|
if v_eintrag.pending_id is null then
|
|
raise exception 'Zu dieser geplanten Änderung ist kein Vorgang hinterlegt. Sie stammt aus der Zeit vor dieser Verknüpfung und lässt sich hier nicht abbrechen.';
|
|
end if;
|
|
|
|
select * into v_plan from pending_org_changes where id = v_eintrag.pending_id for update;
|
|
if not found or v_plan.status <> 'pending' then
|
|
raise exception 'Der geplante Vorgang läuft nicht mehr — er wurde bereits angewendet oder abgebrochen.';
|
|
end if;
|
|
|
|
v_neuer_payload := v_plan.payload;
|
|
for v_aenderung in select * from jsonb_array_elements(v_eintrag.changes) loop
|
|
v_feld := v_aenderung->>'feld';
|
|
if not v_karte ? v_feld then
|
|
continue;
|
|
end if;
|
|
v_spalte := v_karte->v_feld->>0;
|
|
v_gruppe := v_karte->v_feld->>2;
|
|
if v_neuer_payload ? v_gruppe then
|
|
v_neuer_payload := jsonb_set(v_neuer_payload, array[v_gruppe], (v_neuer_payload->v_gruppe) - v_spalte);
|
|
end if;
|
|
end loop;
|
|
|
|
v_leer := coalesce(jsonb_array_length(
|
|
(select jsonb_agg(k) from jsonb_object_keys(coalesce(v_neuer_payload->'person', '{}'::jsonb)) k)), 0) = 0
|
|
and coalesce(jsonb_array_length(
|
|
(select jsonb_agg(k) from jsonb_object_keys(coalesce(v_neuer_payload->'contract', '{}'::jsonb)) k)), 0) = 0
|
|
and coalesce(jsonb_array_length(
|
|
(select jsonb_agg(k) from jsonb_object_keys(coalesce(v_neuer_payload->'role', '{}'::jsonb)) k)), 0) = 0;
|
|
|
|
if v_leer then
|
|
update pending_org_changes set status = 'cancelled' where id = v_plan.id;
|
|
else
|
|
update pending_org_changes set payload = v_neuer_payload where id = v_plan.id;
|
|
end if;
|
|
|
|
delete from employee_history where id = v_id;
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
|
values (app_current_user_id(), current_actor_name(), 'Geplante Änderung abgebrochen', v_name, v_eintrag.employee_id,
|
|
v_eintrag.event_type || ' zum ' || v_eintrag.event_date || ' abgebrochen: ' || app_aenderungsfelder(v_eintrag.changes) ||
|
|
case when v_leer then ' (der Vorgang entfällt ganz)' else ' (der Vorgang läuft mit den übrigen Feldern weiter)' end,
|
|
v_eintrag.changes);
|
|
return;
|
|
end if;
|
|
|
|
-- ── Bereits wirksam: Feld für Feld zurücksetzen ────────────────────
|
|
for v_aenderung in select * from jsonb_array_elements(v_eintrag.changes) loop
|
|
v_feld := v_aenderung->>'feld';
|
|
|
|
if not v_karte ? v_feld then
|
|
continue;
|
|
end if;
|
|
|
|
select exists (
|
|
select 1
|
|
from employee_history h,
|
|
lateral jsonb_array_elements(coalesce(h.changes, '[]'::jsonb)) a
|
|
where h.employee_id = v_eintrag.employee_id
|
|
and h.id <> v_eintrag.id
|
|
and a->>'feld' = v_feld
|
|
and (h.event_date, h.created_at) > (v_eintrag.event_date, v_eintrag.created_at)
|
|
) into v_spaeter;
|
|
|
|
if v_spaeter then
|
|
continue;
|
|
end if;
|
|
|
|
v_spalte := v_karte->v_feld->>0;
|
|
v_typ := v_karte->v_feld->>1;
|
|
v_wert := v_aenderung->>'vorher';
|
|
|
|
if v_typ = 'liste' then
|
|
v_setz := v_setz || format('%I = coalesce(string_to_array(%L, '', ''), ''{}'')', v_spalte, nullif(v_wert, ''));
|
|
else
|
|
v_setz := v_setz || format('%I = %L::%s', v_spalte, nullif(v_wert, ''), v_typ);
|
|
end if;
|
|
|
|
v_zurueckgesetzt := v_zurueckgesetzt || jsonb_build_object(
|
|
'feld', v_feld,
|
|
'vorher', v_aenderung->>'nachher',
|
|
'nachher', v_wert
|
|
);
|
|
end loop;
|
|
|
|
-- Alles in einem UPDATE: chk_weekly_hours verknüpft Beschäftigungsausmaß
|
|
-- und Wochenstunden, und zwischen zwei getrennten Anweisungen stünde
|
|
-- zwangsläufig ein Zwischenstand, den die Bedingung verbietet.
|
|
if array_length(v_setz, 1) > 0 then
|
|
begin
|
|
execute format('update employees set %s where id = %L', array_to_string(v_setz, ', '), v_eintrag.employee_id);
|
|
exception when check_violation then
|
|
raise exception 'Zurücksetzen nicht möglich: die Werte von damals passen nicht mehr zum heutigen Stand (%). Vermutlich wurde ein zusammengehörendes Feld später einzeln geändert.', sqlerrm;
|
|
end;
|
|
end if;
|
|
|
|
delete from employee_history where id = v_id;
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
|
values (app_current_user_id(), current_actor_name(), 'Historieneintrag gelöscht', v_name, v_eintrag.employee_id,
|
|
v_eintrag.event_type || ' vom ' || v_eintrag.event_date ||
|
|
case when jsonb_array_length(v_zurueckgesetzt) = 0
|
|
then ' gelöscht; keine Werte zurückgesetzt (spätere Änderungen gelten)'
|
|
else ' gelöscht und zurückgesetzt: ' || app_aenderungsfelder(v_zurueckgesetzt) end,
|
|
v_zurueckgesetzt);
|
|
end;
|
|
$function$;
|