import type { Prisma } from "@prisma/client"; // Leave-clash detection (Crewing-Implementation-Spec §5.3, R6). Required strength // is treated as 1: approving a leave is a clash when it would leave the vessel // with ZERO active same-rank cover over the leave window — i.e. every other // not-signed-off crew member of that rank on the vessel is either absent or on an // approved leave that overlaps the window. A clash auto-raises a LEAVE requisition. interface ClashInput { assignmentId: string; rankId: string; vesselId: string | null; fromDate: Date; toDate: Date; } export async function leaveLeavesNoCover( tx: Prisma.TransactionClient, { assignmentId, rankId, vesselId, fromDate, toDate }: ClashInput ): Promise { // No vessel cost axis → no rank-cover check. if (!vesselId) return false; const others = await tx.crewAssignment.findMany({ where: { rankId, vesselId, status: { not: "SIGNED_OFF" }, id: { not: assignmentId } }, select: { id: true }, }); // This crew member was the only same-rank cover on the vessel. if (others.length === 0) return true; const otherIds = others.map((o) => o.id); const overlapping = await tx.leaveRequest.findMany({ where: { assignmentId: { in: otherIds }, status: "APPROVED", fromDate: { lte: toDate }, toDate: { gte: fromDate }, }, select: { assignmentId: true }, }); const out = new Set(overlapping.map((l) => l.assignmentId)); const remainingCover = otherIds.filter((id) => !out.has(id)).length; return remainingCover === 0; }