Move the part-time arrangements out of the absence list

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>
This commit is contained in:
2026-08-14 12:56:21 +02:00
parent 6681dcda77
commit e6554e7982
6 changed files with 402 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { ABSENCE_TYPES, absenceLabel, isAbsenceType, statusLabel } from "@/lib/absence";
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
@@ -42,8 +42,10 @@ describe("absenceLabel", () => {
});
describe("ABSENCE_TYPES", () => {
it("holds the thirteen kinds, in the order they are offered", () => {
expect(ABSENCE_TYPES).toHaveLength(13);
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");
});
@@ -54,3 +56,47 @@ describe("ABSENCE_TYPES", () => {
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);
});
});