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