import { type NextRequest } from "next/server"; import { prisma } from "@/lib/prisma"; import { ok, errorResponse, requireRole, parseJson, ApiError } from "@/lib/api"; import { ReorderOperationsSchema } from "@/lib/schemas"; import { audit } from "@/lib/audit"; import { clientIp } from "@/lib/request"; /** * Resequence the operations on a part. Body: { order: [opId, opId, ...] } * The list must contain every operation currently on the part, in the * desired top-to-bottom order. Uses a two-phase update to stay inside the * (partId, sequence) unique constraint. */ export async function POST(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { try { const actor = await requireRole("admin"); const { id: partId } = await ctx.params; const { order } = await parseJson(req, ReorderOperationsSchema); const existing = await prisma.operation.findMany({ where: { partId }, select: { id: true, sequence: true, status: true }, orderBy: { sequence: "asc" }, }); if (existing.length === 0) throw new ApiError(404, "not_found", "No operations on this part"); if (order.length !== existing.length) { throw new ApiError( 400, "incomplete_order", "Order must list every operation on this part exactly once", ); } const ids = new Set(existing.map((o) => o.id)); const seen = new Set(); for (const id of order) { if (!ids.has(id)) throw new ApiError(400, "unknown_op", `Operation ${id} is not on this part`); if (seen.has(id)) throw new ApiError(400, "duplicate_op", `Operation ${id} listed twice`); seen.add(id); } // Refuse if any non-pending op would land at a different sequence number. const byId = new Map(existing.map((o) => [o.id, o])); for (let i = 0; i < order.length; i++) { const target = i + 1; const op = byId.get(order[i])!; if (op.sequence !== target && op.status !== "pending") { throw new ApiError( 409, "op_not_pending", `Cannot move operation ${op.id} — it is already ${op.status}`, ); } } await prisma.$transaction(async (tx) => { // Phase 1: park every row at a negative sequence to clear the unique slot. for (let i = 0; i < order.length; i++) { await tx.operation.update({ where: { id: order[i] }, data: { sequence: -(i + 1) }, }); } // Phase 2: write the final sequence values. for (let i = 0; i < order.length; i++) { await tx.operation.update({ where: { id: order[i] }, data: { sequence: i + 1 }, }); } }); await audit({ actorId: actor.id, action: "reorder", entity: "Part", entityId: partId, before: existing, after: order.map((id, i) => ({ id, sequence: i + 1 })), ipAddress: clientIp(req), }); return ok({ ok: true }); } catch (err) { return errorResponse(err); } }