-- Eine Organisationseinheit aus der Anwendung heraus anlegen -- -- Bisher gab es dafür nur den Import — und für alles andere an einer Einheit -- (umbenennen, verschieben, schliessen) gibt es weiterhin nur die Datenbank. -- Das hier ist bewusst nur der eine Fall: **eine neue Einheit unter einer -- bestehenden**. -- -- Warum gerade der: er ist der einzige, der nichts umschreibt. Die neue -- Einheit ist immer ein Blatt, also -- -- * kann kein Kreis entstehen — und das ist keine Kleinigkeit: die -- rekursive Abfrage `ancestry` in om_reporting_lines hat weder -- Tiefenbegrenzung noch Kreiserkennung, ein Kreis in parent_id würde dort -- nicht zu einem falschen Ergebnis führen, sondern zu keinem; -- * wird keine Vergangenheit verändert. org_units.parent_id trägt kein -- Datum: wer eine bestehende Einheit umhängt, ändert damit auch jede -- Auswertung auf einen vergangenen Stichtag, und der frühere Stand ist -- danach nirgends mehr ablesbar. Beim Anlegen stellt sich die Frage -- nicht, weil vorher nichts da war. -- -- Das Verschieben bestehender Einheiten braucht deshalb eine Entscheidung -- (Vergangenheit überschreiben oder die Aufhängung datieren) und steht -- absichtlich noch nicht hier. create or replace function create_org_unit(payload jsonb) returns uuid language plpgsql set search_path = public, pg_temp as $$ declare v_parent_id uuid := (payload->>'parent_id')::uuid; v_org_number text := nullif(trim(payload->>'org_number'), ''); v_name text := nullif(trim(payload->>'name'), ''); v_unit_type text := nullif(trim(payload->>'unit_type'), ''); v_valid_from date := coalesce(nullif(payload->>'valid_from','')::date, current_date); v_leitung text := nullif(trim(payload->>'leitung_taetigkeit'), ''); v_parent record; v_id uuid; begin perform require_hr_admin(); if v_parent_id is null then -- Eine Einheit ohne Übergeordnete wäre eine zweite Wurzel. Es gibt genau -- eine, und sie entsteht nicht hier. raise exception 'Es muss eine übergeordnete Einheit angegeben werden.'; end if; select id, org_number, name, valid_from, valid_to into v_parent from org_units where id = v_parent_id; if v_parent.id is null then raise exception 'Die übergeordnete Einheit existiert nicht.'; end if; if v_org_number is null then raise exception 'Es muss eine Orgnummer angegeben werden.'; end if; if v_name is null then raise exception 'Es muss eine Bezeichnung angegeben werden.'; end if; if v_unit_type is null then raise exception 'Es muss eine Art angegeben werden.'; end if; -- Die Gesellschaft ist das Ganze und steht über allem. Als Kind einer -- anderen Einheit wäre sie ein Etikett, das seiner Stelle im Baum -- widerspricht — und genau daran ist in diesem Projekt schon dreimal Code -- zerbrochen, der aus dem Etikett auf die Ebene geschlossen hat. if v_unit_type = 'Gesellschaft' then raise exception 'Die Gesellschaft ist die Wurzel und kann nicht unter einer anderen Einheit stehen.'; end if; -- Der eindeutige Index würde das ebenfalls abfangen, aber mit einer -- Meldung, die in der Oberfläche nichts erklärt. if exists (select 1 from org_units where org_number = v_org_number) then raise exception 'Die Orgnummer % ist bereits vergeben.', v_org_number; end if; -- Eine Einheit, die früher beginnt als die über ihr, hängt an einem Tag im -- Baum, an dem es diesen Baum noch nicht gab. Auf einen solchen Stichtag -- ausgewertet stünde sie ohne Vorfahren da. if v_valid_from < v_parent.valid_from then raise exception 'Die übergeordnete Einheit gilt erst ab %. Ein früherer Beginn ist nicht möglich.', v_parent.valid_from; end if; if v_parent.valid_to is not null and v_valid_from >= v_parent.valid_to then raise exception 'Die übergeordnete Einheit gilt nur bis %.', v_parent.valid_to; end if; insert into org_units (org_number, name, parent_id, unit_type, valid_from) values (v_org_number, v_name, v_parent_id, v_unit_type::org_unit_type, v_valid_from) returning id into v_id; insert into audit_log (actor_user_id, actor_name, action, target_label, details) values (app_current_user_id(), current_actor_name(), 'Organisationseinheit angelegt', v_org_number || ' · ' || v_name, 'Unter ' || v_parent.org_number || ' · ' || v_parent.name || ', Art ' || v_unit_type || ', gültig ab ' || v_valid_from || case when v_leitung is not null then ', mit Leitungsplanstelle „' || v_leitung || '“' else '' end); -- Die Leitungsplanstelle entsteht über create_position und nicht durch eine -- zweite Fassung derselben Logik. Dort hängen der Jobkatalog (gleiche -- Tätigkeit = ein Eintrag), die Nummernvergabe und die Prüfung „je Einheit -- genau eine Leitung“ — nachgebaut liefe das hier beim ersten Zusatz -- auseinander. Sie schreibt ihren eigenen Protokolleintrag. if v_leitung is not null then perform create_position(jsonb_build_object( 'org_unit_id', v_id, 'job_title', v_leitung, 'is_chief', true, 'valid_from', v_valid_from )); end if; return v_id; end; $$; comment on function create_org_unit(jsonb) is 'Legt eine Organisationseinheit unter einer bestehenden an, wahlweise gleich mit Leitungsplanstelle. Nur anlegen: umbenennen, verschieben und schliessen gibt es bewusst nicht, weil parent_id kein Datum trägt.'; grant execute on function create_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 = 'create_org_unit' and p.prokind = 'f'; if v_def is null then raise exception 'create_org_unit wurde nicht angelegt'; end if; if v_def not like '%require_hr_admin()%' then raise exception 'create_org_unit prüft die Rechte nicht'; end if; -- Ohne search_path liesse sich der Funktion über einen eigenen Schemapfad -- eine andere Tabelle unterschieben. Hauskonvention für jede Funktion. if v_def not like '%search_path%' then raise exception 'create_org_unit hat keinen festen search_path'; end if; if v_def not like '%audit_log%' then raise exception 'create_org_unit schreibt nichts ins Protokoll'; end if; -- Nicht nachgebaut, sondern aufgerufen — siehe den Kommentar dort. if v_def not like '%create_position(%' then raise exception 'create_org_unit legt die Leitungsplanstelle nicht über create_position an'; end if; -- Ohne Übergeordnete entstünde eine zweite Wurzel. if v_def not like '%parent_id%' then raise exception 'create_org_unit kennt die übergeordnete Einheit nicht'; end if; end $$;