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>
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import type { AufgabenStand } from "./checklist";
|
|
import type { ChecklistArt } from "./checklist-print";
|
|
import type { Tx } from "./db";
|
|
import { jsonArrayFrom, jsonObjectFrom, zeitstempel } from "./db/json";
|
|
import { todayIso } from "./format";
|
|
import { besetzungenAbfrage, orgAsOf, pickPlacements } from "./placement";
|
|
|
|
// Was der Ausdruck einer Checkliste braucht — und nur das.
|
|
//
|
|
// Nicht loadEmployeeDetail: das holt offene Planstellen, Berichtslinien, den
|
|
// Organisationsbaum und die Historie, wovon der Ausdruck nichts zeigt. Eine
|
|
// eigene, schmale Abfrage in einer Rundreise (lib/db/json.ts).
|
|
|
|
export type ChecklistKopf = {
|
|
personnel_number: number;
|
|
first_name: string;
|
|
last_name: string;
|
|
entry_date: string;
|
|
/** Die Bezeichnung der Person; kann von der Planstelle abweichen. */
|
|
job_title: string;
|
|
/** Aus der laufenden Besetzung — bei einer Person ohne Planstelle leer. */
|
|
position_number: string | null;
|
|
position_title: string | null;
|
|
};
|
|
|
|
export type ChecklistDruckDaten = {
|
|
kopf: ChecklistKopf;
|
|
staende: AufgabenStand[];
|
|
};
|
|
|
|
export async function loadChecklistPrint(
|
|
tx: Tx,
|
|
employeeId: string,
|
|
art: ChecklistArt,
|
|
today: string = todayIso()
|
|
): Promise<ChecklistDruckDaten | null> {
|
|
const tabelle = art === "offboarding" ? "offboarding_tasks" : "onboarding_tasks";
|
|
|
|
const gelesen = await tx
|
|
.selectNoFrom((eb) => [
|
|
jsonObjectFrom(
|
|
eb
|
|
.selectFrom("employees")
|
|
.select(["personnel_number", "first_name", "last_name", "entry_date", "exit_date", "job_title"])
|
|
.where("id", "=", employeeId)
|
|
).as("person"),
|
|
jsonArrayFrom(besetzungenAbfrage(eb, [employeeId])).as("placementRows"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom(tabelle)
|
|
.select(["item_key", "erledigt", "wert", "kommentar", "updated_by_name"])
|
|
.select((x) => zeitstempel(x.ref("updated_at")).as("updated_at"))
|
|
.where("employee_id", "=", employeeId)
|
|
).as("staende"),
|
|
])
|
|
.executeTakeFirstOrThrow();
|
|
|
|
const person = gelesen.person;
|
|
if (!person) return null;
|
|
|
|
// Derselbe Stichtag wie in der Akte: bei einer ausgetretenen Person ist die
|
|
// Besetzung am Austrittstag beendet, und zu heute gefragt käme keine heraus
|
|
// — der Kopf stünde dann ohne Planstelle da, obwohl es eine gab.
|
|
const asOf = orgAsOf(person, today);
|
|
const placement = pickPlacements(gelesen.placementRows as never, asOf).get(employeeId) ?? null;
|
|
|
|
return {
|
|
kopf: {
|
|
personnel_number: person.personnel_number,
|
|
first_name: person.first_name,
|
|
last_name: person.last_name,
|
|
entry_date: person.entry_date,
|
|
job_title: person.job_title,
|
|
position_number: placement?.positionNumber ?? null,
|
|
position_title: placement?.jobTitle ?? null,
|
|
},
|
|
staende: gelesen.staende as AufgabenStand[],
|
|
};
|
|
}
|