Kuendigungsschutz bekommt einen Personenkreis, die Behinderung eigene Felder
Anforderungen 5 und 5a aus dem Workshop. Migration 20260915120000.
Bisher gab es ein Kennzeichen und ein Enddatum. Das beantwortet "darf hier
ohne Weiteres beendet werden?" — nicht aber, *warum* jemand geschuetzt ist,
und davon haengt ab, wer zustimmen muss. Nachzusehen war das nur im
Papierakt, also dort, wo unter Zeitdruck niemand nachsieht.
Zwoelf Personenkreise als CHECK auf text und nicht als Aufzaehlungstyp: die
Liste ist Rechtslage und aendert sich mit dem Gesetz, ein Typ liesse einen
zurueckgenommenen Wert fuer immer stehen. Die Oberflaeche liest dieselbe
Liste aus lib/kuendigungsschutz.ts; ein Test liest die Migration und haelt
beide gegeneinander, damit die begruendete Doppelung keine stille wird.
Die begueenstigte Behinderung bekommt Kennzeichen, Grad, Beginn und Ende —
vier Spalten und nicht eine zusammengesetzte, weil in Excel danach
gefiltert und summiert wird. Datenbankseitig sind sie *nicht* an den
Personenkreis gekettet: eine solche Bedingung scheiterte genau dann, wenn
jemand den Kreis korrigiert und der Grad noch dransteht. Die Oberflaeche
stellt den Zusammenhang her.
Dazu zwei Dinge, die auf dem Weg auffielen:
* Der Nachtlauf wendete bei einer auf spaeter datierten Aenderung nur
Person und Vertrag an — die ganze Gruppe "role" fiel weg. Betriebsrat,
Dienstwagen, Kollektivvertrag, Arbeitstage, Teilzeit und
Kuendigungsschutz wurden erfasst, in der Historie vermerkt, protokolliert
und am Stichtag nicht geschrieben. Sichtbar wurde das nie. Die neuen
Felder haetten den Fehler geerbt; er ist jetzt fuer alle behoben.
* Der Mitarbeiter-Export filterte ohne Stichtag ueber die Spalte `status`,
mit Stichtag ueber die Ableitung. Der Export nach "Ausgetreten" liess
damit genau die Leute aus, die gerade ausgetreten sind.
This commit is contained in:
57
lib/kuendigungsschutz.ts
Normal file
57
lib/kuendigungsschutz.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { KuendigungsschutzGrund } from "./types";
|
||||
|
||||
// Die Personenkreise des besonderen Kündigungsschutzes — für die Auswahl in
|
||||
// der Oberfläche und für den Bericht.
|
||||
//
|
||||
// Die Liste steht zweimal: hier und als CHECK
|
||||
// chk_kuendigungsschutz_personenkreis in Migration 20260915120000. Das ist
|
||||
// eine bewusste Doppelung mit einer Begründung, denn die Alternativen sind
|
||||
// schlechter: ein Aufzählungstyp liesse einen zurückgenommenen Wert für immer
|
||||
// im Typ stehen, und eine Tabelle machte aus einer Rechtslage ein Stammdatum,
|
||||
// das jemand pflegen müsste. `npm run types:check` hält die Spalte gegen die
|
||||
// Migration; dass die *Werte* zusammenpassen, sichert der Test in
|
||||
// tests/unit/kuendigungsschutz.test.ts, der die Migration liest.
|
||||
//
|
||||
// Warum der Grund freiwillig ist: der Bestand trägt das Kennzeichen seit
|
||||
// Migration 20260814120000 ohne Grund. Ein Pflichtfeld hätte diese Zeilen
|
||||
// unänderbar gemacht — jede Änderung an einer davon wäre an einer Angabe
|
||||
// gescheitert, die niemand mehr nachträgt.
|
||||
|
||||
export const KUENDIGUNGSSCHUTZ_GRUENDE: readonly KuendigungsschutzGrund[] = [
|
||||
"Eltern",
|
||||
"Schwangerschaft",
|
||||
"Karenzurlaub",
|
||||
"Familienzeit",
|
||||
"Elternteilzeit",
|
||||
"Betriebsräte & Ersatzmitglieder",
|
||||
"Jugendvertrauenspersonen",
|
||||
"Behindertenvertrauenspersonen & Stellvertreter",
|
||||
"Präsenz-, Ausbildungs- oder Zivildienst",
|
||||
"Begünstigte behinderte ArbeitnehmerInnen",
|
||||
"Familienhospizkarenz",
|
||||
"Familienhospizteilzeit",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Der Personenkreis, an dem die Angaben zur Behinderung hängen.
|
||||
*
|
||||
* Die Oberfläche blendet die Felder auf, sobald dieser Kreis gewählt wird.
|
||||
* Eine Bedingung in der Datenbank ist das ausdrücklich nicht — siehe die
|
||||
* Begründung in der Migration: wer den Personenkreis korrigiert, scheiterte
|
||||
* sonst daran, solange der Grad noch dransteht.
|
||||
*/
|
||||
export const GRUND_BEGUENSTIGT_BEHINDERT: KuendigungsschutzGrund = "Begünstigte behinderte ArbeitnehmerInnen";
|
||||
|
||||
export function istKuendigungsschutzGrund(wert: string | null | undefined): wert is KuendigungsschutzGrund {
|
||||
return KUENDIGUNGSSCHUTZ_GRUENDE.includes(wert as KuendigungsschutzGrund);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ob der Grad zur Begünstigung reicht.
|
||||
*
|
||||
* Die Grenze liegt bei 50 Prozent (§ 2 Behinderteneinstellungsgesetz).
|
||||
* Erfasst wird trotzdem jeder bescheidmässige Grad: ein Bescheid über 30
|
||||
* Prozent ist eine Tatsache, auch wenn daraus kein besonderer Schutz folgt,
|
||||
* und wer ihn nicht eintragen darf, trägt ihn in ein Freitextfeld ein.
|
||||
*/
|
||||
export const BEGUENSTIGUNG_AB_GRAD = 50;
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ABSENCE_TYPES } from "./absence";
|
||||
import { AUSTRITTSART_LABELS, BEENDIGUNGSART_WERTE, type Austrittsart } from "./beendigung";
|
||||
import { KUENDIGUNGSSCHUTZ_GRUENDE } from "./kuendigungsschutz";
|
||||
import { parseIsoDateParam } from "./reports";
|
||||
import type { Weekday } from "./types";
|
||||
|
||||
@@ -58,6 +60,22 @@ export const AUSWAHL_KRITERIEN: readonly AuswahlKriterium[] = [
|
||||
},
|
||||
{ key: "dienstwagenArt", label: "Antriebsart Dienstwagen", alle: "Alle Antriebsarten", optionen: werte("Verbrenner", "Elektro") },
|
||||
{ key: "absence", label: "Art der Langzeitabwesenheit", alle: "Alle Arten", optionen: werte(...ABSENCE_TYPES) },
|
||||
{
|
||||
key: "schutzGrund",
|
||||
label: "Personenkreis Kündigungsschutz",
|
||||
alle: "Alle Personenkreise",
|
||||
optionen: werte(...KUENDIGUNGSSCHUTZ_GRUENDE),
|
||||
},
|
||||
// Die Beendigungsart als Auswertungskriterium — für die Fluktuation nach
|
||||
// Grund. Die Austrittsart (freiwillig/unfreiwillig) steht daneben und wird
|
||||
// aus derselben Angabe abgeleitet, nicht zweitgespeichert.
|
||||
{ key: "beendigung", label: "Beendigungsart", alle: "Alle Beendigungsarten", optionen: werte(...BEENDIGUNGSART_WERTE) },
|
||||
{
|
||||
key: "austrittsart",
|
||||
label: "Austrittsart",
|
||||
alle: "Alle Austrittsarten",
|
||||
optionen: (Object.keys(AUSTRITTSART_LABELS) as Austrittsart[]).map((a) => ({ wert: a, label: AUSTRITTSART_LABELS[a] })),
|
||||
},
|
||||
{
|
||||
key: "teilzeitArt",
|
||||
label: "Teilzeitvariante",
|
||||
@@ -76,6 +94,7 @@ export const JANEIN_KRITERIEN: readonly JaNeinKriterium[] = [
|
||||
{ key: "lateral", label: "Laterale Führung" },
|
||||
{ key: "clevel", label: "C-Level" },
|
||||
{ key: "kuendigungsschutz", label: "Besonderer Kündigungsschutz" },
|
||||
{ key: "behindert", label: "Begünstigt behindert" },
|
||||
{ key: "teilzeit", label: "In einer Teilzeitvariante" },
|
||||
{ key: "aufenthaltstitel", label: "Aufenthaltstitel" },
|
||||
{ key: "dependents", label: "Angehörige erfasst" },
|
||||
@@ -88,7 +107,10 @@ export const ZEITRAUM_KRITERIEN: readonly ZeitraumKriterium[] = [
|
||||
{ key: "entry", label: "Eintritt" },
|
||||
{ key: "exit", label: "Austritt" },
|
||||
{ key: "birth", label: "Geburtsdatum" },
|
||||
{ key: "schutzAb", label: "Kündigungsschutz ab" },
|
||||
{ key: "schutz", label: "Kündigungsschutz bis" },
|
||||
{ key: "behinderungAb", label: "Bescheid Behinderung ab" },
|
||||
{ key: "behinderungBis", label: "Bescheid Behinderung bis" },
|
||||
{ key: "teilzeitEnde", label: "Teilzeit endet" },
|
||||
{ key: "titelEnde", label: "Aufenthaltstitel läuft ab" },
|
||||
] as const;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Schema } from "./db/schema";
|
||||
import type { Tx } from "./db";
|
||||
import { ancestorsOf, loadOrgMaps, subtreeOf, type OrgMaps } from "./org";
|
||||
import { loadPlacements } from "./placement";
|
||||
import { beendigungsartenZu, type Austrittsart } from "./beendigung";
|
||||
import { LEERE_CRITERIA, passtImSpeicher, type Criteria } from "./report-criteria";
|
||||
import {
|
||||
deriveStatusAsOf,
|
||||
@@ -22,6 +23,7 @@ import type {
|
||||
EmploymentType,
|
||||
GenderType,
|
||||
HistoryEventType,
|
||||
KuendigungsschutzGrund,
|
||||
PaygradeType,
|
||||
SourceType,
|
||||
TeilzeitArt,
|
||||
@@ -67,6 +69,16 @@ export function applyCriteria<O>(
|
||||
if (a.dienstwagenArt) q = q.where("dienstwagen_art", "=", a.dienstwagenArt as DienstwagenArt);
|
||||
if (a.teilzeitArt) q = q.where("teilzeit_art", "=", a.teilzeitArt as TeilzeitArt);
|
||||
if (a.absence) q = q.where("absence_type", "=", a.absence);
|
||||
if (a.schutzGrund) q = q.where("kuendigungsschutz_grund", "=", a.schutzGrund as KuendigungsschutzGrund);
|
||||
if (a.beendigung) q = q.where("exit_reason", "=", a.beendigung);
|
||||
// Die Austrittsart ist keine Spalte, sondern eine Einteilung der
|
||||
// Beendigungsarten (lib/beendigung.ts). Aus ihr wird deshalb eine
|
||||
// Wertemenge statt eines Vergleichs — dieselbe Zuordnung, die auch das
|
||||
// Formular verwendet, nur als `in`.
|
||||
if (a.austrittsart) {
|
||||
const werte = beendigungsartenZu(a.austrittsart as Austrittsart);
|
||||
q = werte.length > 0 ? q.where("exit_reason", "in", werte) : q;
|
||||
}
|
||||
|
||||
const j = c.jaNein;
|
||||
if (j.dienstwagen) q = q.where("has_dienstwagen", "=", j.dienstwagen === "ja");
|
||||
@@ -78,6 +90,7 @@ export function applyCriteria<O>(
|
||||
if (j.teilzeit === "ja") q = q.where("teilzeit_art", "is not", null);
|
||||
if (j.teilzeit === "nein") q = q.where("teilzeit_art", "is", null);
|
||||
if (j.aufenthaltstitel) q = q.where("hat_aufenthaltstitel", "=", j.aufenthaltstitel === "ja");
|
||||
if (j.behindert) q = q.where("ist_beguenstigt_behindert", "=", j.behindert === "ja");
|
||||
|
||||
// Ein Austrittszeitraum schliesst alle ohne Austritt aus — ein Vergleich
|
||||
// mit NULL ist nicht wahr, und das ist hier genau die richtige Bedeutung.
|
||||
@@ -89,6 +102,12 @@ export function applyCriteria<O>(
|
||||
if (c.zeitraum.birth?.bis) q = q.where("birth_date", "<=", c.zeitraum.birth.bis);
|
||||
if (c.zeitraum.schutz?.von) q = q.where("kuendigungsschutz_bis", ">=", c.zeitraum.schutz.von);
|
||||
if (c.zeitraum.schutz?.bis) q = q.where("kuendigungsschutz_bis", "<=", c.zeitraum.schutz.bis);
|
||||
if (c.zeitraum.schutzAb?.von) q = q.where("kuendigungsschutz_ab", ">=", c.zeitraum.schutzAb.von);
|
||||
if (c.zeitraum.schutzAb?.bis) q = q.where("kuendigungsschutz_ab", "<=", c.zeitraum.schutzAb.bis);
|
||||
if (c.zeitraum.behinderungAb?.von) q = q.where("behinderung_ab", ">=", c.zeitraum.behinderungAb.von);
|
||||
if (c.zeitraum.behinderungAb?.bis) q = q.where("behinderung_ab", "<=", c.zeitraum.behinderungAb.bis);
|
||||
if (c.zeitraum.behinderungBis?.von) q = q.where("behinderung_bis", ">=", c.zeitraum.behinderungBis.von);
|
||||
if (c.zeitraum.behinderungBis?.bis) q = q.where("behinderung_bis", "<=", c.zeitraum.behinderungBis.bis);
|
||||
if (c.zeitraum.teilzeitEnde?.von) q = q.where("teilzeit_bis", ">=", c.zeitraum.teilzeitEnde.von);
|
||||
if (c.zeitraum.teilzeitEnde?.bis) q = q.where("teilzeit_bis", "<=", c.zeitraum.teilzeitEnde.bis);
|
||||
// Wer bis wann einen Titel hat — die Frage hinter „wessen Titel läuft im
|
||||
|
||||
44
lib/types.ts
44
lib/types.ts
@@ -39,6 +39,29 @@ export type DienstwagenArt = "Verbrenner" | "Elektro";
|
||||
*/
|
||||
export type TeilzeitArt = "Bildungsteilzeit" | "Elternteilzeit" | "Pflegeteilzeit" | "Wiedereingliederungsteilzeit";
|
||||
|
||||
/**
|
||||
* Personenkreise des besonderen Kündigungsschutzes.
|
||||
*
|
||||
* Kein Aufzählungstyp in der Datenbank, sondern ein CHECK auf text
|
||||
* (chk_kuendigungsschutz_personenkreis): die Liste ist Rechtslage und ändert
|
||||
* sich mit dem Gesetz — ein Typ liesse einen zurückgenommenen Wert für immer
|
||||
* stehen. Die Beschriftungen für die Oberfläche stehen in
|
||||
* lib/kuendigungsschutz.ts und müssen zur Bedingung passen.
|
||||
*/
|
||||
export type KuendigungsschutzGrund =
|
||||
| "Eltern"
|
||||
| "Schwangerschaft"
|
||||
| "Karenzurlaub"
|
||||
| "Familienzeit"
|
||||
| "Elternteilzeit"
|
||||
| "Betriebsräte & Ersatzmitglieder"
|
||||
| "Jugendvertrauenspersonen"
|
||||
| "Behindertenvertrauenspersonen & Stellvertreter"
|
||||
| "Präsenz-, Ausbildungs- oder Zivildienst"
|
||||
| "Begünstigte behinderte ArbeitnehmerInnen"
|
||||
| "Familienhospizkarenz"
|
||||
| "Familienhospizteilzeit";
|
||||
|
||||
/**
|
||||
* Verhältnis zum Notfallkontakt.
|
||||
*
|
||||
@@ -192,6 +215,21 @@ export type Database = {
|
||||
has_kuendigungsschutz: boolean;
|
||||
/** Ende des Schutzes, falls bekannt. Nur mit dem Kennzeichen zusammen erlaubt. */
|
||||
kuendigungsschutz_bis: string | null;
|
||||
/**
|
||||
* Personenkreis nach dem besonderen Kündigungsschutz — chk_kuendigungsschutz_personenkreis.
|
||||
* Freiwillig, denn der Altbestand trägt das Kennzeichen ohne Grund.
|
||||
*/
|
||||
kuendigungsschutz_grund: KuendigungsschutzGrund | null;
|
||||
/** Beginn des Schutzes, falls bekannt. Nur mit dem Kennzeichen zusammen erlaubt. */
|
||||
kuendigungsschutz_ab: string | null;
|
||||
/** Begünstigung nach dem Behinderteneinstellungsgesetz. */
|
||||
ist_beguenstigt_behindert: boolean;
|
||||
/** Grad der Behinderung in Prozent laut Bescheid, 1..100 — chk_behinderung_grad. */
|
||||
behinderung_grad: number | null;
|
||||
/** Beginn laut Bescheid. Nur mit dem Kennzeichen zusammen erlaubt. */
|
||||
behinderung_ab: string | null;
|
||||
/** Ende laut Bescheid, falls befristet. Nur mit dem Kennzeichen zusammen erlaubt. */
|
||||
behinderung_bis: string | null;
|
||||
/** Bildungs-, Eltern-, Pflege- oder Wiedereingliederungsteilzeit; null bei einer gewöhnlichen Regelung. */
|
||||
teilzeit_art: TeilzeitArt | null;
|
||||
/** Ende der Teilzeit, falls bekannt. Nur mit einer Variante zusammen erlaubt. */
|
||||
@@ -247,6 +285,12 @@ export type Database = {
|
||||
dienstwagen_art?: DienstwagenArt | null;
|
||||
has_kuendigungsschutz?: boolean;
|
||||
kuendigungsschutz_bis?: string | null;
|
||||
kuendigungsschutz_grund?: KuendigungsschutzGrund | null;
|
||||
kuendigungsschutz_ab?: string | null;
|
||||
ist_beguenstigt_behindert?: boolean;
|
||||
behinderung_grad?: number | null;
|
||||
behinderung_ab?: string | null;
|
||||
behinderung_bis?: string | null;
|
||||
teilzeit_art?: TeilzeitArt | null;
|
||||
teilzeit_bis?: string | null;
|
||||
emergency_contact_name?: string | null;
|
||||
|
||||
Reference in New Issue
Block a user