diff --git a/lib/org.ts b/lib/org.ts index f04a267..117a548 100644 --- a/lib/org.ts +++ b/lib/org.ts @@ -136,9 +136,31 @@ export function breadcrumbLabel(maps: OrgMaps, unitId: string | null | undefined return chain.map((u) => u.name).join(" › ") || "–"; } -/** Die oberste Einheit unterhalb der Gesellschaft — das, was früher „Bereich" hiess. */ +/** + * Der Bereich über einer Einheit — der nächste Vorfahre, der als Bereich + * geführt wird, die Einheit selbst eingeschlossen. + * + * Hier stand „die oberste Einheit unterhalb der Gesellschaft", also eine + * Aussage über die *Stellung* im Baum statt über das Etikett. Im Altmodell + * fiel beides zusammen: unter der Gesellschaft hingen genau die Bereiche. + * Sobald eine Einheit dazwischenliegt, fällt es auseinander — bei Manner + * hängt unter der Gesellschaft allein „CEO", und die sechs C-Level-Bereiche + * darunter. Die Funktion gab damit für alle 784 Personen „CEO" zurück: in + * der Mitarbeiterliste stand es in jeder Zeile, und auf der Übersicht lagen + * alle im Balken „CEO", während die sechs übrigen Bereiche auf null standen. + * + * Gesucht wird der **nächste** Bereich, nicht der oberste. „CEO" ist selbst + * einer und liegt über den anderen; ohne diese Wahl stünde er wieder überall. + * Dieselbe Regel gilt beim Sortieren (lib/employee-sort.ts) und in den + * Berichten (lib/reports-data.ts) — alle drei müssen denselben Bereich + * meinen, sonst sortiert die Liste nach etwas anderem, als sie anzeigt. + */ export function divisionOf(maps: OrgMaps, unitId: string | null | undefined): OrgUnit | undefined { - return ancestorsOf(maps, unitId).find((u) => u.unit_type !== "Gesellschaft"); + const kette = ancestorsOf(maps, unitId); + for (let i = kette.length - 1; i >= 0; i--) { + if (kette[i].unit_type === "Bereich") return kette[i]; + } + return undefined; } /** Die Einheit selbst, wenn sie nicht die Gesellschaft ist. */ diff --git a/tests/unit/org.test.ts b/tests/unit/org.test.ts index a62d250..1681afe 100644 --- a/tests/unit/org.test.ts +++ b/tests/unit/org.test.ts @@ -84,6 +84,34 @@ describe("divisionOf", () => { // Die Geschäftsführung sitzt über allen Bereichen, nicht in einem. expect(divisionOf(maps, "gmbh")).toBeUndefined(); }); + + // Der Fall, an dem die frühere Fassung scheiterte: sie nahm die oberste + // Einheit unterhalb der Gesellschaft. Liegt dort eine weitere Ebene — bei + // Manner „CEO" über den sechs C-Level-Bereichen —, gab sie für **jede** + // Person dieselbe Einheit zurück. In der Mitarbeiterliste stand „CEO" in + // jeder Zeile, auf der Übersicht lag die ganze Belegschaft in einem Balken. + describe("wenn über den Bereichen noch eine Einheit liegt", () => { + const tief: OrgUnit[] = [ + { id: "ag", org_number: "0000", name: "Manner AG", parent_id: null, unit_type: "Gesellschaft" }, + { id: "ceo", org_number: "0080", name: "CEO", parent_id: "ag", unit_type: "Bereich" }, + { id: "coo", org_number: "0083", name: "COO", parent_id: "ceo", unit_type: "Bereich" }, + { id: "w17", org_number: "0088", name: "Standortleitung W17", parent_id: "coo", unit_type: "Abteilung" }, + { id: "schnitte", org_number: "0101", name: "Leitung Schnitte", parent_id: "w17", unit_type: "Team" }, + { id: "tl", org_number: "0150", name: "Teamleitung A", parent_id: "schnitte", unit_type: "Team" }, + ]; + const tiefeMaps = buildOrgMaps(tief, locations); + + it("nimmt den nächsten Bereich, nicht den obersten", () => { + expect(divisionOf(tiefeMaps, "tl")?.name).toBe("COO"); + expect(divisionOf(tiefeMaps, "schnitte")?.name).toBe("COO"); + expect(divisionOf(tiefeMaps, "w17")?.name).toBe("COO"); + }); + + it("lässt die oberste Einheit ihren eigenen Bereich behalten", () => { + // Wer direkt am CEO sitzt, gehört dorthin — und nur diese Personen. + expect(divisionOf(tiefeMaps, "ceo")?.name).toBe("CEO"); + }); + }); }); describe("breadcrumbLabel", () => {