Die Kachel verwies auf eine Adresse, die die Liste nicht lesen konnte

Die neue Kachel "Aktives Dienstverhaeltnis" verlinkte auf
?status=Aktiv&status=Karenz. Die Mitarbeiterliste liest den Parameter aber
als *eine* Zeichenkette und trennt selbst an Kommas — zweimal uebergeben
macht Next daraus ein Array, und `.split(",")` lief dagegen. Sichtbar war
nur "Diese Ansicht konnte nicht geladen werden".

Die Kachel schreibt jetzt status=Aktiv,Karenz. Dazu glaettet die Seite alle
ihre Parameter: eine Adresse kommt nicht nur aus der eigenen Anwendung, sie
steht in Lesezeichen und in E-Mails, und ?q=a&q=b haette sie genauso
gefaellt.

Zwei Anmerkungen von Max:

  * Die Reihenfolge der Wochentage wurde beim Speichern mitgenommen — "Mo,
    Di" und "Di, Mo" waren zwei Werte fuer dieselbe Aussage. Da
    change_employee_data die Arbeitstage als zusammengefuegte Zeichenkette
    vergleicht, erzeugte jedes Nachsehen und Wiederherstellen eine
    Vertragsaenderung in der Akte und einen Protokolleintrag — ueber nichts.
    Jetzt sortiert gespeichert (lib/wochentage.ts, an einer Stelle statt in
    vier Kopien), auch im Massenimport. Der Bestand richtet sich beim
    naechsten Speichern von selbst.
  * "Beguenstigt behindert" steht jetzt als eingerueckter Unterpunkt des
    Kuendigungsschutzes statt als eigener Block daneben. In der Datenbank
    bleiben es getrennte Felder, und das mit Absicht: eine Kopplung liesse
    jede Korrektur am Personenkreis scheitern, solange der Grad noch
    dransteht.
This commit is contained in:
2026-09-16 22:06:38 +02:00
parent 05d56bf3b9
commit 4dc27bf212
8 changed files with 202 additions and 60 deletions

View File

@@ -3,6 +3,7 @@ import { sql, type Tx } from "@/lib/db";
import { todayIso } from "@/lib/format";
import { deriveStatusAsOf } from "@/lib/reports";
import { normalizeSvnr } from "@/lib/svnr";
import { sortiereWochentage } from "@/lib/wochentage";
import type { Bestand, Datensatz, Zeile } from "./validate";
// Schreiben einer geprüften Datei.
@@ -217,8 +218,11 @@ export async function laden(
weekly_hours: zahl(w.weekly_hours) ?? 38.5,
// Die Prüfung hat jeden Eintrag gegen die Wochentage abgeglichen und
// auf die Schreibweise der Datenbank gebracht; hier steht deshalb
// sicher nur Mo…So.
work_days: (liste(w.work_days) ?? ["Mo", "Di", "Mi", "Do", "Fr"]) as never,
// sicher nur Mo…So. Sortiert wird trotzdem: die Reihenfolge in der
// Datei ist die der Datei, und „Di, Mo" ist derselbe Sachverhalt wie
// „Mo, Di". Ungeordnet gespeichert erzeugte die erste Änderung an so
// einer Person eine Vertragsänderung über nichts.
work_days: sortiereWochentage(liste(w.work_days) ?? ["Mo", "Di", "Mi", "Do", "Fr"]) as never,
contract_type: (txt(w.contract_type) ?? "unbefristet") as never,
contract_end_date: txt(w.contract_end_date),
paygrade: (txt(w.paygrade) ?? "B") as never,

View File

@@ -1,6 +1,7 @@
import { ABSENCE_TYPES } from "./absence";
import { BEENDIGUNGSART_WERTE } from "./beendigung";
import { fmtName, todayIso, yearsBetweenIso } from "./format";
import { WOCHENTAGE } from "./wochentage";
import type { EmploymentStatus, HistoryEventType, Weekday } from "./types";
export { todayIso };
@@ -234,11 +235,9 @@ export function groupKeyFor(e: ReportEmployee, dim: GroupDimension, lookups: Org
}
}
const WEEKDAY_ORDER: Weekday[] = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
function weekdayRank(key: string): number {
const i = WEEKDAY_ORDER.indexOf(key as Weekday);
return i === -1 ? WEEKDAY_ORDER.length : i;
const i = WOCHENTAGE.indexOf(key as Weekday);
return i === -1 ? WOCHENTAGE.length : i;
}
function sortByWeekday<T extends { key: string }>(items: T[]): T[] {

42
lib/wochentage.ts Normal file
View File

@@ -0,0 +1,42 @@
import type { Weekday } from "./types";
// Die Wochentage in ihrer natürlichen Reihenfolge — an einer Stelle.
//
// Sie stand bisher viermal im Baum: in RoleEmploymentFields, in lib/reports,
// im Mitarbeiter-Export und in lib/import/schema. Vier Kopien einer Liste,
// die sich nie ändert, sind für sich genommen harmlos; was nicht harmlos war,
// ist die fehlende fünfte Verwendung — das Sortieren beim Speichern.
//
// ── Warum sortiert gespeichert wird ─────────────────────────────────
//
// `work_days` wurde in **Klickreihenfolge** abgelegt (so stand es auch in
// docs/datenkatalog.md, als bewusste Entscheidung). Damit sind „Mo, Di" und
// „Di, Mo" zwei verschiedene Werte für dieselbe Aussage, und
// change_employee_data vergleicht die alte mit der neuen Fassung über
// `array_to_string(work_days, ', ')`. Wer die Tage nur noch einmal anklickte,
// um sie zu prüfen, erzeugte damit eine Vertragsänderung in der Personalakte
// und einen Eintrag im Protokoll — über nichts.
//
// Die Reihenfolge trägt keine Bedeutung: welche Tage jemand arbeitet, ist
// eine Menge, keine Folge. Sortiert gespeichert fällt der Scheinunterschied
// weg, ohne dass der Vergleich in SQL etwas davon wissen muss.
//
// Der Bestand kann noch unsortierte Zeilen enthalten (aus dem Massenimport
// oder von früher). Sie richten sich beim nächsten Speichern von selbst —
// und *dieser* eine Eintrag in der Historie ist dann keine Falschmeldung,
// sondern die Aufzeichnung genau dieser Berichtigung.
export const WOCHENTAGE: readonly Weekday[] = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"] as const;
const RANG = new Map(WOCHENTAGE.map((t, i) => [t, i] as const));
/**
* Die Tage in Wochenreihenfolge, ohne Dubletten.
*
* Unbekanntes wandert ans Ende statt verworfen zu werden: die Spalte ist ein
* `text[]` ohne Prüfung, und was der Massenimport einmal hineingeschrieben
* hat, soll eine Sortierung nicht stillschweigend löschen.
*/
export function sortiereWochentage<T extends string>(tage: readonly T[]): T[] {
return [...new Set(tage)].sort((a, b) => (RANG.get(a as Weekday) ?? 99) - (RANG.get(b as Weekday) ?? 99));
}