-- Auf eine Planstelle darf nur besetzt werden, solange sie gilt. -- -- Bisher prüften hire_employee und transfer_employee nur, ob die Stelle frei -- ist — nicht, ob es sie zum fraglichen Zeitpunkt überhaupt gibt. Damit liess -- sich heute jemand auf eine Planstelle einstellen, die erst im Oktober -- entsteht, oder auf eine, die im Frühjahr ausgelaufen ist. Die Besetzung -- stand dann in der Datenbank, die Stelle im Organigramm aber nicht, und die -- Person hing an einer Struktur, die es zu ihrem Eintrittsdatum nicht gab. -- -- Seit die Oberfläche künftige Planstellen anzeigt, ist das kein -- theoretischer Fall mehr: sie stehen in derselben Auswahl. -- -- Die Regel: das Datum der Besetzung — Eintritt bzw. Wirksamkeit der -- Versetzung — muss in [valid_from, valid_to) liegen. `valid_to` ist wie -- überall im Modell ausschliessend; eine Planstelle mit valid_to = heute gilt -- heute nicht mehr (siehe lib/positions.ts). -- -- Zweite Korrektur im selben Zug: die Belegungsprüfung sah nur Zuordnungen -- mit offenem Ende. Eine, die erst später endet, blieb unsichtbar — dieselbe -- Lücke, die die Übersicht der unbesetzten Planstellen hatte. create or replace function app_funktion_ersetzen(p_funktion text, p_muster text, p_neu text) returns void language plpgsql set search_path = public, pg_temp as $$ declare v_def text; v_neu 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 = p_funktion limit 1; if v_def is null then raise exception 'Funktion %() nicht gefunden.', p_funktion; end if; -- Über ein Muster statt über festen Text, weil die Rümpfe je nach Herkunft -- CRLF oder LF enthalten. Ein wörtlicher Vergleich fände dann nichts und -- die Migration liefe erfolgreich durch, ohne etwas zu ändern. v_neu := regexp_replace(v_def, p_muster, p_neu, 'g'); if v_neu = v_def then raise exception 'In %() passte das Muster auf nichts — nichts geändert.', p_funktion; end if; execute v_neu; end; $$; -- ═══ Eintritt ════════════════════════════════════════════════════ select app_funktion_ersetzen( 'hire_employee', 'select\s+pa\.employee_id\s+into\s+v_besetzt\s+from\s+position_assignments\s+pa\s+where\s+pa\.position_id\s*=\s*v_position_id\s+and\s+pa\.valid_to\s+is\s+null;', $neu$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);$neu$ ); -- ═══ Versetzung ══════════════════════════════════════════════════ select app_funktion_ersetzen( 'transfer_employee', 'select\s+pa\.employee_id\s+into\s+v_besetzt\s+from\s+position_assignments\s+pa\s+where\s+pa\.position_id\s*=\s*v_target_position\s+and\s+pa\.valid_to\s+is\s+null;', $neu$declare v_ab date; v_bis date; begin select valid_from, valid_to into v_ab, v_bis from om_positions where id = v_target_position; if v_ab is null then raise exception 'Die Zielplanstelle existiert nicht.'; end if; if v_effective < v_ab then raise exception 'Die Zielplanstelle gilt erst ab %. Eine Versetzung zum % ist darauf nicht möglich.', v_ab, v_effective; end if; if v_bis is not null and v_effective >= v_bis then raise exception 'Die Zielplanstelle gilt nur bis %. Eine Versetzung zum % ist darauf nicht möglich.', v_bis, v_effective; end if; end; select pa.employee_id into v_besetzt from position_assignments pa where pa.position_id = v_target_position and (pa.valid_to is null or pa.valid_to > v_effective);$neu$ ); drop function app_funktion_ersetzen(text, text, text); -- ═══ Gegenprobe ══════════════════════════════════════════════════ do $$ declare r text; begin foreach r in array array['hire_employee', 'transfer_employee'] loop if (select pg_get_functiondef(p.oid) from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = r limit 1) not like '%gilt erst ab%' then raise exception '%() enthält die Gültigkeitsprüfung nicht.', r; end if; end loop; end; $$;