Manager ID verweist ueber die Cornerstone-ID, nicht ueber die Personalnummer
Im Feld Manager stand die Personalnummer der vorgesetzten Person -- also der Wert, der in derselben Zeile als Local System ID gefuehrt wird. Cornerstone verknuepft aber ueber die User ID. Der Verweis lief damit entweder ins Leere oder, schlimmer, auf jemand anderen, dessen User ID zufaellig aussieht wie eine Personalnummer. Leer, wenn die vorgesetzte Person selbst keine Cornerstone-ID traegt. Aus demselben Grund wie bei User ID und Username: eine Kennung der falschen Art ist schlimmer als keine, weil niemand ihr ansieht, dass sie falsch ist. Bei 435 von 785 Personen fehlt die Kennung noch -- das ist eine Luecke in der Zuordnungstabelle, kein Fehler im Export. Die Abbildung im Kontext heisst entsprechend managerKennung und traegt jetzt Zeichenketten, damit der Typ selbst keine Nummer mehr zulaesst.
This commit is contained in:
@@ -21,14 +21,14 @@ export async function GET(request: NextRequest) {
|
|||||||
const stichtag = asOf ?? todayIso();
|
const stichtag = asOf ?? todayIso();
|
||||||
|
|
||||||
const { zeilen } = await withUser(gate.userId, async (tx) => {
|
const { zeilen } = await withUser(gate.userId, async (tx) => {
|
||||||
const { rows, managerNummer } = await ladeExportMitarbeiter(tx, { asOf, filters });
|
const { rows, managerKennung } = await ladeExportMitarbeiter(tx, { asOf, filters });
|
||||||
|
|
||||||
// Die eine Angabe, die die gemeinsame Auswahl nicht mitbringt: die
|
// Die eine Angabe, die die gemeinsame Auswahl nicht mitbringt: die
|
||||||
// Kostenstelle hängt an der Planstelle, nicht an der Person. Erst jetzt,
|
// Kostenstelle hängt an der Planstelle, nicht an der Person. Erst jetzt,
|
||||||
// weil erst jetzt feststeht, um welche Planstellen es geht.
|
// weil erst jetzt feststeht, um welche Planstellen es geht.
|
||||||
const positionIds = [...new Set(rows.map((r) => r.position_id).filter((id): id is string => Boolean(id)))];
|
const positionIds = [...new Set(rows.map((r) => r.position_id).filter((id): id is string => Boolean(id)))];
|
||||||
const kontext: CornerstoneKontext = {
|
const kontext: CornerstoneKontext = {
|
||||||
managerNummer,
|
managerKennung,
|
||||||
kostenstelle: await loadKontierungen(tx, { asOf: stichtag, positionIds }),
|
kostenstelle: await loadKontierungen(tx, { asOf: stichtag, positionIds }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -178,8 +178,8 @@ export type CornerstoneQuelle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type CornerstoneKontext = {
|
export type CornerstoneKontext = {
|
||||||
/** Personalnummer je Mitarbeiterkennung — Cornerstone verweist über die User ID. */
|
/** Cornerstone-ID je Mitarbeiterkennung — der Verweis auf die vorgesetzte Person. */
|
||||||
managerNummer: Map<string, number>;
|
managerKennung: Map<string, string | null>;
|
||||||
/** Kostenstelle je Planstelle zum Stichtag. */
|
/** Kostenstelle je Planstelle zum Stichtag. */
|
||||||
kostenstelle: Map<string, Kostenstelle>;
|
kostenstelle: Map<string, Kostenstelle>;
|
||||||
};
|
};
|
||||||
@@ -209,7 +209,16 @@ export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext
|
|||||||
const nummer = String(p.personnel_number);
|
const nummer = String(p.personnel_number);
|
||||||
const adName = benutzername(p.first_name, p.last_name);
|
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;
|
// Der Verweis auf die vorgesetzte Person geht über **deren** User ID, also
|
||||||
|
// über ihre Cornerstone-ID. Hier stand die Personalnummer: die ist in dieser
|
||||||
|
// Datei die Local System ID, und Cornerstone hätte den Verweis entweder ins
|
||||||
|
// Leere laufen lassen oder — schlimmer — auf jemand anderen gelegt, dessen
|
||||||
|
// User ID zufällig so aussieht wie eine Personalnummer.
|
||||||
|
//
|
||||||
|
// Leer, wenn die vorgesetzte Person selbst keine Cornerstone-ID trägt. Aus
|
||||||
|
// demselben Grund wie oben: eine Kennung der falschen Art ist schlimmer als
|
||||||
|
// keine, weil niemand ihr ansieht, dass sie falsch ist.
|
||||||
|
const chef = p.manager_id ? k.managerKennung.get(p.manager_id) : undefined;
|
||||||
const ausgetreten = p.status === "Ausgetreten";
|
const ausgetreten = p.status === "Ausgetreten";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -222,7 +231,7 @@ export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext
|
|||||||
Suffix: p.title_suffix.join(" "),
|
Suffix: p.title_suffix.join(" "),
|
||||||
Username: kennung,
|
Username: kennung,
|
||||||
Approver: "",
|
Approver: "",
|
||||||
Manager: chef ? String(chef) : "",
|
Manager: chef ?? "",
|
||||||
Absent: "",
|
Absent: "",
|
||||||
"Allow Reconciliation": "",
|
"Allow Reconciliation": "",
|
||||||
Email: p.company_email ?? "",
|
Email: p.company_email ?? "",
|
||||||
@@ -274,6 +283,10 @@ export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext
|
|||||||
// Cornerstone-Position-ID. Sie hier einzutragen hiesse, eine Kennung des
|
// Cornerstone-Position-ID. Sie hier einzutragen hiesse, eine Kennung des
|
||||||
// Zielsystems zu erfinden.
|
// Zielsystems zu erfinden.
|
||||||
"Position ID": "",
|
"Position ID": "",
|
||||||
|
// Der Hay-Grade, so wie er gespeichert ist. Wer keinen trägt, bekommt den
|
||||||
|
// „Generic Grade" — und der ist laut der Tabelle des Kunden der
|
||||||
|
// Bindestrich, nicht die leere Zelle. Hier wird deshalb nichts übersetzt;
|
||||||
|
// siehe lib/hay-grade.ts.
|
||||||
"Grade ID": p.paygrade,
|
"Grade ID": p.paygrade,
|
||||||
"Cost Center ID": kst?.code ?? "",
|
"Cost Center ID": kst?.code ?? "",
|
||||||
// Leer: Cornerstone erwartet seine eigene Standortkennung („01"), und
|
// Leer: Cornerstone erwartet seine eigene Standortkennung („01"), und
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ export async function ladeExportMitarbeiter(
|
|||||||
lookups: OrgLookups;
|
lookups: OrgLookups;
|
||||||
orgMaps: OrgMaps;
|
orgMaps: OrgMaps;
|
||||||
managerName: Map<string, string>;
|
managerName: Map<string, string>;
|
||||||
/** Personalnummer je Kennung — für Exporte, die über Nummern verweisen. */
|
/** Cornerstone-ID je Kennung — für Exporte, die über die Kennung des Zielsystems verweisen. */
|
||||||
managerNummer: Map<string, number>;
|
managerKennung: Map<string, string | null>;
|
||||||
dependentsCounts: Map<string, number>;
|
dependentsCounts: Map<string, number>;
|
||||||
}> {
|
}> {
|
||||||
const statuses = parseStatuses(filters.status);
|
const statuses = parseStatuses(filters.status);
|
||||||
@@ -86,7 +86,9 @@ export async function ladeExportMitarbeiter(
|
|||||||
const [employees, lookupResult, allEmployees, dependentsCounts, placements, lines] = await Promise.all([
|
const [employees, lookupResult, allEmployees, dependentsCounts, placements, lines] = await Promise.all([
|
||||||
employeeQuery().execute(),
|
employeeQuery().execute(),
|
||||||
loadOrgLookups(tx),
|
loadOrgLookups(tx),
|
||||||
tx.selectFrom("employees").select(["id", "first_name", "last_name", "personnel_number"]).orderBy("id").execute(),
|
// Absichtlich schmal: die Liste umfasst **alle** Personen, nicht nur die
|
||||||
|
// gefilterten, und dient nur dazu, einen Verweis aufzulösen.
|
||||||
|
tx.selectFrom("employees").select(["id", "first_name", "last_name", "cornerstone_id"]).orderBy("id").execute(),
|
||||||
loadDependentsCounts(tx),
|
loadDependentsCounts(tx),
|
||||||
loadPlacements(tx, { asOf: stichtag }),
|
loadPlacements(tx, { asOf: stichtag }),
|
||||||
loadReportingLineMap(tx, stichtag),
|
loadReportingLineMap(tx, stichtag),
|
||||||
@@ -119,7 +121,7 @@ export async function ladeExportMitarbeiter(
|
|||||||
lookups: lookupResult.lookups,
|
lookups: lookupResult.lookups,
|
||||||
orgMaps,
|
orgMaps,
|
||||||
managerName: new Map(allEmployees.map((e) => [e.id, fmtName(e.first_name, e.last_name)])),
|
managerName: new Map(allEmployees.map((e) => [e.id, fmtName(e.first_name, e.last_name)])),
|
||||||
managerNummer: new Map(allEmployees.map((e) => [e.id, e.personnel_number])),
|
managerKennung: new Map(allEmployees.map((e) => [e.id, e.cornerstone_id])),
|
||||||
dependentsCounts,
|
dependentsCounts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ const UUID = "8f2b1c40-9d3e-4a77-b2c1-5e6f70a81234";
|
|||||||
const CS_ID = "10000382";
|
const CS_ID = "10000382";
|
||||||
|
|
||||||
const kontext: CornerstoneKontext = {
|
const kontext: CornerstoneKontext = {
|
||||||
managerNummer: new Map([["chef", 1000]]),
|
managerKennung: new Map([
|
||||||
|
["chef", "10000117"],
|
||||||
|
// Eine vorgesetzte Person, die selbst keine Cornerstone-ID trägt — bei 435
|
||||||
|
// von 785 Personen der Normalfall, nicht der Sonderfall.
|
||||||
|
["chef-ohne-kennung", null],
|
||||||
|
]),
|
||||||
kostenstelle: new Map([["pos-1", { id: "k", code: "41130", name: "Waffelproduktion" }]]),
|
kostenstelle: new Map([["pos-1", { id: "k", code: "41130", name: "Waffelproduktion" }]]),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,12 +289,20 @@ describe("Benutzername und Benutzer-ID", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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 Cornerstone-ID", () => {
|
||||||
// Cornerstone verknüpft über die User ID, nicht über den Namen.
|
// Cornerstone verknüpft über die User ID, nicht über den Namen und nicht
|
||||||
expect(zeile({ manager_id: "chef" }).Manager).toBe("1000");
|
// über die Personalnummer: die ist in dieser Datei die Local System ID.
|
||||||
|
expect(zeile({ manager_id: "chef" }).Manager).toBe("10000117");
|
||||||
expect(zeile({ manager_id: null }).Manager).toBe("");
|
expect(zeile({ manager_id: null }).Manager).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("lässt den Verweis leer, wenn die vorgesetzte Person keine Kennung trägt", () => {
|
||||||
|
// Lieber keine Angabe als eine Kennung der falschen Art: eine
|
||||||
|
// Personalnummer im Feld Manager sähe aus wie eine User ID und träfe im
|
||||||
|
// Zielsystem womöglich eine andere Person.
|
||||||
|
expect(zeile({ manager_id: "chef-ohne-kennung" }).Manager).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
it("nimmt als Kostenstelle den Code der Planstelle", () => {
|
it("nimmt als Kostenstelle den Code der Planstelle", () => {
|
||||||
expect(zeile()["Cost Center ID"]).toBe("41130");
|
expect(zeile()["Cost Center ID"]).toBe("41130");
|
||||||
expect(zeile({ position_id: null })["Cost Center ID"]).toBe("");
|
expect(zeile({ position_id: null })["Cost Center ID"]).toBe("");
|
||||||
|
|||||||
Reference in New Issue
Block a user