Workshop-Anforderungen, erster Teil: was ohne Migration geht

Anforderung 1 — Freiwilliger vs unfreiwilliger Austritt. Die Liste der
Beendigungsarten zieht aus TerminatePanel.tsx nach lib/beendigung.ts um: der
Berichtemanager braucht sie ebenso, und zwei Listen liefen auseinander. Zwei
neue Arten (Beendigung in der Probezeit, je Seite). Auf wessen Betreiben
beendet wurde, wird aus der Art **abgeleitet** und nicht daneben gespeichert
— als zweites freies Feld liesse sich "Entlassung, freiwillig" erfassen. Das
Dropdown im Formular schraenkt die Auswahl darunter ein.

Drei Gruppen statt zwei: Befristungsablauf geschieht auf niemandes
Betreiben, ein Nichtantritt ist kein Austritt. Beide einer Seite
zuzuschlagen wuerde jede Fluktuationsquote verfaelschen.

Anforderung 2 — Namensfilter in "Anstehend", ab neun Eintraegen.

Anforderung 3 — die zwei Unterschriftenfelder im gedruckten Blatt sind weg;
"Firmenfahrzeug" steht in beiden Checklisten. has_dienstwagen sagt, ob eines
zusteht, nicht ob es uebergeben wurde.

Anforderung 4 — "+794 weitere" ist ein Knopf geworden; die Namen waren
vorher nur ueber den Export erreichbar. Stammdatenaenderung und
Gehaltsanpassung stehen nicht mehr zur Auswahl: die eine entsteht bei jeder
geaenderten Telefonnummer, die andere ist ein totes Ereignis, seit das
Gehalt in Loga liegt. Neu ist der Untertyp — Beendigungsart beim Austritt,
Art der Abwesenheit bei der Langzeitabwesenheit, im Bericht und im Export.

Anforderung 10 — zwei Kacheln. "Aktives Dienstverhaeltnis" ist nicht
dasselbe wie "Aktive Mitarbeiter:innen": dort steht, wer heute arbeitet,
hier, mit wem ein Vertrag laeuft. Sichtbar waren 806 und 10, addieren musste
man selbst.
This commit is contained in:
2026-09-15 22:35:47 +02:00
parent 1cbed1a8f5
commit 8d0c9b4b65
15 changed files with 426 additions and 48 deletions

View File

@@ -183,11 +183,12 @@ export function PrintChecklist({
))}
</div>
{/* ── Fuss: Platz für Unterschriften ─────────────────── */}
<div className="mt-6 flex gap-10 text-[8pt] text-ink-muted">
<div className="flex-1 border-t border-ink pt-1">Datum, Unterschrift Mitarbeiter:in</div>
<div className="flex-1 border-t border-ink pt-1">Datum, Unterschrift Personalabteilung</div>
</div>
{/* Hier standen zwei Unterschriftenfelder — „Datum, Unterschrift
Mitarbeiter:in" und „… Personalabteilung". Der Kunde hat sie
gestrichen (Anforderung 3 aus dem Workshop): das Blatt ist eine
Arbeitsliste für die Personalabteilung und kein Dokument, das
jemand gegenzeichnet. Wer eine Aufgabe abgehakt hat, steht ohnehin
an der Aufgabe selbst. */}
</div>
</div>
);

View File

