Files
alpenwerk-hr/lib/supabase
Maximilian Stubhan 4b9c23472c SAP OM: org units, jobs, positions, and a derived reporting line
The org structure was three fixed tables — divisions -> departments ->
teams — with people hanging directly off them and a hand-maintained
manager_id. The depth was therefore wired into the schema: an Abteilungsleitung
could not exist without a migration, and a team directly under a Bereich not
at all. That is what this replaces.

The SAP OM object types, one table each:

  O  org_units             recursive over parent_id
  C  jobs                  catalogue, so many positions can share a job
  S  om_positions          belongs to exactly one org unit
  P  employees             existing table
  A012 om_positions.is_chief          "ist Leiter von"
  A008 position_assignments           "Inhaber ist", time-dependent

Two consequences worth stating, because they are the point of the exercise:

- GF/Bereich/Abteilung/Team are now a label (unit_type), not a structure.
  Adding a fifth level, or hanging a team straight off a Bereich, becomes a
  data question rather than a migration.
- Nobody hangs off an org unit any more: person -> position -> unit. A
  vacancy stops being its own concept — it is a position with no current
  assignment.

The reporting line is derived rather than stored: an ordinary position
reports to the chief of its own unit, a chief to the chief of the parent
unit, and if that chief is vacant or on a long-term absence it keeps
climbing. An unfilled Abteilungsleitung therefore needs no special case —
it is simply skipped. Both ids come back, formal and acting, so the UI can
show a stand-in as a stand-in instead of passing it off as the real manager.

The rule exists twice, as om_reporting_lines() in SQL and
resolveReportingLines() in TypeScript, because the as-of chart computes it
per date in the app and a round trip per date change would buy nothing. Two
copies drift silently — the org chart would just show a different manager
than the export — so an integration test runs both over the whole roster and
requires identical answers, plus that every line terminates at the top.

Unit tests cover the rule itself: unfilled levels, several absent levels in
a row, nobody above, a chief who also leads the parent unit, and a cycle in
parent_id, which is an ordinary column an import could get wrong.

Additive so far. The old tables still stand and the app still reads them;
the cut-over follows.
2026-07-27 12:47:14 +02:00
..