Files
alpenwerk-hr/db/migrations/20260923180000_austritt_anmerkung.sql
Andrei Laas db02fb4762
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m23s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m18s
Elf Punkte aus der Rueckmeldung, und die verlorene Anmerkung
Kleinigkeiten zuerst: "Gelaufen" heisst jetzt "Vergangen", die Kachel
"Langzeitabwesend" heisst "Langzeitabwesende", "Eintritte/Austritte
(Jahr)" heissen "(YTD)" -- gezaehlt wurde ohnehin seit Jahresbeginn.
"Personenkreis" heisst "Grund". Der Hinweis unter dem Grad der
Behinderung und der Erklaertext ueber dem Honestly-Report sind weg.
"Gehaltsanpassung" steht nicht mehr im Filter des Protokolls: das Gehalt
ist aus dem Funktionsumfang, keine Funktion schreibt die Art mehr, und
ein Filter, der immer leer ausgeht, sieht aus wie ein Fehler.

Im Fenster fuer den Notfallkontakt faellt "Wirksam ab" weg -- eine
Telefonnummer fuer den Ernstfall gilt ab sofort. In "Daten aendern"
wandert der Block unter die Angehoerigen, dieselbe Reihenfolge wie im
Reiter "Stammdaten". Auf der Uebersicht fuehren die Namen unter "Letzte
Aktivitaeten" in die Akte; vorher war die Karte eine Sackgasse.

"Eintrag berichtigen" bot fuer jedes Feld ein Textfeld an, auch fuer
"Notfallkontakt Verhaeltnis", wo die Erfassung sonst eine Liste fuehrt.
Wer dort "Gattin" statt "Gattin/Gatte" tippte, erzeugte einen Wert, den
keine Auswertung mehr findet -- die Berichtigung schreibt in dieselbe
Spalte wie das Formular, nur ohne dessen Pruefung. Welche Felder eine
Liste bekommen, steht in lib/historie-felder.ts; ein Test prueft jeden
Schluessel gegen app_feld_karte(), damit ein Tippfehler dort nicht still
auf ein Textfeld zurueckfaellt. Felder mit Aufzaehlungstyp bleiben
bewusst aussen vor: dort ist der gespeicherte Wert nicht die Anzeige.

Und die Antwort auf "wo sieht man die Anmerkung beim Austritt?": nirgends.
Das Formular sammelte sie ein, terminate_employee liess sie fallen. Bis
20260814100000 stand sie in der Beschreibung des Ereignisses; beim
Umschreiben fuer den Nichtantritt ging sie verloren, und die Migration
fuer die Austrittsart reichte die verkuerzte Fassung weiter. Sie steht
jetzt wieder in der Personalakte und im Protokoll, und die Selbstpruefung
faengt den naechsten Verlust ab.
2026-09-23 16:01:05 +02:00

130 lines
5.8 KiB
PL/PgSQL

