In der Mitarbeiterliste nur die eigene Einheit zeigen, nicht den Bereich
Die Spalte fuehrte zwei Zeilen: den Bereich und darunter die Einheit der
Person. Beim Kunden heissen die Bereiche CEO, CFO, CSMO, CPO, COO, CHRO --
Rollenbezeichnungen, die ueber jedem Namen dasselbe wiederholten. Auf
seinen Wunsch bleibt nur die Einheit stehen; der ganze Weg von oben steht
weiterhin im title, denn eine Einheit wie "Shopleitung" sagt allein nicht,
welche gemeint ist.
Sortiert wird jetzt ebenfalls nach der Einheit. Bliebe der Bereich das
erste Kriterium, ordnete die Spalte nach einem Wert, den sie nicht mehr
anzeigt -- von aussen sieht das aus wie gar keine Sortierung. Der
rekursive Ausdruck aus 3e49be5 entfaellt damit; divisionOf bleibt in
Gebrauch, die Uebersicht gruppiert weiter nach Bereich.
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
|||||||
} from "@/lib/employee-sort";
|
} from "@/lib/employee-sort";
|
||||||
import { derivedStatusFilter } from "@/lib/employee-status-filter";
|
import { derivedStatusFilter } from "@/lib/employee-status-filter";
|
||||||
import { fmtDate, fmtName, todayIso } from "@/lib/format";
|
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 { loadPlacements } from "@/lib/placement";
|
||||||
import type { EmploymentStatus } from "@/lib/types";
|
import type { EmploymentStatus } from "@/lib/types";
|
||||||
|
|
||||||
@@ -335,7 +335,6 @@ export default async function EmployeesPage({ searchParams }: EmployeesPageProps
|
|||||||
<tbody>
|
<tbody>
|
||||||
{employees.map((e) => {
|
{employees.map((e) => {
|
||||||
const placement = placements.get(e.id);
|
const placement = placements.get(e.id);
|
||||||
const division = divisionOf(orgMaps, placement?.orgUnitId);
|
|
||||||
const unit = unitOf(orgMaps, placement?.orgUnitId);
|
const unit = unitOf(orgMaps, placement?.orgUnitId);
|
||||||
const location = e.location_id ? orgMaps.locations.get(e.location_id) : undefined;
|
const location = e.location_id ? orgMaps.locations.get(e.location_id) : undefined;
|
||||||
return (
|
return (
|
||||||
@@ -359,14 +358,17 @@ export default async function EmployeesPage({ searchParams }: EmployeesPageProps
|
|||||||
{/* tabular-nums keeps the numeric columns aligned down the
|
{/* tabular-nums keeps the numeric columns aligned down the
|
||||||
page instead of jittering per row. */}
|
page instead of jittering per row. */}
|
||||||
<td className="px-4 py-2.5 tabular-nums text-ink-body">{e.personnel_number}</td>
|
<td className="px-4 py-2.5 tabular-nums text-ink-body">{e.personnel_number}</td>
|
||||||
<td className="px-4 py-2.5 text-ink-body">
|
{/* Nur die eigene Einheit, egal auf welcher Ebene sie hängt.
|
||||||
<div>{division?.name ?? "–"}</div>
|
Darüber stand der Bereich; der Kunde wollte ihn weg, weil
|
||||||
{/* Die eigene Einheit, egal auf welcher Ebene sie hängt —
|
seine Bereiche CEO, CFO, COO heissen und über jedem Namen
|
||||||
eine Bereichsleitung sitzt am Bereich, nicht an einem
|
dasselbe wiederholten. Der ganze Weg von oben steht
|
||||||
Team, und stand vorher deshalb ohne Zuordnung da. */}
|
weiterhin im `title` — eine Einheit wie „Shopleitung"
|
||||||
<div className="text-xs text-ink-muted" title={breadcrumbLabel(orgMaps, placement?.orgUnitId)}>
|
sagt allein nicht, welche gemeint ist. */}
|
||||||
{unit && unit.id !== division?.id ? unit.name : "–"}
|
<td
|
||||||
</div>
|
className="px-4 py-2.5 text-ink-body"
|
||||||
|
title={breadcrumbLabel(orgMaps, placement?.orgUnitId)}
|
||||||
|
>
|
||||||
|
{unit?.name ?? "–"}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2.5 text-ink-body">{location?.name ?? "–"}</td>
|
<td className="px-4 py-2.5 text-ink-body">{location?.name ?? "–"}</td>
|
||||||
<td className="px-4 py-2.5 tabular-nums text-ink-body">{fmtDate(e.entry_date)}</td>
|
<td className="px-4 py-2.5 tabular-nums text-ink-body">{fmtDate(e.entry_date)}</td>
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import type { Schema } from "@/lib/db/schema";
|
|||||||
//
|
//
|
||||||
// Bedient wird sie über die Spaltenköpfe. Damit das keine leere Zusage ist,
|
// Bedient wird sie über die Spaltenköpfe. Damit das keine leere Zusage ist,
|
||||||
// sortiert **jede** dieser Spalten über den gesamten Bestand, auch die drei,
|
// sortiert **jede** dieser Spalten über den gesamten Bestand, auch die drei,
|
||||||
// die nicht auf der Person stehen: Bereich/Team hängt an der Planstelle,
|
// die nicht auf der Person stehen: die Organisationseinheit hängt an der
|
||||||
// Standort an einer Nachschlagetabelle, und der Status ist eine Aufzählung.
|
// Planstelle, der Standort an einer Nachschlagetabelle, und der Status ist
|
||||||
|
// eine Aufzählung.
|
||||||
|
|
||||||
export const SORTIERFELDER = [
|
export const SORTIERFELDER = [
|
||||||
{ value: "name", label: "Mitarbeiter:in" },
|
{ value: "name", label: "Mitarbeiter:in" },
|
||||||
{ value: "persnr", label: "Pers.-Nr." },
|
{ value: "persnr", label: "Pers.-Nr." },
|
||||||
{ value: "bereich", label: "Bereich/Team" },
|
{ value: "einheit", label: "Organisationseinheit" },
|
||||||
{ value: "standort", label: "Standort" },
|
{ value: "standort", label: "Standort" },
|
||||||
{ value: "eintritt", label: "Eintritt" },
|
{ value: "eintritt", label: "Eintritt" },
|
||||||
{ value: "beschaeftigung", label: "Beschäftigung" },
|
{ value: "beschaeftigung", label: "Beschäftigung" },
|
||||||
@@ -72,39 +73,13 @@ const einheitAusdruck = sql<string>`(
|
|||||||
where pa.employee_id = employees.id and pa.valid_to is null
|
where pa.employee_id = employees.id and pa.valid_to is null
|
||||||
limit 1)`;
|
limit 1)`;
|
||||||
|
|
||||||
/**
|
// Hier stand ein zweiter Ausdruck, der den Bereich über der Person suchte:
|
||||||
* Der Bereich darüber — die nächste Einheit über der Person, die als Bereich
|
// die Liste sortierte nach Bereich und erst darin nach der Einheit. Der
|
||||||
* geführt wird.
|
// 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
|
||||||
* Aufgestiegen wird rekursiv, nicht mit einer festen Zahl von Sprüngen. Hier
|
// heissen und als Zeile über jedem Namen nichts beitragen. Sortiert wird
|
||||||
* standen zwei `left join`, weil der Baum als höchstens vierstufig galt
|
// seither nach dem, was auch dasteht. Eine Sortierung nach einem Wert, den
|
||||||
* (Gesellschaft, Bereich, Abteilung, Team) und zwei Sprünge damit reichten.
|
// die Spalte nicht zeigt, sieht von aussen aus wie gar keine Sortierung.
|
||||||
* `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<string>`(
|
|
||||||
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<string>`(select name from locations where id = employees.location_id)`;
|
const standortAusdruck = sql<string>`(select name from locations where id = employees.location_id)`;
|
||||||
|
|
||||||
@@ -169,8 +144,8 @@ export function sortiere<O>(
|
|||||||
switch (feld) {
|
switch (feld) {
|
||||||
case "persnr":
|
case "persnr":
|
||||||
return q.orderBy(ordne(sql.ref("personnel_number")));
|
return q.orderBy(ordne(sql.ref("personnel_number")));
|
||||||
case "bereich":
|
case "einheit":
|
||||||
return nachName(q.orderBy(ordne(bereichAusdruck)).orderBy(ordne(einheitAusdruck)));
|
return nachName(q.orderBy(ordne(einheitAusdruck)));
|
||||||
case "standort":
|
case "standort":
|
||||||
return nachName(q.orderBy(ordne(standortAusdruck)));
|
return nachName(q.orderBy(ordne(standortAusdruck)));
|
||||||
case "eintritt":
|
case "eintritt":
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ describe("SORTIERFELDER", () => {
|
|||||||
expect(SORTIERFELDER.map((f) => f.label)).toEqual([
|
expect(SORTIERFELDER.map((f) => f.label)).toEqual([
|
||||||
"Mitarbeiter:in",
|
"Mitarbeiter:in",
|
||||||
"Pers.-Nr.",
|
"Pers.-Nr.",
|
||||||
"Bereich/Team",
|
"Organisationseinheit",
|
||||||
"Standort",
|
"Standort",
|
||||||
"Eintritt",
|
"Eintritt",
|
||||||
"Beschäftigung",
|
"Beschäftigung",
|
||||||
@@ -191,25 +191,17 @@ describe("das erzeugte SQL", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("holt Bereich und Team über die laufende Besetzung", () => {
|
it("holt die Organisationseinheit über die laufende Besetzung", () => {
|
||||||
const sql = ordnung("bereich", "asc");
|
const sql = ordnung("einheit", "asc");
|
||||||
expect(sql).toContain("position_assignments");
|
expect(sql).toContain("position_assignments");
|
||||||
expect(sql).toContain("pa.valid_to is null");
|
expect(sql).toContain("pa.valid_to is null");
|
||||||
// Der Bereich ist die Ebene unter der Gesellschaft, nicht die Einheit
|
expect(sql).toContain("org_units");
|
||||||
// selbst — sonst stünde in der Spalte etwas anderes als sortiert wird.
|
|
||||||
expect(sql).toContain("unit_type = 'Bereich'");
|
|
||||||
|
|
||||||
// Und über beliebig viele Ebenen, nicht über eine feste Zahl von
|
// Sortiert wird nach der Einheit selbst, nicht nach dem Bereich darüber:
|
||||||
// Sprüngen. Vorher standen hier zwei `left join`; bei sieben Ebenen lag
|
// die Spalte zeigt seit 22.09.2026 nur noch die Einheit, und eine
|
||||||
// der Bereich für 317 von 784 Personen ausserhalb ihrer Reichweite, und
|
// Sortierung nach einem Wert, der nirgends steht, sieht von aussen aus
|
||||||
// diese 317 rutschten ohne Bereich ans Listenende.
|
// wie gar keine Sortierung.
|
||||||
expect(sql).toContain("with recursive");
|
expect(sql).not.toContain("unit_type");
|
||||||
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", () => {
|
it("sortiert den Standort nach seinem Namen, nicht nach seiner Kennung", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user