import { describe, expect, it } from "vitest"; import { resolveReportingLines, type OmHolder, type OmUnit } from "@/lib/om-reporting"; // Vier Ebenen wie in der Zielstruktur: Gesellschaft -> Bereich -> Abteilung // -> Team. Die Hierarchie steckt allein in parent_id; unit_type ist ein // Etikett und für die Ableitung ohne Bedeutung. const UNITS: OmUnit[] = [ { id: "gf", parentId: null }, { id: "bereich", parentId: "gf" }, { id: "abteilung", parentId: "bereich" }, { id: "team", parentId: "abteilung" }, ]; function holder(employeeId: string, orgUnitId: string, isChief: boolean, absent = false): OmHolder { return { employeeId, positionId: `pos-${employeeId}`, orgUnitId, isChief, absent }; } function lineFor(employeeId: string, holders: OmHolder[], units: OmUnit[] = UNITS) { return resolveReportingLines(units, holders).find((l) => l.employeeId === employeeId)!; } describe("Berichtslinie aus dem Organisationsbaum", () => { const full = [ holder("gf-person", "gf", true), holder("bl", "bereich", true), holder("al", "abteilung", true), holder("tl", "team", true), holder("ma", "team", false), ]; it("lässt Mitarbeitende an die Leitung der eigenen Einheit berichten", () => { expect(lineFor("ma", full).actingManagerId).toBe("tl"); }); it("lässt eine Leitung an die Leitung der übergeordneten Einheit berichten", () => { expect(lineFor("tl", full).actingManagerId).toBe("al"); expect(lineFor("al", full).actingManagerId).toBe("bl"); expect(lineFor("bl", full).actingManagerId).toBe("gf-person"); }); it("gibt der obersten Leitung keine Führungskraft", () => { const gf = lineFor("gf-person", full); expect(gf.actingManagerId).toBeNull(); expect(gf.formalManagerId).toBeNull(); }); }); describe("unbesetzte Leitung", () => { // Der eigentliche Grund für die Aufwärtsregel: eine Abteilung ohne // Leitung soll die Kette nicht abreißen lassen und braucht keine // Sonderbehandlung im Code. const ohneAbteilungsleitung = [ holder("gf-person", "gf", true), holder("bl", "bereich", true), holder("tl", "team", true), holder("ma", "team", false), ]; it("überspringt eine unbesetzte Abteilungsleitung", () => { expect(lineFor("tl", ohneAbteilungsleitung).actingManagerId).toBe("bl"); }); it("nennt die unbesetzte Ebene auch nicht als formale Leitung", () => { expect(lineFor("tl", ohneAbteilungsleitung).formalManagerId).toBeNull(); }); it("überspringt mehrere unbesetzte Ebenen hintereinander", () => { const nurGf = [holder("gf-person", "gf", true), holder("ma", "team", false)]; expect(lineFor("ma", nurGf).actingManagerId).toBe("gf-person"); }); it("lässt die Führungskraft leer, wenn oberhalb niemand besetzt ist", () => { const allein = [holder("ma", "team", false)]; expect(lineFor("ma", allein).actingManagerId).toBeNull(); }); }); describe("abwesende Leitung", () => { const teamleitungAbwesend = [ holder("gf-person", "gf", true), holder("bl", "bereich", true), holder("al", "abteilung", true), holder("tl", "team", true, true), holder("ma", "team", false), ]; it("hebt die Berichtslinie auf die nächste anwesende Ebene", () => { expect(lineFor("ma", teamleitungAbwesend).actingManagerId).toBe("al"); }); it("nennt weiterhin die formal zuständige Leitung, damit die Vertretung erkennbar bleibt", () => { // Ohne das würde die Oberfläche die Vertretung als die echte // Führungskraft ausgeben. expect(lineFor("ma", teamleitungAbwesend).formalManagerId).toBe("tl"); }); it("steigt über mehrere abwesende Ebenen hinweg", () => { const zweiAbwesend = [ holder("gf-person", "gf", true), holder("bl", "bereich", true), holder("al", "abteilung", true, true), holder("tl", "team", true, true), holder("ma", "team", false), ]; expect(lineFor("ma", zweiAbwesend).actingManagerId).toBe("bl"); expect(lineFor("ma", zweiAbwesend).formalManagerId).toBe("tl"); }); it("gibt die abwesende Leitung selbst an ihre eigene übergeordnete Ebene", () => { expect(lineFor("tl", teamleitungAbwesend).actingManagerId).toBe("al"); }); }); describe("Randfälle", () => { it("lässt niemanden an sich selbst berichten", () => { // Eine Leitung, deren übergeordnete Einheit sie ebenfalls führt. const doppelrolle = [holder("chef", "bereich", true), holder("chef2", "abteilung", true)]; const units: OmUnit[] = [ { id: "bereich", parentId: null }, { id: "abteilung", parentId: "bereich" }, ]; expect(lineFor("chef", doppelrolle, units).actingManagerId).toBeNull(); }); it("bricht bei einem Ring in parent_id ab, statt ewig zu laufen", () => { // parent_id ist eine gewöhnliche Spalte; ein fehlerhafter Import kann // einen Ring erzeugen. const ring: OmUnit[] = [ { id: "a", parentId: "b" }, { id: "b", parentId: "a" }, ]; const holders = [holder("ma", "a", false)]; expect(() => resolveReportingLines(ring, holders)).not.toThrow(); expect(lineFor("ma", holders, ring).actingManagerId).toBeNull(); }); it("kommt mit mehreren Teams unter derselben Abteilung zurecht", () => { const units: OmUnit[] = [ { id: "abteilung", parentId: null }, { id: "team-a", parentId: "abteilung" }, { id: "team-b", parentId: "abteilung" }, ]; const holders = [ holder("al", "abteilung", true), holder("tl-a", "team-a", true), holder("tl-b", "team-b", true), holder("ma-a", "team-a", false), holder("ma-b", "team-b", false), ]; expect(lineFor("ma-a", holders, units).actingManagerId).toBe("tl-a"); expect(lineFor("ma-b", holders, units).actingManagerId).toBe("tl-b"); expect(lineFor("tl-b", holders, units).actingManagerId).toBe("al"); }); });