From a3fac47f473dd125e52c56067169deef9cfc3d4d Mon Sep 17 00:00:00 2001 From: Maximilian Stubhan Date: Fri, 14 Aug 2026 07:06:58 +0200 Subject: [PATCH] Give each characteristic its own line, and name the car MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contract sheet had one field, "Merkmale", holding whatever applied, comma-separated — and a dash when nothing did. Two problems in one row. A dash cannot distinguish "has no company car" from "nobody ever answered the question", and the entry read "Dienstwagen" without saying which kind, which is the thing worth knowing since electric vehicles are tracked separately. Betriebsrat, Dienstwagen, laterale Führung and C-Level are now four lines like every other line on the sheet, each with Ja or Nein. The company car shows its drivetrain instead: E-KFZ or Verbrenner. That label existed in three places — the dropdown, the hire summary and now here. It lives in lib/dienstwagen.ts, so the same car cannot end up named differently depending on which screen you are looking at. Co-Authored-By: Claude Opus 5 --- components/employees/tabs/VertragTab.tsx | 19 +++-- components/hire/StepSummary.tsx | 3 +- lib/dienstwagen.ts | 17 ++++ tests/components/VertragTab.test.tsx | 104 +++++++++++++++++++++++ 4 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 lib/dienstwagen.ts create mode 100644 tests/components/VertragTab.test.tsx diff --git a/components/employees/tabs/VertragTab.tsx b/components/employees/tabs/VertragTab.tsx index cce7547..84ce60e 100644 --- a/components/employees/tabs/VertragTab.tsx +++ b/components/employees/tabs/VertragTab.tsx @@ -1,3 +1,4 @@ +import { dienstwagenLabel } from "@/lib/dienstwagen"; import { fmtDate } from "@/lib/format"; import type { Database } from "@/lib/supabase/types"; @@ -13,12 +14,11 @@ const PAYGRADE_LABELS: Record = { }; export function VertragTab({ employee }: { employee: EmployeeRow }) { - const flags = [ - employee.is_betriebsrat && "Betriebsrat", - employee.has_dienstwagen && "Dienstwagen", - employee.is_laterale_fuehrung && "Laterale Führung", - employee.is_c_level && "C-Level", - ].filter(Boolean); + // Früher stand hier eine einzige Zeile „Merkmale" mit allem, was zutraf, + // durch Kommas getrennt — und ein Gedankenstrich, wenn nichts zutraf. Damit + // liess sich nicht ablesen, ob jemand *keinen* Dienstwagen hat oder ob + // niemand die Frage je beantwortet hat. Jedes Merkmal steht jetzt für sich, + // mit Ja oder Nein, wie jede andere Zeile auf diesem Blatt auch. const rows: [string, string][] = [ ["Eintrittsdatum", fmtDate(employee.entry_date)], ["Vertragsart", employee.contract_type === "befristet" ? `befristet bis ${fmtDate(employee.contract_end_date)}` : "unbefristet"], @@ -29,7 +29,12 @@ export function VertragTab({ employee }: { employee: EmployeeRow }) { ["Paygrade", PAYGRADE_LABELS[employee.paygrade] ?? employee.paygrade], ["Angestellte:r / Arbeiter:in", employee.worker_type ?? "–"], ["Arbeitstage", employee.work_days?.join(", ") || "–"], - ["Merkmale", flags.length > 0 ? flags.join(", ") : "–"], + // Beim Dienstwagen steht die Antriebsart statt eines blossen „Ja" — das + // war die Frage dahinter, seit E-Fahrzeuge getrennt zu führen sind. + ["Dienstwagen", employee.has_dienstwagen ? dienstwagenLabel(employee.dienstwagen_art) : "Nein"], + ["Betriebsrat", employee.is_betriebsrat ? "Ja" : "Nein"], + ["Laterale Führung", employee.is_laterale_fuehrung ? "Ja" : "Nein"], + ["C-Level", employee.is_c_level ? "Ja" : "Nein"], ]; if (employee.exit_date) rows.push(["Austrittsdatum", fmtDate(employee.exit_date)]); diff --git a/components/hire/StepSummary.tsx b/components/hire/StepSummary.tsx index 5d816dd..5d086f3 100644 --- a/components/hire/StepSummary.tsx +++ b/components/hire/StepSummary.tsx @@ -1,3 +1,4 @@ +import { dienstwagenLabel } from "@/lib/dienstwagen"; import { fmtDate, fmtFullName } from "@/lib/format"; import type { OpenPositionResolved } from "@/lib/positions"; import type { HireDraftData } from "./types"; @@ -21,7 +22,7 @@ export function StepSummary({ draft, selectedPosition, locations }: StepSummaryP const location = locations.find((l) => l.id === draft.locationId); const flags = [ draft.isBetriebsrat && "Betriebsrat", - draft.hasDienstwagen && `Dienstwagen (${draft.dienstwagenArt === "Elektro" ? "E-KFZ" : "Verbrenner"})`, + draft.hasDienstwagen && `Dienstwagen (${dienstwagenLabel(draft.dienstwagenArt)})`, draft.isLateraleFuehrung && "Laterale Führung", draft.isCLevel && "C-Level", ].filter(Boolean); diff --git a/lib/dienstwagen.ts b/lib/dienstwagen.ts new file mode 100644 index 0000000..cb12a2b --- /dev/null +++ b/lib/dienstwagen.ts @@ -0,0 +1,17 @@ +import type { DienstwagenArt } from "./supabase/types"; + +// Wie ein Dienstwagen benannt wird. +// +// Gespeichert steht „Elektro"; gesprochen wird von einem E-KFZ, und danach +// wird auch gefragt. Die Beschriftung stand an drei Stellen einzeln — im +// Auswahlfeld, in der Zusammenfassung des Einstellungsassistenten und auf dem +// Vertragsblatt. Drei Kopien einer Beschriftung laufen auseinander, und dann +// heisst dasselbe Auto je nach Bildschirm anders. + +export function dienstwagenLabel(art: DienstwagenArt | null | undefined): string { + if (art === "Elektro") return "E-KFZ"; + if (art === "Verbrenner") return "Verbrenner"; + // Der CHECK erlaubt es nicht, aber gelesen wird auch, was jemand über den + // Import hineingelegt hat. + return art ?? "–"; +} diff --git a/tests/components/VertragTab.test.tsx b/tests/components/VertragTab.test.tsx new file mode 100644 index 0000000..3532fc3 --- /dev/null +++ b/tests/components/VertragTab.test.tsx @@ -0,0 +1,104 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { VertragTab } from "@/components/employees/tabs/VertragTab"; +import type { Database } from "@/lib/supabase/types"; + +type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"]; + +// Auf dem Vertragsblatt stand ein Feld „Merkmale" mit allem Zutreffenden, +// durch Kommas getrennt — und einem Gedankenstrich, wenn nichts zutraf. +// Daraus liess sich nicht ablesen, ob jemand keinen Dienstwagen hat oder ob +// die Frage nie beantwortet wurde. Und beim Dienstwagen fehlte, worum es +// eigentlich geht: ob Verbrenner oder E-KFZ. + +function mitarbeiter(teil: Partial = {}): EmployeeRow { + return { + id: "e1", + personnel_number: 3038, + first_name: "Hannah", + last_name: "Winkler", + gender: "w", + birth_date: "1990-04-01", + sv_nummer: null, + nationality: "Österreich", + address: null, + address_country: null, + postal_code: null, + city: null, + email: null, + phone: null, + job_title: "IT-Security-Spezialist:in", + location_id: "l1", + employment_type: "Vollzeit", + weekly_hours: 38.5, + monthly_salary_gross: null, + contract_type: "unbefristet", + contract_end_date: null, + paygrade: "C", + source: "Extern", + status: "Aktiv", + entry_date: "2017-07-15", + exit_date: null, + exit_reason: null, + karenz_start_date: null, + karenz_return_date: null, + absence_type: null, + avatar_color: null, + worker_type: "Angestellte:r", + collective_agreement: "Handel", + work_days: ["Mo", "Di", "Mi", "Do", "Fr"], + is_betriebsrat: false, + has_dienstwagen: false, + dienstwagen_art: null, + is_laterale_fuehrung: false, + is_c_level: false, + title_prefix: [], + title_suffix: [], + emergency_contact_name: null, + emergency_contact_phone: null, + emergency_contact_relation: null, + created_at: "2017-07-15T00:00:00.000Z", + updated_at: "2017-07-15T00:00:00.000Z", + ...teil, + } as EmployeeRow; +} + +/** Der Wert, der unter einer Beschriftung steht. */ +function wertZu(label: string): string { + return screen.getByText(label).parentElement!.querySelector("dd")!.textContent ?? ""; +} + +describe("VertragTab — Merkmale", () => { + it("führt jedes Merkmal als eigene Zeile, nicht als Aufzählung", () => { + render(); + for (const label of ["Dienstwagen", "Betriebsrat", "Laterale Führung", "C-Level"]) { + expect(screen.getByText(label)).toBeInTheDocument(); + } + expect(screen.queryByText("Merkmale")).not.toBeInTheDocument(); + }); + + it("sagt Nein, statt das Feld leer zu lassen", () => { + // „–" liesse offen, ob niemand die Frage beantwortet hat. + render(); + expect(wertZu("Dienstwagen")).toBe("Nein"); + expect(wertZu("Betriebsrat")).toBe("Nein"); + expect(wertZu("C-Level")).toBe("Nein"); + }); + + it("nennt beim Dienstwagen die Antriebsart statt eines blossen Ja", () => { + render(); + expect(wertZu("Dienstwagen")).toBe("E-KFZ"); + }); + + it("unterscheidet den Verbrenner davon", () => { + render(); + expect(wertZu("Dienstwagen")).toBe("Verbrenner"); + }); + + it("zeigt gesetzte Merkmale als Ja", () => { + render(); + expect(wertZu("Betriebsrat")).toBe("Ja"); + expect(wertZu("C-Level")).toBe("Ja"); + expect(wertZu("Laterale Führung")).toBe("Nein"); + }); +});