Den Bereich einer Einheit ueber das Etikett suchen, nicht ueber die Stellung
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m34s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m17s

divisionOf nahm "die oberste Einheit unterhalb der Gesellschaft" -- eine
Aussage ueber die Stellung im Baum statt ueber unit_type. Im Altmodell
fiel beides zusammen, weil unter der Gesellschaft genau die Bereiche
hingen. Bei Manner liegt dort allein "CEO", darunter erst die sechs
C-Level-Bereiche: die Funktion gab fuer alle 784 Personen "CEO" zurueck.
In der Mitarbeiterliste stand es in jeder Zeile, auf der Uebersicht lag
die ganze Belegschaft im Balken "CEO", waehrend die sechs uebrigen
Bereiche auf null standen.

Gesucht wird jetzt der naechste Vorfahre mit unit_type = 'Bereich', die
Einheit selbst eingeschlossen. Der naechste und nicht der oberste: "CEO"
ist selbst ein Bereich und liegt ueber den anderen, sonst stuende er
wieder ueberall. Damit meinen Liste, Uebersicht, Sortierung
(lib/employee-sort.ts) und Berichte (lib/reports-data.ts) denselben
Bereich -- vorher sortierte die Liste bereits richtig, zeigte aber in
der Spalte etwas anderes an.
This commit is contained in:
2026-09-22 15:10:31 +02:00
parent 3e49be5fc1
commit d08e4f86fe
2 changed files with 52 additions and 2 deletions

View File

@@ -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. */

View File

@@ -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", () => {