Files
alpenwerk-hr/lib/lolyo.ts
Andrei Laas 771020af72
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m21s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m10s
LOLYO-Export: typografische Zeichen auf ASCII
Der Gedankenstrich kam im Zielsystem als "–" an -- seine UTF-8-Bytes, gelesen
als Windows-1252. Nachgemessen hat die Vorlage des Anbieters dieselbe Kodierung
wie unsere Datei: UTF-8 ohne BOM, zu sehen an dem "bestätigt" in ihrer
Kopfzeile. Die Ursache liegt also nicht in dem, was wir schreiben, und solange
sie nicht geklaert ist, geht der Weg ueber das Zeichen selbst: ein Bindestrich
sagt dasselbe und ist in beiden Kodierungen dasselbe Byte.

Gilt fuer jede Zelle aus den Daten, nicht nur fuer die Position -- aufgefallen
ist es dort, weil die Taetigkeiten einen Gedankenstrich fuehren, aber dieselben
Zeichen stehen auch in Namen und Titeln.

Zwei Ausnahmen: Umlaute bleiben, weil "Baeckerei" ein Problem verstecken wuerde,
das dann auch Namen betraefe. Und das Passwort bleibt Zeichen fuer Zeichen, wie
es ist -- ein ersetztes Zeichen faellt niemandem auf, es gibt dann nur ein
Konto, in das niemand hineinkommt.
2026-09-28 14:05:41 +02:00

