Let planned changes be taken back and corrected too
Deleting and correcting a history entry stopped at the present: anything not yet effective stayed put. That was not a principle, it was a missing link. A planned change lives as a payload in pending_org_changes, and nothing tied it to the history row — only a person and a date, and the data already holds an Eintritt and a Vertragsänderung sharing one. So employee_history now carries pending_id, set by change_employee_data when it schedules something. One planned change can carry two history rows: Stammdaten and Vertrag are kept apart but scheduled together. Taking one back therefore strips only that group's fields from the payload, and cancels the operation only when nothing is left. Correcting one rewrites its group and the effective date, and touches no employee data — the change has not happened yet. An entry stays on its side of the present. Pulling a planned change into today, or pushing an effective one into the future, would mean adjusting the employee record and the pending payload in opposite directions; that is what the real operations are for. Existing rows were linked where exactly one running operation matched the person and date and no other row had claimed it. All five of them matched. Anything ambiguous would have kept the old refusal, which now says the actual reason. The edit dialog surfaced a bug in useDialogFocus that predates it: the effect depended on the identity of onClose, which almost every caller rebuilds on render, so it re-ran after each keystroke and its cleanup pulled focus back to whatever opened the dialog. Any dialog with a text field would have accepted one character. It never showed because until now no dialog kept its own state next to its own onClose. Rehearsed against real data: a two-row planned change corrected, one row taken back with the operation continuing on the rest, the second taken back with the operation cancelled, and both refusals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ kommt.
|
||||
| Spalten | 142 |
|
||||
| Aufzählungstypen | 10 |
|
||||
| Sichten (Views) | 0 |
|
||||
| Eigene SQL-Funktionen | 36 (plus 31 aus der Erweiterung `pg_trgm`) |
|
||||
| Eigene SQL-Funktionen | 38 (plus 31 aus der Erweiterung `pg_trgm`) |
|
||||
| RLS-Policies | 21, auf jeder Tabelle mindestens eine |
|
||||
|
||||
---
|
||||
@@ -51,11 +51,12 @@ kein Termin im Kalender, sondern eine Zeile, die erst dann greift.
|
||||
`select` und `insert` zu. Eine Korrektur ist ein neuer Eintrag, nie eine
|
||||
geänderte Zeile.
|
||||
|
||||
Eine Ausnahme gibt es, und sie ist eng: `delete_history_entry` nimmt eine
|
||||
Zwei Ausnahmen gibt es, und sie sind eng: `delete_history_entry` nimmt eine
|
||||
irrtümlich erfasste Stammdaten- oder Vertragsänderung samt ihrer Wirkung
|
||||
zurück. Die Policies bleiben dabei unangetastet — die Funktion läuft als
|
||||
`SECURITY DEFINER` an ihnen vorbei und prüft die Berechtigung selbst. Das
|
||||
Protokoll behält den Vorgang, dort verschwindet nichts.
|
||||
zurück, `update_history_entry` berichtigt Wert und Datum einer solchen. Die
|
||||
Policies bleiben dabei unangetastet — beide laufen als `SECURITY DEFINER` an
|
||||
ihnen vorbei und prüfen die Berechtigung selbst. Das Protokoll behält den
|
||||
Vorgang, dort verschwindet nichts.
|
||||
|
||||
Die Namen der OM-Tabellen sind nicht zufällig gewählt: `org_units` ist der
|
||||
SAP-Objekttyp O, `jobs` ist C, `om_positions` ist S, `employees` ist P, und
|
||||
@@ -317,11 +318,15 @@ Transaktion mit.
|
||||
**Planstellen:** `create_position`, `update_position`, `delete_position`,
|
||||
`next_position_number`
|
||||
|
||||
**Historie:** `delete_history_entry` — nimmt eine irrtümliche Stammdaten-
|
||||
oder Vertragsänderung zurück: setzt je Feld auf den Wert davor, sofern kein
|
||||
späterer Eintrag dasselbe Feld angefasst hat, und entfernt die Zeile. Der
|
||||
einzige Weg an der fehlenden `delete`-Policy vorbei, deshalb `SECURITY
|
||||
DEFINER` und mit `require_hr_admin()` davor.
|
||||
**Historie:** `delete_history_entry` nimmt eine irrtümliche Stammdaten- oder
|
||||
Vertragsänderung zurück: setzt je Feld auf den Wert davor, sofern kein
|
||||
späterer Eintrag dasselbe Feld angefasst hat, und entfernt die Zeile.
|
||||
`update_history_entry` berichtigt stattdessen Wert und Datum — das „vorher"
|
||||
bleibt unangetastet, und der heutige Stand wird je Feld aus dem jüngsten
|
||||
Eintrag abgeleitet, der es trägt. Beide sind der einzige Weg an den fehlenden
|
||||
`update`- und `delete`-Policies vorbei, deshalb `SECURITY DEFINER` und mit
|
||||
`require_hr_admin()` davor. `app_feld_karte` liefert beiden die Zuordnung
|
||||
Beschriftung → Spalte und Typ.
|
||||
|
||||
**Umfeld:** `add_employee_dependent`, `delete_employee_dependent`,
|
||||
`add_employee_note`, `complete_employee_note`
|
||||
@@ -338,14 +343,15 @@ wen berichtet, samt Vertretung bei Abwesenheit (`acting_manager_id` neben
|
||||
(hängt am Ereignis-Trigger `ensure_rls`: neue Tabellen bekommen sofort RLS),
|
||||
die vier `fn_*`-Trigger, `is_valid_svnr`
|
||||
|
||||
Fünf Funktionen laufen als `SECURITY DEFINER`, also mit den Rechten ihrer
|
||||
Sechs Funktionen laufen als `SECURITY DEFINER`, also mit den Rechten ihrer
|
||||
Eigentümerin statt der aufrufenden Person: `is_hr_user`,
|
||||
`app_current_user_id`, `app_upsert_user`, `apply_due_pending_changes` und
|
||||
`delete_history_entry`. Die ersten drei müssen es sein, weil sie sonst gegen
|
||||
dieselben Policies liefen, die sie gerade auswerten sollen — eine Rekursion.
|
||||
Die vierte läuft ohne angemeldete Person, es gibt ja nur den Zeitplan. Die
|
||||
fünfte muss löschen können, wo es absichtlich keine `delete`-Policy gibt —
|
||||
und prüft die Berechtigung deshalb selbst, in ihrer ersten Zeile.
|
||||
`app_current_user_id`, `app_upsert_user`, `apply_due_pending_changes`,
|
||||
`delete_history_entry` und `update_history_entry`. Die ersten drei müssen es
|
||||
sein, weil sie sonst gegen dieselben Policies liefen, die sie gerade auswerten
|
||||
sollen — eine Rekursion. Die vierte läuft ohne angemeldete Person, es gibt ja
|
||||
nur den Zeitplan. Die letzten beiden müssen an `employee_history` schreiben,
|
||||
wo es absichtlich weder eine `update`- noch eine `delete`-Policy gibt; beide
|
||||
prüfen die Berechtigung deshalb selbst, in ihrer ersten Zeile.
|
||||
|
||||
**Der Türsteher:** `require_hr_admin()` steht am Anfang von **16**
|
||||
Funktionen — jeder ändernden. Es wirft, wenn `is_hr_user()` falsch ist, und
|
||||
|
||||
Reference in New Issue
Block a user