Rename Karenz to Langzeitabwesenheit and record its type

Karenz was doing duty as the name for every kind of extended absence, but
the cases behave differently in payroll and reporting — Wochenhilfe, a
Präsenzdienst, a long sick leave and a sabbatical are not the same thing.
The concept is now called Langzeitabwesenheit and carries which kind it is.

- employees.absence_type, constrained to the thirteen kinds. start_karenz
  stores it on both paths (written straight away, or parked in the
  pending_org_changes payload when the absence starts later);
  record_karenz_return and the karenz_return branch of
  apply_due_pending_changes clear it, so a returned employee does not keep
  looking like they are still away. It also reaches employee_history, the
  audit log and the employee export.
- The status enum value stays 'Karenz'. Postgres can rename an enum value in
  place, but every stored function body that spells it would then reference
  a value that no longer exists — a dozen functions across fifteen
  migrations, rewritten for a label. The mapping lives in lib/absence.ts
  instead, which is the single place the UI reads the display name from.
- Where a kind is recorded the chip shows it — "Bildungskarenz" says more
  than "Langzeitabwesenheit". Absences predating the field have none and
  fall back to the generic name rather than to a guess, and a value outside
  the list is dropped rather than echoed into the UI.
- The export prints the display name, not the raw enum: a payroll hand-off
  reading "Karenz" for what the app calls Langzeitabwesenheit only causes
  questions. Audit filter options keep their stored values and change only
  their labels.
- The seed spreads the twelve absences across the kinds; all of them being
  Karenz would leave any breakdown by kind invisible.
This commit is contained in:
2026-07-25 14:34:28 +02:00
parent 37bb107cd4
commit 8282d7f581
16 changed files with 471 additions and 45 deletions

View File

@@ -11,6 +11,7 @@ import { randomUUID } from "node:crypto";
// Explicit .ts extension: this file is run directly by Node (type-stripping,
// ESM), where an extensionless relative import does not resolve.
import { svnrCheckDigit } from "../lib/svnr.ts";
import { ABSENCE_TYPES } from "../lib/absence.ts";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
@@ -344,7 +345,9 @@ type EmployeeRow = {
entry_date: string;
exit_date: string | null;
exit_reason: string | null;
karenz_start_date: string | null;
karenz_return_date: string | null;
absence_type: string | null;
};
type HistoryRow = {
@@ -460,7 +463,9 @@ function finalizeEmployee(base: ReturnType<typeof newHireBase>, opts: { paygrade
entry_date: isoDate(entryDate),
exit_date: null,
exit_reason: null,
karenz_start_date: null,
karenz_return_date: null,
absence_type: null,
};
history.push({
@@ -578,19 +583,23 @@ for (let i = 0; i < 40 && cursor < shuffledIcs.length; i++, cursor++) {
history.push({ employee_id: e.id, event_date: e.exit_date, event_type: "Austritt", description: `Austritt (${e.exit_reason})` });
}
// ~12 Karenz
// ~12 Langzeitabwesenheiten, über die Arten gestreut statt alle als Karenz
// die Auswertung nach Art ist sonst nicht zu sehen.
for (let i = 0; i < 12 && cursor < shuffledIcs.length; i++, cursor++) {
const e = shuffledIcs[cursor];
const entryDate = new Date(e.entry_date);
const karenzStart = randomDateBetween(addDays(entryDate, 180), addDays(TODAY, -10));
const returnDate = addDays(TODAY, randInt(10, 300));
const absenceType = pick(ABSENCE_TYPES);
e.status = "Karenz";
e.karenz_start_date = isoDate(karenzStart);
e.karenz_return_date = isoDate(returnDate);
e.absence_type = absenceType;
history.push({
employee_id: e.id,
event_date: isoDate(karenzStart),
event_type: "Karenz",
description: `Karenzantritt, geplante Rückkehr am ${isoDate(returnDate)}`,
description: `${absenceType}, geplante Rückkehr am ${isoDate(returnDate)}`,
});
}