Final slice of Phase 3 (stacked on 3b pipeline). The onboarding transaction that turns a SELECTED candidate into active crew, per Crewing-Implementation-Spec §8.5/§9/§11. Behind NEXT_PUBLIC_CREWING_ENABLED; production unchanged. What's in - Schema (crewing_onboarding migration): CrewAssignment + AssignmentStatus (ACTIVE/ON_LEAVE/SIGNED_OFF — leave/sign-off are Phase 4); ContractLetter (salaryRestricted); SalaryStructure += assignmentId; CrewActionType += CREW_ONBOARDED. Employee numbers CRW-xxxx via lib/employee-number.ts. - Action (onboardCandidate, onboard_crew): one transaction off a SELECTED application — assign employeeId, create CrewAssignment(ACTIVE, signOnDate), bind the approved SalaryStructure (assignmentId + effectiveFrom), Application → ONBOARDED, Requisition → FILLED, CrewMember → EMPLOYEE (+ currentRank); contract letter stored after. Guards flag + permission + SELECTED state. - Screen: the SELECTED action card's "Onboard to crew" modal (joining date, contract upload, starts-automatically chips); the CRW- number shows on the ONBOARDED card. Tests & docs - Integration: onboarding.test.ts (5) — full transaction, requisition FILLED + salary binding, joining-date + SELECTED-only guards, permission gating, sequential CRW- ids. type-check clean; full unit (234) + integration (168) green. - CLAUDE.md updated with the Phase 3c surface. Deferred: SITE_STAFF login creation for management ranks (grantsLogin) — a follow-up; attendance/experience/PPE records begin in Phase 4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
29 lines
951 B
TypeScript
29 lines
951 B
TypeScript
/**
|
|
* Crew employee-number generator. Format: CRW-<id>, e.g. CRW-1000.
|
|
*
|
|
* Sequential, floored at 1000, scanning existing CrewMember.employeeId values.
|
|
* Assigned at onboarding (Phase 3c). Call inside the onboarding transaction to
|
|
* minimise the race window (the unique constraint is the backstop).
|
|
*/
|
|
|
|
import { db } from "@/lib/db";
|
|
import type { Prisma } from "@prisma/client";
|
|
|
|
const PREFIX = "CRW-";
|
|
const FLOOR = 999; // first generated id is 1000
|
|
|
|
export async function generateEmployeeId(
|
|
client: Prisma.TransactionClient | typeof db = db
|
|
): Promise<string> {
|
|
const rows = await client.crewMember.findMany({
|
|
where: { employeeId: { startsWith: PREFIX } },
|
|
select: { employeeId: true },
|
|
});
|
|
let maxId = FLOOR;
|
|
for (const { employeeId } of rows) {
|
|
if (!employeeId) continue;
|
|
const n = parseInt(employeeId.slice(PREFIX.length), 10);
|
|
if (!isNaN(n) && n > maxId) maxId = n;
|
|
}
|
|
return `${PREFIX}${maxId + 1}`;
|
|
}
|