A participant list for the employee survey on honestly.de, offered as CSV and Excel right under the full data export. Columns: Personalnummer, Email, Firstname, Last Name, Language, Location, OU, OU+1, ... , Role. Language is always "de", Role always "Respondee". The org columns run bottom-up: OU is the person's own unit, OU+1 the one above, up to the top node. Their number follows the deepest chain in the file, shorter chains are padded with empty cells, and there is always at least one OU column so the file keeps its shape. It exports exactly the people the full export would, with the same filters. To guarantee that, the selection moved out of the full export's route into lib/export-auswahl.ts and both routes use it; the full export's columns are untouched. Email is employees.email, which is the private address and optional -- there is no work address in the schema. Missing addresses stay empty rather than getting a placeholder that would receive an invitation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
import type { ExportColumn } from "./export";
|
|
import { ancestorsOf, type OrgMaps } from "./org";
|
|
|
|
// Der Export für Honestly (https://www.honestly.de/), die Plattform für
|
|
// Mitarbeiterbefragungen: eine Zeile je Person, die befragt werden soll.
|
|
//
|
|
// Zwei Spalten stehen fest, statt aus den Daten zu kommen — so ist es
|
|
// vorgegeben: die Sprache ist immer Deutsch, die Rolle immer „Respondee".
|
|
// Wer anders befragt werden soll, wird in Honestly umgestellt, nicht hier.
|
|
//
|
|
// Der Organisationsbaum kommt von unten nach oben: „OU" ist die Einheit der
|
|
// Person, „OU+1" die darüber, und so weiter bis zur obersten. Die Zahl dieser
|
|
// Spalten richtet sich nach der tiefsten Kette im Export. Wer weiter oben
|
|
// hängt, hat hinten leere Zellen — und die oberste Einheit steht deshalb
|
|
// nicht bei allen in derselben Spalte. So ist es bestellt.
|
|
|
|
export const HONESTLY_SPRACHE = "de";
|
|
export const HONESTLY_ROLLE = "Respondee";
|
|
|
|
export type HonestlyZeile = {
|
|
personalnummer: number | string | null;
|
|
email: string | null;
|
|
vorname: string;
|
|
nachname: string;
|
|
standort: string;
|
|
/** Die Einheit der Person zuerst, die oberste zuletzt. */
|
|
kette: string[];
|
|
};
|
|
|
|
/**
|
|
* Die Namen der Einheiten von der eigenen bis zur obersten.
|
|
*
|
|
* ancestorsOf liefert die Wurzel zuerst; hier wird es umgedreht, weil die
|
|
* Spalten bei der eigenen Einheit anfangen.
|
|
*/
|
|
export function orgKette(maps: OrgMaps, unitId: string | null | undefined): string[] {
|
|
return ancestorsOf(maps, unitId)
|
|
.map((u) => u.name)
|
|
.reverse();
|
|
}
|
|
|
|
/** Überschrift der n-ten Organisationsspalte: OU, OU+1, OU+2, … */
|
|
export function ouUeberschrift(stufe: number): string {
|
|
return stufe === 0 ? "OU" : `OU+${stufe}`;
|
|
}
|
|
|
|
/**
|
|
* Die Spalten in der Reihenfolge der Vorgabe.
|
|
*
|
|
* Mindestens eine OU-Spalte, auch wenn niemand im Export einer Einheit
|
|
* zugeordnet ist: eine Datei, deren Spalten je nach Inhalt verschwinden,
|
|
* bricht jede Zuordnung, die auf der anderen Seite eingerichtet wurde.
|
|
*/
|
|
export function honestlySpalten(tiefe: number): ExportColumn<HonestlyZeile>[] {
|
|
const ou: ExportColumn<HonestlyZeile>[] = Array.from({ length: Math.max(1, tiefe) }, (_, stufe) => ({
|
|
header: ouUeberschrift(stufe),
|
|
get: (z) => z.kette[stufe] ?? "",
|
|
}));
|
|
return [
|
|
{ header: "Personalnummer", get: (z) => z.personalnummer },
|
|
{ header: "Email", get: (z) => z.email ?? "" },
|
|
{ header: "Firstname", get: (z) => z.vorname },
|
|
{ header: "Last Name", get: (z) => z.nachname },
|
|
{ header: "Language", get: () => HONESTLY_SPRACHE },
|
|
{ header: "Location", get: (z) => z.standort },
|
|
...ou,
|
|
{ header: "Role", get: () => HONESTLY_ROLLE },
|
|
];
|
|
}
|
|
|
|
/** Aus den Exportzeilen die Honestly-Zeilen und die Zahl der OU-Spalten. */
|
|
export function baueHonestlyZeilen(
|
|
personen: {
|
|
personnel_number: number | string | null;
|
|
email: string | null;
|
|
first_name: string;
|
|
last_name: string;
|
|
location_id: string;
|
|
org_unit_id: string | null;
|
|
}[],
|
|
maps: OrgMaps,
|
|
standortName: Map<string, string>
|
|
): { zeilen: HonestlyZeile[]; tiefe: number } {
|
|
const zeilen = personen.map((p) => ({
|
|
personalnummer: p.personnel_number,
|
|
email: p.email,
|
|
vorname: p.first_name,
|
|
nachname: p.last_name,
|
|
standort: standortName.get(p.location_id) ?? "",
|
|
kette: orgKette(maps, p.org_unit_id),
|
|
}));
|
|
return { zeilen, tiefe: Math.max(1, ...zeilen.map((z) => z.kette.length)) };
|
|
}
|