Files
alpenwerk-hr/components/hire/StepSummary.tsx
Maximilian Stubhan f91a69147e Phase 3: Hire wizard, draft resume, and two real SQL bugfixes
- components/hire/: 4-step Hire Wizard (Person/Position/Vertrag/
  Zusammenfassung) matching sec4.4, with a HireWizardProvider context so it
  can be opened both from the global "+ Neueinstellung" button and from a
  "Fortsetzen" link on a saved draft.
- actions/hireDrafts.ts: save/delete hire_drafts (owner-scoped RLS already
  in place from Phase 1). Dashboard now shows the "Entwuerfe" card the
  Phase 1 plan deferred, since the wizard it depends on now exists.
- lib/positions.ts: shared open-positions loader (position number, org
  breadcrumb, resolved manager name) used by both the wizard and (later)
  the Positions page.

Two real bugs found via live testing and fixed in supabase/functions.sql:
1. hire_employee/rehire_employee: a two-branch CASE returning bare string
   literals defaults to `text`, not the target enum, so `status = case
   when ... then 'Geplant' else 'Aktiv' end` failed against the
   employment_status column. Fixed with an explicit ::employment_status
   cast on the whole CASE expression.
2. Postgres precedence gotcha: ->> and || sit at the *same* precedence
   tier and left-associate, so `payload->>'first_name' || ' ' ||
   payload->>'last_name'` does not group the way it reads - it tries to
   apply ->> to an intermediate text value and fails with "operator does
   not exist: text ->> unknown". Fixed by parenthesizing every ->>'...'
   expression that participates in a || chain.

Also fixed: hire_employee referenced v_position.title outside the branch
that assigns v_position, raising "record not assigned" whenever a hire
wasn't tied to a position_id; extracted a v_job_title variable instead.

Verified live end-to-end: wizard search -> select position -> submit
creates the employee, closes the position, and writes matching
employee_history + audit_log rows atomically.
2026-07-13 22:37:59 +02:00

57 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { fmtDate, fmtEUR } from "@/lib/format";
import type { OpenPositionResolved } from "@/lib/positions";
import type { HireDraftData } from "./types";
const PAYGRADE_LABELS: Record<string, string> = {
A: "A Einstieg",
B: "B Qualifiziert",
C: "C Erfahren",
D: "D Spezialist:in",
E: "E Teamleitung",
F: "F Bereichsleitung / GF",
};
type StepSummaryProps = {
draft: HireDraftData;
selectedPosition: OpenPositionResolved | null;
locations: { id: string; name: string; country: string }[];
};
export function StepSummary({ draft, selectedPosition, locations }: StepSummaryProps) {
const location = locations.find((l) => l.id === draft.locationId);
const rows: [string, string][] = [
["Name", `${draft.firstName} ${draft.lastName}`],
["Geschlecht", draft.gender === "m" ? "männlich" : "weiblich"],
["Geburtsdatum", fmtDate(draft.birthDate)],
["SV-Nummer", draft.svNummer || ""],
["E-Mail (privat)", draft.email || ""],
["Telefon", draft.phone || ""],
["Standort", location ? `${location.name} (${location.country})` : ""],
["Position", selectedPosition ? `${selectedPosition.title} (${selectedPosition.position_number})` : ""],
["Organisationseinheit", selectedPosition?.orgLabel ?? ""],
["Führungskraft", selectedPosition?.managerName ?? ""],
["Besetzung", draft.besetzung],
["Eintrittsdatum", fmtDate(draft.entryDate)],
["Vertragsart", draft.contractType === "befristet" ? `befristet bis ${fmtDate(draft.contractEndDate)}` : "unbefristet"],
["Beschäftigungsausmaß", `${draft.employmentType} (${draft.weeklyHours} h)`],
["Bruttogehalt (14x/Jahr)", fmtEUR(Number(draft.salary))],
["Paygrade", PAYGRADE_LABELS[draft.paygrade]],
];
return (
<div className="flex flex-col gap-4">
<dl className="grid grid-cols-2 gap-x-6 gap-y-3">
{rows.map(([label, value]) => (
<div key={label}>
<dt className="text-xs font-semibold uppercase tracking-wide text-ink-muted">{label}</dt>
<dd className="mt-0.5 text-sm text-ink">{value}</dd>
</div>
))}
</dl>
<p className="rounded bg-info-bg px-3 py-2 text-sm text-info-text">
Personalnummer und Firmen-E-Mail-Adresse werden automatisch vergeben.
</p>
</div>
);
}