Files
alpenwerk-hr/lib/checklist-print.ts
Maximilian Stubhan 19e3170b00
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 6m6s
CI / Integrationstests (echtes Postgres) (push) Failing after 5m22s
Print a checklist without printing the application around it
The checklist gets a print view: one A4 page, header carrying personnel
number, name, date of extract, entry date and position, the items in two
flowing columns so twenty-five fit, and two signature lines at the foot.
It prints the *state*, dated — not a blank form.

The default filename in the save dialog comes from the document title,
which is set to the agreed convention:

  20260818_2884_Aigner-Manuel_Onboarding-Checklist

Surname first, like everywhere else in the application, so a folder of
these sorts by person and within a person by date. Umlauts are resolved
rather than stripped: the naive route (NFKD, then every non-ASCII to a
dash) turns "Müller" into "Mu-ller", because decomposition splits the
umlaut and the diaeresis becomes the dash. "Weiß" needs its own rule —
it has no decomposition and would otherwise vanish.

The reported defect: the printout carried the application's own top bar
— hamburger, bell, "Neueinstellung", sign-out. Those are controls; on
paper they are decoration, and on a checklist filed in a personnel
record, misleading. The rule now sits on AppShell rather than on this
one page, so the org-chart print view — which had the same problem —
gets it too, along with anything printed later. The shell's padding goes
with it: the type area is set by @page on the print page itself, and the
shell's would have been added on top.

The browser's own header line (date, title, URL) is separate — that is a
checkbox in the print dialog, not something CSS can reach.

440 tests pass, including 13 new ones pinning the filename convention.
Not yet seen in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 20:19:58 +02:00

66 lines
2.6 KiB
TypeScript

// Namensgebung und Kopfdaten für den Ausdruck einer Checkliste.
//
// Reine Funktionen, ohne Datenbank und ohne React — damit die Konvention
// geprüft werden kann, ohne eine Seite zu bauen.
export type ChecklistArt = "onboarding" | "offboarding";
export const CHECKLIST_ARTEN: readonly ChecklistArt[] = ["onboarding", "offboarding"];
export function parseArt(wert: string | undefined): ChecklistArt {
return wert === "offboarding" ? "offboarding" : "onboarding";
}
/** Wie die Liste im Titel und im Dateinamen heisst. */
export function artLabel(art: ChecklistArt): string {
return art === "offboarding" ? "Offboarding-Checklist" : "Onboarding-Checklist";
}
export function artTitel(art: ChecklistArt): string {
return art === "offboarding" ? "Offboarding-Checkliste" : "Onboarding-Checkliste";
}
/**
* Umlaute und Sonderzeichen in etwas, das in jedem Dateisystem und in jedem
* Skript überlebt.
*
* Ein blosses NFKD mit anschliessendem Ersetzen aller Nicht-ASCII-Zeichen
* würde aus „Müller" ein „Mu-ller" machen: die Zerlegung trennt den Umlaut in
* `u` + kombinierenden Trema, und der Trema wird dann zum Trennstrich. Deshalb
* werden die kombinierenden Zeichen ausdrücklich entfernt, nicht ersetzt — und
* das scharfe S vorher zu „ss", weil es keine Zerlegung hat.
*/
export function dateiSicher(text: string): string {
return text
.replace(/ß/g, "ss")
.normalize("NFKD")
// \p{M} statt eines Zeichenbereichs: die kombinierenden Zeichen sind
// unsichtbar, und ein als Literal geschriebener Bereich könnte bei einem
// Kodierungswechsel stillschweigend kaputtgehen — dann fiele die Zeile aus
// und die nächste machte aus „Müller" ein „Mu-ller", ohne zu scheitern.
.replace(/\p{M}/gu, "")
.replace(/[^a-zA-Z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
/**
* `YYYYMMDD_PersNummer_Name_Onboarding-Checklist`
*
* Der Name steht als `Nachname-Vorname` darin, in derselben Reihenfolge, in
* der die Anwendung ihn überall anzeigt — so sortiert sich ein Ordner voller
* solcher Dateien von selbst nach Person, und innerhalb einer Person nach
* Datum, weil das Datum vorne steht.
*
* Das Datum ist das des Auszugs, nicht des Eintritts: der Dateiname sagt,
* wann dieser Stand gezogen wurde.
*/
export function checklistDateiname(
art: ChecklistArt,
person: { personnel_number: number; first_name: string; last_name: string },
auszugsdatum: string
): string {
const datum = auszugsdatum.slice(0, 10).replace(/-/g, "");
const name = dateiSicher(`${person.last_name}-${person.first_name}`);
return `${datum}_${person.personnel_number}_${name}_${artLabel(art)}`;
}