Freiwillig oder unfreiwillig wird erhoben, nicht abgeleitet
Migration 20260917100000. Ruecknahme einer eigenen Entscheidung, nach der
Erklaerung des Kunden am 17.09.2026.
Wir hatten den Anstoss aus der Beendigungsart abgeleitet — Kuendigung AN
gilt als freiwillig, Kuendigung AG als unfreiwillig. Das schien sauberer,
weil es zwei Felder ausschliesst, die einander widersprechen koennen.
In Oesterreich stimmt es nicht. Die einvernehmliche Aufloesung ist hier der
Regelfall und sagt ueber den Anstoss nichts aus: sie kann von der Person
ausgehen ("ich moechte kuendigen", worauf einvernehmlich aufgeloest wird,
damit das AMS zahlt) oder vom Dienstgeber ("ich will die Trennung, dafuer
gibt es eine Abfindung"). Dieselbe Beendigungsart, zwei gegensaetzliche
Antworten — und das ist genau die Unterscheidung, auf die es bei einer
Fluktuationsanalyse ankommt. Die Ableitung haette die Haelfte der Faelle
still falsch einsortiert.
Im Formular steht jetzt die Beendigungsart oben mit allen Werten, darunter
"Freiwillig oder unfreiwillig". Keine der beiden schraenkt die andere ein.
Der abgeleitete Hinweis unter der Beendigungsart ist weg — er erschien von
selbst und sah aus wie ein Fehler des Formulars.
Die Angabe ist freiwillig: der Bestand traegt sie nicht, und ein
Befristungsablauf geschieht auf niemandes Betreiben. Ob sie fuer gewoehnliche
Austritte Pflicht werden soll, ist eine Frage an den Kunden.
rehire_employee raeumt sie mit dem Austritt weg. Die Funktion ist dabei
ausgeschrieben worden; die Selbstpruefung haelt fest, dass die beiden
Umstellungen, die sie schon hinter sich hatte (app_current_user_id statt
auth.uid, fester search_path), dabei nicht verlorengehen — genau das ist der
Fehler, den ein create-or-replace aus einer alten Vorlage leise macht.
This commit is contained in:
@@ -7,15 +7,8 @@ import { Button } from "@/components/ui/Button";
|
||||
import { SelectField, TextField, TextareaField } from "@/components/ui/Field";
|
||||
import { SlideOver } from "@/components/ui/SlideOver";
|
||||
import { useToast } from "@/components/ui/Toast";
|
||||
import {
|
||||
AUSTRITTSART_LABELS,
|
||||
BEENDIGUNG_NO_SHOW,
|
||||
BEENDIGUNGSART_WERTE,
|
||||
austrittsartVon,
|
||||
beendigungsartenZu,
|
||||
type Austrittsart,
|
||||
} from "@/lib/beendigung";
|
||||
import type { Database, EmploymentStatus } from "@/lib/types";
|
||||
import { AUSTRITTSART_LABELS, AUSTRITTSARTEN, BEENDIGUNG_NO_SHOW, BEENDIGUNGSART_WERTE } from "@/lib/beendigung";
|
||||
import type { Austrittsart, Database, EmploymentStatus } from "@/lib/types";
|
||||
import { fmtDate, fmtName } from "@/lib/format";
|
||||
|
||||
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
|
||||
@@ -41,11 +34,9 @@ export function TerminatePanel({ open, onClose, employee, status, directReportCo
|
||||
// aus. Die Vorbelegung nimmt den wahrscheinlichen Fall vorweg, ohne die
|
||||
// übrigen zu verstellen.
|
||||
const [reason, setReason] = useState(status === "Geplant" ? NO_SHOW : BEENDIGUNGSART_WERTE[0]);
|
||||
// „Alle" statt einer Vorauswahl: die Einschränkung ist eine Hilfe beim
|
||||
// Suchen, keine Pflichtangabe. Wer die gewünschte Art auswendig kennt,
|
||||
// soll sie unmittelbar wählen können.
|
||||
// Leer als Vorbelegung: die Angabe ist freiwillig, und eine vorausgewählte
|
||||
// Seite wäre eine Behauptung, die niemand getroffen hat.
|
||||
const [austrittsart, setAustrittsart] = useState<Austrittsart | "">("");
|
||||
const waehlbar = beendigungsartenZu(austrittsart || null);
|
||||
const [note, setNote] = useState("");
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
@@ -66,6 +57,9 @@ export function TerminatePanel({ open, onClose, employee, status, directReportCo
|
||||
employee_id: employee.id,
|
||||
exit_date: wirksamesDatum,
|
||||
exit_reason: reason,
|
||||
// Leer statt undefined: die SQL-Funktion macht daraus null, und null
|
||||
// heisst „nicht erfasst" — das ist der ehrliche Wert.
|
||||
austrittsart,
|
||||
note,
|
||||
});
|
||||
setPending(false);
|
||||
@@ -120,36 +114,38 @@ export function TerminatePanel({ open, onClose, employee, status, directReportCo
|
||||
{directReportCount} direkte Berichte werden automatisch der nächsthöheren Führungskraft zugeordnet.
|
||||
</div>
|
||||
)}
|
||||
{/* Die Austrittsart wählt nicht mit, sie engt ein. Gespeichert wird
|
||||
allein die Beendigungsart; auf wessen Betreiben beendet wurde,
|
||||
leitet lib/beendigung.ts daraus ab. Als zweites freies Feld liesse
|
||||
sich „Entlassung, freiwillig" erfassen, und zwei Auswertungen über
|
||||
dieselbe Person kämen zu verschiedenen Ergebnissen. */}
|
||||
<SelectField
|
||||
label="Austrittsart"
|
||||
value={austrittsart}
|
||||
onChange={(v) => {
|
||||
const neu = (v || "") as Austrittsart | "";
|
||||
setAustrittsart(neu);
|
||||
// Passt der bisher gewählte Grund nicht mehr zur Einschränkung,
|
||||
// wandert die Auswahl auf den ersten passenden. Stehenzulassen,
|
||||
// was in der Liste nicht mehr sichtbar ist, wäre ein Formular,
|
||||
// das etwas anderes absendet, als es zeigt.
|
||||
const erlaubt = beendigungsartenZu(neu || null);
|
||||
if (!erlaubt.includes(reason)) setReason(erlaubt[0]);
|
||||
}}
|
||||
options={[
|
||||
{ value: "", label: "Alle Beendigungsarten" },
|
||||
...(Object.keys(AUSTRITTSART_LABELS) as Austrittsart[]).map((a) => ({ value: a, label: AUSTRITTSART_LABELS[a] })),
|
||||
]}
|
||||
hint="Schränkt die Auswahl darunter ein. Gespeichert wird die Beendigungsart."
|
||||
/>
|
||||
{/* ── Zwei Angaben, die einander nicht bestimmen ──────────────
|
||||
Erst die Beendigungsart, darunter der Anstoss. Beide Listen sind
|
||||
vollständig und keine schränkt die andere ein.
|
||||
|
||||
Der erste Entwurf hatte es umgekehrt: oben die Austrittsart, und
|
||||
sie filterte die Beendigungsarten darunter, weil der Anstoss aus
|
||||
der Art ableitbar schien. In Österreich ist er das nicht — die
|
||||
einvernehmliche Auflösung ist der Regelfall und kann von beiden
|
||||
Seiten ausgehen. Die Begründung steht in lib/beendigung.ts.
|
||||
|
||||
Unter der Beendigungsart stand ausserdem der abgeleitete Anstoss
|
||||
als Hinweis. Er erschien von selbst und sah aus wie ein Fehler des
|
||||
Formulars; er ist weg. */}
|
||||
<SelectField
|
||||
label="Beendigungsart"
|
||||
value={reason}
|
||||
onChange={setReason}
|
||||
options={waehlbar.map((r) => ({ value: r, label: r === NO_SHOW ? "No Show (nicht angetreten)" : r }))}
|
||||
hint={austrittsartVon(reason) ? AUSTRITTSART_LABELS[austrittsartVon(reason)!] : undefined}
|
||||
options={BEENDIGUNGSART_WERTE.map((r) => ({ value: r, label: r === NO_SHOW ? "No Show (nicht angetreten)" : r }))}
|
||||
/>
|
||||
<SelectField
|
||||
label="Freiwillig oder unfreiwillig"
|
||||
value={austrittsart}
|
||||
onChange={(v) => setAustrittsart((v || "") as Austrittsart | "")}
|
||||
options={[
|
||||
// „Nicht erfasst" als Vorbelegung und nicht eine der beiden
|
||||
// Seiten: ein Befristungsablauf geschieht auf niemandes
|
||||
// Betreiben, und ein Nichtantritt ist gar kein Austritt. Eine
|
||||
// erzwungene Antwort wäre dort eine erfundene Zahl.
|
||||
{ value: "", label: "Nicht erfasst" },
|
||||
...AUSTRITTSARTEN.map((a) => ({ value: a, label: AUSTRITTSART_LABELS[a] })),
|
||||
]}
|
||||
hint="Für die Fluktuationsauswertung. Lässt sich aus der Beendigungsart nicht ableiten."
|
||||
/>
|
||||
<TextField
|
||||
label={istNoShow ? "Wirksam am (Eintrittstag)" : "Austrittsdatum"}
|
||||
|
||||
Reference in New Issue
Block a user