Bildungsteilzeit, Elternteilzeit, Pflegeteilzeit and Wiedereingliederungsteilzeit were offered as kinds of long-term absence. Recorded that way, the person counted as absent: they dropped out of headcount, their reporting line fell to a stand-in, and reports stopped counting them — while they were in the building every week, just for fewer hours. A part-time arrangement is not an absence; it is a change of hours. They now sit where they belong. Wiedereingliederungs- and Elternteilzeit appear when recording a return from absence, as the reason someone comes back on reduced hours — both typically begin exactly when the absence ends. Bildungs- and Pflegeteilzeit appear under "Daten ändern" beside the hours, next to the ordinary contractual change. The reason is recorded with the change, not as a state on the person. A state would have to be maintained, and nobody goes back to note when a Bildungsteilzeit ended; a field that quietly goes stale is worse than none. In the history it stands next to the value it explains, and stays readable for good. The check constraint on absence_type is deliberately untouched. Three people carry the old values right now — two Pflegeteilzeit, one Wiedereingliederungsteilzeit. Forbidding them would make existing rows illegal. They are gone from the list of choices; the history stays readable. Those three are worth revisiting, but that is a data decision, not a code one. Rehearsed against real data: an hours change with a reason and one without, a reduced return with a reason and an unchanged one — checked by reading both new history rows rather than "the latest", since now() stands still inside a transaction and made an earlier probe report a false negative. I also overwrote tests/unit/absence.test.ts instead of extending it. The original cases are restored; the diff is 49 added lines and 3 changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
4.4 KiB
TypeScript
103 lines
4.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { ABSENCE_TYPES, absenceLabel, isAbsenceType, RUECKKEHR_GRUENDE, statusLabel, STUNDEN_GRUENDE } from "@/lib/absence";
|
|
|
|
// The stored employment status is still 'Karenz' — renaming the enum value
|
|
// would mean rewriting every stored function that spells it. The rename
|
|
// therefore lives entirely in this mapping, which makes it the one thing
|
|
// standing between the database value and what an HR user reads.
|
|
describe("statusLabel", () => {
|
|
it("renders the stored 'Karenz' value as Langzeitabwesenheit", () => {
|
|
expect(statusLabel("Karenz")).toBe("Langzeitabwesenheit");
|
|
});
|
|
|
|
it("leaves the other statuses alone", () => {
|
|
expect(statusLabel("Aktiv")).toBe("Aktiv");
|
|
expect(statusLabel("Geplant")).toBe("Geplant");
|
|
expect(statusLabel("Ausgetreten")).toBe("Ausgetreten");
|
|
});
|
|
});
|
|
|
|
describe("absenceLabel", () => {
|
|
it("shows the specific kind when one is recorded", () => {
|
|
expect(absenceLabel("Karenz", "Bildungskarenz")).toBe("Bildungskarenz");
|
|
expect(absenceLabel("Karenz", "Präsenzdienst")).toBe("Präsenzdienst");
|
|
});
|
|
|
|
it("falls back to the generic name for absences recorded before the field existed", () => {
|
|
expect(absenceLabel("Karenz", null)).toBe("Langzeitabwesenheit");
|
|
expect(absenceLabel("Karenz", "")).toBe("Langzeitabwesenheit");
|
|
});
|
|
|
|
it("ignores a type that is not on the list rather than displaying it", () => {
|
|
// The column has a check constraint, but the value arrives here as a
|
|
// plain string — echoing an unknown one would put unvalidated text in
|
|
// the status chip.
|
|
expect(absenceLabel("Karenz", "Ausgedachte Karenz")).toBe("Langzeitabwesenheit");
|
|
});
|
|
|
|
it("ignores the absence type entirely for any other status", () => {
|
|
expect(absenceLabel("Aktiv", "Sabbatical")).toBe("Aktiv");
|
|
expect(absenceLabel("Ausgetreten", "Papamonat")).toBe("Ausgetreten");
|
|
});
|
|
});
|
|
|
|
describe("ABSENCE_TYPES", () => {
|
|
it("holds the ten kinds, in the order they are offered", () => {
|
|
// Waren dreizehn. Die vier Teilzeiten sind dorthin gewandert, wo sie
|
|
// hingehören — siehe unten.
|
|
expect(ABSENCE_TYPES).toHaveLength(10);
|
|
expect(ABSENCE_TYPES[0]).toBe("Wochenhilfe (Mutterschutz)");
|
|
expect(ABSENCE_TYPES.at(-1)).toBe("Sabbatical");
|
|
});
|
|
|
|
it("guards every entry with isAbsenceType", () => {
|
|
for (const t of ABSENCE_TYPES) expect(isAbsenceType(t)).toBe(true);
|
|
expect(isAbsenceType("Karenz")).toBe(false);
|
|
expect(isAbsenceType(null)).toBe(false);
|
|
});
|
|
});
|
|
|
|
// Eine Teilzeit ist keine Abwesenheit. Wer in Bildungs-, Pflege-, Eltern-
|
|
// oder Wiedereingliederungsteilzeit als „langzeitabwesend" geführt wurde,
|
|
// verschwand aus dem Bestand, verlor die Berichtslinie an eine Vertretung und
|
|
// zählte in Auswertungen nicht mehr mit — obwohl er jede Woche im Haus war,
|
|
// nur kürzer.
|
|
const TEILZEITEN = ["Bildungsteilzeit", "Elternteilzeit", "Wiedereingliederungsteilzeit", "Pflegeteilzeit"];
|
|
|
|
describe("Teilzeiten sind keine Abwesenheiten", () => {
|
|
it("führt keine Teilzeit mehr als Abwesenheit", () => {
|
|
for (const t of TEILZEITEN) {
|
|
expect(ABSENCE_TYPES).not.toContain(t);
|
|
expect(isAbsenceType(t)).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("behält die Karenzen — sie setzen aus, statt zu reduzieren", () => {
|
|
// Die Wörter ähneln sich, die Sachverhalte nicht: Bildungskarenz ist
|
|
// eine Abwesenheit, Bildungsteilzeit sind weniger Stunden.
|
|
for (const t of ["Elternkarenz (inkl. Väterkarenz)", "Bildungskarenz", "Pflegekarenz"]) {
|
|
expect(ABSENCE_TYPES).toContain(t);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("Gründe für weniger Stunden", () => {
|
|
it("bietet bei der Rückkehr die zwei an, die dort hingehören", () => {
|
|
expect([...RUECKKEHR_GRUENDE]).toEqual(["Wiedereingliederungsteilzeit", "Elternteilzeit"]);
|
|
});
|
|
|
|
it("bietet bei der Stundenänderung die übrigen an, mit dem Normalfall zuerst", () => {
|
|
expect(STUNDEN_GRUENDE[0]).toBe("Vertragliche Stundenänderung");
|
|
expect([...STUNDEN_GRUENDE]).toContain("Bildungsteilzeit");
|
|
expect([...STUNDEN_GRUENDE]).toContain("Pflegeteilzeit");
|
|
});
|
|
|
|
it("verteilt die vier Teilzeiten vollständig und ohne Überschneidung", () => {
|
|
// Keine darf verlorengehen, und keine an beiden Stellen auftauchen —
|
|
// sonst hinge dieselbe Sache an zwei Vorgängen.
|
|
const verteilt = [...RUECKKEHR_GRUENDE, ...STUNDEN_GRUENDE].filter((g) => TEILZEITEN.includes(g));
|
|
expect(new Set(verteilt)).toEqual(new Set(TEILZEITEN));
|
|
expect(verteilt.length).toBe(TEILZEITEN.length);
|
|
});
|
|
});
|