Second slice of Phase 3 (stacked on 3a candidates). The gated 7-stage recruitment pipeline per Crewing-Implementation-Spec §5.1/§8.4–8.5/§8.13. Behind NEXT_PUBLIC_CREWING_ENABLED; production unchanged. What's in - Schema (crewing_pipeline migration): Application (one per requisition+candidate) + 7-stage ApplicationStage; ApplicationGate (SALARY/SELECTION/WAIVER pending = Manager queue items); ReferenceCheck; effective-dated SalaryStructure (attached to the Application now, bound to the assignment in 3c); minimal BankDetail/EpfDetail captured at DOC_VERIFICATION (PII encryption deferred to Phase 4). CrewAction += applicationId; pipeline CrewActionTypes. - State machine: lib/application-pipeline.ts — sourcing advances MPO/Manager; approve_salary + select are Manager-only; orthogonal canReject; BOARD_STAGES. - Actions: addApplication (first candidate → requisition SHORTLISTING), advanceStage, recordReferenceCheck, verifyDocuments (bank/EPF), agreeSalary→approveSalary/returnSalary, recordInterviewResult, requestInterviewWaiver→approve/decline, selectCandidate (→ requisition SELECTED)/returnSelection, rejectApplication. Waiver never automatic (R2). Notifications SALARY/SELECTION/WAIVER + CANDIDATE_PROPOSED. - Screens: pipeline board per requisition (7 columns + Add candidate); application workhorse (7-step stepper + adaptive per-stage action card); "Open pipeline" on the requisition detail. Central /approvals gains a crewing section (inline Approve/Return) for one unified Manager queue (§8.13 R8). Tests & docs - Unit: application-pipeline.test.ts (9). Integration: applications.test.ts (10) — full happy path, salary/selection/waiver approvals + Manager-only gating, failed interview, reject, site-staff lockout. type-check clean; full unit (234) + integration (163) green. - CLAUDE.md "Crewing" updated with the Phase 3b surface. Deferred: onboarding (Epic D, Phase 3c) — SELECTED → ONBOARDED, CrewAssignment, employeeId, requisition → FILLED, salary bound to the assignment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
1.4 KiB
TypeScript
47 lines
1.4 KiB
TypeScript
import type { ApplicationStage } from "@prisma/client";
|
||
import type { BadgeProps } from "@/components/ui/badge";
|
||
|
||
type Variant = NonNullable<BadgeProps["variant"]>;
|
||
|
||
// The 7 board columns in order (mirrors lib/application-pipeline BOARD_STAGES;
|
||
// kept here as a client-safe constant for the stepper/board UI).
|
||
export const STAGE_ORDER: ApplicationStage[] = [
|
||
"SHORTLISTED",
|
||
"COMPETENCY_AND_REFERENCES",
|
||
"DOC_VERIFICATION",
|
||
"SALARY_AGREEMENT",
|
||
"PROPOSED",
|
||
"INTERVIEW",
|
||
"SELECTED",
|
||
];
|
||
|
||
export const STAGE_LABEL: Record<ApplicationStage, string> = {
|
||
SHORTLISTED: "Shortlisted",
|
||
COMPETENCY_AND_REFERENCES: "Competency & references",
|
||
DOC_VERIFICATION: "Documents",
|
||
SALARY_AGREEMENT: "Salary",
|
||
PROPOSED: "Proposed",
|
||
INTERVIEW: "Interview",
|
||
SELECTED: "Selected",
|
||
REJECTED: "Rejected",
|
||
ONBOARDED: "Onboarded",
|
||
};
|
||
|
||
export const STAGE_VARIANT: Record<ApplicationStage, Variant> = {
|
||
SHORTLISTED: "outline",
|
||
COMPETENCY_AND_REFERENCES: "default",
|
||
DOC_VERIFICATION: "default",
|
||
SALARY_AGREEMENT: "warning",
|
||
PROPOSED: "default",
|
||
INTERVIEW: "warning",
|
||
SELECTED: "success",
|
||
REJECTED: "danger",
|
||
ONBOARDED: "success",
|
||
};
|
||
|
||
// Index of a stage within the 7-step flow (−1 for REJECTED; 7 for ONBOARDED).
|
||
export function stageIndex(stage: ApplicationStage): number {
|
||
if (stage === "REJECTED") return -1;
|
||
if (stage === "ONBOARDED") return STAGE_ORDER.length;
|
||
return STAGE_ORDER.indexOf(stage);
|
||
}
|