27669e0359f89bb5a8f67b8142a43e026398aee7
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 27669e0359 |
Put the whole application on the OM model, and delete what it replaced
Die Datenbank stand seit dem Cut-over auf org_units/om_positions/
position_assignments, die Anwendung fragte weiter nach employees.division_id,
team_id und manager_id — Spalten, die es nicht mehr gab. Die Oberfläche war
deshalb leer, obwohl die Daten vollständig da waren. Das ist jetzt behoben,
und zwar nicht durch Nachbau der alten Begriffe, sondern indem sie verschwinden.
Neu ist eine dünne Schicht, die die Verkettung Person → Besetzung →
Planstelle → Einheit einmal auflöst (lib/placement.ts) und der Baum als reine
Funktionen darauf (lib/org.ts): Vorfahrenkette, Teilbaum, Brotkrume. Alles
Weitere hängt daran.
Was sich dadurch von selbst erledigt hat:
- Das Organigramm musste drei Quellen versöhnen, weil keine den ganzen
Zeitstrahl abdeckte. position_assignments ist zeitabhängig, also
beantwortet eine Abfrage "wer besetzte am Stichtag welche Planstelle" —
für Vergangenheit und Zukunft gleichermassen. Wer keine Planstelle hatte,
war nicht da; eine zweite Zugehörigkeitsregel braucht es nicht mehr.
- Die Struktursicht war auf genau vier Ebenen verdrahtet und rendert jetzt
rekursiv über parent_id. Liste und Grafik entstehen aus *einem* Baum;
vorher lag dieselbe Hierarchie zweimal vor und konnte auseinanderlaufen.
- Eine offene Stelle ist keine eigene Tabelle mehr, sondern eine Planstelle
ohne laufende Besetzung — das Komplement kann nicht aus dem Tritt geraten.
- Eine Versetzung ist der Wechsel auf eine Zielplanstelle statt Zielteam
plus frei getipptem Titel. Sie kann damit nicht mehr dort landen, wo es
keine Stelle gibt, und die Tätigkeit kommt aus dem Job-Katalog.
- Beim Anlegen einer Planstelle entfällt die Suche nach der vorgesetzten
Person: sie ergibt sich aus der Einheit, die Frage kann nicht mehr falsch
beantwortet werden.
Zwei Auswertungen werden dabei richtiger, nicht nur anders. Ein
Stichtagsbericht gruppierte bisher nach der *heutigen* Zuordnung, weil es
keine Historie gab; er löst sie jetzt zum Stichtag auf. Und ein Ereignis
trägt die Einheit, in der die Person am Tag des Ereignisses sass — vorher
stand ein Austritt von vor zwei Jahren unter einem Team, in das sie nie
versetzt worden war. Der Bereichsfilter greift überall auf den ganzen
Teilbaum; auf den Bereich allein angewandt lieferte er nur die
Bereichsleitung.
Gelöscht: die Reorganisations-Werkbank samt Szenarien und Zügen (sie
verschob Teams und Abteilungen zwischen Bereichen — Objekte, die es nicht
mehr gibt; im OM-Modell ist das ein Umhängen von parent_id), die
Mitarbeiter- und Vorgesetztensuche, die nur sie und die Ausschreibung
brauchten, und aus lib/supabase/types.ts die Tabellen divisions,
departments, teams, positions und employee_assignments.
Die beiliegende Migration räumt die Datenbank entsprechend auf. Sie entfernt
auch Funktionen, die der Cut-over verfehlt hat: create_position,
delete_position und undo_reorg existierten zusätzlich in einer
jsonb-Variante und tauchen deshalb weiter in der PostgREST-Schnittstelle auf,
obwohl ihre Tabellen weg sind — ein Aufruf wäre erst zur Laufzeit
gescheitert. An ihre Stelle treten create_position und delete_position im
OM-Sinn; letzteres schliesst eine früher besetzte Planstelle, statt sie zu
löschen, sonst verschwände mit ihr die Besetzungshistorie.
Typecheck, Lint, Build und 182 Tests sind grün. Die Integrationstests sind
mitgezogen, aber weiterhin ungelaufen — dafür braucht es eine laufende
lokale Datenbank.
|
|||
| 8d978981b0 |
SVNR validation, CI, and a dependency/security pass
Positions
- Removed the "Besetzen" action, the StaffInternallyModal behind it and the
now-unreachable staffPositionInternally server action: a position is filled
through the hire process, not from the positions list. Note that
transfer_employee has no position_id at all and never touched `positions`,
so with staff_position_internally out of the UI, hire_employee is the only
thing that closes a position — a transfer into an open one leaves it open.
The RPC itself is still in the database and still covered by its tests.
SVNR
- Austrian social security numbers are now validated: ten digits, weighted
check digit mod 11, and the TTMMJJ tail cross-checked against birth_date,
which is what catches a transposed date that a valid check digit would let
through. A serial whose weighted sum lands on 11 is rejected rather than
wrapped — those are never issued.
- Applies to Austrian locations only; the German/Czech/Slovenian equivalents
have their own formats and stay free-form.
- Enforced by a trigger, not inside hire_employee/change_employee_data, for
the same reason as the assignment history: both have been redefined by
half a dozen migrations. Only a *newly written* value is checked, so a
legacy number never blocks an unrelated transfer or address change.
- The seed drew a random four-digit prefix, so its check digit was right
only by chance and every seeded Austrian row would now be rejected;
it computes the check digit properly now.
Tech stack
- next 16.2.11 closes nine advisories against 16.2.10, including a
middleware/proxy bypass in App Router apps on Turbopack — proxy.ts is this
app's entry gate. RLS remains the real boundary, so the blast radius was a
blank page rather than data, but it is a patch-level fix. Also react
19.2.8, tailwind 4.3.3, lucide-react 1.26, supabase-js/ssr, postcss.
- CI runs lint, typecheck, schema/type drift, tests and build; a second job
replays every migration onto an empty database and runs the integration
suite against it, so a migration that cannot be replayed from scratch
fails here instead of during a restore.
- scripts/check-schema-types.mjs diffs the hand-written lib/supabase/types.ts
against the migrations. Reading the SQL rather than a live database keeps
Postgres out of the fast CI job. Verified in both directions.
- vitest now runs two projects: node for logic, jsdom for components. The
first component test covers the org chart expand control, which broke
earlier this session when elementsSelectable={false} made React Flow
compute pointer-events:none for the whole node; re-introducing that prop
fails three of these tests.
- Content-Security-Policy is emitted report-only. Enforcing a policy derived
from inspection rather than from violation reports risks blanking the app;
'unsafe-inline' on script-src is required until a nonce is threaded through
proxy.ts, which is a separate change.
- Fixed supabase/seed.ts, which this session's SVNR change had broken: the
extensionless "../lib/svnr" import does not resolve under Node's ESM
loader, so the seed failed at startup.
- engines pinned to node >=22 <25, tsconfig target ES2022, and the dead
test:e2e script removed (no Playwright is installed).
|