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.
124 lines
5.0 KiB
TypeScript
124 lines
5.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
OFFBOARDING_GRUPPEN,
|
|
OFFBOARDING_PUNKTE,
|
|
fortschritt,
|
|
gehoertOffboarding,
|
|
istErledigt,
|
|
punktZu,
|
|
type AufgabenStand,
|
|
} from "@/lib/offboarding";
|
|
|
|
// Vom selben Blatt Papier wie die Onboarding-Liste, für den anderen Weg. Die
|
|
// Mechanik selbst (was als erledigt zählt, wie der Fortschritt entsteht) ist
|
|
// mit lib/onboarding.ts geteilt (lib/checklist.ts) und dort schon geprüft —
|
|
// hier geht es um das, was an dieser Liste besonders ist.
|
|
|
|
function stand(teil: Partial<AufgabenStand> & { item_key: string }): AufgabenStand {
|
|
return { erledigt: false, wert: null, kommentar: null, updated_at: null, updated_by_name: null, ...teil };
|
|
}
|
|
|
|
describe("Das Verzeichnis", () => {
|
|
it("hat eindeutige Schlüssel — sie stehen in den Daten", () => {
|
|
const keys = OFFBOARDING_PUNKTE.map((p) => p.key);
|
|
expect(new Set(keys).size).toBe(keys.length);
|
|
});
|
|
|
|
it("bildet jeden Punkt aus dem Blatt ab", () => {
|
|
// 11 vom Blatt, dazu „Firmenfahrzeug" aus dem Workshop.
|
|
expect(OFFBOARDING_PUNKTE).toHaveLength(12);
|
|
for (const key of [
|
|
"firmenfahrzeug",
|
|
"br_verstaendigung",
|
|
"kuendigungsschreiben",
|
|
"bestaetigung_kdn_bei",
|
|
"lgv_austritt",
|
|
"gkk_abmeldung",
|
|
"if6020_austritt",
|
|
"endabrechnung",
|
|
"offboarding_gestartet",
|
|
]) {
|
|
expect(punktZu(key), key).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it("führt die beiden Vermerke als Text, nicht als Haken", () => {
|
|
// Ein Resturlaubssaldo abgehakt wäre wertlos — die Zahl selbst ist der Zweck.
|
|
expect(punktZu("resturlaub")?.art).toBe("text");
|
|
expect(punktZu("ueberleitung_salden_betrag")?.art).toBe("text");
|
|
});
|
|
|
|
it("unterscheidet die Aufgabe von ihrem Betrag", () => {
|
|
// Das Blatt nennt „Überleitung Salden für Auszahlung" zweimal: einmal als
|
|
// abzuhakende Aufgabe, einmal als Feld für den tatsächlichen Betrag. Zwei
|
|
// verschiedene Fragen, zwei verschiedene Punkte.
|
|
expect(punktZu("ueberleitung_salden")?.art).toBe("haken");
|
|
expect(punktZu("ueberleitung_salden_betrag")?.art).toBe("text");
|
|
expect(punktZu("ueberleitung_salden")?.key).not.toBe(punktZu("ueberleitung_salden_betrag")?.key);
|
|
});
|
|
|
|
it("führt keinen Ja/Nein-Punkt — anders als beim Onboarding", () => {
|
|
// Auf diesem Blatt gibt es keine Stelle mit zwei Kästchen nebeneinander.
|
|
expect(OFFBOARDING_PUNKTE.some((p) => p.art === "janein")).toBe(false);
|
|
});
|
|
|
|
it("gruppiert vollständig — kein Punkt fällt zwischen die Gruppen", () => {
|
|
const ausGruppen = OFFBOARDING_GRUPPEN.flatMap((g) => g.punkte.map((p) => p.key));
|
|
expect(new Set(ausGruppen)).toEqual(new Set(OFFBOARDING_PUNKTE.map((p) => p.key)));
|
|
});
|
|
});
|
|
|
|
describe("fortschritt", () => {
|
|
it("ist auf einer leeren Liste vollständig offen", () => {
|
|
const f = fortschritt([]);
|
|
expect(f.erledigt).toBe(0);
|
|
expect(f.gesamt).toBe(OFFBOARDING_PUNKTE.length);
|
|
});
|
|
|
|
it("zählt Haken und Text zusammen", () => {
|
|
const f = fortschritt([
|
|
stand({ item_key: "endabrechnung", erledigt: true }),
|
|
stand({ item_key: "resturlaub", wert: "12 Tage" }),
|
|
]);
|
|
expect(f.erledigt).toBe(2);
|
|
expect(f.offen).not.toContain("Endabrechnung");
|
|
});
|
|
});
|
|
|
|
describe("istErledigt", () => {
|
|
it("verlangt bei einem Haken die ausdrückliche Markierung", () => {
|
|
const punkt = punktZu("gkk_abmeldung")!;
|
|
expect(istErledigt(punkt, stand({ item_key: punkt.key, erledigt: true }))).toBe(true);
|
|
expect(istErledigt(punkt, stand({ item_key: punkt.key }))).toBe(false);
|
|
expect(istErledigt(punkt, undefined)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("gehoertOffboarding", () => {
|
|
// Geprüft wird exit_date, nicht status: eine künftige Beendigung setzt
|
|
// exit_date sofort, aber status erst am Stichtag selbst. Bis dahin steht
|
|
// die Person weiterhin als „Aktiv" — mit status als Bedingung bliebe der
|
|
// Reiter bis zum letzten Arbeitstag unsichtbar, genau dann, wenn er
|
|
// gebraucht wird.
|
|
it("bejaht bei einem bereits wirksamen Austritt", () => {
|
|
expect(gehoertOffboarding({ exit_date: "2026-08-01", exit_reason: "Kündigung AN" })).toBe(true);
|
|
});
|
|
|
|
it("bejaht schon bei einem erst künftig wirksamen Austritt", () => {
|
|
expect(gehoertOffboarding({ exit_date: "2099-01-01", exit_reason: "Kündigung AN" })).toBe(true);
|
|
});
|
|
|
|
it("verneint bei No Show, obwohl exit_date ebenso gesetzt ist", () => {
|
|
// Nie einen Tag gearbeitet: kein IT-Zugang, keine GKK-Anmeldung, kein
|
|
// Dienstzettel. Der Reiter wäre ein Etikett ohne Inhalt.
|
|
expect(gehoertOffboarding({ exit_date: "2026-08-01", exit_reason: "No Show" })).toBe(false);
|
|
});
|
|
|
|
it("verneint ohne Austritt — auch nach einer Wiedereinstellung, die exit_date zurücksetzt", () => {
|
|
// Beides führt auf dieselbe Datenlage: wer nie ausgeschieden ist, und wer
|
|
// wiedereingestellt wurde (rehire_employee setzt exit_date und
|
|
// exit_reason ausdrücklich auf null), sehen für diese Regel gleich aus.
|
|
expect(gehoertOffboarding({ exit_date: null, exit_reason: null })).toBe(false);
|
|
});
|
|
});
|