-- 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;
