diff --git a/app/(app)/employees/page.tsx b/app/(app)/employees/page.tsx index db5afa5..c7af32b 100644 --- a/app/(app)/employees/page.tsx +++ b/app/(app)/employees/page.tsx @@ -20,7 +20,7 @@ import { } from "@/lib/employee-sort"; import { derivedStatusFilter } from "@/lib/employee-status-filter"; import { fmtDate, fmtName, todayIso } from "@/lib/format"; -import { breadcrumbLabel, divisionOf, loadOrgMaps, subtreeOf, unitOf, type OrgEb } from "@/lib/org"; +import { breadcrumbLabel, loadOrgMaps, subtreeOf, unitOf, type OrgEb } from "@/lib/org"; import { loadPlacements } from "@/lib/placement"; import type { EmploymentStatus } from "@/lib/types"; @@ -335,7 +335,6 @@ export default async function EmployeesPage({ searchParams }: EmployeesPageProps {employees.map((e) => { const placement = placements.get(e.id); - const division = divisionOf(orgMaps, placement?.orgUnitId); const unit = unitOf(orgMaps, placement?.orgUnitId); const location = e.location_id ? orgMaps.locations.get(e.location_id) : undefined; return ( @@ -359,14 +358,17 @@ export default async function EmployeesPage({ searchParams }: EmployeesPageProps {/* tabular-nums keeps the numeric columns aligned down the page instead of jittering per row. */} {e.personnel_number} - -
{division?.name ?? "–"}
- {/* Die eigene Einheit, egal auf welcher Ebene sie hängt — - eine Bereichsleitung sitzt am Bereich, nicht an einem - Team, und stand vorher deshalb ohne Zuordnung da. */} -
- {unit && unit.id !== division?.id ? unit.name : "–"} -
+ {/* Nur die eigene Einheit, egal auf welcher Ebene sie hängt. + Darüber stand der Bereich; der Kunde wollte ihn weg, weil + seine Bereiche CEO, CFO, COO heissen und über jedem Namen + dasselbe wiederholten. Der ganze Weg von oben steht + weiterhin im `title` — eine Einheit wie „Shopleitung" + sagt allein nicht, welche gemeint ist. */} + + {unit?.name ?? "–"} {location?.name ?? "–"} {fmtDate(e.entry_date)} diff --git a/lib/employee-sort.ts b/lib/employee-sort.ts index 82a935a..d62b712 100644 --- a/lib/employee-sort.ts +++ b/lib/employee-sort.ts @@ -10,13 +10,14 @@ import type { Schema } from "@/lib/db/schema"; // // Bedient wird sie über die Spaltenköpfe. Damit das keine leere Zusage ist, // sortiert **jede** dieser Spalten über den gesamten Bestand, auch die drei, -// die nicht auf der Person stehen: Bereich/Team hängt an der Planstelle, -// Standort an einer Nachschlagetabelle, und der Status ist eine Aufzählung. +// die nicht auf der Person stehen: die Organisationseinheit hängt an der +// Planstelle, der Standort an einer Nachschlagetabelle, und der Status ist +// eine Aufzählung. export const SORTIERFELDER = [ { value: "name", label: "Mitarbeiter:in" }, { value: "persnr", label: "Pers.-Nr." }, - { value: "bereich", label: "Bereich/Team" }, + { value: "einheit", label: "Organisationseinheit" }, { value: "standort", label: "Standort" }, { value: "eintritt", label: "Eintritt" }, { value: "beschaeftigung", label: "Beschäftigung" }, @@ -72,39 +73,13 @@ const einheitAusdruck = sql`( where pa.employee_id = employees.id and pa.valid_to is null limit 1)`; -/** - * Der Bereich darüber — die nächste Einheit über der Person, die als Bereich - * geführt wird. - * - * 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`( - 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)`; +// Hier stand ein zweiter Ausdruck, der den Bereich über der Person suchte: +// die Liste sortierte nach Bereich und erst darin nach der Einheit. Der +// Bereich steht seit 22.09.2026 nicht mehr in der Spalte — der Kunde wollte +// dort nur die eigene Einheit sehen, weil die Bereiche bei ihm CEO, CFO, COO +// heissen und als Zeile über jedem Namen nichts beitragen. Sortiert wird +// seither nach dem, was auch dasteht. Eine Sortierung nach einem Wert, den +// die Spalte nicht zeigt, sieht von aussen aus wie gar keine Sortierung. const standortAusdruck = sql`(select name from locations where id = employees.location_id)`; @@ -169,8 +144,8 @@ export function sortiere( switch (feld) { case "persnr": return q.orderBy(ordne(sql.ref("personnel_number"))); - case "bereich": - return nachName(q.orderBy(ordne(bereichAusdruck)).orderBy(ordne(einheitAusdruck))); + case "einheit": + return nachName(q.orderBy(ordne(einheitAusdruck))); case "standort": return nachName(q.orderBy(ordne(standortAusdruck))); case "eintritt": diff --git a/tests/unit/employee-sort.test.ts b/tests/unit/employee-sort.test.ts index 783e08b..50f1a4e 100644 --- a/tests/unit/employee-sort.test.ts +++ b/tests/unit/employee-sort.test.ts @@ -89,7 +89,7 @@ describe("SORTIERFELDER", () => { expect(SORTIERFELDER.map((f) => f.label)).toEqual([ "Mitarbeiter:in", "Pers.-Nr.", - "Bereich/Team", + "Organisationseinheit", "Standort", "Eintritt", "Beschäftigung", @@ -191,25 +191,17 @@ describe("das erzeugte SQL", () => { } }); - it("holt Bereich und Team über die laufende Besetzung", () => { - const sql = ordnung("bereich", "asc"); + it("holt die Organisationseinheit über die laufende Besetzung", () => { + const sql = ordnung("einheit", "asc"); expect(sql).toContain("position_assignments"); expect(sql).toContain("pa.valid_to is null"); - // Der Bereich ist die Ebene unter der Gesellschaft, nicht die Einheit - // selbst — sonst stünde in der Spalte etwas anderes als sortiert wird. - expect(sql).toContain("unit_type = 'Bereich'"); + expect(sql).toContain("org_units"); - // 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"); + // Sortiert wird nach der Einheit selbst, nicht nach dem Bereich darüber: + // die Spalte zeigt seit 22.09.2026 nur noch die Einheit, und eine + // Sortierung nach einem Wert, der nirgends steht, sieht von aussen aus + // wie gar keine Sortierung. + expect(sql).not.toContain("unit_type"); }); it("sortiert den Standort nach seinem Namen, nicht nach seiner Kennung", () => {