import { ABSENCE_TYPES } from "./absence"; import { parseIsoDateParam } from "./reports"; import type { Weekday } from "./supabase/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: "Angestellte:r / Arbeiter:in", alle: "Alle", optionen: werte("Angestellte:r", "Arbeiter:in") }, { key: "kv", label: "Kollektivvertrag", alle: "Alle Kollektivverträge", optionen: werte("Handel", "Süßwaren") }, { key: "paygrade", label: "Paygrade", alle: "Alle Paygrades", optionen: werte("A", "B", "C", "D", "E", "F") }, { 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: "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: "teilzeit", label: "In einer Teilzeitvariante" }, { key: "dependents", label: "Angehörige erfasst" }, ] as const; /** Zeiträume: je ein Parameter `From` und `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: "schutz", label: "Kündigungsschutz bis" }, { key: "teilzeitEnde", label: "Teilzeit endet" }, ] as const; export type Criteria = { /** Werteliste je Kriteriumsschlüssel aus AUSWAHL_KRITERIEN. */ auswahl: Record; /** Ja/Nein je Schlüssel aus JANEIN_KRITERIEN. */ jaNein: Record; /** ISO-Daten je Schlüssel aus ZEITRAUM_KRITERIEN, jeweils von/bis. */ zeitraum: Record; /** 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 = {}; 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 = {}; for (const k of JANEIN_KRITERIEN) { const roh = get(k.key); if (roh === "ja" || roh === "nein") jaNein[k.key] = roh; } const zeitraum: Record = {}; 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; }