Bisher gab es dafuer nur den Import. Jetzt sitzt auf jeder Einheit im Organigramm -- in der Grafik wie in der Liste -- ein Plus, das eine untergeordnete Einheit anlegt, wahlweise gleich mit Leitungsplanstelle. Bewusst nur dieser eine Fall. Umbenennen, verschieben und schliessen fehlen nicht aus Zeitmangel: org_units.parent_id traegt kein Datum, ein Verschieben aenderte damit auch jede Auswertung auf einen vergangenen Stichtag, und der fruehere Stand waere danach nirgends mehr ablesbar. Anlegen stellt diese Frage nicht, weil vorher nichts da war -- und es kann dabei auch kein Kreis entstehen, was hier mehr wiegt als es klingt: die rekursive Abfrage in om_reporting_lines hat weder Tiefenbegrenzung noch Kreiserkennung. Die Leitungsplanstelle entsteht ueber create_position statt durch eine zweite Fassung derselben Logik -- dort haengen Jobkatalog, Nummernvergabe und die Pruefung "je Einheit genau eine Leitung". Die Orgnummer wird eingetragen, nicht vergeben. Vorgeschlagen wird die naechste freie Nummer der bestehenden Reihe, und nur dann, wenn sich im Bestand genau eine Systematik ablesen laesst -- eine plausibel aussehende, aber erfundene Nummer prueft niemand nach. Das Datum kommt nicht aus dem Stichtag der Ansicht. Wer sich die Struktur zum letzten Jahresende ansieht und auf Plus drueckt, will in aller Regel eine Einheit von heute anlegen.
157 lines
6.6 KiB
PL/PgSQL
157 lines
6.6 KiB
PL/PgSQL
-- 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
|
|
$$;
|