Headcount heisst ueberall dasselbe
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m36s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m12s

"Headcount" stand auf der Uebersicht fuer die Aktiven und im Bericht fuer eine
andere Zahl -- 785 gegen 788, dasselbe Wort fuer Verschiedenes (N.02). Max hat
die Begriffe festgelegt: Headcount Aktiv fuer die Aktiven, Headcount Aktives
Dienstverhaeltnis fuer Aktive plus Langzeitabwesende.

Die beiden Kacheln auf der Uebersicht tragen jetzt genau diese Namen. Beide
Zahlen gab es dort schon, nur hiess die eine "Aktives Dienstverhaeltnis" und
die andere "Aktive Mitarbeiter:innen (HC)".

Im Bericht ginge ein fester Name nicht: die Kennzahl zaehlt, was gerade
ausgewaehlt ist, auch Geplante oder Ausgetretene. Trifft die Auswahl eine der
beiden Groessen, steht ihr Name in der Ueberschrift; sonst steht dabei, welche
Status gezaehlt wurden -- mit den Anzeigenamen, also "Langzeitabwesenheit" und
nicht "Karenz".
This commit is contained in:
2026-09-29 20:36:22 +02:00
parent 38f8c99968
commit 0039ce5069
4 changed files with 65 additions and 6 deletions

View File

@@ -198,8 +198,13 @@ export default async function DashboardPage({
// wer heute arbeitet, hier, mit wem ein Vertrag läuft — // wer heute arbeitet, hier, mit wem ein Vertrag läuft —
// Langzeitabwesende eingeschlossen. Sichtbar waren 805 und 10, // Langzeitabwesende eingeschlossen. Sichtbar waren 805 und 10,
// addieren musste man selbst. // 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, value: activeCount + karenzCount,
tone: "default", tone: "default",
// Mit Komma, nicht zweimal `status=`: die Liste liest den Parameter // 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(",")`. // Next daraus ein Array, und die Seite scheiterte an `.split(",")`.
href: "/employees?status=Aktiv,Karenz", href: "/employees?status=Aktiv,Karenz",
}, },
// „(HC)" für Headcount — der Unterschied zur Kachel links ist genau { label: "Headcount Aktiv", value: activeCount, tone: "default", href: "/employees?status=Aktiv" },
// 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: "Langzeitabwesende", value: karenzCount, tone: "warning", href: "/employees?status=Karenz" }, { label: "Langzeitabwesende", value: karenzCount, tone: "warning", href: "/employees?status=Karenz" },
// Ohne Zeitgrenze — anders als die Karte „Anstehend" darunter, die nur // Ohne Zeitgrenze — anders als die Karte „Anstehend" darunter, die nur
// den eingestellten Vorschauzeitraum zeigt. Ein Eintritt in vier // den eingestellten Vorschauzeitraum zeigt. Ein Eintritt in vier

View File

@@ -29,6 +29,7 @@ import {
EVENT_TYPE_LABELS, EVENT_TYPE_LABELS,
GROUP_LABELS, GROUP_LABELS,
MEASURE_LABELS, MEASURE_LABELS,
messgroesseLabel,
parseStatuses, parseStatuses,
REPORT_PRESETS, REPORT_PRESETS,
sortKeysForDimension, sortKeysForDimension,
@@ -413,7 +414,7 @@ export function ReportsPageClient(props: ReportsPageClientProps) {
const heading = const heading =
mode === "snapshot" 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]}`; : `${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)); const totalDisplay = mode === "snapshot" ? formatValue(props.measure, total) : String(Math.round(total));

View File

@@ -1,4 +1,4 @@
import { ABSENCE_TYPES } from "./absence"; import { ABSENCE_TYPES, statusLabel } from "./absence";
import { BEENDIGUNGSART_WERTE } from "./beendigung"; import { BEENDIGUNGSART_WERTE } from "./beendigung";
import { fmtName, todayIso, yearsBetweenIso } from "./format"; import { fmtName, todayIso, yearsBetweenIso } from "./format";
import { HAY_GRADE_WERTE } from "./hay-grade"; import { HAY_GRADE_WERTE } from "./hay-grade";
@@ -32,6 +32,33 @@ export type GroupDimension =
| "teilzeit_art" | "teilzeit_art"
| "weekday"; | "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<Measure, string> = { export const MEASURE_LABELS: Record<Measure, string> = {
headcount: "Headcount", headcount: "Headcount",
fte: "FTE", fte: "FTE",

View File

@@ -7,6 +7,7 @@ import {
groupKeysFor, groupKeysFor,
measureValue, measureValue,
MEASURE_LABELS, MEASURE_LABELS,
messgroesseLabel,
type OrgLookups, type OrgLookups,
type ReportEmployee, type ReportEmployee,
type ReportEvent, 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", () => { describe("aggregateReport", () => {
it("groups rows, sorts groups descending by value, and never includes a salary field", () => { it("groups rows, sorts groups descending by value, and never includes a salary field", () => {
const employees = [ const employees = [