import { revalidatePath } from "next/cache"; import { serverAuth } from "@/lib/pb"; export const dynamic = "force-dynamic"; // POST /api/operators — add a person to the operator register from the app. // // This exists because people join and leave, and the rate sheet is only re-issued // occasionally. A row written here is NOT in data/source/operators.json, which is why // the seed reconciles that collection by employee code instead of clearing it — this // row survives every future re-seed and the log names it. // // An operator with NO employee code is rejected: the code is what a later allocation // is preserved against, so a row without one could never keep its machine across a // re-seed. export async function POST(req: Request) { let body: { name?: string; employeeCode?: string; rate?: number | string; assetId?: string } = {}; try { body = await req.json(); } catch { return Response.json({ ok: false, error: "Invalid JSON body" }, { status: 400 }); } const name = (body.name || "").trim(); const employeeCode = (body.employeeCode || "").trim(); const rate = typeof body.rate === "string" ? Number(body.rate) : body.rate; if (!name) return Response.json({ ok: false, error: "Name is required" }, { status: 400 }); if (!employeeCode) { return Response.json( { ok: false, error: "Employee number is required — it is what keeps this person's machine across a re-seed" }, { status: 400 }, ); } if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0) { return Response.json({ ok: false, error: "Rate must be a number greater than zero" }, { status: 400 }); } try { const pb = await serverAuth(); // Names are not unique, employee codes are. Two rows sharing a code would also // break the allocation capture, so this is enforced, not advisory. const clash = (await pb.collection("operators").getFullList()) .find((o) => (o.employee_code || "").trim().toUpperCase() === employeeCode.toUpperCase()); if (clash) { return Response.json( { ok: false, error: `${employeeCode} is already used by ${clash.name}` }, { status: 409 }, ); } let assetId = ""; if (body.assetId) { const asset = await pb.collection("assets").getOne(body.assetId).catch(() => null); if (!asset) { return Response.json({ ok: false, error: "That plant item no longer exists" }, { status: 400 }); } assetId = asset.id; } const created = await pb.collection("operators").create({ name, employee_code: employeeCode, rate: Math.round(rate * 100) / 100, asset: assetId, // Marked as app-added so the seed log can name it, and so it is obvious where // the row came from when someone audits the register. source: "Added in app", }); revalidatePath("/running"); revalidatePath("/employees", "layout"); return Response.json({ ok: true, operator: { id: created.id, name: created.name, employee_code: created.employee_code, rate: created.rate }, }); } catch (e: any) { return Response.json({ ok: false, error: e?.message || "Could not add the operator" }, { status: 500 }); } }