update_position returned quietly when no field differed, and the interface answered "Planstelle geändert." — a confirmation for something that had not happened. It now raises, and the message says so. This is reachable without the user doing anything wrong: the chief checkbox is dropped on the way out when the unit already has a chief position, so a save consisting only of that tick arrives as an empty change set. The reply was a green toast and an unchanged list, which sends someone looking in the wrong place. It also separates the two explanations for "I saved and nothing happened", which is why it went in now: an empty change set is refused in red, so a green confirmation with a stale card can only mean the page did not reload. Verified against the live database: an unchanged payload is refused, a changed one goes through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
133 lines
5.5 KiB
PL/PgSQL
133 lines
5.5 KiB
PL/PgSQL
-- Eine Planstelle ändern.
|
|
--
|
|
-- Bisher liess sie sich nur anlegen und löschen. Ein Tippfehler in der
|
|
-- Tätigkeit oder ein falsches Gültigkeitsdatum bedeutete: löschen und neu —
|
|
-- mit neuer Planstellennummer. Die Nummer steht aber in Stellenausschreibungen
|
|
-- und Budgets, und die Protokollspur reisst ab.
|
|
--
|
|
-- Was hier bewusst **nicht** geht, steht als Sperre drin, nicht als
|
|
-- Anmerkung — siehe die drei Prüfungen unten.
|
|
create or replace function update_position(payload jsonb)
|
|
returns void
|
|
language plpgsql
|
|
set search_path = public, pg_temp
|
|
as $function$
|
|
declare
|
|
v_id uuid := (payload->>'position_id')::uuid;
|
|
v_alt om_positions%rowtype;
|
|
v_alt_titel text;
|
|
v_alt_einheit text;
|
|
v_org_unit_id uuid;
|
|
v_job_title text := nullif(trim(payload->>'job_title'), '');
|
|
v_is_chief boolean;
|
|
v_valid_from date;
|
|
v_valid_to date;
|
|
v_job_id uuid;
|
|
v_unit_name text;
|
|
v_besetzt boolean;
|
|
v_changes jsonb := '[]'::jsonb;
|
|
begin
|
|
perform require_hr_admin();
|
|
|
|
select * into v_alt from om_positions where id = v_id;
|
|
if v_alt.id is null then
|
|
raise exception 'Die Planstelle existiert nicht.';
|
|
end if;
|
|
|
|
select title into v_alt_titel from jobs where id = v_alt.job_id;
|
|
select name into v_alt_einheit from org_units where id = v_alt.org_unit_id;
|
|
|
|
-- Fehlende Schlüssel heissen „unverändert", nicht „leeren".
|
|
v_org_unit_id := coalesce(nullif(payload->>'org_unit_id','')::uuid, v_alt.org_unit_id);
|
|
v_job_title := coalesce(v_job_title, v_alt_titel);
|
|
v_is_chief := coalesce((payload->>'is_chief')::boolean, v_alt.is_chief);
|
|
v_valid_from := coalesce(nullif(payload->>'valid_from','')::date, v_alt.valid_from);
|
|
v_valid_to := case when payload ? 'valid_to' then nullif(payload->>'valid_to','')::date else v_alt.valid_to end;
|
|
|
|
select name into v_unit_name from org_units where id = v_org_unit_id;
|
|
if v_unit_name is null then
|
|
raise exception 'Die Organisationseinheit existiert nicht.';
|
|
end if;
|
|
if v_valid_to is not null and v_valid_to < v_valid_from then
|
|
raise exception 'Das Ende der Gültigkeit liegt vor ihrem Beginn.';
|
|
end if;
|
|
|
|
select exists (
|
|
select 1 from position_assignments
|
|
where position_id = v_id and (valid_to is null or valid_to > current_date)
|
|
) into v_besetzt;
|
|
|
|
-- (1) Die Einheit einer vergebenen Planstelle zu wechseln wäre eine
|
|
-- Versetzung — mit allem, was dazugehört: Historie, Berichtslinie,
|
|
-- Protokoll. Das gehört in transfer_employee und nicht hierher, sonst
|
|
-- wandert jemand lautlos in eine andere Abteilung.
|
|
if v_besetzt and v_org_unit_id is distinct from v_alt.org_unit_id then
|
|
raise exception 'Diese Planstelle ist vergeben. Für einen Wechsel der Einheit die Versetzung benutzen.';
|
|
end if;
|
|
|
|
-- (2) Ein Ende, während noch jemand darauf sitzt, hinterlässt eine
|
|
-- Besetzung ohne Planstelle.
|
|
if v_besetzt and v_valid_to is not null then
|
|
raise exception 'Diese Planstelle ist vergeben und kann kein Ende der Gültigkeit bekommen.';
|
|
end if;
|
|
|
|
-- (3) Je Einheit nur eine gültige Leitung. Der Unique-Index fängt das auch,
|
|
-- aber mit einer Meldung, die in der Oberfläche nichts erklärt.
|
|
if v_is_chief and exists (
|
|
select 1 from om_positions
|
|
where org_unit_id = v_org_unit_id and is_chief and valid_to is null and id <> v_id
|
|
) then
|
|
raise exception 'Für % besteht bereits eine Leitungsplanstelle.', v_unit_name;
|
|
end if;
|
|
|
|
-- Gleiche Tätigkeit, ein Katalogeintrag — dieselbe Regel wie beim Anlegen.
|
|
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;
|
|
|
|
v_changes := app_aenderung(v_changes, 'Tätigkeit', v_alt_titel, v_job_title);
|
|
v_changes := app_aenderung(v_changes, 'Organisationseinheit', v_alt_einheit, v_unit_name);
|
|
v_changes := app_aenderung(v_changes, 'Leitungsplanstelle', v_alt.is_chief::text, v_is_chief::text);
|
|
v_changes := app_aenderung(v_changes, 'Gültig ab', v_alt.valid_from::text, v_valid_from::text);
|
|
v_changes := app_aenderung(v_changes, 'Gültig bis', v_alt.valid_to::text, v_valid_to::text);
|
|
|
|
-- Nichts geändert ist ein Ergebnis, kein Erfolg.
|
|
--
|
|
-- Vorher kehrte die Funktion hier stumm zurück, und die Oberfläche meldete
|
|
-- „Planstelle geändert." Wer etwas eingetragen hatte, das unterwegs
|
|
-- verworfen wurde — etwa das Leitungshäkchen, das bei bereits vergebener
|
|
-- Leitung nicht durchkommt —, sah eine Erfolgsmeldung und eine unveränderte
|
|
-- Liste. Das ist genau die Rückmeldung, die einen suchen lässt.
|
|
if jsonb_array_length(v_changes) = 0 then
|
|
raise exception 'Es wurde nichts geändert.';
|
|
end if;
|
|
|
|
update om_positions
|
|
set org_unit_id = v_org_unit_id,
|
|
job_id = v_job_id,
|
|
is_chief = v_is_chief,
|
|
valid_from = v_valid_from,
|
|
valid_to = v_valid_to
|
|
where id = v_id;
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, details, changes)
|
|
values (app_current_user_id(), current_actor_name(), 'Planstelle geändert',
|
|
v_job_title || ' (' || v_unit_name || ')',
|
|
app_aenderungsfelder(v_changes), v_changes);
|
|
end;
|
|
$function$;
|
|
|
|
do $$
|
|
declare r text;
|
|
begin
|
|
foreach r in array array['anon', 'authenticated', 'service_role', 'alpenwerk_app'] loop
|
|
if exists (select 1 from pg_roles where rolname = r) then
|
|
execute format('grant execute on function update_position(jsonb) to %I', r);
|
|
end if;
|
|
end loop;
|
|
end;
|
|
$$;
|