import { type NextRequest } from "next/server"; import { prisma } from "@/lib/prisma"; import { ok, errorResponse, requireRole, parseJson, ApiError } from "@/lib/api"; import { CloseOperationSchema } from "@/lib/schemas"; import { audit } from "@/lib/audit"; import { clientIp } from "@/lib/request"; /** * Complete an operation. Only the current claim holder may close, and if * the operation is flagged qcRequired the payload must include an inline * QC block (pass/fail + optional measurements). Close does four things * atomically: * * 1. marks the operation completed + records completedAt, * 2. closes the open TimeLog with unitsProcessed / note if provided, * 3. writes a QCRecord if this op requires QC (or if the operator passed * one in voluntarily), * 4. audits the close for later reporting. */ export async function POST(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { try { const actor = await requireRole("operator"); const { id } = await ctx.params; const body = await parseJson(req, CloseOperationSchema); const existing = await prisma.operation.findUnique({ where: { id }, select: { id: true, status: true, claimedByUserId: true, qcRequired: true }, }); if (!existing) throw new ApiError(404, "not_found", "Operation not found"); if (existing.claimedByUserId !== actor.id) { throw new ApiError(409, "not_claim_holder", "Only the current operator can complete this step"); } if (existing.status !== "in_progress") { throw new ApiError(409, "op_not_active", "Step is not active"); } if (existing.qcRequired && !body.qc) { throw new ApiError(400, "qc_required", "This step requires an inline QC check before completing"); } const now = new Date(); await prisma.$transaction(async (tx) => { const openLog = await tx.timeLog.findFirst({ where: { operationId: id, operatorId: actor.id, endedAt: null }, orderBy: { startedAt: "desc" }, }); if (openLog) { await tx.timeLog.update({ where: { id: openLog.id }, data: { endedAt: now, unitsProcessed: body.unitsProcessed ?? null, note: body.note ?? null, }, }); } if (body.qc) { await tx.qCRecord.create({ data: { operationId: id, operatorId: actor.id, kind: "inline", passed: body.qc.passed, measurements: body.qc.measurements ?? null, notes: body.qc.notes ?? null, }, }); } await tx.operation.update({ where: { id }, data: { status: "completed", completedAt: now, claimedByUserId: null, claimedAt: null, }, }); }); const op = await prisma.operation.findUnique({ where: { id } }); await audit({ actorId: actor.id, action: "close_op", entity: "Operation", entityId: id, after: { status: "completed", unitsProcessed: body.unitsProcessed ?? null, qcPassed: body.qc?.passed ?? null, }, ipAddress: clientIp(req), }); return ok({ operation: op }); } catch (err) { return errorResponse(err); } }