-- Die Anmerkung beim Austritt wird wieder aufgeschrieben.
--
-- Gefragt vom Kunden: „beim Austritt gibt es eine Anmerkung — wo sieht man
-- die nachher?" Die Antwort war: nirgends. Das Formular sammelt sie ein, die
-- Server Action reicht sie durch, und terminate_employee liess sie fallen.
--
-- Bis 20260814100000 stand sie in der Beschreibung des Ereignisses:
--
-- 'Austritt (' || exit_reason || ')'
-- || case when payload->>'note' <> '' then ' — ' || note else '' end
--
-- Beim Umschreiben der Funktion für den Nichtantritt ist der zweite Teil
-- verlorengegangen, und bei der Austrittsart (20260917100000) wurde die
-- bereits verkürzte Fassung weitergereicht. Genau davor warnt die
-- Selbstprüfung jener Migration im Kommentar — „der Fehler, den ein
-- `create or replace` aus einer alten Vorlage leise macht" —, und für die
-- Anmerkung ist er trotzdem passiert. Deshalb steht unten eine Prüfung, die
-- ihn beim nächsten Mal abfängt.
--
-- Die Anmerkung geht in **beide** Einträge: in die Personalakte, wo sie
-- hingehört, und ins Protokoll, wo ohne sie nicht nachvollziehbar wäre,
-- was jemand beim Austritt festgehalten hat. Angehängt an die fertige
-- Beschreibung und nicht in einen der Zweige: der Nichtantritt darf sie
-- genauso tragen.
--
-- Bestehende Einträge bleiben, wie sie sind. Der Text ist nicht mehr da; er
-- wurde nie geschrieben, und es gibt nichts, woraus er sich rekonstruieren
-- liesse.
CREATE OR REPLACE FUNCTION public.terminate_employee(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_exit date := (payload->>'exit_date')::date;
v_name text;
-- „No Show" ist kein gewöhnlicher Austritt: die Person hat nie
-- angefangen. Deshalb hängt an diesem einen Grund anderes Verhalten.
v_no_show boolean := coalesce(payload->>'exit_reason', '') = 'No Show';
v_entry date;
v_art text := nullif(payload->>'austrittsart', '');
v_note text := nullif(btrim(payload->>'note'), '');
begin
perform require_hr_admin();
select first_name || ' ' || last_name, entry_date into v_name, v_entry
from employees where id = v_employee_id;
-- Wer nie angetreten ist, tritt am Tag seines Eintritts wieder aus.
-- Damit gibt es keinen einzigen Tag, an dem die Person beschäftigt war:
-- die Statusableitung verlangt exit_date > Stichtag, um jemanden als
-- beschäftigt zu zählen, und das ist hier nie erfüllt. „Nie aktiv" ist
-- damit keine zusätzliche Regel, sondern folgt aus dem Datum.
if v_no_show then
v_exit := v_entry;
end if;
update employees set
-- Bei einem Nichtantritt sofort, auch wenn der Eintritt noch in der
-- Zukunft lag: sonst bliebe in der Spalte auf Dauer „Geplant" stehen,
-- denn es gibt keinen Lauf, der sie später nachzieht.
status = case when v_no_show or v_exit <= current_date then 'Ausgetreten' else status end,
exit_date = v_exit,
exit_reason = payload->>'exit_reason',
austrittsart = v_art
where id = v_employee_id;
-- Die Planstelle wird frei. Direkte Berichte müssen nicht umgehängt
-- werden: die Berichtslinie wird abgeleitet und rutscht von selbst auf
-- die nächste besetzte Ebene.
if v_no_show then
-- Die Planstelle war nie besetzt. Sie auf [Eintritt, Eintritt) zu
-- schliessen ginge nicht — chk_assignment_range verlangt ein echtes
-- Intervall. Die Zuordnung wird deshalb entfernt: die Stelle ist wieder
-- frei, und es steht nirgends, jemand hätte sie je innegehabt.
delete from position_assignments
where employee_id = v_employee_id and valid_to is null;
else
update position_assignments set valid_to = v_exit
where employee_id = v_employee_id and valid_to is null;
end if;
insert into employee_history (employee_id, event_date, event_type, description)
values (v_employee_id, v_exit, 'Austritt',
case when v_no_show
then 'Kein Antritt am ' || v_entry || ' (No Show)'
else 'Austritt (' || coalesce(payload->>'exit_reason', '-') || ')'
|| case when v_art is not null then ', ' || v_art else '' end end
|| case when v_note is not null then ' — ' || v_note else '' end);
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
values (app_current_user_id(), current_actor_name(), 'Austritt', v_name, v_employee_id,
case when v_no_show
then 'Kein Antritt am ' || v_entry || ' (No Show)'
else 'Austritt am ' || v_exit || case when v_art is not null then ', ' || v_art else '' end end
|| case when v_note is not null then ' — ' || v_note else '' end);
end;
$function$;
grant execute on function public.terminate_employee(payload jsonb) to alpenwerk_app;
-- Selbstprüfung.
do $$
declare
v_term text := pg_get_functiondef('public.terminate_employee(jsonb)'::regprocedure);
begin
if v_term not like '%v_note%' then
raise exception 'terminate_employee schreibt die Anmerkung nicht — das Feld im Formular ginge wieder ins Leere.';
end if;
-- Was diese Funktion schon einmal verloren hat, darf sie nicht wieder
-- verlieren. Dieselben Prüfungen wie in 20260917100000, hier um die
-- Anmerkung ergänzt.
if v_term not like '%austrittsart%' then
raise exception 'terminate_employee schreibt die Austrittsart nicht mehr';
end if;
if v_term not like '%v_no_show%' or v_term not like '%delete from position_assignments%' then
raise exception 'terminate_employee kennt den Nichtantritt nicht mehr';
end if;
if position('SET search_path' in v_term) = 0 then
raise exception 'terminate_employee hat keinen festen search_path mehr';
end if;
if v_term like '%auth.uid()%' then
raise exception 'terminate_employee ruft wieder auth.uid()';
end if;
end
$$;