User ID and Username carry the Alpenwerk UUID
Both columns now hold employees.id, and they hold the same value: in Alpenwerk the user name and the user id never diverge. If they did, Cornerstone would show two identities for one person and every reference to them -- manager, reports -- could hit the wrong one. The UUID rather than the personnel number, because it is the identifier that never changes. A personnel number can be corrected, and the person would then be somebody else in Cornerstone. Local System ID keeps the personnel number as LOGA assigns it, which is what that column is for. Customfield ID AD keeps the directory account name (vorname.nachname); it is a separate thing from the Cornerstone user name and always was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -99,10 +99,13 @@ export function csDatum(iso: string | null | undefined): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der Anmeldename: vorname.nachname, klein, ohne Umlaute und Leerzeichen.
|
* Der Kontoname im Verzeichnisdienst: vorname.nachname, klein, ohne Umlaute.
|
||||||
|
*
|
||||||
|
* Nur für das Customfield AD. Der **Username** in Cornerstone ist etwas
|
||||||
|
* anderes: er ist die UUID aus Alpenwerk, dieselbe wie die User ID.
|
||||||
*
|
*
|
||||||
* Abgeleitet und nicht aus der dienstlichen Adresse geschnitten: die Adresse
|
* Abgeleitet und nicht aus der dienstlichen Adresse geschnitten: die Adresse
|
||||||
* ist freiwillig, der Name ist es nicht. Cornerstone braucht das Feld.
|
* ist freiwillig, der Name ist es nicht.
|
||||||
*/
|
*/
|
||||||
export function benutzername(vorname: string, nachname: string): string {
|
export function benutzername(vorname: string, nachname: string): string {
|
||||||
const sauber = (s: string) =>
|
const sauber = (s: string) =>
|
||||||
@@ -182,21 +185,36 @@ export type CornerstoneKontext = {
|
|||||||
|
|
||||||
/** Eine Zeile — die Zuordnung selbst, ohne Datenbank und ohne Dateiformat. */
|
/** Eine Zeile — die Zuordnung selbst, ohne Datenbank und ohne Dateiformat. */
|
||||||
export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext): CornerstoneZeile {
|
export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext): CornerstoneZeile {
|
||||||
const nummer = p.personnel_number;
|
// Zwei Kennungen, zwei Herkünfte:
|
||||||
const name = benutzername(p.first_name, p.last_name);
|
//
|
||||||
|
// User ID + Username die UUID aus Alpenwerk (employees.id)
|
||||||
|
// Local System ID die Personalnummer, wie LOGA sie vergibt
|
||||||
|
//
|
||||||
|
// Benutzername und Benutzer-ID sind in Alpenwerk stets derselbe Wert —
|
||||||
|
// liefen sie auseinander, zeigte Cornerstone zwei Kennungen für eine
|
||||||
|
// Person, und jeder Verweis darauf träfe womöglich die falsche.
|
||||||
|
//
|
||||||
|
// Die UUID und nicht die Personalnummer, weil sie die Kennung ist, die
|
||||||
|
// sich nie ändert: eine Personalnummer kann berichtigt werden, und dann
|
||||||
|
// wäre die Person in Cornerstone eine andere.
|
||||||
|
//
|
||||||
|
// Der Anmeldename des Verzeichnisdienstes steht nur im Customfield AD.
|
||||||
|
const kennung = p.id;
|
||||||
|
const nummer = String(p.personnel_number);
|
||||||
|
const adName = benutzername(p.first_name, p.last_name);
|
||||||
const kst = p.position_id ? k.kostenstelle.get(p.position_id) : undefined;
|
const kst = p.position_id ? k.kostenstelle.get(p.position_id) : undefined;
|
||||||
const chef = p.manager_id ? k.managerNummer.get(p.manager_id) : undefined;
|
const chef = p.manager_id ? k.managerNummer.get(p.manager_id) : undefined;
|
||||||
const ausgetreten = p.status === "Ausgetreten";
|
const ausgetreten = p.status === "Ausgetreten";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"User ID": String(nummer),
|
"User ID": kennung,
|
||||||
"Local System ID": String(nummer),
|
"Local System ID": nummer,
|
||||||
Prefix: p.title_prefix.join(" "),
|
Prefix: p.title_prefix.join(" "),
|
||||||
"First Name": p.first_name,
|
"First Name": p.first_name,
|
||||||
"Middle Name": "",
|
"Middle Name": "",
|
||||||
"Last Name": p.last_name,
|
"Last Name": p.last_name,
|
||||||
Suffix: p.title_suffix.join(" "),
|
Suffix: p.title_suffix.join(" "),
|
||||||
Username: name,
|
Username: kennung,
|
||||||
Approver: "",
|
Approver: "",
|
||||||
Manager: chef ? String(chef) : "",
|
Manager: chef ? String(chef) : "",
|
||||||
Absent: "",
|
Absent: "",
|
||||||
@@ -261,7 +279,7 @@ export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext
|
|||||||
// Kopfzeile, weil die Vorlage sie führt.
|
// Kopfzeile, weil die Vorlage sie führt.
|
||||||
"Retention Rules": "",
|
"Retention Rules": "",
|
||||||
Organisationsstufe: "",
|
Organisationsstufe: "",
|
||||||
"Customfield ID AD": name,
|
"Customfield ID AD": adName,
|
||||||
"Customfield ID Doxis": `${DOXIS_PRAEFIX}${nummer}`,
|
"Customfield ID Doxis": `${DOXIS_PRAEFIX}${nummer}`,
|
||||||
"Customfield ID Interflex": String(nummer),
|
"Customfield ID Interflex": String(nummer),
|
||||||
"Customfield ID LGVplus": `${LGV_PRAEFIX}${nummer}`,
|
"Customfield ID LGVplus": `${LGV_PRAEFIX}${nummer}`,
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import { toCsv } from "@/lib/export";
|
|||||||
const VORGABE =
|
const VORGABE =
|
||||||
"User ID,Local System ID,Prefix,First Name,Middle Name,Last Name,Suffix,Username,Approver,Manager,Absent,Allow Reconciliation,Email,Required Training Approvals,Gender,Exempt,Ethnicity,Months of Service,Compensation Currency,Address Line 1,Address Line 2,City,State,Zip,Mailstop,Country,Original Hire Date,Last Hire Date,Phone,Mobile,Home Phone,Fax,Status,User activation date,User de-activation date,Personal Email,Language,Time Zone,User Type,Employment Status,Leave Reason,Termination Type,Termination Reason,Termination Date,Eligible For Rehire,Rehired Employee,Division ID,Position ID,Grade ID,Cost Center ID,Location ID,Retention Rules,Organisationsstufe,Customfield ID AD,Customfield ID Doxis,Customfield ID Interflex,Customfield ID LGVplus";
|
"User ID,Local System ID,Prefix,First Name,Middle Name,Last Name,Suffix,Username,Approver,Manager,Absent,Allow Reconciliation,Email,Required Training Approvals,Gender,Exempt,Ethnicity,Months of Service,Compensation Currency,Address Line 1,Address Line 2,City,State,Zip,Mailstop,Country,Original Hire Date,Last Hire Date,Phone,Mobile,Home Phone,Fax,Status,User activation date,User de-activation date,Personal Email,Language,Time Zone,User Type,Employment Status,Leave Reason,Termination Type,Termination Reason,Termination Date,Eligible For Rehire,Rehired Employee,Division ID,Position ID,Grade ID,Cost Center ID,Location ID,Retention Rules,Organisationsstufe,Customfield ID AD,Customfield ID Doxis,Customfield ID Interflex,Customfield ID LGVplus";
|
||||||
|
|
||||||
|
const UUID = "8f2b1c40-9d3e-4a77-b2c1-5e6f70a81234";
|
||||||
|
|
||||||
const kontext: CornerstoneKontext = {
|
const kontext: CornerstoneKontext = {
|
||||||
managerNummer: new Map([["chef", 1000]]),
|
managerNummer: new Map([["chef", 1000]]),
|
||||||
kostenstelle: new Map([["pos-1", { id: "k", code: "41130", name: "Waffelproduktion" }]]),
|
kostenstelle: new Map([["pos-1", { id: "k", code: "41130", name: "Waffelproduktion" }]]),
|
||||||
@@ -37,7 +39,7 @@ const kontext: CornerstoneKontext = {
|
|||||||
|
|
||||||
function person(teil: Partial<CornerstoneQuelle> = {}): CornerstoneQuelle {
|
function person(teil: Partial<CornerstoneQuelle> = {}): CornerstoneQuelle {
|
||||||
return {
|
return {
|
||||||
id: "e1",
|
id: UUID,
|
||||||
personnel_number: 9002,
|
personnel_number: 9002,
|
||||||
first_name: "Max",
|
first_name: "Max",
|
||||||
last_name: "Alpentest",
|
last_name: "Alpentest",
|
||||||
@@ -140,7 +142,7 @@ describe("Eine Zeile gegen das Muster vom 27.09.2026", () => {
|
|||||||
"Local System ID": "9002",
|
"Local System ID": "9002",
|
||||||
"First Name": "Max",
|
"First Name": "Max",
|
||||||
"Last Name": "Alpentest",
|
"Last Name": "Alpentest",
|
||||||
Username: "max.alpentest",
|
Username: UUID,
|
||||||
Email: "m.test@alpenwerk.test.com",
|
Email: "m.test@alpenwerk.test.com",
|
||||||
Gender: "male",
|
Gender: "male",
|
||||||
"Compensation Currency": "EUR",
|
"Compensation Currency": "EUR",
|
||||||
@@ -235,6 +237,36 @@ describe("Abhängige Felder", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Benutzername und Benutzer-ID", () => {
|
||||||
|
it("tragen denselben Wert", () => {
|
||||||
|
// In Alpenwerk sind die beiden stets im Gleichklang. Liefen sie
|
||||||
|
// auseinander, zeigte Cornerstone zwei Kennungen für eine Person, und
|
||||||
|
// jeder Verweis darauf (Manager, Berichte) träfe womöglich die falsche.
|
||||||
|
const z = zeile();
|
||||||
|
expect(z.Username).toBe(z["User ID"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sind die UUID aus Alpenwerk, nicht die Personalnummer", () => {
|
||||||
|
// Die UUID aendert sich nie; eine Personalnummer kann berichtigt werden,
|
||||||
|
// und dann waere die Person in Cornerstone eine andere.
|
||||||
|
const z = zeile();
|
||||||
|
expect(z["User ID"]).toBe(UUID);
|
||||||
|
expect(z.Username).toBe(UUID);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fuehren die LOGA-Personalnummer in Local System ID", () => {
|
||||||
|
expect(zeile()["Local System ID"]).toBe("9002");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("lassen den Anmeldenamen im Customfield AD stehen", () => {
|
||||||
|
// Der Verzeichnisdienst kennt die Person unter vorname.nachname; das ist
|
||||||
|
// nicht der Username in Cornerstone, sondern eine eigene Angabe.
|
||||||
|
const z = zeile();
|
||||||
|
expect(z["Customfield ID AD"]).toBe("max.alpentest");
|
||||||
|
expect(z["Customfield ID AD"]).not.toBe(z.Username);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Kennungen statt Namen", () => {
|
describe("Kennungen statt Namen", () => {
|
||||||
it("verweist auf die vorgesetzte Person über deren Personalnummer", () => {
|
it("verweist auf die vorgesetzte Person über deren Personalnummer", () => {
|
||||||
// Cornerstone verknüpft über die User ID, nicht über den Namen.
|
// Cornerstone verknüpft über die User ID, nicht über den Namen.
|
||||||
|
|||||||
Reference in New Issue
Block a user