159 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { CsvForm, ExportColumn } from "./export";
// Der Export für LOLYO, die Mitarbeiter-App: eine Zeile je Person, die dort
// einen Zugang bekommen soll.
//
// Diese Datei tut etwas anderes als die beiden Exporte daneben. Honestly lädt
// zu einer Befragung ein, Cornerstone führt Stammdaten nach — hier entstehen
// **Konten**, samt Passwort in der Zeile. Wer die Datei hat, hat damit einen
// Zugang zu jedem Konto darin, solange niemand sein Passwort gewechselt hat.
// Deshalb steht das Passwort nicht in dieser Datei: die Route reicht es herein
// (app/api/export/lolyo/route.ts), und es gelangt damit nie in ein Bündel, das
// der Browser lädt. Dieselbe Überlegung steht in lib/passwort.ts.
//
// Kopfzeile und Trennzeichen kommen aus der Vorlage des Anbieters, Zeichen für
// Zeichen. Die gemischte Schreibweise — „Titel prefix" deutsch, „Title Suffix"
// englisch — steht so darin; sie hier stillschweigend zu vereinheitlichen
// hiesse, eine Spalte zu liefern, die LOLYO nicht zuordnet.
/**
* Semikolon, kein BOM, kein Formelschutz — so liest LOLYO die Datei.
*
* Die Vorgabe von toCsv ist für Excel gemacht. Das BOM hinge unsichtbar am
* Namen der ersten Spalte, und „Benutzer/Code" käme beim Einlesen in keiner
* Zuordnung mehr vor.
*
* Der Formelschutz stellt jedem Wert, der mit `=`, `+`, `-` oder `@` beginnt,
* ein Hochkomma voran, damit Excel ihn nicht als Formel rechnet. Hier wäre das
* falsch: **jede** österreichische Telefonnummer beginnt mit „+", und in LOLYO
* stünde danach `'+4366…` als Nummer — in jedem Konto, und niemandem fiele es
* beim Erzeugen auf. Die Datei liest eine Maschine, kein Mensch in Excel.
*/
export const LOLYO_CSV: CsvForm = { trenner: ";", bom: false, formelschutz: false };
/** Für alle dieselbe. Wer eine andere braucht, stellt sie in LOLYO um, nicht hier. */
export const LOLYO_SPRACHE = "DE";
/**
* Die Gruppe, in die alle kommen.
*
* Fest vorgegeben, nicht aus der Organisationseinheit abgeleitet: LOLYO-Gruppen
* sind Verteiler in der App und nicht die Aufbauorganisation. Beides
* gleichzusetzen erzeugte 78 Gruppen, die dort niemand bestellt hat.
*/
export const LOLYO_GRUPPE = "Demo-Gruppe";
/** LOLYO holt die Bestätigung selbst ein; aus Alpenwerk kommt sie nie bestätigt. */
export const LOLYO_EMAIL_BESTAETIGT = "0";
/**
* Die Anrede aus dem Geschlecht.
*
* Alpenwerk kennt genau zwei Werte (gender_type), und die Vorgabe nennt genau
* zwei Anreden. Ein dritter Wert bliebe hier leer statt geraten — eine falsche
* Anrede steht in jeder Nachricht, die die App verschickt.
*/
export function anrede(gender: string): string {
if (gender === "m") return "Herr";
if (gender === "w") return "Frau";
return "";
}
/**
* Typografische Zeichen auf ihre ASCII-Entsprechung.
*
* Im Zielsystem kam der Gedankenstrich „–" als `–` an — das sind seine
* UTF-8-Bytes, gelesen als Windows-1252. Gemessen hat die Vorlage des Anbieters
* dieselbe Kodierung wie unsere Datei (UTF-8 ohne BOM, nachgeprüft am
* „bestätigt" in ihrer Kopfzeile), die Ursache liegt also nicht in dem, was wir
* schreiben. Solange sie nicht geklärt ist, geht der Weg über das Zeichen
* selbst: ein Bindestrich sagt dasselbe und überlebt jede Fehlinterpretation,
* weil er in beiden Kodierungen dasselbe Byte ist.
*
* Umlaute bleiben. „Bäckerei" zu „Baeckerei" zu machen hiesse, ein Problem zu
* verstecken, das es entweder gar nicht gibt — oder das dann auch Namen
* betrifft, und dort wäre es keine Kosmetik mehr.
*/
export function ohneTypografie(text: string): string {
return text
.replace(/[‐-―−]/g, "-")
.replace(/[‘’‚‛′]/g, "'")
.replace(/[“”„‟″]/g, '"')
.replace(/…/g, "...")
.replace(/[   ]/g, " ");
}
/** Die Person, so wie der Export sie braucht. */
export type LolyoQuelle = {
id: string;
company_email: string | null;
title_prefix: string[];
title_suffix: string[];
first_name: string;
last_name: string;
job_title: string;
phone: string | null;
gender: string;
};
export type LolyoZeile = Record<string, string>;
/**
* Eine Zeile.
*
* Der Benutzercode ist die Alpenwerk-UUID. Anders als bei Cornerstone ist das
* hier richtig: LOLYO kennt die Person noch nicht und vergibt keine eigene
* Kennung, die wir treffen müssten — die UUID ist der Schlüssel, über den sich
* ein späterer Abgleich wieder auf dieselbe Person bezieht.
*
* Die E-Mail-Spalte trägt die **Firmenadresse** und niemals die private aus
* employees.email: die Datei legt Konten bei einem fremden Anbieter an.
*/
export function baueLolyoZeile(p: LolyoQuelle, passwort: string): LolyoZeile {
// ohneTypografie steht an jeder Zelle, die aus den Daten kommt, und nicht nur
// an der Position: aufgefallen ist es dort, weil die Tätigkeiten einen
// Gedankenstrich führen — dieselben Zeichen stehen aber auch in Namen und
// Titeln, und die fielen erst bei der Person auf, die einen trägt.
const aus = (wert: string) => ohneTypografie(wert);
return {
"Benutzer/Code": p.id,
Email: aus(p.company_email ?? ""),
// **Ohne** Umschrift: das Passwort muss Zeichen für Zeichen das sein, was
// die Person eingibt. Ein ersetztes Zeichen fiele niemandem auf — es gäbe
// nur ein Konto, in das niemand hineinkommt.
Passwort: passwort,
"Titel prefix": aus(p.title_prefix.join(" ")),
Vorname: aus(p.first_name),
Nachname: aus(p.last_name),
"Title Suffix": aus(p.title_suffix.join(" ")),
Position: aus(p.job_title),
Gruppen: LOLYO_GRUPPE,
Telefon: aus(p.phone ?? ""),
Anrede: anrede(p.gender),
Sprache: LOLYO_SPRACHE,
"E-Mail bestätigt": LOLYO_EMAIL_BESTAETIGT,
};
}
/** Die Spalten in der Reihenfolge der Vorlage. */
export const LOLYO_SPALTEN: readonly string[] = [
"Benutzer/Code",
"Email",
"Passwort",
"Titel prefix",
"Vorname",
"Nachname",
"Title Suffix",
"Position",
"Gruppen",
"Telefon",
"Anrede",
"Sprache",
"E-Mail bestätigt",
];
export function lolyoSpalten(): ExportColumn<LolyoZeile>[] {
return LOLYO_SPALTEN.map((header) => ({ header, get: (z) => z[header] ?? "" }));
}