import { type NextRequest } from "next/server"; import { prisma } from "@/lib/prisma"; import { ok, errorResponse, requireRole, parseJson, ApiError } from "@/lib/api"; import { UpdateTimeLogSchema } from "@/lib/schemas"; import { audit } from "@/lib/audit"; import { clientIp } from "@/lib/request"; /** * Admin-only correction of a TimeLog row. Intended for "operator forgot to * pause overnight" cleanup — plan-vs-actual hours reports are only as good * as the data on the floor, and a 16-hour phantom entry is worse than a * deleted one. We audit the before/after so the raw operator entry is still * traceable. * * Does NOT mutate Operation.status or claims. If the op itself is stuck * in_progress, the admin should use the existing release/close routes. */ export async function PATCH(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { try { const actor = await requireRole("admin"); const { id } = await ctx.params; const body = await parseJson(req, UpdateTimeLogSchema); const before = await prisma.timeLog.findUnique({ where: { id } }); if (!before) throw new ApiError(404, "not_found", "Time log not found"); // Resolve the effective startedAt / endedAt we'd persist and reject obvious // nonsense (endedAt before startedAt). Null endedAt is still allowed — // reopening a log for the operator to close themselves is a legitimate // undo of a premature admin-close. const startedAt = body.startedAt ?? before.startedAt; const endedAt = body.endedAt !== undefined ? body.endedAt : before.endedAt; if (endedAt !== null && endedAt < startedAt) { throw new ApiError( 400, "invalid_range", "endedAt must be on or after startedAt", ); } const updated = await prisma.timeLog.update({ where: { id }, data: { ...(body.startedAt !== undefined ? { startedAt: body.startedAt } : {}), ...(body.endedAt !== undefined ? { endedAt: body.endedAt } : {}), ...(body.unitsProcessed !== undefined ? { unitsProcessed: body.unitsProcessed } : {}), ...(body.note !== undefined ? { note: body.note } : {}), }, }); await audit({ actorId: actor.id, action: "correct_timelog", entity: "TimeLog", entityId: id, before, after: updated, ipAddress: clientIp(req), }); return ok({ timeLog: updated }); } catch (err) { return errorResponse(err); } } /** * Admin-only. Deletes a TimeLog row outright — reserve for obviously-bogus * entries (duplicate scans, test pings). Note this does NOT walk back the * operation's `unitsCompleted` counter; if a real unit count was logged * you almost always want to PATCH to zero it out instead. */ export async function DELETE(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { try { const actor = await requireRole("admin"); const { id } = await ctx.params; const before = await prisma.timeLog.findUnique({ where: { id } }); if (!before) throw new ApiError(404, "not_found", "Time log not found"); await prisma.timeLog.delete({ where: { id } }); await audit({ actorId: actor.id, action: "delete_timelog", entity: "TimeLog", entityId: id, before, ipAddress: clientIp(req), }); return ok({ ok: true }); } catch (err) { return errorResponse(err); } }