diff --git a/lib/employee-sort.ts b/lib/employee-sort.ts index 1af061f..82a935a 100644 --- a/lib/employee-sort.ts +++ b/lib/employee-sort.ts @@ -73,24 +73,38 @@ const einheitAusdruck = sql`( limit 1)`; /** - * Der Bereich darüber — die erste Ebene unter der Gesellschaft. + * Der Bereich darüber — die nächste Einheit über der Person, die als Bereich + * geführt wird. * - * Ohne Rekursion: der Baum hat höchstens vier Ebenen (Gesellschaft, Bereich, - * Abteilung, Team), also genügen zwei Sprünge nach oben. Sitzt jemand direkt - * am Bereich, greift schon der erste Fall. + * Aufgestiegen wird rekursiv, nicht mit einer festen Zahl von Sprüngen. Hier + * standen zwei `left join`, weil der Baum als höchstens vierstufig galt + * (Gesellschaft, Bereich, Abteilung, Team) und zwei Sprünge damit reichten. + * `unit_type` ist aber nur ein Etikett, und die Organisation kann beliebig + * tief sein: bei Manner sind es sieben Ebenen, und für 317 der 784 Personen + * lag der Bereich drei oder vier Sprünge über der eigenen Einheit. Der + * Ausdruck lieferte null, und diese 317 rutschten beim Sortieren nicht unter + * ihre Bereiche, sondern allesamt in einen Block am Ende der Liste. + * + * Der Aufstieg hält beim ersten Bereich an, nimmt also den **nächsten** und + * nicht den obersten. Ohne das Anhalten stünde bei fast allen „CEO": diese + * Einheit liegt über den sechs C-Level-Bereichen und ist selbst einer. In den + * Berichten gilt dieselbe Regel — lib/reports-data.ts steigt von der Wurzel + * ab, dort gewinnt der letzte Treffer, und das ist derselbe Bereich. */ const bereichAusdruck = sql`( - select coalesce( - case when ou.unit_type = 'Bereich' then ou.name end, - case when e1.unit_type = 'Bereich' then e1.name end, - case when e2.unit_type = 'Bereich' then e2.name end) - from position_assignments pa - join om_positions p on p.id = pa.position_id - join org_units ou on ou.id = p.org_unit_id - left join org_units e1 on e1.id = ou.parent_id - left join org_units e2 on e2.id = e1.parent_id - where pa.employee_id = employees.id and pa.valid_to is null - limit 1)`; + with recursive kette as ( + select ou.id, ou.name, ou.unit_type, ou.parent_id, 0 as tiefe + from position_assignments pa + join om_positions p on p.id = pa.position_id + join org_units ou on ou.id = p.org_unit_id + where pa.employee_id = employees.id and pa.valid_to is null + union all + select o.id, o.name, o.unit_type, o.parent_id, k.tiefe + 1 + from kette k + join org_units o on o.id = k.parent_id + where k.unit_type <> 'Bereich' + ) + select name from kette where unit_type = 'Bereich' order by tiefe limit 1)`; const standortAusdruck = sql`(select name from locations where id = employees.location_id)`; diff --git a/tests/unit/employee-sort.test.ts b/tests/unit/employee-sort.test.ts index 0f0666b..783e08b 100644 --- a/tests/unit/employee-sort.test.ts +++ b/tests/unit/employee-sort.test.ts @@ -199,16 +199,17 @@ describe("das erzeugte SQL", () => { // selbst — sonst stünde in der Spalte etwas anderes als sortiert wird. expect(sql).toContain("unit_type = 'Bereich'"); - // Und über *alle drei* Ebenen: wer in einem Team sitzt, hat den Bereich - // zwei Sprünge über sich. Bliebe nur die eigene Einheit übrig, stünden - // alle Teammitglieder ohne Bereich da und rutschten ans Listenende. - // - // Dass drei Ebenen reichen, ist eine Aussage über die Daten (der - // Aufzählungstyp org_unit_type kennt genau vier Stufen) und lässt sich - // hier nicht prüfen — dass sie überhaupt abgefragt werden, schon. - expect(sql).toContain("ou.unit_type = 'Bereich'"); - expect(sql).toContain("e1.unit_type = 'Bereich'"); - expect(sql).toContain("e2.unit_type = 'Bereich'"); + // Und über beliebig viele Ebenen, nicht über eine feste Zahl von + // Sprüngen. Vorher standen hier zwei `left join`; bei sieben Ebenen lag + // der Bereich für 317 von 784 Personen ausserhalb ihrer Reichweite, und + // diese 317 rutschten ohne Bereich ans Listenende. + expect(sql).toContain("with recursive"); + expect(sql).toContain("join org_units o on o.id = k.parent_id"); + + // Der Aufstieg hält beim ersten Bereich an. Ohne das Anhalten liefe er + // bis zur obersten Einheit weiter, und bei fast allen stünde „CEO". + expect(sql).toContain("k.unit_type <> 'Bereich'"); + expect(sql).toContain("order by tiefe limit 1"); }); it("sortiert den Standort nach seinem Namen, nicht nach seiner Kennung", () => {