Files
alpenwerk-hr/lib/offboarding.ts
Maximilian Stubhan 521e4ecf00
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 6m6s
CI / Integrationstests (echtes Postgres) (push) Failing after 5m26s
Show the Offboarding tab from the day the exit is recorded, not the day it takes effect
The tab was keyed off employee.status === "Ausgetreten", and that stays
wrong for weeks: terminate_employee writes exit_date immediately no
matter how far out the date is, but only flips status once the date
itself arrives. A termination entered today for four weeks out left the
tab invisible for the entire notice period — exactly the stretch in
which IT access, hardware and deregistration actually get worked
through, and exactly where the checklist was supposed to live "next to
Onboarding," per the report that caught this.

The rule now reads exit_date instead: not null, and not a No Show
(which sets exit_date too, to the entry day, but never worked a day and
gets no checklist). rehire_employee resets both exit_date and
exit_reason to null, so a rehired person's tab still disappears the
same way it did before — nothing about that case changed, only the
signal the check reads.

Verified against the real database with the exact shape from the
report: a termination dated 30 days out. Status stays "Aktiv", the
checklist exists immediately, and the tab's own predicate says yes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:41:07 +02:00

91 lines
4.1 KiB
TypeScript

import {
alleReichungen,
fortschrittVon,
punktZuVon,
type AufgabenGruppe,
type AufgabenPunkt,
type AufgabenStand,
type Fortschritt,
} from "./checklist";
export type { AufgabenArt, AufgabenGruppe, AufgabenPunkt, AufgabenStand, Fortschritt } from "./checklist";
export { istErledigt, wertIstGueltig } from "./checklist";
// Die Offboarding-Checkliste — vom selben Blatt Papier wie die Onboarding-
// Liste, nur für den anderen Weg. Was dort steht, ist hier eins zu eins
// übernommen; wo die Bezeichnung „GKK" auf dem Blatt steht statt des heute
// gebräuchlichen „ÖGK", ist sie unverändert stehengeblieben — das ist der
// Name, unter dem der Vorgang im Haus läuft, nicht ein Tippfehler.
//
// Zwei Punkte auf dem Blatt tragen denselben Namen zweimal: „Überleitung
// Salden für Auszahlung" steht einmal als abzuhakende Aufgabe und daneben als
// Feld für den tatsächlichen Betrag. Das ist kein Versehen im Blatt, sondern
// zwei verschiedene Fragen — „ist es veranlasst?" und „wie viel ist es?" —,
// die hier auch zwei verschiedene Punkte sind.
//
// Warum kein Ja/Nein-Punkt hier steht (anders als Prämienanspruch beim
// Onboarding): auf diesem Blatt gibt es keine Stelle mit zwei Kästchen
// nebeneinander. Taucht künftig eine auf, ist `art: "janein"` bereit.
export const OFFBOARDING_GRUPPEN: readonly AufgabenGruppe[] = [
{
key: "aufgaben",
label: "Zu erledigende Aufgaben",
punkte: [
{ key: "br_verstaendigung", label: "BR Verständigung bei KDG", art: "haken" },
{ key: "kuendigungsschreiben", label: "Kündigungsschreiben (KDG)", art: "haken" },
{ key: "bestaetigung_kdn_bei", label: "Bestätigung KDN / BEI", art: "haken" },
{ key: "lgv_austritt", label: "LGV Austritt", art: "haken" },
{ key: "gkk_abmeldung", label: "GKK-Abmeldung", art: "haken" },
{ key: "if6020_austritt", label: "IF 6020 Austritt", art: "haken" },
{ key: "ueberleitung_salden", label: "Überleitung Salden für Auszahlung", art: "haken" },
{ key: "endabrechnung", label: "Endabrechnung", art: "haken" },
{ key: "offboarding_gestartet", label: "Offboarding gestartet", art: "haken" },
],
},
{
key: "vermerke",
label: "Vermerke",
punkte: [
{ key: "resturlaub", label: "Resturlaub", art: "text" },
{ key: "ueberleitung_salden_betrag", label: "Überleitung Salden für Auszahlung — Betrag", art: "text" },
],
},
] as const;
export const OFFBOARDING_PUNKTE: readonly AufgabenPunkt[] = alleReichungen(OFFBOARDING_GRUPPEN);
export function punktZu(key: string): AufgabenPunkt | undefined {
return punktZuVon(OFFBOARDING_PUNKTE, key);
}
export function fortschritt(staende: AufgabenStand[]): Fortschritt {
return fortschrittVon(OFFBOARDING_PUNKTE, staende);
}
/**
* Ob eine Person offzuboarden ist — die Regel hinter dem Reiter.
*
* Geprüft wird `exit_date`, nicht `status`. Eine künftige Beendigung setzt
* beide Felder sofort — terminate_employee schreibt `exit_date` unabhängig
* davon, ob der Stichtag schon erreicht ist —, während `status` erst am
* Stichtag selbst auf „Ausgetreten" wechselt. Bis dahin steht die Person
* weiterhin als „Aktiv"; die vier Wochen bis zum Austritt sind aber genau die
* Zeit, in der IT-Zugänge, Hardware und Anmeldungen abgewickelt werden. Mit
* `status` als Bedingung bliebe der Reiter bis zum letzten Arbeitstag
* unsichtbar — zu spät, um dort etwas vorzubereiten.
*
* No Show hat `exit_date` ebenfalls gesetzt (den Eintrittstag), aber nie
* einen Tag gearbeitet: keinen IT-Zugang, der eingerichtet wurde, keine
* GKK-Anmeldung, kein Dienstzettel. Ein Reiter mit einer leeren Liste wäre
* dort ein Etikett ohne Inhalt — deshalb der Ausschluss über `exit_reason`.
*
* Wird die Person wiedereingestellt, setzt rehire_employee `exit_date` und
* `exit_reason` ausdrücklich auf null zurück, und die Regel verneint wieder —
* die Checkliste bleibt in den Daten stehen, nur ihr Reiter verschwindet,
* solange sie ohne Gegenstand ist.
*/
export function gehoertOffboarding(employee: { exit_date: string | null; exit_reason: string | null }): boolean {
return employee.exit_date !== null && employee.exit_reason !== "No Show";
}