The list existed on paper: one printed sheet per entry, twenty-five
boxes. What is on it is known only to whoever holds the sheet — it
cannot be searched, cannot be covered for while someone is away, and
says nothing about who ticked what.
Not every box on the sheet is a checkbox, and the differences carry
meaning, so the field kind is derived from the thing rather than
flattened:
Haken — the normal case. The Meldezettel is there or it is not.
Ja/Nein — Prämienanspruch had *two* boxes on the sheet, and that is
not decoration: "nein" is a finding, "not asked yet" is not.
One checkbox cannot say both.
Text — shoe, shirt and trouser size. The value is the point;
ticked off it would be worthless.
Every item takes a comment, and every item records who last touched it
and when — the part the sheet could never do.
Saved on click, not on submit. A checklist is worked through over days,
between other things; a save button at the end is where half a morning
goes missing.
The items live in lib/onboarding.ts, not in a table: a checklist is a
company process, not a master record. Stored per person is only the
answer, under the item's key — so an item dropped later leaves its old
answers standing instead of taking them along, and a file from back then
stays readable.
A list is created by hire and rehire, in the same transaction as the
hire itself: a hire without a checklist would be a half-recorded hire.
Rehire only adds what is missing and never clears an old tick — what
genuinely has to be redone is HR's call, and a program deciding it would
be guessing. People hired before this feature have no list and get a
button to start one.
Checked against the real database end to end: hire creates 25 open
items; checkbox, ja/nein, size and comment all land; a comment-only edit
leaves the tick alone; rehire tops the list up and keeps what was done.
The probe employee was removed afterwards — audit rows first, since the
log has no delete policy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
56 lines
2.0 KiB
TypeScript
56 lines
2.0 KiB
TypeScript
import { notFound } from "next/navigation";
|
|
import { EmployeeDetail } from "@/components/employees/EmployeeDetail";
|
|
import { currentUserId } from "@/lib/auth/session";
|
|
import { withUser } from "@/lib/db";
|
|
import { todayIso } from "@/lib/format";
|
|
import { breadcrumbLabel } from "@/lib/org";
|
|
import { loadEmployeeDetail } from "@/lib/employee-detail-data";
|
|
|
|
type PageProps = { params: Promise<{ id: string }> };
|
|
|
|
export default async function EmployeeDetailPage({ params }: PageProps) {
|
|
const { id } = await params;
|
|
const today = todayIso();
|
|
|
|
// Was die Akte liest und in wie vielen Rundreisen, steht in
|
|
// lib/employee-detail-data.ts.
|
|
const data = await withUser(await currentUserId(), (tx) => loadEmployeeDetail(tx, id, today));
|
|
|
|
if (!data) notFound();
|
|
const { employee, line, reports, history, dependents, notes, orgMaps, placement, kostenstelle, onboarding, openPositions, byId } = data;
|
|
|
|
return (
|
|
<EmployeeDetail
|
|
employee={employee}
|
|
placement={
|
|
placement && {
|
|
positionNumber: placement.positionNumber,
|
|
jobTitle: placement.jobTitle,
|
|
isChief: placement.isChief,
|
|
current: placement.current,
|
|
}
|
|
}
|
|
breadcrumb={breadcrumbLabel(orgMaps, placement?.orgUnitId)}
|
|
kostenstelle={kostenstelle}
|
|
onboarding={onboarding}
|
|
manager={(line?.acting_manager_id ? byId.get(line.acting_manager_id) : null) ?? null}
|
|
// Nur wenn eine Vertretung im Spiel ist — sonst stünde dieselbe Person
|
|
// zweimal da.
|
|
formalManager={
|
|
line?.formal_manager_id && line.formal_manager_id !== line.acting_manager_id
|
|
? (byId.get(line.formal_manager_id) ?? null)
|
|
: null
|
|
}
|
|
directReports={reports.flatMap((r) => {
|
|
const e = byId.get(r.employee_id);
|
|
return e ? [e] : [];
|
|
})}
|
|
history={history ?? []}
|
|
dependents={dependents ?? []}
|
|
notes={notes ?? []}
|
|
locations={orgMaps.locationList}
|
|
openPositions={openPositions}
|
|
/>
|
|
);
|
|
}
|