-- Organisationsmanagement nach SAP-OM-Vorbild.
--
-- Bisher: drei feste Tabellen (divisions -> departments -> teams) und
-- Personen, die direkt daran hängen (employees.division_id/team_id) mit
-- einer frei gepflegten manager_id. Damit ist die Hierarchie in ihrer Tiefe
-- fest verdrahtet — eine Abteilungsleitung liess sich nicht abbilden, ohne
-- das Schema zu ändern, und ein Team direkt unter einem Bereich gar nicht.
--
-- SAP OM löst das über wenige Objekttypen und Verknüpfungen dazwischen:
--
--   O  Organisationseinheit   org_units            (rekursiv über parent_id)
--   C  Stelle / Job           jobs                 (Katalog)
--   S  Planstelle             positions            (gehört zu genau einer O)
--   P  Person                 employees            (besetzt eine S)
--
--   A003  "gehört zu"         positions.org_unit_id
--   A012  "ist Leiter von"    positions.is_chief
--   A008  "Inhaber ist"       position_assignments
--
-- Die Berichtslinie wird daraus abgeleitet statt gepflegt, siehe die
-- folgende Migration. Ebenen sind nur noch ein Etikett (unit_type), keine
-- Struktur — eine fünfte Ebene ist damit eine Datenfrage, keine Migration.

-- ── O: Organisationseinheit ────────────────────────────────────────
create type org_unit_type as enum ('Gesellschaft', 'Bereich', 'Abteilung', 'Team');

create table org_units (
  id uuid primary key default gen_random_uuid(),
  org_number text not null unique,
  name text not null,
  -- Die Hierarchie selbst. Null nur für die Wurzel.
  parent_id uuid references org_units(id),
  -- Nur Beschriftung und Nummernkreis-Konvention; die Struktur steckt in
  -- parent_id. Eine Abteilung unter einer Abteilung wäre technisch möglich
  -- und ist bewusst nicht verboten.
  unit_type org_unit_type not null,
  valid_from date not null default current_date,
  valid_to date,
  created_at timestamptz not null default now(),
  constraint chk_org_unit_range check (valid_to is null or valid_to > valid_from),
  constraint chk_org_unit_not_own_parent check (parent_id is null or parent_id <> id)
);

create index on org_units (parent_id);
create index on org_units (unit_type);
-- Genau eine Wurzel: ohne das kann ein Fehlgriff beim Import einen zweiten
-- Baum aufmachen, und die Ableitung der Berichtslinie liefe ins Leere.
create unique index org_units_single_root on org_units ((parent_id is null)) where parent_id is null;

comment on table org_units is 'SAP-OM-Objekttyp O. Rekursiv über parent_id; unit_type ist nur ein Etikett.';

-- ── C: Stelle / Job-Katalog ────────────────────────────────────────
-- Trennt die Tätigkeitsbeschreibung von der einzelnen Planstelle: viele
-- Planstellen teilen sich einen Job. Bisher war job_title Freitext je
-- Person, weshalb "Schlosser:in" und "Schlosser" nebeneinander existieren
-- konnten und keine Auswertung über Tätigkeiten möglich war.
create table jobs (
  id uuid primary key default gen_random_uuid(),
  code text not null unique,
  title text not null unique,
  created_at timestamptz not null default now()
);

comment on table jobs is 'SAP-OM-Objekttyp C. Katalog der Tätigkeiten; Planstellen verweisen darauf.';

-- ── S: Planstelle ──────────────────────────────────────────────────
-- Anders als die bisherige positions-Tabelle, die nur *offene* Stellen
-- führte: hier bekommt jede Person eine Planstelle. Eine offene Stelle ist
-- schlicht eine Planstelle ohne laufende Besetzung — Vakanz ist damit eine
-- Eigenschaft der Planstelle, kein eigenes Objekt.
create table om_positions (
  id uuid primary key default gen_random_uuid(),
  position_number text not null unique,
  org_unit_id uuid not null references org_units(id),
  job_id uuid not null references jobs(id),
  -- A012 "ist Leiter von": diese Planstelle führt ihre Organisationseinheit.
  is_chief boolean not null default false,
  valid_from date not null default current_date,
  valid_to date,
  created_at timestamptz not null default now(),
  constraint chk_om_position_range check (valid_to is null or valid_to > valid_from)
);

create index on om_positions (org_unit_id);
create index on om_positions (job_id);
-- Höchstens eine Leitungsplanstelle je Einheit, solange sie gültig ist.
create unique index om_positions_one_chief on om_positions (org_unit_id) where is_chief and valid_to is null;

comment on table om_positions is 'SAP-OM-Objekttyp S. is_chief entspricht der Verknüpfung A012 "ist Leiter von".';

-- ── A008: Person besetzt Planstelle ────────────────────────────────
create table position_assignments (
  id uuid primary key default gen_random_uuid(),
  position_id uuid not null references om_positions(id) on delete cascade,
  employee_id uuid not null references employees(id) on delete cascade,
  valid_from date not null,
  valid_to date,
  created_at timestamptz not null default now(),
  constraint chk_assignment_range check (valid_to is null or valid_to > valid_from)
);

create index on position_assignments (position_id);
create index on position_assignments (employee_id);
-- Eine Planstelle ist zu einem Zeitpunkt von höchstens einer Person besetzt,
-- und eine Person hat höchstens eine laufende Planstelle. Beides sind die
-- Invarianten, auf die sich die Ableitung der Berichtslinie stützt.
create unique index position_assignments_one_holder on position_assignments (position_id) where valid_to is null;
create unique index position_assignments_one_position on position_assignments (employee_id) where valid_to is null;

comment on table position_assignments is 'SAP-OM-Verknüpfung A008 "Inhaber ist", zeitabhängig.';

-- ── RLS, wie bei allen anderen Tabellen ────────────────────────────
alter table org_units enable row level security;
alter table jobs enable row level security;
alter table om_positions enable row level security;
alter table position_assignments enable row level security;

create policy "org_units_hr_all" on org_units for all using (is_hr_user()) with check (is_hr_user());
create policy "jobs_hr_all" on jobs for all using (is_hr_user()) with check (is_hr_user());
create policy "om_positions_hr_all" on om_positions for all using (is_hr_user()) with check (is_hr_user());
create policy "position_assignments_hr_all" on position_assignments for all using (is_hr_user()) with check (is_hr_user());

grant all on table org_units, jobs, om_positions, position_assignments to anon, authenticated, service_role;
