Anforderung 9 aus dem Workshop. Migration 20260915140000.
Im Dokument standen die beiden untereinander:
Arbeiter, Angestellte, Lehrlinge
Standard / Praktikant / Geringfuegige Beschaeftigung / Altersteilzeit
Die erste Zeile gibt es schon als worker_type, Lehrling seit 20260910140000.
Die zweite ist eine andere Frage: nicht *als was* jemand angestellt ist,
sondern *in welcher Form*. Beide gelten gleichzeitig — ein Praktikant ist
Arbeiter oder Angestellter, nicht statt dessen. In eine Liste gepresst
muesste man sich fuer eine der Antworten entscheiden und verloere die andere.
NOT NULL mit Vorgabe "Standard": jede Person ist in irgendeiner Form
beschaeftigt, und "nicht erfasst" waere keine Aussage, sondern eine Luecke,
die sich durch jede Auswertung zieht. Der Bestand bekommt damit einen
sichtbaren, korrigierbaren Ausgangswert statt eines leeren Feldes.
Die vier Funktionen wurden vollstaendig neu geschrieben. Die Selbstpruefung
haelt deshalb auch die Felder der vorigen Migration fest: eines beim
Uebertragen zu verlieren waere ein Fehler, der nirgends auffiele — die
Oberflaeche schickte den Wert weiter, und die Funktion ignorierte ihn.
724 lines
45 KiB
PL/PgSQL
724 lines
45 KiB
PL/PgSQL
-- Mitarbeiterart — Standard, Praktikum, geringfügig, Altersteilzeit.
|
|
--
|
|
-- Anforderung 9 aus dem Workshop. Dort stand eine Liste, die zwei
|
|
-- verschiedene Fragen vermischt:
|
|
--
|
|
-- Arbeiter, Angestellte, Lehrlinge
|
|
-- Standard Mitarbeiter / Praktikant / Geringfügige Beschäftigung / Altersteilzeit
|
|
--
|
|
-- Die erste Zeile ist die Beschäftigtengruppe und steht schon als
|
|
-- `worker_type` in der Datenbank — Lehrling ist seit Migration 20260910140000
|
|
-- dabei. Die zweite Zeile ist etwas anderes: sie sagt nicht, *als was* jemand
|
|
-- angestellt ist, sondern *in welcher Form*. Beide Fragen haben dieselbe
|
|
-- Person zum Gegenstand und schliessen einander nicht aus — ein Praktikant
|
|
-- kann Arbeiter oder Angestellter sein, eine Altersteilzeit gibt es in beiden
|
|
-- Gruppen. In *eine* Liste gepresst müsste man sich für eine der beiden
|
|
-- Antworten entscheiden und verlöre die andere.
|
|
--
|
|
-- Deshalb eine zweite Spalte. Sie ist NOT NULL mit Vorgabe 'Standard': jede
|
|
-- Person hat eine Form, und „nicht erfasst" wäre hier keine Aussage, sondern
|
|
-- eine Lücke, die sich durch jede Auswertung zieht. Der Bestand bekommt damit
|
|
-- 'Standard' — was für die weit überwiegende Mehrheit stimmt und für den Rest
|
|
-- ein sichtbarer, korrigierbarer Ausgangswert ist statt eines leeren Feldes,
|
|
-- das niemand bemerkt.
|
|
--
|
|
-- CHECK auf text und kein Aufzählungstyp, aus demselben Grund wie beim
|
|
-- Personenkreis eine Migration zuvor: die Liste kann sich ändern, und ein Typ
|
|
-- liesse einen zurückgenommenen Wert für immer stehen.
|
|
|
|
alter table employees
|
|
add column if not exists mitarbeiterart text not null default 'Standard';
|
|
|
|
comment on column employees.mitarbeiterart is
|
|
'Form der Beschäftigung — Standard, Praktikum, geringfügig, Altersteilzeit. Zweite Achse neben worker_type (Angestellte:r/Arbeiter:in/Lehrling): die beiden Fragen sind unabhängig voneinander.';
|
|
|
|
alter table employees drop constraint if exists chk_mitarbeiterart;
|
|
alter table employees add constraint chk_mitarbeiterart
|
|
check (mitarbeiterart in (
|
|
'Standard',
|
|
'Praktikant:in',
|
|
'Geringfügige Beschäftigung',
|
|
'Altersteilzeit'
|
|
));
|
|
|
|
comment on constraint chk_mitarbeiterart on employees is
|
|
'Die Formen der Beschäftigung. Als CHECK auf text und nicht als Aufzählungstyp — die Liste kann sich ändern, und ein Typ liesse einen zurückgenommenen Wert für immer stehen. Die Oberfläche liest dieselbe Liste aus lib/mitarbeiterart.ts.';
|
|
|
|
-- Die drei Formen neben „Standard" sind wenige Zeilen; danach wird gefiltert,
|
|
-- nicht gruppiert. Ein Teilindex ohne den Normalfall kostet fast nichts.
|
|
create index if not exists idx_employees_mitarbeiterart
|
|
on employees (mitarbeiterart) where mitarbeiterart <> 'Standard';
|
|
|
|
CREATE OR REPLACE FUNCTION public.app_feld_karte()
|
|
RETURNS jsonb
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
select jsonb_build_object(
|
|
'Vorname', jsonb_build_array('first_name', 'text', 'person'),
|
|
'Nachname', jsonb_build_array('last_name', 'text', 'person'),
|
|
'Geschlecht', jsonb_build_array('gender', 'gender_type', 'person'),
|
|
'Geburtsdatum', jsonb_build_array('birth_date', 'date', 'person'),
|
|
'SV-Nummer', jsonb_build_array('sv_nummer', 'text', 'person'),
|
|
'Staatsbürgerschaft', jsonb_build_array('nationality', 'text', 'person'),
|
|
'Adresse', jsonb_build_array('address', 'text', 'person'),
|
|
'Postleitzahl', jsonb_build_array('postal_code', 'text', 'person'),
|
|
'Ort', jsonb_build_array('city', 'text', 'person'),
|
|
'Land', jsonb_build_array('address_country', 'text', 'person'),
|
|
'E-Mail', jsonb_build_array('email', 'text', 'person'),
|
|
'Telefon', jsonb_build_array('phone', 'text', 'person'),
|
|
'Notfallkontakt', jsonb_build_array('emergency_contact_name', 'text', 'person'),
|
|
'Notfallkontakt Telefon', jsonb_build_array('emergency_contact_phone', 'text', 'person'),
|
|
'Notfallkontakt Verhältnis', jsonb_build_array('emergency_contact_relation', 'text', 'person'),
|
|
'Aufenthaltstitel', jsonb_build_array('hat_aufenthaltstitel', 'boolean', 'person'),
|
|
'Aufenthaltstitel bis', jsonb_build_array('aufenthaltstitel_bis', 'date', 'person'),
|
|
'Titel (vorangestellt)', jsonb_build_array('title_prefix', 'liste', 'person'),
|
|
'Titel (nachgestellt)', jsonb_build_array('title_suffix', 'liste', 'person'),
|
|
'Beschäftigungsausmaß', jsonb_build_array('employment_type', 'employment_type', 'contract'),
|
|
'Wochenstunden', jsonb_build_array('weekly_hours', 'numeric', 'contract'),
|
|
'Vertragsart', jsonb_build_array('contract_type', 'contract_type', 'contract'),
|
|
'Befristet bis', jsonb_build_array('contract_end_date', 'date', 'contract'),
|
|
'Angestellte:r/Arbeiter:in', jsonb_build_array('worker_type', 'worker_type', 'role'),
|
|
'Mitarbeiterart', jsonb_build_array('mitarbeiterart', 'text', 'role'),
|
|
'Kollektivvertrag', jsonb_build_array('collective_agreement', 'collective_agreement', 'role'),
|
|
'Arbeitstage', jsonb_build_array('work_days', 'liste', 'role'),
|
|
'Betriebsrat', jsonb_build_array('is_betriebsrat', 'boolean', 'role'),
|
|
'Dienstwagen', jsonb_build_array('has_dienstwagen', 'boolean', 'role'),
|
|
'Laterale Führung', jsonb_build_array('is_laterale_fuehrung', 'boolean', 'role'),
|
|
'C-Level', jsonb_build_array('is_c_level', 'boolean', 'role'),
|
|
'Dienstwagen Antrieb', jsonb_build_array('dienstwagen_art', 'text', 'role'),
|
|
'Besonderer Kündigungsschutz', jsonb_build_array('has_kuendigungsschutz', 'boolean', 'role'),
|
|
'Kündigungsschutz Personenkreis', jsonb_build_array('kuendigungsschutz_grund', 'text', 'role'),
|
|
'Kündigungsschutz ab', jsonb_build_array('kuendigungsschutz_ab', 'date', 'role'),
|
|
'Kündigungsschutz bis', jsonb_build_array('kuendigungsschutz_bis', 'date', 'role'),
|
|
'Begünstigt behindert', jsonb_build_array('ist_beguenstigt_behindert', 'boolean', 'role'),
|
|
-- 'integer' und nicht 'numeric': der Typ geht in update_history_entry
|
|
-- unmittelbar als Umwandlung ins SQL (`%L::%s`). 'numeric' liefe über
|
|
-- eine zweite, stillschweigende Umwandlung auf die Spalte und rundete
|
|
-- dabei — der Grad ist eine ganze Zahl aus einem Bescheid.
|
|
'Grad der Behinderung', jsonb_build_array('behinderung_grad', 'integer', 'role'),
|
|
'Behinderung ab', jsonb_build_array('behinderung_ab', 'date', 'role'),
|
|
'Behinderung bis', jsonb_build_array('behinderung_bis', 'date', 'role'),
|
|
'Teilzeitvariante', jsonb_build_array('teilzeit_art', 'text', 'role'),
|
|
'Teilzeit bis', jsonb_build_array('teilzeit_bis', 'date', 'role'),
|
|
-- Die Felder der Abwesenheit. Dritter Eintrag null: sie gehören zu
|
|
-- keiner Gruppe im payload einer geplanten Änderung, weil Abwesenheit
|
|
-- und Rückkehr ihre eigenen Vorgänge haben.
|
|
'Status', jsonb_build_array('status', 'employment_status', null),
|
|
'Art der Abwesenheit', jsonb_build_array('absence_type', 'text', null),
|
|
'Abwesend ab', jsonb_build_array('karenz_start_date', 'date', null),
|
|
'Geplante Rückkehr', jsonb_build_array('karenz_return_date', 'date', null)
|
|
);
|
|
$function$;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION public.hire_employee(payload jsonb)
|
|
RETURNS uuid
|
|
LANGUAGE plpgsql
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
declare
|
|
v_id uuid;
|
|
v_position_id uuid := (payload->>'position_id')::uuid;
|
|
v_entry date := (payload->>'entry_date')::date;
|
|
v_besetzt uuid;
|
|
v_schutz boolean := coalesce((payload->>'has_kuendigungsschutz')::boolean, false);
|
|
v_behindert boolean := coalesce((payload->>'ist_beguenstigt_behindert')::boolean, false);
|
|
begin
|
|
perform require_hr_admin();
|
|
|
|
if v_position_id is null then
|
|
raise exception 'Es muss eine Planstelle angegeben werden.';
|
|
end if;
|
|
|
|
if payload->>'personnel_number' is null or btrim(payload->>'personnel_number') = '' then
|
|
raise exception 'Es muss eine Personalnummer angegeben werden.';
|
|
end if;
|
|
if exists (select 1 from employees where personnel_number = (payload->>'personnel_number')::int) then
|
|
raise exception 'Die Personalnummer % ist bereits vergeben.', payload->>'personnel_number';
|
|
end if;
|
|
|
|
declare
|
|
v_ab date;
|
|
v_bis date;
|
|
begin
|
|
select valid_from, valid_to into v_ab, v_bis from om_positions where id = v_position_id;
|
|
if v_ab is null then
|
|
raise exception 'Die Planstelle existiert nicht.';
|
|
end if;
|
|
if v_entry < v_ab then
|
|
raise exception 'Die Planstelle gilt erst ab %. Ein Eintritt am % ist darauf nicht möglich.', v_ab, v_entry;
|
|
end if;
|
|
if v_bis is not null and v_entry >= v_bis then
|
|
raise exception 'Die Planstelle gilt nur bis %. Ein Eintritt am % ist darauf nicht möglich.', v_bis, v_entry;
|
|
end if;
|
|
end;
|
|
|
|
select pa.employee_id into v_besetzt
|
|
from position_assignments pa
|
|
where pa.position_id = v_position_id
|
|
and (pa.valid_to is null or pa.valid_to > v_entry);
|
|
if v_besetzt is not null then
|
|
raise exception 'Diese Planstelle ist bereits besetzt.';
|
|
end if;
|
|
|
|
insert into employees (
|
|
personnel_number, first_name, last_name, gender, birth_date, sv_nummer, nationality, email, phone,
|
|
address, postal_code, city, address_country, location_id, job_title,
|
|
employment_type, weekly_hours, contract_type, contract_end_date, paygrade,
|
|
source, status, entry_date, title_prefix, title_suffix,
|
|
worker_type, mitarbeiterart, collective_agreement, work_days,
|
|
is_betriebsrat, has_dienstwagen, is_laterale_fuehrung, is_c_level,
|
|
has_kuendigungsschutz, kuendigungsschutz_grund, kuendigungsschutz_ab, kuendigungsschutz_bis,
|
|
ist_beguenstigt_behindert, behinderung_grad, behinderung_ab, behinderung_bis,
|
|
hat_aufenthaltstitel, aufenthaltstitel_bis,
|
|
dienstwagen_art, emergency_contact_name, emergency_contact_phone, emergency_contact_relation
|
|
)
|
|
values (
|
|
(payload->>'personnel_number')::int, payload->>'first_name', payload->>'last_name', (payload->>'gender')::gender_type,
|
|
(payload->>'birth_date')::date, payload->>'sv_nummer',
|
|
coalesce(payload->>'nationality', 'Österreich'), payload->>'email', payload->>'phone',
|
|
payload->>'address', payload->>'postal_code', payload->>'city',
|
|
coalesce(payload->>'address_country', 'Österreich'),
|
|
(payload->>'location_id')::uuid,
|
|
(select j.title from om_positions p join jobs j on j.id = p.job_id where p.id = v_position_id),
|
|
coalesce((payload->>'employment_type')::employment_type, 'Vollzeit'),
|
|
coalesce((payload->>'weekly_hours')::numeric, 38.5),
|
|
coalesce((payload->>'contract_type')::contract_type, 'unbefristet'),
|
|
nullif(payload->>'contract_end_date', '')::date,
|
|
coalesce((payload->>'paygrade')::paygrade_type, 'B'),
|
|
coalesce((payload->>'source')::source_type, 'Extern'),
|
|
case when v_entry > current_date then 'Geplant' else 'Aktiv' end::employment_status,
|
|
v_entry,
|
|
coalesce(array(select jsonb_array_elements_text(payload->'title_prefix')), '{}'),
|
|
coalesce(array(select jsonb_array_elements_text(payload->'title_suffix')), '{}'),
|
|
coalesce((payload->>'worker_type')::worker_type, 'Angestellte:r'),
|
|
coalesce(nullif(payload->>'mitarbeiterart', ''), 'Standard'),
|
|
coalesce((payload->>'collective_agreement')::collective_agreement, 'Süßwaren'),
|
|
coalesce(nullif(array(select jsonb_array_elements_text(payload->'work_days'))::text[], '{}'), '{Mo,Di,Mi,Do,Fr}'),
|
|
coalesce((payload->>'is_betriebsrat')::boolean, false),
|
|
coalesce((payload->>'has_dienstwagen')::boolean, false),
|
|
coalesce((payload->>'is_laterale_fuehrung')::boolean, false),
|
|
coalesce((payload->>'is_c_level')::boolean, false),
|
|
v_schutz,
|
|
-- Grund, Beginn und Ende nur, wenn der Schutz überhaupt gesetzt ist:
|
|
-- sonst bliebe ein Rest ohne Bezug stehen, und die Bedingungen wiesen
|
|
-- den ganzen Vorgang zurück.
|
|
case when v_schutz then nullif(payload->>'kuendigungsschutz_grund', '') else null end,
|
|
case when v_schutz then nullif(payload->>'kuendigungsschutz_ab', '')::date else null end,
|
|
case when v_schutz then nullif(payload->>'kuendigungsschutz_bis', '')::date else null end,
|
|
v_behindert,
|
|
case when v_behindert then nullif(payload->>'behinderung_grad', '')::integer else null end,
|
|
case when v_behindert then nullif(payload->>'behinderung_ab', '')::date else null end,
|
|
case when v_behindert then nullif(payload->>'behinderung_bis', '')::date else null end,
|
|
coalesce((payload->>'hat_aufenthaltstitel')::boolean, false),
|
|
case when coalesce((payload->>'hat_aufenthaltstitel')::boolean, false)
|
|
then nullif(payload->>'aufenthaltstitel_bis', '')::date else null end,
|
|
case when coalesce((payload->>'has_dienstwagen')::boolean, false) then coalesce(nullif(payload->>'dienstwagen_art', ''), 'Verbrenner') else null end,
|
|
nullif(payload->>'emergency_contact_name', ''),
|
|
nullif(payload->>'emergency_contact_phone', ''),
|
|
nullif(payload->>'emergency_contact_relation', '')
|
|
)
|
|
returning id into v_id;
|
|
|
|
insert into position_assignments (position_id, employee_id, valid_from)
|
|
values (v_position_id, v_id, v_entry);
|
|
|
|
insert into employee_history (employee_id, event_date, event_type, description)
|
|
values (v_id, v_entry, 'Eintritt', 'Eintritt auf Planstelle ' ||
|
|
(select position_number from om_positions where id = v_position_id));
|
|
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
|
|
values (app_current_user_id(), current_actor_name(), 'Neueinstellung',
|
|
(payload->>'first_name') || ' ' || (payload->>'last_name'), v_id, 'Eintritt am ' || v_entry);
|
|
|
|
return v_id;
|
|
end;
|
|
$function$;
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION public.change_employee_data(payload jsonb)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
declare
|
|
v_employee_id uuid := (payload->>'employee_id')::uuid;
|
|
v_effective_date date := coalesce(nullif(payload->>'effective_date', '')::date, current_date);
|
|
v_old employees%rowtype;
|
|
v_name text;
|
|
v_person_changes jsonb := '[]'::jsonb;
|
|
v_contract_changes jsonb := '[]'::jsonb;
|
|
v_person jsonb := payload->'person';
|
|
v_contract jsonb := payload->'contract';
|
|
v_role jsonb := payload->'role';
|
|
v_immediate boolean;
|
|
v_new_work_days text[];
|
|
v_new_title_prefix text[];
|
|
v_new_title_suffix text[];
|
|
v_pending_id uuid;
|
|
begin
|
|
perform require_hr_admin();
|
|
select * into v_old from employees where id = v_employee_id;
|
|
v_name := v_old.first_name || ' ' || v_old.last_name;
|
|
v_immediate := v_effective_date <= current_date;
|
|
|
|
-- Der `?`-Test bleibt: ein fehlender Schlüssel heisst „nicht übermittelt",
|
|
-- nicht „geleert". Ohne ihn würde jedes nicht gesendete Feld als Änderung
|
|
-- auf null gemeldet.
|
|
if v_person ? 'first_name' then v_person_changes := app_aenderung(v_person_changes, 'Vorname', v_old.first_name, v_person->>'first_name'); end if;
|
|
if v_person ? 'last_name' then v_person_changes := app_aenderung(v_person_changes, 'Nachname', v_old.last_name, v_person->>'last_name'); end if;
|
|
if v_person ? 'gender' then v_person_changes := app_aenderung(v_person_changes, 'Geschlecht', v_old.gender::text, v_person->>'gender'); end if;
|
|
-- Datumswerte über ::date::text vergleichen, damit „2026-8-3" und
|
|
-- „2026-08-03" nicht als Änderung gelten.
|
|
if v_person ? 'birth_date' then v_person_changes := app_aenderung(v_person_changes, 'Geburtsdatum', v_old.birth_date::text, (nullif(v_person->>'birth_date','')::date)::text); end if;
|
|
if v_person ? 'sv_nummer' then v_person_changes := app_aenderung(v_person_changes, 'SV-Nummer', v_old.sv_nummer, v_person->>'sv_nummer'); end if;
|
|
if v_person ? 'nationality' then v_person_changes := app_aenderung(v_person_changes, 'Staatsbürgerschaft', v_old.nationality, v_person->>'nationality'); end if;
|
|
if v_person ? 'address' then v_person_changes := app_aenderung(v_person_changes, 'Adresse', v_old.address, v_person->>'address'); end if;
|
|
if v_person ? 'postal_code' then v_person_changes := app_aenderung(v_person_changes, 'Postleitzahl', v_old.postal_code, v_person->>'postal_code'); end if;
|
|
if v_person ? 'city' then v_person_changes := app_aenderung(v_person_changes, 'Ort', v_old.city, v_person->>'city'); end if;
|
|
if v_person ? 'address_country' then v_person_changes := app_aenderung(v_person_changes, 'Land', v_old.address_country, v_person->>'address_country'); end if;
|
|
if v_person ? 'email' then v_person_changes := app_aenderung(v_person_changes, 'E-Mail', v_old.email, v_person->>'email'); end if;
|
|
if v_person ? 'phone' then v_person_changes := app_aenderung(v_person_changes, 'Telefon', v_old.phone, v_person->>'phone'); end if;
|
|
if v_person ? 'emergency_contact_name' then v_person_changes := app_aenderung(v_person_changes, 'Notfallkontakt', v_old.emergency_contact_name, v_person->>'emergency_contact_name'); end if;
|
|
if v_person ? 'emergency_contact_phone' then v_person_changes := app_aenderung(v_person_changes, 'Notfallkontakt Telefon', v_old.emergency_contact_phone, v_person->>'emergency_contact_phone'); end if;
|
|
if v_person ? 'emergency_contact_relation' then v_person_changes := app_aenderung(v_person_changes, 'Notfallkontakt Verhältnis', v_old.emergency_contact_relation, v_person->>'emergency_contact_relation'); end if;
|
|
if v_person ? 'hat_aufenthaltstitel' then v_person_changes := app_aenderung(v_person_changes, 'Aufenthaltstitel', v_old.hat_aufenthaltstitel::text, v_person->>'hat_aufenthaltstitel'); end if;
|
|
if v_person ? 'aufenthaltstitel_bis' then v_person_changes := app_aenderung(v_person_changes, 'Aufenthaltstitel bis', v_old.aufenthaltstitel_bis::text, (nullif(v_person->>'aufenthaltstitel_bis','')::date)::text); end if;
|
|
|
|
if v_person ? 'title_prefix' then
|
|
v_new_title_prefix := coalesce((select array_agg(elem) from jsonb_array_elements_text(v_person->'title_prefix') elem), '{}');
|
|
v_person_changes := app_aenderung(v_person_changes, 'Titel (vorangestellt)',
|
|
array_to_string(v_old.title_prefix, ', '), array_to_string(v_new_title_prefix, ', '));
|
|
end if;
|
|
if v_person ? 'title_suffix' then
|
|
v_new_title_suffix := coalesce((select array_agg(elem) from jsonb_array_elements_text(v_person->'title_suffix') elem), '{}');
|
|
v_person_changes := app_aenderung(v_person_changes, 'Titel (nachgestellt)',
|
|
array_to_string(v_old.title_suffix, ', '), array_to_string(v_new_title_suffix, ', '));
|
|
end if;
|
|
|
|
if v_contract ? 'employment_type' then v_contract_changes := app_aenderung(v_contract_changes, 'Beschäftigungsausmaß', v_old.employment_type::text, v_contract->>'employment_type'); end if;
|
|
-- Über ::numeric::text, damit „38.50" und „38.5" gleich zählen.
|
|
if v_contract ? 'weekly_hours' then v_contract_changes := app_aenderung(v_contract_changes, 'Wochenstunden', v_old.weekly_hours::text, (nullif(v_contract->>'weekly_hours','')::numeric)::text); end if;
|
|
if v_contract ? 'contract_type' then v_contract_changes := app_aenderung(v_contract_changes, 'Vertragsart', v_old.contract_type::text, v_contract->>'contract_type'); end if;
|
|
if v_contract ? 'contract_end_date' then v_contract_changes := app_aenderung(v_contract_changes, 'Befristet bis', v_old.contract_end_date::text, (nullif(v_contract->>'contract_end_date','')::date)::text); end if;
|
|
|
|
if v_role ? 'worker_type' then v_contract_changes := app_aenderung(v_contract_changes, 'Angestellte:r/Arbeiter:in', v_old.worker_type::text, v_role->>'worker_type'); end if;
|
|
if v_role ? 'mitarbeiterart' then v_contract_changes := app_aenderung(v_contract_changes, 'Mitarbeiterart', v_old.mitarbeiterart, v_role->>'mitarbeiterart'); end if;
|
|
if v_role ? 'collective_agreement' then v_contract_changes := app_aenderung(v_contract_changes, 'Kollektivvertrag', v_old.collective_agreement::text, v_role->>'collective_agreement'); end if;
|
|
if v_role ? 'work_days' then
|
|
v_new_work_days := coalesce((select array_agg(elem) from jsonb_array_elements_text(v_role->'work_days') elem), '{}');
|
|
v_contract_changes := app_aenderung(v_contract_changes, 'Arbeitstage',
|
|
array_to_string(v_old.work_days, ', '), array_to_string(v_new_work_days, ', '));
|
|
end if;
|
|
if v_role ? 'is_betriebsrat' then v_contract_changes := app_aenderung(v_contract_changes, 'Betriebsrat', v_old.is_betriebsrat::text, v_role->>'is_betriebsrat'); end if;
|
|
if v_role ? 'has_dienstwagen' then v_contract_changes := app_aenderung(v_contract_changes, 'Dienstwagen', v_old.has_dienstwagen::text, v_role->>'has_dienstwagen'); end if;
|
|
if v_role ? 'is_laterale_fuehrung' then v_contract_changes := app_aenderung(v_contract_changes, 'Laterale Führung', v_old.is_laterale_fuehrung::text, v_role->>'is_laterale_fuehrung'); end if;
|
|
if v_role ? 'is_c_level' then v_contract_changes := app_aenderung(v_contract_changes, 'C-Level', v_old.is_c_level::text, v_role->>'is_c_level'); end if;
|
|
if v_role ? 'has_kuendigungsschutz' then v_contract_changes := app_aenderung(v_contract_changes, 'Besonderer Kündigungsschutz', v_old.has_kuendigungsschutz::text, v_role->>'has_kuendigungsschutz'); end if;
|
|
if v_role ? 'kuendigungsschutz_grund' then v_contract_changes := app_aenderung(v_contract_changes, 'Kündigungsschutz Personenkreis', v_old.kuendigungsschutz_grund, nullif(v_role->>'kuendigungsschutz_grund', '')); end if;
|
|
if v_role ? 'kuendigungsschutz_ab' then v_contract_changes := app_aenderung(v_contract_changes, 'Kündigungsschutz ab', v_old.kuendigungsschutz_ab::text, (nullif(v_role->>'kuendigungsschutz_ab','')::date)::text); end if;
|
|
if v_role ? 'kuendigungsschutz_bis' then v_contract_changes := app_aenderung(v_contract_changes, 'Kündigungsschutz bis', v_old.kuendigungsschutz_bis::text, (nullif(v_role->>'kuendigungsschutz_bis','')::date)::text); end if;
|
|
if v_role ? 'ist_beguenstigt_behindert' then v_contract_changes := app_aenderung(v_contract_changes, 'Begünstigt behindert', v_old.ist_beguenstigt_behindert::text, v_role->>'ist_beguenstigt_behindert'); end if;
|
|
if v_role ? 'behinderung_grad' then v_contract_changes := app_aenderung(v_contract_changes, 'Grad der Behinderung', v_old.behinderung_grad::text, (nullif(v_role->>'behinderung_grad','')::integer)::text); end if;
|
|
if v_role ? 'behinderung_ab' then v_contract_changes := app_aenderung(v_contract_changes, 'Behinderung ab', v_old.behinderung_ab::text, (nullif(v_role->>'behinderung_ab','')::date)::text); end if;
|
|
if v_role ? 'behinderung_bis' then v_contract_changes := app_aenderung(v_contract_changes, 'Behinderung bis', v_old.behinderung_bis::text, (nullif(v_role->>'behinderung_bis','')::date)::text); end if;
|
|
if v_role ? 'teilzeit_art' then v_contract_changes := app_aenderung(v_contract_changes, 'Teilzeitvariante', v_old.teilzeit_art, nullif(v_role->>'teilzeit_art', '')); end if;
|
|
if v_role ? 'teilzeit_bis' then v_contract_changes := app_aenderung(v_contract_changes, 'Teilzeit bis', v_old.teilzeit_bis::text, (nullif(v_role->>'teilzeit_bis','')::date)::text); end if;
|
|
if v_role ? 'dienstwagen_art' then v_contract_changes := app_aenderung(v_contract_changes, 'Dienstwagen Antrieb', v_old.dienstwagen_art, nullif(v_role->>'dienstwagen_art', '')); end if;
|
|
|
|
if v_immediate then
|
|
update employees set
|
|
first_name = coalesce(v_person->>'first_name', first_name),
|
|
last_name = coalesce(v_person->>'last_name', last_name),
|
|
gender = coalesce((v_person->>'gender')::gender_type, gender),
|
|
birth_date = coalesce((v_person->>'birth_date')::date, birth_date),
|
|
sv_nummer = coalesce(v_person->>'sv_nummer', sv_nummer),
|
|
nationality = coalesce(v_person->>'nationality', nationality),
|
|
address = coalesce(v_person->>'address', address),
|
|
postal_code = coalesce(v_person->>'postal_code', postal_code),
|
|
city = coalesce(v_person->>'city', city),
|
|
address_country = coalesce(v_person->>'address_country', address_country),
|
|
email = coalesce(v_person->>'email', email),
|
|
phone = coalesce(v_person->>'phone', phone),
|
|
emergency_contact_name = case when v_person ? 'emergency_contact_name' then nullif(v_person->>'emergency_contact_name', '') else emergency_contact_name end,
|
|
emergency_contact_phone = case when v_person ? 'emergency_contact_phone' then nullif(v_person->>'emergency_contact_phone', '') else emergency_contact_phone end,
|
|
emergency_contact_relation = case when v_person ? 'emergency_contact_relation' then nullif(v_person->>'emergency_contact_relation', '') else emergency_contact_relation end,
|
|
hat_aufenthaltstitel = coalesce((v_person->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel),
|
|
-- Fällt der Titel weg, fällt das Datum mit. Sonst bliebe ein
|
|
-- Enddatum ohne Titel stehen, und chk_aufenthaltstitel_bis liesse
|
|
-- die ganze Änderung scheitern statt das Offensichtliche zu tun.
|
|
aufenthaltstitel_bis = case
|
|
when coalesce((v_person->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel) then
|
|
case when v_person ? 'aufenthaltstitel_bis'
|
|
then nullif(v_person->>'aufenthaltstitel_bis','')::date
|
|
else aufenthaltstitel_bis end
|
|
else null
|
|
end,
|
|
title_prefix = case when v_person ? 'title_prefix' then v_new_title_prefix else title_prefix end,
|
|
title_suffix = case when v_person ? 'title_suffix' then v_new_title_suffix else title_suffix end,
|
|
employment_type = coalesce((v_contract->>'employment_type')::employment_type, employment_type),
|
|
weekly_hours = coalesce((v_contract->>'weekly_hours')::numeric, weekly_hours),
|
|
contract_type = coalesce((v_contract->>'contract_type')::contract_type, contract_type),
|
|
contract_end_date = case when v_contract ? 'contract_end_date' then nullif(v_contract->>'contract_end_date','')::date else contract_end_date end,
|
|
worker_type = coalesce((v_role->>'worker_type')::worker_type, worker_type),
|
|
-- Ohne nullif: die Spalte ist NOT NULL, ein leerer Text waere kein
|
|
-- geloeschter Wert, sondern ein Wert, den chk_mitarbeiterart abweist.
|
|
mitarbeiterart = coalesce(nullif(v_role->>'mitarbeiterart', ''), mitarbeiterart),
|
|
collective_agreement = coalesce((v_role->>'collective_agreement')::collective_agreement, collective_agreement),
|
|
work_days = case when v_role ? 'work_days' then v_new_work_days else work_days end,
|
|
is_betriebsrat = coalesce((v_role->>'is_betriebsrat')::boolean, is_betriebsrat),
|
|
has_dienstwagen = coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen),
|
|
is_laterale_fuehrung = coalesce((v_role->>'is_laterale_fuehrung')::boolean, is_laterale_fuehrung),
|
|
is_c_level = coalesce((v_role->>'is_c_level')::boolean, is_c_level),
|
|
has_kuendigungsschutz = coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz),
|
|
teilzeit_art = case when v_role ? 'teilzeit_art' then nullif(v_role->>'teilzeit_art', '') else teilzeit_art end,
|
|
-- Ohne Variante kein Enddatum: chk_teilzeit_bis verlangt es so, und
|
|
-- ein Datum ohne Sache wäre ein Rest, den niemand mehr deutet.
|
|
teilzeit_bis = case
|
|
when coalesce(nullif(v_role->>'teilzeit_art', ''), case when v_role ? 'teilzeit_art' then null else teilzeit_art end) is null then null
|
|
when v_role ? 'teilzeit_bis' then nullif(v_role->>'teilzeit_bis','')::date
|
|
else teilzeit_bis
|
|
end,
|
|
-- Fällt der Schutz weg, fallen Grund und beide Daten mit. Andernfalls
|
|
-- bliebe ein Rest ohne Bezug stehen — die Bedingungen verbieten das,
|
|
-- und der Vorgang schlüge fehl, statt das Offensichtliche zu tun.
|
|
kuendigungsschutz_grund = case
|
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
|
case when v_role ? 'kuendigungsschutz_grund'
|
|
then nullif(v_role->>'kuendigungsschutz_grund','')
|
|
else kuendigungsschutz_grund end
|
|
else null
|
|
end,
|
|
kuendigungsschutz_ab = case
|
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
|
case when v_role ? 'kuendigungsschutz_ab'
|
|
then nullif(v_role->>'kuendigungsschutz_ab','')::date
|
|
else kuendigungsschutz_ab end
|
|
else null
|
|
end,
|
|
kuendigungsschutz_bis = case
|
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
|
case when v_role ? 'kuendigungsschutz_bis'
|
|
then nullif(v_role->>'kuendigungsschutz_bis','')::date
|
|
else kuendigungsschutz_bis end
|
|
else null
|
|
end,
|
|
ist_beguenstigt_behindert = coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert),
|
|
behinderung_grad = case
|
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
|
case when v_role ? 'behinderung_grad' then nullif(v_role->>'behinderung_grad','')::integer else behinderung_grad end
|
|
else null
|
|
end,
|
|
behinderung_ab = case
|
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
|
case when v_role ? 'behinderung_ab' then nullif(v_role->>'behinderung_ab','')::date else behinderung_ab end
|
|
else null
|
|
end,
|
|
behinderung_bis = case
|
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
|
case when v_role ? 'behinderung_bis' then nullif(v_role->>'behinderung_bis','')::date else behinderung_bis end
|
|
else null
|
|
end,
|
|
dienstwagen_art = case
|
|
when coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen) then
|
|
coalesce(nullif(v_role->>'dienstwagen_art', ''), dienstwagen_art, 'Verbrenner')
|
|
else null
|
|
end
|
|
where id = v_employee_id;
|
|
elsif jsonb_array_length(v_person_changes) > 0 or jsonb_array_length(v_contract_changes) > 0 then
|
|
insert into pending_org_changes (employee_id, change_type, effective_date, payload)
|
|
values (v_employee_id, 'contract_change', v_effective_date, payload)
|
|
returning id into v_pending_id;
|
|
end if;
|
|
|
|
if jsonb_array_length(v_person_changes) > 0 then
|
|
insert into employee_history (employee_id, event_date, event_type, description, changes, pending_id)
|
|
values (v_employee_id, v_effective_date, 'Stammdatenänderung',
|
|
'Geänderte Felder: ' || app_aenderungsfelder(v_person_changes) || ', wirksam ab ' || v_effective_date, v_person_changes, v_pending_id);
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
|
values (app_current_user_id(), current_actor_name(), 'Stammdatenänderung', v_name, v_employee_id,
|
|
app_aenderungsfelder(v_person_changes) || ', wirksam ab ' || v_effective_date, v_person_changes);
|
|
end if;
|
|
|
|
if jsonb_array_length(v_contract_changes) > 0 then
|
|
insert into employee_history (employee_id, event_date, event_type, description, changes, pending_id)
|
|
values (v_employee_id, v_effective_date, 'Vertragsänderung',
|
|
'Geänderte Felder: ' || app_aenderungsfelder(v_contract_changes) || ', wirksam ab ' || v_effective_date, v_contract_changes, v_pending_id);
|
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
|
values (app_current_user_id(), current_actor_name(), 'Vertragsänderung', v_name, v_employee_id,
|
|
app_aenderungsfelder(v_contract_changes) || ', wirksam ab ' || v_effective_date, v_contract_changes);
|
|
end if;
|
|
end;
|
|
$function$;
|
|
|
|
|
|
-- ═══ Der Nachtlauf holt nach, was er bisher liegenliess ═══
|
|
--
|
|
-- Eine auf später datierte Änderung landet als 'contract_change' in
|
|
-- pending_org_changes; apply_due_pending_changes schreibt sie am Stichtag in
|
|
-- die Zeile. Bisher schrieb es dabei **nur** Person und Vertrag zurück — die
|
|
-- ganze Gruppe `role` fehlte. Betriebsrat, Dienstwagen, Kollektivvertrag,
|
|
-- Arbeitstage, Teilzeitvariante und der Kündigungsschutz wurden also erfasst,
|
|
-- in der Historie vermerkt, als Vertragsänderung protokolliert — und am
|
|
-- Stichtag nicht angewendet. Sichtbar wurde das nie: der Eintrag in der Akte
|
|
-- steht da, nur die Spalte blieb alt.
|
|
--
|
|
-- Das ist älter als diese Migration und gehört streng genommen nicht zu ihr.
|
|
-- Die neuen Felder erben den Fehler aber unmittelbar, und ihn nur für sie zu
|
|
-- beheben hiesse, zwei Klassen von Feldern mit zwei Verhaltensweisen zu
|
|
-- hinterlassen. Deshalb hier vollständig.
|
|
--
|
|
-- Die Reihenfolge der Zuweisungen entspricht der in change_employee_data;
|
|
-- dieselben Kopplungen (fällt das Kennzeichen, fällt das Datum) gelten hier
|
|
-- genauso, sonst wiese eine Bedingung den Nachtlauf zurück.
|
|
CREATE OR REPLACE FUNCTION public.apply_due_pending_changes()
|
|
RETURNS integer
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $function$
|
|
declare
|
|
v_rec record;
|
|
v_count integer := 0;
|
|
v_role jsonb;
|
|
begin
|
|
for v_rec in
|
|
select * from pending_org_changes
|
|
where status = 'pending' and effective_date <= current_date
|
|
order by effective_date, created_at
|
|
loop
|
|
if v_rec.change_type = 'transfer' then
|
|
update employees set job_title = coalesce(v_rec.payload->>'new_title', job_title)
|
|
where id = v_rec.employee_id;
|
|
|
|
elsif v_rec.change_type = 'promotion' then
|
|
update employees set
|
|
job_title = coalesce(v_rec.payload->>'new_title', job_title),
|
|
paygrade = coalesce((v_rec.payload->>'new_paygrade')::paygrade_type, paygrade)
|
|
where id = v_rec.employee_id;
|
|
|
|
elsif v_rec.change_type = 'karenz_start' then
|
|
update employees set
|
|
status = 'Karenz',
|
|
karenz_return_date = (v_rec.payload->>'planned_return_date')::date,
|
|
absence_type = coalesce(nullif(v_rec.payload->>'absence_type', ''), absence_type)
|
|
where id = v_rec.employee_id;
|
|
|
|
elsif v_rec.change_type = 'karenz_return' then
|
|
update employees set
|
|
status = 'Aktiv',
|
|
karenz_return_date = null,
|
|
karenz_start_date = null,
|
|
absence_type = null,
|
|
employment_type = coalesce((v_rec.payload->>'employment_type')::employment_type, employment_type),
|
|
weekly_hours = coalesce((v_rec.payload->>'weekly_hours')::numeric, weekly_hours),
|
|
-- Auch bei einer *geplanten* Rückkehr: sonst käme jemand am
|
|
-- Stichtag mit reduzierten Stunden zurück, und der Grund dafür
|
|
-- wäre verschwunden.
|
|
teilzeit_art = case when v_rec.payload ? 'teilzeit_art'
|
|
then nullif(v_rec.payload->>'teilzeit_art', '') else teilzeit_art end,
|
|
teilzeit_bis = case when v_rec.payload ? 'teilzeit_art'
|
|
then nullif(v_rec.payload->>'teilzeit_bis', '')::date else teilzeit_bis end
|
|
where id = v_rec.employee_id;
|
|
|
|
elsif v_rec.change_type = 'contract_change' then
|
|
v_role := v_rec.payload->'role';
|
|
update employees set
|
|
first_name = coalesce(v_rec.payload->'person'->>'first_name', first_name),
|
|
last_name = coalesce(v_rec.payload->'person'->>'last_name', last_name),
|
|
gender = coalesce((v_rec.payload->'person'->>'gender')::gender_type, gender),
|
|
birth_date = coalesce((v_rec.payload->'person'->>'birth_date')::date, birth_date),
|
|
sv_nummer = coalesce(v_rec.payload->'person'->>'sv_nummer', sv_nummer),
|
|
nationality = coalesce(v_rec.payload->'person'->>'nationality', nationality),
|
|
address = coalesce(v_rec.payload->'person'->>'address', address),
|
|
postal_code = coalesce(v_rec.payload->'person'->>'postal_code', postal_code),
|
|
city = coalesce(v_rec.payload->'person'->>'city', city),
|
|
address_country = coalesce(v_rec.payload->'person'->>'address_country', address_country),
|
|
email = coalesce(v_rec.payload->'person'->>'email', email),
|
|
phone = coalesce(v_rec.payload->'person'->>'phone', phone),
|
|
employment_type = coalesce((v_rec.payload->'contract'->>'employment_type')::employment_type, employment_type),
|
|
weekly_hours = coalesce((v_rec.payload->'contract'->>'weekly_hours')::numeric, weekly_hours),
|
|
contract_type = coalesce((v_rec.payload->'contract'->>'contract_type')::contract_type, contract_type),
|
|
contract_end_date = case when v_rec.payload->'contract' ? 'contract_end_date'
|
|
then nullif(v_rec.payload->'contract'->>'contract_end_date','')::date
|
|
else contract_end_date end,
|
|
worker_type = coalesce((v_role->>'worker_type')::worker_type, worker_type),
|
|
mitarbeiterart = coalesce(nullif(v_role->>'mitarbeiterart', ''), mitarbeiterart),
|
|
collective_agreement = coalesce((v_role->>'collective_agreement')::collective_agreement, collective_agreement),
|
|
work_days = case when v_role ? 'work_days'
|
|
then coalesce((select array_agg(elem) from jsonb_array_elements_text(v_role->'work_days') elem), '{}')
|
|
else work_days end,
|
|
is_betriebsrat = coalesce((v_role->>'is_betriebsrat')::boolean, is_betriebsrat),
|
|
has_dienstwagen = coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen),
|
|
is_laterale_fuehrung = coalesce((v_role->>'is_laterale_fuehrung')::boolean, is_laterale_fuehrung),
|
|
is_c_level = coalesce((v_role->>'is_c_level')::boolean, is_c_level),
|
|
dienstwagen_art = case
|
|
when coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen) then
|
|
coalesce(nullif(v_role->>'dienstwagen_art', ''), dienstwagen_art, 'Verbrenner')
|
|
else null
|
|
end,
|
|
teilzeit_art = case when v_role ? 'teilzeit_art' then nullif(v_role->>'teilzeit_art', '') else teilzeit_art end,
|
|
teilzeit_bis = case
|
|
when coalesce(nullif(v_role->>'teilzeit_art', ''), case when v_role ? 'teilzeit_art' then null else teilzeit_art end) is null then null
|
|
when v_role ? 'teilzeit_bis' then nullif(v_role->>'teilzeit_bis','')::date
|
|
else teilzeit_bis
|
|
end,
|
|
hat_aufenthaltstitel = coalesce((v_rec.payload->'person'->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel),
|
|
aufenthaltstitel_bis = case
|
|
when coalesce((v_rec.payload->'person'->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel) then
|
|
case when v_rec.payload->'person' ? 'aufenthaltstitel_bis'
|
|
then nullif(v_rec.payload->'person'->>'aufenthaltstitel_bis','')::date
|
|
else aufenthaltstitel_bis end
|
|
else null
|
|
end,
|
|
has_kuendigungsschutz = coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz),
|
|
kuendigungsschutz_grund = case
|
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
|
case when v_role ? 'kuendigungsschutz_grund' then nullif(v_role->>'kuendigungsschutz_grund','') else kuendigungsschutz_grund end
|
|
else null
|
|
end,
|
|
kuendigungsschutz_ab = case
|
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
|
case when v_role ? 'kuendigungsschutz_ab' then nullif(v_role->>'kuendigungsschutz_ab','')::date else kuendigungsschutz_ab end
|
|
else null
|
|
end,
|
|
kuendigungsschutz_bis = case
|
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
|
case when v_role ? 'kuendigungsschutz_bis' then nullif(v_role->>'kuendigungsschutz_bis','')::date else kuendigungsschutz_bis end
|
|
else null
|
|
end,
|
|
ist_beguenstigt_behindert = coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert),
|
|
behinderung_grad = case
|
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
|
case when v_role ? 'behinderung_grad' then nullif(v_role->>'behinderung_grad','')::integer else behinderung_grad end
|
|
else null
|
|
end,
|
|
behinderung_ab = case
|
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
|
case when v_role ? 'behinderung_ab' then nullif(v_role->>'behinderung_ab','')::date else behinderung_ab end
|
|
else null
|
|
end,
|
|
behinderung_bis = case
|
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
|
case when v_role ? 'behinderung_bis' then nullif(v_role->>'behinderung_bis','')::date else behinderung_bis end
|
|
else null
|
|
end
|
|
where id = v_rec.employee_id;
|
|
|
|
elsif v_rec.change_type = 'dependent_add' then
|
|
insert into employee_dependents (employee_id, first_name, last_name, relationship, sv_nummer, birth_date)
|
|
values (v_rec.employee_id, v_rec.payload->>'first_name', v_rec.payload->>'last_name',
|
|
(v_rec.payload->>'relationship')::text,
|
|
nullif(v_rec.payload->>'sv_nummer', ''), (v_rec.payload->>'birth_date')::date);
|
|
|
|
elsif v_rec.change_type = 'dependent_remove' then
|
|
delete from employee_dependents where id = (v_rec.payload->>'dependent_id')::uuid;
|
|
end if;
|
|
|
|
update pending_org_changes set status = 'applied', applied_at = now() where id = v_rec.id;
|
|
v_count := v_count + 1;
|
|
end loop;
|
|
|
|
return v_count;
|
|
end;
|
|
$function$;
|
|
|
|
grant execute on function public.apply_due_pending_changes() to alpenwerk_app;
|
|
|
|
|
|
-- Selbstprüfung.
|
|
do $$
|
|
declare
|
|
v_hire text := pg_get_functiondef('public.hire_employee(jsonb)'::regprocedure);
|
|
v_chg text := pg_get_functiondef('public.change_employee_data(jsonb)'::regprocedure);
|
|
v_lauf text := pg_get_functiondef('public.apply_due_pending_changes()'::regprocedure);
|
|
v_spalte text;
|
|
begin
|
|
-- Die Felder der vorigen Migration stehen hier mit: die vier Funktionen
|
|
-- werden erneut vollständig geschrieben, und ein Feld beim Übertragen zu
|
|
-- verlieren wäre ein Fehler, der nirgends auffiele — die Oberfläche schickte
|
|
-- den Wert weiter, und die Funktion ignorierte ihn stillschweigend.
|
|
foreach v_spalte in array array[
|
|
'mitarbeiterart',
|
|
'kuendigungsschutz_grund', 'kuendigungsschutz_ab',
|
|
'ist_beguenstigt_behindert', 'behinderung_grad', 'behinderung_ab', 'behinderung_bis'
|
|
] loop
|
|
if not exists (
|
|
select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'employees' and column_name = v_spalte
|
|
) then
|
|
raise exception 'Die Spalte % fehlt', v_spalte;
|
|
end if;
|
|
if v_hire not like '%' || v_spalte || '%' then
|
|
raise exception 'hire_employee nimmt % nicht entgegen', v_spalte;
|
|
end if;
|
|
if v_chg not like '%' || v_spalte || '%' then
|
|
raise exception 'change_employee_data schreibt % nicht', v_spalte;
|
|
end if;
|
|
if v_lauf not like '%' || v_spalte || '%' then
|
|
raise exception 'Der Nachtlauf wendet % nicht an', v_spalte;
|
|
end if;
|
|
end loop;
|
|
|
|
-- Die Feldtabelle: ohne sie liesse sich ein Historieneintrag zu diesen
|
|
-- Feldern nicht berichtigen (delete_/update_history_entry lesen sie).
|
|
foreach v_spalte in array array[
|
|
'Mitarbeiterart',
|
|
'Kündigungsschutz Personenkreis', 'Kündigungsschutz ab',
|
|
'Begünstigt behindert', 'Grad der Behinderung', 'Behinderung ab', 'Behinderung bis'
|
|
] loop
|
|
if not (app_feld_karte() ? v_spalte) then
|
|
raise exception 'Die Feldtabelle kennt % nicht', v_spalte;
|
|
end if;
|
|
if app_feld_karte()->v_spalte->>2 <> 'role' then
|
|
raise exception 'Die Gruppe im payload stimmt bei % nicht', v_spalte;
|
|
end if;
|
|
end loop;
|
|
|
|
-- Der Nachtlauf holt jetzt auch die übrigen Rollenfelder nach. Ohne diese
|
|
-- Prüfung fiele ein späteres „create or replace" stillschweigend wieder
|
|
-- auf die alte, unvollständige Fassung zurück.
|
|
if v_lauf not like '%is_betriebsrat%' or v_lauf not like '%collective_agreement%' then
|
|
raise exception 'Der Nachtlauf wendet die Rollenfelder nicht an';
|
|
end if;
|
|
|
|
-- Die Bedingungen selbst. Geprüft wird ihre Definition und nicht ein
|
|
-- Probeschreiben: diese Migration muss auch auf einer **leeren** Datenbank
|
|
-- durchlaufen (CI-Job „migrationen"), und dort gäbe es keine Zeile, an der
|
|
-- sich eine Verletzung auslösen liesse — der Versuch ginge folgenlos aus
|
|
-- und die Prüfung meldete fälschlich einen Fehler.
|
|
if not exists (
|
|
select 1 from pg_constraint
|
|
where conname = 'chk_kuendigungsschutz_personenkreis'
|
|
and pg_get_constraintdef(oid) like '%Familienhospizteilzeit%'
|
|
and pg_get_constraintdef(oid) like '%Jugendvertrauenspersonen%'
|
|
) then
|
|
raise exception 'Die Bedingung auf den Personenkreis fehlt oder kennt die Liste nicht';
|
|
end if;
|
|
|
|
foreach v_spalte in array array[
|
|
'chk_mitarbeiterart',
|
|
'chk_kuendigungsschutz_grund', 'chk_kuendigungsschutz_zeitraum',
|
|
'chk_behinderung', 'chk_behinderung_grad', 'chk_behinderung_zeitraum'
|
|
] loop
|
|
if not exists (select 1 from pg_constraint where conname = v_spalte) then
|
|
raise exception 'Die Bedingung % fehlt', v_spalte;
|
|
end if;
|
|
end loop;
|
|
|
|
-- Die Spalte ist NOT NULL mit Vorgabe. Beides zusammen, sonst stünde beim
|
|
-- Bestand nichts drin und jede Auswertung hätte eine Lücke, die niemand
|
|
-- sieht.
|
|
if not exists (
|
|
select 1 from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'employees'
|
|
and column_name = 'mitarbeiterart'
|
|
and is_nullable = 'NO' and column_default like '%Standard%'
|
|
) then
|
|
raise exception 'mitarbeiterart ist nicht NOT NULL mit Vorgabe Standard';
|
|
end if;
|
|
end
|
|
$$;
|