A--F stammte aus der Spezifikation, nicht vom Kunden: sechs erfundene Stufen mit erfundenen Beschreibungen. Der Kunde bewertet nach Hay und hat die Liste geschickt -- 13 Stufen plus einen Generic Grade. Dieselbe Spalte fuellt im Cornerstone-Extrakt die Grade ID. Solange dort A--F steht, ist die Datei in jeder Zeile falsch, ohne dass es beim Erzeugen auffaellt: das Zielsystem kennt diese Kennungen nicht. Der Typ heisst weiter paygrade_type, ist aber jetzt eine Domain ueber text mit CHECK statt eines Aufzaehlungstyps. Ein Enum laesst sich nicht umschreiben -- Werte entfernen geht gar nicht, und add value darf im selben Vorgang, der den neuen Wert schreibt, nicht benutzt werden. Wichtiger: die rund zehn SQL-Funktionen, die den Wert nach paygrade_type umwandeln, bleiben unveraendert gueltig. Jede von ihnen neu zu erzeugen hiesse, zehnmal die Gelegenheit zu haben, aus einer veralteten Vorlage zu kopieren. Zwei Funktionen muessen doch angefasst werden, beide per Punktaenderung an der laufenden Definition statt per Kopie aus einer Datei: der Vorgabewert B in hire_employee und die Beschriftung im Protokoll von promote_employee. Der Bestand bekommt den Generic Grade. Aus A--F liesse sich kein Hay-Grade ableiten: andere Einteilung, andere Anzahl. Geraten saehe im Extrakt genauso aus wie erhoben.
210 lines
8.8 KiB
TypeScript
210 lines
8.8 KiB
TypeScript
import { ABSENCE_TYPES } from "./absence";
|
|
import { AUSTRITTSART_LABELS, AUSTRITTSARTEN, BEENDIGUNGSART_WERTE } from "./beendigung";
|
|
import { HAY_GRADE_WERTE } from "./hay-grade";
|
|
import { KUENDIGUNGSSCHUTZ_GRUENDE } from "./kuendigungsschutz";
|
|
import { MITARBEITERARTEN } from "./mitarbeiterart";
|
|
import { parseIsoDateParam } from "./reports";
|
|
import type { Weekday } from "./types";
|
|
|
|
// Ein Verzeichnis aller Auswahlkriterien — für die Oberfläche, die Abfrage
|
|
// und den Export dasselbe.
|
|
//
|
|
// Ein Export, der weniger filtern kann als der Bericht daneben, ist der
|
|
// Grund, warum Leute Daten nach Excel kippen und dort weiterarbeiten: was
|
|
// die Anwendung nicht hergibt, wird eben von Hand nachgebaut, und ab da
|
|
// stimmt es mit nichts mehr überein. Deshalb steht hier jedes Kriterium
|
|
// einmal, mit seinen erlaubten Werten, und alle drei Seiten lesen aus
|
|
// derselben Liste:
|
|
//
|
|
// * die Oberfläche baut ihre Felder daraus,
|
|
// * `parseCriteria` prüft die Adresszeile dagegen,
|
|
// * `applyCriteria` (serverseitig) macht Bedingungen daraus.
|
|
//
|
|
// Ein neues Kriterium ist damit ein Eintrag in dieser Datei und sonst
|
|
// nichts — und es kann nicht passieren, dass es im Bericht wirkt, im Export
|
|
// aber stillschweigend ignoriert wird.
|
|
//
|
|
// Die Prüfung ist nicht bloss Ordnungsliebe: die Werte landen in
|
|
// SQL-Vergleichen und im Dateinamen des Downloads, also in einem
|
|
// Content-Disposition-Header. Was nicht in der Liste steht, kommt nicht
|
|
// durch.
|
|
|
|
export type JaNein = "ja" | "nein";
|
|
|
|
/** Ein Kriterium mit fester Werteliste. */
|
|
export type AuswahlKriterium = {
|
|
key: string;
|
|
label: string;
|
|
/** Beschriftung für „keine Einschränkung". */
|
|
alle: string;
|
|
optionen: readonly { wert: string; label: string }[];
|
|
};
|
|
|
|
function werte(...w: string[]): { wert: string; label: string }[] {
|
|
return w.map((x) => ({ wert: x, label: x }));
|
|
}
|
|
|
|
export const AUSWAHL_KRITERIEN: readonly AuswahlKriterium[] = [
|
|
{ key: "employment", label: "Beschäftigungsart", alle: "Alle Beschäftigungsarten", optionen: werte("Vollzeit", "Teilzeit") },
|
|
{ key: "contract", label: "Vertragsart", alle: "Alle Vertragsarten", optionen: werte("unbefristet", "befristet") },
|
|
{ key: "worker", label: "Beschäftigtengruppe", alle: "Alle", optionen: werte("Angestellte:r", "Arbeiter:in", "Lehrling") },
|
|
{ key: "mitarbeiterart", label: "Mitarbeiterart", alle: "Alle Mitarbeiterarten", optionen: werte(...MITARBEITERARTEN) },
|
|
{ key: "kv", label: "Kollektivvertrag", alle: "Alle Kollektivverträge", optionen: werte("Handel", "Süßwaren") },
|
|
{ key: "paygrade", label: "Hay-Grade", alle: "Alle Hay-Grades", optionen: werte(...HAY_GRADE_WERTE) },
|
|
{ key: "source", label: "Intern/Extern", alle: "Alle", optionen: werte("Intern", "Extern") },
|
|
{
|
|
key: "gender",
|
|
label: "Geschlecht",
|
|
alle: "Alle",
|
|
optionen: [
|
|
{ wert: "m", label: "männlich" },
|
|
{ wert: "w", label: "weiblich" },
|
|
],
|
|
},
|
|
{ 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) },
|
|
// Eigene Spalte, nicht aus der Beendigungsart abgeleitet — siehe
|
|
// lib/beendigung.ts. Der Wert steht in der Datenbank genauso da; nur die
|
|
// Beschriftung kommt von hier.
|
|
{
|
|
key: "austrittsart",
|
|
label: "Freiwillig oder unfreiwillig",
|
|
alle: "Alle Austrittsarten",
|
|
optionen: AUSTRITTSARTEN.map((a) => ({ wert: a, label: AUSTRITTSART_LABELS[a] })),
|
|
},
|
|
{
|
|
key: "teilzeitArt",
|
|
label: "Teilzeitvariante",
|
|
alle: "Alle Teilzeitvarianten",
|
|
optionen: werte("Bildungsteilzeit", "Elternteilzeit", "Pflegeteilzeit", "Wiedereingliederungsteilzeit"),
|
|
},
|
|
{ key: "weekday", label: "Arbeitet am", alle: "Beliebiger Wochentag", optionen: werte("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So") },
|
|
] as const;
|
|
|
|
/** Kriterien über ein Ja/Nein-Feld. */
|
|
export type JaNeinKriterium = { key: string; label: string };
|
|
|
|
export const JANEIN_KRITERIEN: readonly JaNeinKriterium[] = [
|
|
{ key: "dienstwagen", label: "Dienstwagen" },
|
|
{ key: "betriebsrat", label: "Betriebsrat" },
|
|
{ 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" },
|
|
] as const;
|
|
|
|
/** Zeiträume: je ein Parameter `<key>From` und `<key>To`. */
|
|
export type ZeitraumKriterium = { key: string; label: string };
|
|
|
|
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;
|
|
|
|
export type Criteria = {
|
|
/** Werteliste je Kriteriumsschlüssel aus AUSWAHL_KRITERIEN. */
|
|
auswahl: Record<string, string>;
|
|
/** Ja/Nein je Schlüssel aus JANEIN_KRITERIEN. */
|
|
jaNein: Record<string, JaNein>;
|
|
/** ISO-Daten je Schlüssel aus ZEITRAUM_KRITERIEN, jeweils von/bis. */
|
|
zeitraum: Record<string, { von?: string; bis?: string }>;
|
|
/** Wochenstunden von/bis. */
|
|
stundenVon?: number;
|
|
stundenBis?: number;
|
|
};
|
|
|
|
export const LEERE_CRITERIA: Criteria = { auswahl: {}, jaNein: {}, zeitraum: {} };
|
|
|
|
function parseZahl(value: string | null | undefined): number | undefined {
|
|
if (!value) return undefined;
|
|
const n = Number(value.replace(",", "."));
|
|
// Negative Stunden oder eine Woche mit 200 Stunden sind keine Eingabe,
|
|
// sondern ein Tippfehler oder ein Versuch.
|
|
return Number.isFinite(n) && n >= 0 && n <= 168 ? n : undefined;
|
|
}
|
|
|
|
/**
|
|
* Liest die Kriterien aus der Adresszeile und verwirft alles, was nicht in
|
|
* den Listen oben steht.
|
|
*/
|
|
export function parseCriteria(get: (key: string) => string | null | undefined): Criteria {
|
|
const auswahl: Record<string, string> = {};
|
|
for (const k of AUSWAHL_KRITERIEN) {
|
|
const roh = get(k.key);
|
|
if (roh && k.optionen.some((o) => o.wert === roh)) auswahl[k.key] = roh;
|
|
}
|
|
|
|
const jaNein: Record<string, JaNein> = {};
|
|
for (const k of JANEIN_KRITERIEN) {
|
|
const roh = get(k.key);
|
|
if (roh === "ja" || roh === "nein") jaNein[k.key] = roh;
|
|
}
|
|
|
|
const zeitraum: Record<string, { von?: string; bis?: string }> = {};
|
|
for (const k of ZEITRAUM_KRITERIEN) {
|
|
const von = parseIsoDateParam(get(`${k.key}From`));
|
|
const bis = parseIsoDateParam(get(`${k.key}To`));
|
|
if (von || bis) zeitraum[k.key] = { von, bis };
|
|
}
|
|
|
|
return { auswahl, jaNein, zeitraum, stundenVon: parseZahl(get("hoursFrom")), stundenBis: parseZahl(get("hoursTo")) };
|
|
}
|
|
|
|
/** Wie viele Kriterien gesetzt sind — für die Anzeige „(3)" am Aufklapper. */
|
|
export function anzahlKriterien(c: Criteria): number {
|
|
let n = Object.keys(c.auswahl).length + Object.keys(c.jaNein).length;
|
|
for (const z of Object.values(c.zeitraum)) n += (z.von ? 1 : 0) + (z.bis ? 1 : 0);
|
|
if (c.stundenVon !== undefined) n += 1;
|
|
if (c.stundenBis !== undefined) n += 1;
|
|
return n;
|
|
}
|
|
|
|
/** Alle Parameternamen, die zu den Kriterien gehören — zum Zurücksetzen. */
|
|
export function kriterienParameter(): string[] {
|
|
return [
|
|
...AUSWAHL_KRITERIEN.map((k) => k.key),
|
|
...JANEIN_KRITERIEN.map((k) => k.key),
|
|
...ZEITRAUM_KRITERIEN.flatMap((k) => [`${k.key}From`, `${k.key}To`]),
|
|
"hoursFrom",
|
|
"hoursTo",
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Die zwei Kriterien, die keine Spalte sind: der Wochentag steckt in einem
|
|
* Array, die Angehörigen in einer anderen Tabelle. Beide werden an den
|
|
* geladenen Zeilen geprüft, statt den Enum-Vergleich bzw. einen Join in die
|
|
* Abfrage zu zwingen.
|
|
*/
|
|
export function passtImSpeicher(
|
|
zeile: { work_days: Weekday[] | string[]; dependentsCount: number },
|
|
c: Criteria
|
|
): boolean {
|
|
const tag = c.auswahl.weekday;
|
|
if (tag && !zeile.work_days.includes(tag as Weekday)) return false;
|
|
const angehoerige = c.jaNein.dependents;
|
|
if (angehoerige === "ja" && zeile.dependentsCount === 0) return false;
|
|
if (angehoerige === "nein" && zeile.dependentsCount > 0) return false;
|
|
return true;
|
|
}
|