Eine leere Organisationseinheit wieder entfernen
Der Papierkorb neben dem Plus, im Organigramm wie in der Liste, und nur dort, wo nichts darunter haengt. Ein Knopf, der beim Klick eine Absage erteilt, ist schlechter als kein Knopf -- die Datenbank prueft es trotzdem, denn sie sieht auch geschlossene Planstellen, die im Baum gar nicht gezeichnet sind. Abgewiesen wird mit Grund: untergeordnete Einheiten, Planstellen (geschlossene zaehlen mit), Kostenstellen, die darauf verweisen, und die Wurzel selbst. Geloescht und nicht geschlossen, und das ist eine bewusste Einschraenkung. delete_position macht seit dem OM-Umbau den Unterschied vor: besetzt gewesen → valid_to, nie besetzt gewesen → weg. Fuer Einheiten laesst sich davon heute nur die zweite Haelfte umsetzen, weil ein valid_to an einer Einheit zwar eingetragen, aber nirgends gelesen wuerde -- weder Organigramm noch orgMapsAbfragen, Berichte, Druck oder die Auswahl beim Anlegen einer Planstelle schraenken org_units auf den Stichtag ein. Die geschlossene Einheit staende ueberall weiter da, nur mit einem Datum, das niemand sieht. Das Schliessen zum Stichtag kommt, wenn org_units gegen den Stichtag gelesen wird -- dieselbe Arbeit, die auch das Verschieben braucht.
This commit is contained in:
127
db/migrations/20260929140000_organisationseinheit_entfernen.sql
Normal file
127
db/migrations/20260929140000_organisationseinheit_entfernen.sql
Normal file
@@ -0,0 +1,127 @@
|
||||
-- Eine Organisationseinheit wieder entfernen
|
||||
--
|
||||
-- Das Gegenstück zu create_org_unit und mit derselben Einschränkung: es geht
|
||||
-- nur dort, wo nichts umgeschrieben wird.
|
||||
--
|
||||
-- ═══ Warum gelöscht und nicht geschlossen ═════════════════════════
|
||||
--
|
||||
-- delete_position macht es seit dem OM-Umbau vor: besetzt → Abweisung, einmal
|
||||
-- besetzt gewesen → `valid_to` statt Löschen (sonst verschwände mit der Stelle
|
||||
-- die Besetzungshistorie), nie besetzt gewesen → weg. Der Unterschied ist
|
||||
-- keine Vorsicht, sondern eine Aussage: was nie getragen hat, hat es auch nie
|
||||
-- gegeben.
|
||||
--
|
||||
-- Für Einheiten lässt sich davon heute nur die letzte Stufe umsetzen. Ein
|
||||
-- `valid_to` an einer Einheit wäre zwar eingetragen, aber **nirgends
|
||||
-- gelesen**: weder das Organigramm noch orgMapsAbfragen, die Berichte, der
|
||||
-- Druck oder die Auswahl beim Anlegen einer Planstelle schränken org_units auf
|
||||
-- den Stichtag ein. Die geschlossene Einheit stünde überall weiter da, nur mit
|
||||
-- einem Datum, das niemand sieht — das wäre schlechter als die klare
|
||||
-- Abweisung unten, weil es wie Erledigt aussieht.
|
||||
--
|
||||
-- Deshalb hier nur: eine Einheit, unter der nie etwas hing, verschwindet
|
||||
-- ganz. Alles andere wird abgewiesen, mit dem Grund. Das Schliessen zum
|
||||
-- Stichtag kommt, wenn org_units überall gegen den Stichtag gelesen wird —
|
||||
-- dieselbe Arbeit, die auch das Verschieben braucht.
|
||||
|
||||
create or replace function delete_org_unit(payload jsonb)
|
||||
returns void language plpgsql
|
||||
set search_path = public, pg_temp
|
||||
as $$
|
||||
declare
|
||||
v_id uuid := (payload->>'org_unit_id')::uuid;
|
||||
v_unit record;
|
||||
v_einheiten int;
|
||||
v_stellen int;
|
||||
begin
|
||||
perform require_hr_admin();
|
||||
|
||||
select id, org_number, name, parent_id into v_unit from org_units where id = v_id;
|
||||
if v_unit.id is null then
|
||||
raise exception 'Die Organisationseinheit existiert nicht.';
|
||||
end if;
|
||||
|
||||
if v_unit.parent_id is null then
|
||||
raise exception 'Die Gesellschaft ist die Wurzel und kann nicht entfernt werden.';
|
||||
end if;
|
||||
|
||||
select count(*) into v_einheiten from org_units where parent_id = v_id;
|
||||
if v_einheiten > 0 then
|
||||
raise exception 'Unter dieser Einheit hängen noch % Einheiten. Sie müssen zuerst woanders hin.', v_einheiten;
|
||||
end if;
|
||||
|
||||
-- Geschlossene Planstellen zählen mit. Sie tragen weiterhin diese Einheit,
|
||||
-- und mit ihr verschwände die Angabe, wo jemand einmal gesessen hat.
|
||||
select count(*) into v_stellen from om_positions where org_unit_id = v_id;
|
||||
if v_stellen > 0 then
|
||||
raise exception
|
||||
'In dieser Einheit bestehen noch % Planstellen (geschlossene zählen mit). Sie müssen zuerst entfernt oder verschoben werden.',
|
||||
v_stellen;
|
||||
end if;
|
||||
|
||||
-- Die Kostenstellen tragen die Einheit nur als Hinweis (siehe Migration
|
||||
-- 20260816100000), nicht als Zuordnung. Sie stünden nach dem Löschen auf
|
||||
-- einer Einheit, die es nicht mehr gibt — der Fremdschlüssel würde das
|
||||
-- abweisen, also vorher und mit Grund.
|
||||
if exists (select 1 from cost_centers where org_unit_id = v_id) then
|
||||
raise exception 'Auf diese Einheit verweisen noch Kostenstellen.';
|
||||
end if;
|
||||
|
||||
delete from org_units where id = v_id;
|
||||
|
||||
insert into audit_log (actor_user_id, actor_name, action, target_label, details)
|
||||
values (app_current_user_id(), current_actor_name(), 'Organisationseinheit gelöscht',
|
||||
v_unit.org_number || ' · ' || v_unit.name,
|
||||
'Ohne untergeordnete Einheiten und ohne Planstellen');
|
||||
end;
|
||||
$$;
|
||||
|
||||
comment on function delete_org_unit(jsonb) is
|
||||
'Entfernt eine Organisationseinheit, unter der nichts hängt und nie etwas hing. Einheiten mit Planstellen — auch geschlossenen — oder mit untergeordneten Einheiten werden abgewiesen; ein Schliessen zum Stichtag gibt es noch nicht, weil org_units nirgends gegen den Stichtag gelesen wird.';
|
||||
|
||||
grant execute on function delete_org_unit(jsonb) to alpenwerk_app;
|
||||
|
||||
|
||||
-- Selbstprüfung.
|
||||
do $$
|
||||
declare
|
||||
v_def text;
|
||||
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 = 'delete_org_unit' and p.prokind = 'f';
|
||||
|
||||
if v_def is null then
|
||||
raise exception 'delete_org_unit wurde nicht angelegt';
|
||||
end if;
|
||||
if v_def not like '%require_hr_admin()%' then
|
||||
raise exception 'delete_org_unit prüft die Rechte nicht';
|
||||
end if;
|
||||
if v_def not like '%search_path%' then
|
||||
raise exception 'delete_org_unit hat keinen festen search_path';
|
||||
end if;
|
||||
if v_def not like '%audit_log%' then
|
||||
raise exception 'delete_org_unit schreibt nichts ins Protokoll';
|
||||
end if;
|
||||
-- Die drei Abweisungen. Fällt eine bei einer Neuerzeugung weg, löscht die
|
||||
-- Funktion weiter — nur mehr, als sie darf.
|
||||
if v_def not like '%om_positions%' then
|
||||
raise exception 'delete_org_unit prüft die Planstellen nicht';
|
||||
end if;
|
||||
if v_def not like '%parent_id = v_id%' then
|
||||
raise exception 'delete_org_unit prüft die untergeordneten Einheiten nicht';
|
||||
end if;
|
||||
if v_def not like '%v_unit.parent_id is null%' then
|
||||
raise exception 'delete_org_unit schützt die Wurzel nicht';
|
||||
end if;
|
||||
|
||||
-- Das Gegenstück muss weiterhin dastehen: ein Löschen ohne Anlegen wäre
|
||||
-- eine Einbahnstrasse.
|
||||
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 = 'create_org_unit'
|
||||
) then
|
||||
raise exception 'create_org_unit fehlt';
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
Reference in New Issue
Block a user