@@ -7,23 +7,23 @@ import { Button } from "@/components/ui/Button";
import { SelectField, TextField, TextareaField } from "@/components/ui/Field";
import { SlideOver } from "@/components/ui/SlideOver";
import { useToast } from "@/components/ui/Toast";
import {
AUSTRITTSART_LABELS,
BEENDIGUNG_NO_SHOW,
BEENDIGUNGSART_WERTE,
austrittsartVon,
beendigungsartenZu,
type Austrittsart,
} from "@/lib/beendigung";
import type { Database } from "@/lib/types";
import { fmtDate, fmtName } from "@/lib/format";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
// „No Show" steht am Ende und getrennt: es ist kein Austritt im gewohnten
// Sinn, sondern der Fall, dass jemand nie angetreten ist.
const NO_SHOW = "No Show";
const EXIT_REASONS = [
"Einvernehmliche Auflösung",
"Kündigung AN",
"Kündigung AG",
"Befristungsablauf",
"Pensionierung",
"Entlassung",
NO_SHOW,
];
// Die Liste steht in lib/beendigung.ts, nicht mehr hier: der Berichtemanager
// braucht sie ebenso, und zwei Listen liefen auseinander.
const NO_SHOW = BEENDIGUNG_NO_SHOW;
type TerminatePanelProps = {
open: boolean;
onClose: () => void;
@@ -38,7 +38,12 @@ export function TerminatePanel({ open, onClose, employee, directReportCount }: T
// Wer noch gar nicht angefangen hat, tritt fast nie aus einem anderen Grund
// aus. Die Vorbelegung nimmt den wahrscheinlichen Fall vorweg, ohne die
// übrigen zu verstellen.
const [reason, setReason] = useState(employee.status === "Geplant" ? NO_SHOW : EXIT_REASONS[0]);
const [reason, setReason] = useState(employee.status === "Geplant" ? NO_SHOW : BEENDIGUNGSART_WERTE[0]);
// „Alle" statt einer Vorauswahl: die Einschränkung ist eine Hilfe beim
// Suchen, keine Pflichtangabe. Wer die gewünschte Art auswendig kennt,
// soll sie unmittelbar wählen können.
const [austrittsart, setAustrittsart] = useState<Austrittsart | "">("");
const waehlbar = beendigungsartenZu(austrittsart || null);
const [note, setNote] = useState("");
const [pending, setPending] = useState(false);
@@ -113,11 +118,36 @@ export function TerminatePanel({ open, onClose, employee, directReportCount }: T
{directReportCount} direkte Berichte werden automatisch der nächsthöheren Führungskraft zugeordnet.
</div>
)}
{/* Die Austrittsart wählt nicht mit, sie engt ein. Gespeichert wird
allein die Beendigungsart; auf wessen Betreiben beendet wurde,
leitet lib/beendigung.ts daraus ab. Als zweites freies Feld liesse
sich „Entlassung, freiwillig" erfassen, und zwei Auswertungen über
dieselbe Person kämen zu verschiedenen Ergebnissen. */}
<SelectField
label="Austrittsart"
value={austrittsart}
onChange={(v) => {
const neu = (v || "") as Austrittsart | "";
setAustrittsart(neu);
// Passt der bisher gewählte Grund nicht mehr zur Einschränkung,
// wandert die Auswahl auf den ersten passenden. Stehenzulassen,
// was in der Liste nicht mehr sichtbar ist, wäre ein Formular,
// das etwas anderes absendet, als es zeigt.
const erlaubt = beendigungsartenZu(neu || null);
if (!erlaubt.includes(reason)) setReason(erlaubt[0]);
}}
options={[
{ value: "", label: "Alle Beendigungsarten" },
...(Object.keys(AUSTRITTSART_LABELS) as Austrittsart[]).map((a) => ({ value: a, label: AUSTRITTSART_LABELS[a] })),
]}
hint="Schränkt die Auswahl darunter ein. Gespeichert wird die Beendigungsart."
/>
<SelectField
label="Beendigungsart"
value={reason}
onChange={setReason}
options={EXIT_REASONS.map((r) => ({ value: r, label: r === NO_SHOW ? "No Show (nicht angetreten)" : r }))}
options={waehlbar.map((r) => ({ value: r, label: r === NO_SHOW ? "No Show (nicht angetreten)" : r }))}
hint={austrittsartVon(reason) ? AUSTRITTSART_LABELS[austrittsartVon(reason)!] : undefined}
/>
<TextField
label={istNoShow ? "Wirksam am (Eintrittstag)" : "Austrittsdatum"}