The database moved to a container of our own; the platform is gone. This takes out what was left of it — and, where the leftovers were load bearing, moves rather than deletes. Moved, not deleted: supabase/migrations/ -> db/migrations/ the schema's source of truth supabase/build-org.ts -> scripts/build-org.ts lib/supabase/types.ts -> lib/types.ts 52 import sites repointed The bookkeeping needed care. It lived in `supabase_migrations.schema_migrations`, and simply renaming the schema would have left the runner facing an empty table: it would have called all 67 migrations pending and replayed them against a database that is long since current. So the runner now creates `migrationen.schema_migrations` and, once, copies the old rows across — guarded so a second run does nothing and a fresh database skips it entirely. Only then does migration 20260907100000 drop the old schema. Deleted: the CLI config, the seed, the historical schema/function dumps (nothing read them), scripts/umzug-von-supabase.sh (the move is done), and both Supabase packages plus the CLI. Nothing in the application imported them — the build now succeeds with no environment variables at all, which is the proof. Integration tests: six of them signed in through Supabase Auth and asserted against the anon key and the service role. That model is gone, so the tests were not portable — they are deleted. session-context and employee-status-filter already ran on pg and are untouched; om-reporting is ported to a direct connection because it guards a real risk (the reporting line rule exists twice, once in SQL and once in TypeScript). CI: the integration job started a Supabase stack. It now runs a postgres service, applies deploy/db-init and every migration to an empty database — that was the valuable part, and it still holds — then checks that a second run is a no-op, which is what proves the bookkeeping works. Docs: security-review.md audited a service-role key, a cookie adapter and auth.users, none of which exist. Restating findings about removed components would suggest today's system had been reviewed; it has not. It now records what was removed and says a fresh review is due. data-model.md was already marked obsolete and described the pre-OM schema; azure-migration.md was a plan for a route not taken. Both deleted. Verified: npm ci, typecheck, lint, 445 tests, build — all clean without the packages. Integration tests skip cleanly with no database. Migration SQL and the runner are reviewed but NOT executed: no Docker here, and the old instance no longer resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
4.0 KiB
PL/PgSQL
102 lines
4.0 KiB
PL/PgSQL
-- Die Berichtslinie wird abgeleitet, nicht gepflegt.
|
|
--
|
|
-- Bisher stand sie als employees.manager_id in der Tabelle und wurde von
|
|
-- resolve_manager_for() bei jeder Mutation neu geraten. Damit konnte sie von
|
|
-- der Organisationsstruktur abweichen, und tat es auch.
|
|
--
|
|
-- SAP-OM-Regel, hier eins zu eins:
|
|
--
|
|
-- Wer eine gewöhnliche Planstelle innehat, berichtet an die Leitung der
|
|
-- eigenen Organisationseinheit. Wer selbst die Leitung innehat, berichtet
|
|
-- an die Leitung der übergeordneten Einheit.
|
|
--
|
|
-- Dazu kommt die Aufwärtsregel: Ist diese Leitungsplanstelle unbesetzt oder
|
|
-- ihre Inhaberin langzeitabwesend, geht es weiter nach oben, bis eine
|
|
-- besetzte und anwesende Leitung gefunden ist. Genau deshalb braucht eine
|
|
-- unbesetzte Abteilungsleitung keine Sonderbehandlung — sie wird schlicht
|
|
-- übersprungen.
|
|
--
|
|
-- Beides wird zurückgegeben: die formale Leitung (auch wenn abwesend) und
|
|
-- die tatsächliche. Nur so lässt sich in der Oberfläche zeigen, dass eine
|
|
-- Vertretung im Spiel ist, statt sie stillschweigend als die echte
|
|
-- Führungskraft auszugeben.
|
|
|
|
create or replace function om_reporting_lines(p_as_of date default current_date)
|
|
returns table (
|
|
employee_id uuid,
|
|
position_id uuid,
|
|
org_unit_id uuid,
|
|
is_chief boolean,
|
|
formal_manager_id uuid,
|
|
acting_manager_id uuid
|
|
)
|
|
language sql
|
|
stable
|
|
as $$
|
|
with recursive
|
|
-- Laufende Besetzungen: Planstelle und Zuordnung müssen beide am Stichtag
|
|
-- gültig sein.
|
|
holder as (
|
|
select pa.employee_id, pa.position_id, p.org_unit_id, p.is_chief
|
|
from position_assignments pa
|
|
join om_positions p on p.id = pa.position_id
|
|
where pa.valid_from <= p_as_of and (pa.valid_to is null or pa.valid_to > p_as_of)
|
|
and p.valid_from <= p_as_of and (p.valid_to is null or p.valid_to > p_as_of)
|
|
),
|
|
-- Leitung je Einheit, samt Abwesenheit am Stichtag. Die Ableitung ist
|
|
-- dieselbe wie in deriveStatusAsOf() auf der Anwendungsseite.
|
|
chief as (
|
|
select h.org_unit_id, h.employee_id,
|
|
(e.karenz_start_date is not null
|
|
and e.karenz_start_date <= p_as_of
|
|
and (e.karenz_return_date is null or p_as_of < e.karenz_return_date)) as absent
|
|
from holder h
|
|
join employees e on e.id = h.employee_id
|
|
where h.is_chief
|
|
),
|
|
-- Vorfahrenkette je Einheit; Tiefe 0 ist die Einheit selbst. Bei rund
|
|
-- sechzig Einheiten ist das billig, und es macht die Suche nach der
|
|
-- nächsten geeigneten Leitung zu einem einfachen "erster Treffer".
|
|
ancestry as (
|
|
select u.id as unit_id, u.id as ancestor_id, u.parent_id, 0 as depth
|
|
from org_units u
|
|
union all
|
|
select a.unit_id, p.id, p.parent_id, a.depth + 1
|
|
from ancestry a
|
|
join org_units p on p.id = a.parent_id
|
|
),
|
|
-- Die Einheit, ab der gesucht wird: für eine Leitung die übergeordnete,
|
|
-- sonst die eigene.
|
|
base as (
|
|
select h.employee_id, h.position_id, h.org_unit_id, h.is_chief,
|
|
case when h.is_chief then u.parent_id else h.org_unit_id end as base_unit_id
|
|
from holder h
|
|
join org_units u on u.id = h.org_unit_id
|
|
)
|
|
select
|
|
b.employee_id,
|
|
b.position_id,
|
|
b.org_unit_id,
|
|
b.is_chief,
|
|
-- Formale Leitung: die der Ausgangseinheit, unabhängig von Abwesenheit.
|
|
(select c.employee_id from chief c where c.org_unit_id = b.base_unit_id) as formal_manager_id,
|
|
-- Tatsächliche Leitung: die nächste besetzte und anwesende oberhalb,
|
|
-- die Ausgangseinheit eingeschlossen.
|
|
(
|
|
select c.employee_id
|
|
from ancestry a
|
|
join chief c on c.org_unit_id = a.ancestor_id
|
|
where a.unit_id = b.base_unit_id
|
|
and not c.absent
|
|
and c.employee_id <> b.employee_id
|
|
order by a.depth
|
|
limit 1
|
|
) as acting_manager_id
|
|
from base b;
|
|
$$;
|
|
|
|
comment on function om_reporting_lines(date) is
|
|
'Leitet die Berichtslinie zum Stichtag aus dem Organisationsbaum ab. formal_manager_id ist die zuständige Leitung, acting_manager_id die nächste besetzte und anwesende darüber.';
|
|
|
|
grant execute on function om_reporting_lines(date) to anon, authenticated, service_role;
|