diff --git a/app/(app)/page.tsx b/app/(app)/page.tsx index 4484e7c..fa204e7 100644 --- a/app/(app)/page.tsx +++ b/app/(app)/page.tsx @@ -198,8 +198,13 @@ export default async function DashboardPage({ // wer heute arbeitet, hier, mit wem ein Vertrag läuft — // Langzeitabwesende eingeschlossen. Sichtbar waren 805 und 10, // addieren musste man selbst. + // Die Namen der beiden stehen so fest: Max am 30.09. — „Es gibt zwei + // Headcounts: Headcount Aktiv (nur Aktive) und Headcount Aktives + // Dienstverhältnis (Aktiv + Langzeitabwesenheit)". Vorher hiess nur die + // rechte Kachel „(HC)", und im Bericht stand daneben „Headcount" für + // eine dritte Zahl — dasselbe Wort für Verschiedenes (N.02). { - label: "Aktives Dienstverhältnis", + label: "Headcount Aktives Dienstverhältnis", value: activeCount + karenzCount, tone: "default", // Mit Komma, nicht zweimal `status=`: die Liste liest den Parameter @@ -207,9 +212,7 @@ export default async function DashboardPage({ // Next daraus ein Array, und die Seite scheiterte an `.split(",")`. href: "/employees?status=Aktiv,Karenz", }, - // „(HC)" für Headcount — der Unterschied zur Kachel links ist genau - // der, den die Abkürzung benennt: Köpfe, die heute arbeiten. - { label: "Aktive Mitarbeiter:innen (HC)", value: activeCount, tone: "default", href: "/employees?status=Aktiv" }, + { label: "Headcount Aktiv", value: activeCount, tone: "default", href: "/employees?status=Aktiv" }, { label: "Langzeitabwesende", value: karenzCount, tone: "warning", href: "/employees?status=Karenz" }, // Ohne Zeitgrenze — anders als die Karte „Anstehend" darunter, die nur // den eingestellten Vorschauzeitraum zeigt. Ein Eintritt in vier diff --git a/components/reports/ReportsPageClient.tsx b/components/reports/ReportsPageClient.tsx index 6816692..9380a23 100644 --- a/components/reports/ReportsPageClient.tsx +++ b/components/reports/ReportsPageClient.tsx @@ -29,6 +29,7 @@ import { EVENT_TYPE_LABELS, GROUP_LABELS, MEASURE_LABELS, + messgroesseLabel, parseStatuses, REPORT_PRESETS, sortKeysForDimension, @@ -413,7 +414,7 @@ export function ReportsPageClient(props: ReportsPageClientProps) { const heading = mode === "snapshot" - ? `${MEASURE_LABELS[props.measure]} nach ${GROUP_LABELS[props.group]}` + ? `${messgroesseLabel(props.measure, selectedStatuses)} nach ${GROUP_LABELS[props.group]}` : `${props.eventType ? EVENT_TYPE_LABELS[props.eventType as HistoryEventType] : "Ereignisse"} nach ${EVENT_GROUP_LABELS[props.eventGroup]}`; const totalDisplay = mode === "snapshot" ? formatValue(props.measure, total) : String(Math.round(total)); diff --git a/lib/reports.ts b/lib/reports.ts index 1cda040..6dbafcd 100644 --- a/lib/reports.ts +++ b/lib/reports.ts @@ -1,4 +1,4 @@ -import { ABSENCE_TYPES } from "./absence"; +import { ABSENCE_TYPES, statusLabel } from "./absence"; import { BEENDIGUNGSART_WERTE } from "./beendigung"; import { fmtName, todayIso, yearsBetweenIso } from "./format"; import { HAY_GRADE_WERTE } from "./hay-grade"; @@ -32,6 +32,33 @@ export type GroupDimension = | "teilzeit_art" | "weekday"; +/** + * Die Kennzahl mit dem, was sie tatsächlich zählt. + * + * „Headcount" stand auf der Übersicht für die Aktiven und im Bericht für eine + * andere Zahl — dasselbe Wort für Verschiedenes, gemeldet als N.02. Der Kunde + * führt zwei Grössen mit festen Namen (Max, 30.09.): **Headcount Aktiv** für + * die Aktiven und **Headcount Aktives Dienstverhältnis** für Aktive plus + * Langzeitabwesende. + * + * Die Kennzahl im Bericht zählt aber, was gerade ausgewählt ist — auch + * Geplante oder Ausgetretene. Ein fester Name wäre dort also schlicht falsch. + * Deshalb: trifft die Auswahl eine der beiden Grössen, steht ihr Name da; + * sonst steht dabei, welche Status gezählt wurden. + */ +export function messgroesseLabel(measure: Measure, statuses: readonly string[]): string { + const basis = MEASURE_LABELS[measure]; + if (measure !== "headcount") return basis; + + const gewaehlt = new Set(statuses); + if (gewaehlt.size === 1 && gewaehlt.has("Aktiv")) return "Headcount Aktiv"; + if (gewaehlt.size === 2 && gewaehlt.has("Aktiv") && gewaehlt.has("Karenz")) { + return "Headcount Aktives Dienstverhältnis"; + } + if (gewaehlt.size === 0) return basis; + return `${basis} (${statuses.map((s) => statusLabel(s as EmploymentStatus)).join(", ")})`; +} + export const MEASURE_LABELS: Record = { headcount: "Headcount", fte: "FTE", diff --git a/tests/unit/reports.test.ts b/tests/unit/reports.test.ts index 746e077..bc77e92 100644 --- a/tests/unit/reports.test.ts +++ b/tests/unit/reports.test.ts @@ -7,6 +7,7 @@ import { groupKeysFor, measureValue, MEASURE_LABELS, + messgroesseLabel, type OrgLookups, type ReportEmployee, type ReportEvent, @@ -179,6 +180,33 @@ describe("measureValue", () => { }); }); +describe("messgroesseLabel", () => { + // „Headcount" stand auf der Übersicht für die Aktiven und im Bericht für + // eine andere Zahl (N.02). Der Kunde führt zwei Grössen mit festen Namen; + // die Kennzahl im Bericht zählt aber, was ausgewählt ist. + it("nennt die beiden Grössen des Kunden beim Namen", () => { + expect(messgroesseLabel("headcount", ["Aktiv"])).toBe("Headcount Aktiv"); + expect(messgroesseLabel("headcount", ["Aktiv", "Karenz"])).toBe("Headcount Aktives Dienstverhältnis"); + expect(messgroesseLabel("headcount", ["Karenz", "Aktiv"])).toBe("Headcount Aktives Dienstverhältnis"); + }); + + it("schreibt sonst dazu, was gezählt wurde", () => { + // Mit Anzeigenamen: „Karenz" heisst in der Oberfläche überall + // „Langzeitabwesenheit". + expect(messgroesseLabel("headcount", ["Aktiv", "Karenz", "Geplant"])).toBe( + "Headcount (Aktiv, Langzeitabwesenheit, Geplant)" + ); + expect(messgroesseLabel("headcount", ["Ausgetreten"])).toBe("Headcount (Ausgetreten)"); + }); + + it("lässt die übrigen Kennzahlen unberührt", () => { + // Nur beim Headcount hing an der Auswahl ein zweiter Begriff. „Ø Alter" + // der Aktiven heisst weiter „Ø Alter". + expect(messgroesseLabel("fte", ["Aktiv"])).toBe("FTE"); + expect(messgroesseLabel("avg_age", ["Aktiv", "Karenz"])).toBe("Ø Alter"); + }); +}); + describe("aggregateReport", () => { it("groups rows, sorts groups descending by value, and never includes a salary field", () => { const employees = [