Wiedereintritt wieder moeglich, und die Panels lesen die Akte neu
Zwei Befunde aus dem Test vom 29.09., beide Klasse A. K.01 -- rehire_employee scheitert bei jedem Aufruf: der case-Ausdruck fuer den Status liefert text, die Spalte ist ein Aufzaehlungstyp. Genau das wurde am 10.08. schon einmal behoben. Vier spaetere Migrationen haben die Funktion neu erzeugt und den Zusatz nicht mitgenommen -- zwei davon aus einer aelteren Datei, zwei aus der laufenden Definition. Daraus die Lehre, die vorher nicht dastand: aus dem laufenden Stand zu erzeugen schuetzt davor, Verhalten zu verlieren, nicht davor, einen bereits vorhandenen Fehler mitzunehmen. Die Selbstpruefung benennt deshalb jetzt das Erwartete und nicht nur das Neue. H.05 -- "Daten aendern" schrieb veraltete Werte zurueck. Die Panels bleiben eingebunden, damit ihr Ein- und Ausfahren laufen kann, belegen ihre Felder aber mit useState(employee.…) vor -- und das laeuft nur beim ersten Aufbau. Nach einer Befoerderung brachte router.refresh() die frische Akte herein, der Zustand im Panel blieb der von vorhin, und beim naechsten Speichern ging er als Ganzes an change_employee_data: der eben gesetzte Hay-Grade stand wieder auf dem alten Wert. Jedes Panel bekommt jetzt einen key aus employees.updated_at, der sich bei jeder Aenderung an der Zeile bewegt und sonst nie. Der Wiedereintritts-Assistent daneben macht es seit jeher so.
This commit is contained in:
@@ -217,20 +217,44 @@ export function EmployeeDetail(props: EmployeeDetailProps) {
|
||||
{tab === "HR-Notizen" && <NotizenTab employeeId={employee.id} notes={notes} />}
|
||||
</div>
|
||||
|
||||
{/* Der `key` an jedem Panel ist kein Feinschliff, sondern der Unterschied
|
||||
zwischen „zeigt Altes" und „schreibt Altes zurück".
|
||||
|
||||
Die Panels bleiben eingebunden, damit ihr Ein- und Ausfahren laufen
|
||||
kann. Sie belegen ihre Felder aber mit useState(employee.…) vor, und
|
||||
das läuft nur beim ersten Aufbau. Nach einer Beförderung bringt
|
||||
router.refresh() zwar die frische Akte herein — der Zustand im Panel
|
||||
bleibt der von vorhin. Beim nächsten Speichern geht er als Ganzes an
|
||||
change_employee_data, und der eben gesetzte Hay-Grade steht wieder
|
||||
auf dem alten Wert. Gemeldet im Test vom 29.09. (H.05).
|
||||
|
||||
employees.updated_at wechselt bei jeder Änderung an der Zeile
|
||||
(Trigger trg_employees_touch_updated_at), also genau dann, wenn die
|
||||
Vorbelegung neu zu lesen ist — und sonst nie. Der Wiedereintritts-
|
||||
Assistent darunter macht es seit jeher so. */}
|
||||
<TransferPanel
|
||||
key={`transfer-${employee.updated_at}`}
|
||||
open={panel === "transfer"}
|
||||
onClose={() => setPanel(null)}
|
||||
employee={employee}
|
||||
openPositions={openPositions}
|
||||
/>
|
||||
<PromotePanel
|
||||
key={`promote-${employee.updated_at}`}
|
||||
open={panel === "promote"}
|
||||
onClose={() => setPanel(null)}
|
||||
employee={employee}
|
||||
openPositions={openPositions}
|
||||
/>
|
||||
<KarenzPanel open={panel === "karenz"} onClose={() => setPanel(null)} employee={employee} status={status} />
|
||||
<KarenzPanel
|
||||
key={`karenz-${employee.updated_at}`}
|
||||
open={panel === "karenz"}
|
||||
onClose={() => setPanel(null)}
|
||||
employee={employee}
|
||||
status={status}
|
||||
/>
|
||||
<DatenAendernPanel
|
||||
key={`daten-${employee.updated_at}`}
|
||||
open={panel === "daten"}
|
||||
onClose={() => setPanel(null)}
|
||||
employee={employee}
|
||||
@@ -238,6 +262,7 @@ export function EmployeeDetail(props: EmployeeDetailProps) {
|
||||
locationCountry={location?.country}
|
||||
/>
|
||||
<TerminatePanel
|
||||
key={`terminate-${employee.updated_at}`}
|
||||
open={panel === "terminate"}
|
||||
onClose={() => setPanel(null)}
|
||||
employee={employee}
|
||||
|
||||
103
db/migrations/20260929160000_wiedereintritt_status_cast.sql
Normal file
103
db/migrations/20260929160000_wiedereintritt_status_cast.sql
Normal file
@@ -0,0 +1,103 @@
|
||||
-- Wiedereintritt: der Umwandlungsschritt beim Status, zum zweiten Mal
|
||||
--
|
||||
-- rehire_employee scheitert bei jedem Aufruf mit
|
||||
--
|
||||
-- column "status" is of type employment_status but expression is of type text
|
||||
--
|
||||
-- Der case-Ausdruck liefert `text` (beide Zweige sind Literale ohne Typ), die
|
||||
-- Spalte ist ein Aufzählungstyp, und Postgres wandelt beim Zuweisen nicht von
|
||||
-- selbst um. Genau dieser Fehler wurde am 10.08. mit
|
||||
-- 20260810130000_fix_rehire_status_cast.sql behoben.
|
||||
--
|
||||
-- ═══ Warum er wieder da ist ═══════════════════════════════════════
|
||||
--
|
||||
-- Vier spätere Migrationen haben rehire_employee neu erzeugt und dabei den
|
||||
-- Zusatz nicht mitgenommen: 20260917100000 (Austrittsart), 20260917130000
|
||||
-- (Wiedereintritt mit Stammdaten), 20260923120000 (Firmen-E-Mail) und
|
||||
-- 20260928100000 (Cornerstone-ID).
|
||||
--
|
||||
-- Die ersten beiden haben den Rumpf aus einer älteren Datei kopiert — der
|
||||
-- Fall, vor dem der Abschnitt „create or replace aus veraltetem Muster" warnt.
|
||||
-- Die letzten beiden sind aus der **laufenden** Definition erzeugt worden und
|
||||
-- haben damit getreu weitergetragen, was seit dem 17.09. dort stand. Daraus
|
||||
-- die Lehre, die vorher nicht dastand: das Erzeugen aus dem laufenden Stand
|
||||
-- schützt davor, Verhalten zu **verlieren** — nicht davor, einen bereits
|
||||
-- vorhandenen Fehler **mitzunehmen**. Dagegen hilft nur eine Selbstprüfung,
|
||||
-- die das Erwartete benennt, statt nur das Neue zu prüfen.
|
||||
--
|
||||
-- Deshalb steht unten nicht bloss „der Zusatz ist da", sondern eine Prüfung,
|
||||
-- die jede künftige Neuerzeugung dieser Funktion mitnehmen kann.
|
||||
|
||||
do $migration$
|
||||
declare
|
||||
v_alt constant text := $anker$status = case when v_date <= current_date then 'Aktiv' else 'Geplant' end,$anker$;
|
||||
v_neu constant text := $anker$status = (case when v_date <= current_date then 'Aktiv' else 'Geplant' end)::employment_status,$anker$;
|
||||
v_def text;
|
||||
v_anzahl int;
|
||||
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 = 'rehire_employee' and p.prokind = 'f';
|
||||
|
||||
if v_def is null then
|
||||
raise exception 'rehire_employee ist nicht vorhanden.';
|
||||
end if;
|
||||
|
||||
-- Schon in Ordnung? Dann nichts tun. Die Migration soll auch auf einer
|
||||
-- Datenbank durchlaufen, auf der die Funktion aus einer künftigen Datei mit
|
||||
-- Umwandlung entstanden ist.
|
||||
if v_def like '%end)::employment_status%' then
|
||||
raise notice 'rehire_employee wandelt den Status bereits um.';
|
||||
return;
|
||||
end if;
|
||||
|
||||
v_anzahl := (length(v_def) - length(replace(v_def, v_alt, ''))) / length(v_alt);
|
||||
if v_anzahl <> 1 then
|
||||
raise exception 'Die Statuszuweisung kommt % mal vor, erwartet genau einmal.', v_anzahl;
|
||||
end if;
|
||||
|
||||
execute replace(v_def, v_alt, v_neu);
|
||||
end
|
||||
$migration$;
|
||||
|
||||
|
||||
-- Selbstprüfung.
|
||||
--
|
||||
-- Geprüft wird die Umwandlung **und** alles, was an dieser Funktion schon
|
||||
-- einmal verlorengegangen ist. Wer sie das nächste Mal neu erzeugt, kopiert
|
||||
-- diesen Block am besten mit.
|
||||
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 = 'rehire_employee' and p.prokind = 'f';
|
||||
|
||||
if v_def not like '%end)::employment_status%' then
|
||||
raise exception 'rehire_employee wandelt den Status nicht um — der Wiedereintritt scheitert bei jedem Aufruf.';
|
||||
end if;
|
||||
if v_def not like '%require_hr_admin()%' then
|
||||
raise exception 'rehire_employee prueft die Rechte nicht.';
|
||||
end if;
|
||||
if v_def not like '%search_path%' then
|
||||
raise exception 'rehire_employee hat keinen festen search_path.';
|
||||
end if;
|
||||
-- Die Stammdaten, die der Wiedereintritt seit 20260917130000 mitschreibt.
|
||||
-- Ohne sie zeigt der Assistent Felder an und wirft ihre Werte weg.
|
||||
if v_def not like '%cornerstone_id%' then
|
||||
raise exception 'rehire_employee hat die Cornerstone-ID verloren.';
|
||||
end if;
|
||||
if v_def not like '%company_email%' then
|
||||
raise exception 'rehire_employee hat die Firmen-E-Mail verloren.';
|
||||
end if;
|
||||
if v_def not like '%title_prefix%' then
|
||||
raise exception 'rehire_employee hat die Titel verloren.';
|
||||
end if;
|
||||
if v_def not like '%paygrade%' then
|
||||
raise exception 'rehire_employee hat den Hay-Grade verloren.';
|
||||
end if;
|
||||
|
||||
raise notice 'rehire_employee: Statusumwandlung und Stammdaten vollstaendig.';
|
||||
end
|
||||
$$;
|
||||
Reference in New Issue
Block a user