import { permissions } from "@mrp/shared"; import { workOrderStatuses } from "@mrp/shared/dist/manufacturing/types.js"; import { Router } from "express"; import { z } from "zod"; import { fail, ok } from "../../lib/http.js"; import { requirePermissions } from "../../lib/rbac.js"; import { createManufacturingStation, createWorkOrder, getWorkOrderById, issueWorkOrderMaterial, listManufacturingItemOptions, listManufacturingProjectOptions, listManufacturingStations, listWorkOrders, recordWorkOrderCompletion, updateWorkOrder, updateWorkOrderStatus, } from "./service.js"; const stationSchema = z.object({ code: z.string().trim().min(1).max(64), name: z.string().trim().min(1).max(160), description: z.string(), queueDays: z.number().int().min(0).max(365), isActive: z.boolean(), }); const workOrderSchema = z.object({ itemId: z.string().trim().min(1), projectId: z.string().trim().min(1).nullable(), status: z.enum(workOrderStatuses), quantity: z.number().int().positive(), warehouseId: z.string().trim().min(1), locationId: z.string().trim().min(1), dueDate: z.string().datetime().nullable(), notes: z.string(), }); const workOrderFiltersSchema = z.object({ q: z.string().optional(), status: z.enum(workOrderStatuses).optional(), projectId: z.string().optional(), itemId: z.string().optional(), }); const statusUpdateSchema = z.object({ status: z.enum(workOrderStatuses), }); const materialIssueSchema = z.object({ componentItemId: z.string().trim().min(1), warehouseId: z.string().trim().min(1), locationId: z.string().trim().min(1), quantity: z.number().int().positive(), notes: z.string(), }); const completionSchema = z.object({ quantity: z.number().int().positive(), notes: z.string(), }); function getRouteParam(value: unknown) { return typeof value === "string" ? value : null; } export const manufacturingRouter = Router(); manufacturingRouter.get("/items/options", requirePermissions([permissions.manufacturingRead]), async (_request, response) => { return ok(response, await listManufacturingItemOptions()); }); manufacturingRouter.get("/projects/options", requirePermissions([permissions.manufacturingRead]), async (_request, response) => { return ok(response, await listManufacturingProjectOptions()); }); manufacturingRouter.get("/stations", requirePermissions([permissions.manufacturingRead]), async (_request, response) => { return ok(response, await listManufacturingStations()); }); manufacturingRouter.post("/stations", requirePermissions([permissions.manufacturingWrite]), async (request, response) => { const parsed = stationSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Manufacturing station payload is invalid."); } return ok(response, await createManufacturingStation(parsed.data), 201); }); manufacturingRouter.get("/work-orders", requirePermissions([permissions.manufacturingRead]), async (request, response) => { const parsed = workOrderFiltersSchema.safeParse(request.query); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Work-order filters are invalid."); } return ok(response, await listWorkOrders(parsed.data)); }); manufacturingRouter.get("/work-orders/:workOrderId", requirePermissions([permissions.manufacturingRead]), async (request, response) => { const workOrderId = getRouteParam(request.params.workOrderId); if (!workOrderId) { return fail(response, 400, "INVALID_INPUT", "Work-order id is invalid."); } const workOrder = await getWorkOrderById(workOrderId); if (!workOrder) { return fail(response, 404, "WORK_ORDER_NOT_FOUND", "Work order was not found."); } return ok(response, workOrder); }); manufacturingRouter.post("/work-orders", requirePermissions([permissions.manufacturingWrite]), async (request, response) => { const parsed = workOrderSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Work-order payload is invalid."); } const result = await createWorkOrder(parsed.data); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.workOrder, 201); }); manufacturingRouter.put("/work-orders/:workOrderId", requirePermissions([permissions.manufacturingWrite]), async (request, response) => { const workOrderId = getRouteParam(request.params.workOrderId); if (!workOrderId) { return fail(response, 400, "INVALID_INPUT", "Work-order id is invalid."); } const parsed = workOrderSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Work-order payload is invalid."); } const result = await updateWorkOrder(workOrderId, parsed.data); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.workOrder); }); manufacturingRouter.patch("/work-orders/:workOrderId/status", requirePermissions([permissions.manufacturingWrite]), async (request, response) => { const workOrderId = getRouteParam(request.params.workOrderId); if (!workOrderId) { return fail(response, 400, "INVALID_INPUT", "Work-order id is invalid."); } const parsed = statusUpdateSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Work-order status payload is invalid."); } const result = await updateWorkOrderStatus(workOrderId, parsed.data.status); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.workOrder); }); manufacturingRouter.post("/work-orders/:workOrderId/issues", requirePermissions([permissions.manufacturingWrite]), async (request, response) => { const workOrderId = getRouteParam(request.params.workOrderId); if (!workOrderId) { return fail(response, 400, "INVALID_INPUT", "Work-order id is invalid."); } const parsed = materialIssueSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Material-issue payload is invalid."); } const result = await issueWorkOrderMaterial(workOrderId, parsed.data, request.authUser?.id); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.workOrder, 201); }); manufacturingRouter.post("/work-orders/:workOrderId/completions", requirePermissions([permissions.manufacturingWrite]), async (request, response) => { const workOrderId = getRouteParam(request.params.workOrderId); if (!workOrderId) { return fail(response, 400, "INVALID_INPUT", "Work-order id is invalid."); } const parsed = completionSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Completion payload is invalid."); } const result = await recordWorkOrderCompletion(workOrderId, parsed.data, request.authUser?.id); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.workOrder, 201); });