-- 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);

  if jsonb_array_length(v_changes) = 0 then
    return;
  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;
$$;
