import { permissions } from "@mrp/shared"; import { shipmentStatuses } from "@mrp/shared/dist/shipping/types.js"; import { Router } from "express"; import { z } from "zod"; import { fail, ok } from "../../lib/http.js"; import { requirePermissions } from "../../lib/rbac.js"; import { createShipment, getShipmentById, listShipmentOrderOptions, listShipments, postShipmentPick, updateShipment, updateShipmentStatus } from "./service.js"; const shipmentSchema = z.object({ salesOrderId: z.string().trim().min(1), status: z.enum(shipmentStatuses), shipDate: z.string().datetime().nullable(), carrier: z.string(), serviceLevel: z.string(), trackingNumber: z.string(), packageCount: z.number().int().positive(), notes: z.string(), }); const shipmentListQuerySchema = z.object({ q: z.string().optional(), status: z.enum(shipmentStatuses).optional(), salesOrderId: z.string().optional(), }); const shipmentStatusUpdateSchema = z.object({ status: z.enum(shipmentStatuses), }); const shipmentPickSchema = z.object({ salesOrderLineId: z.string().trim().min(1), warehouseId: z.string().trim().min(1), locationId: z.string().trim().min(1), quantity: z.number().positive(), notes: z.string(), }); function getRouteParam(value: unknown) { return typeof value === "string" ? value : null; } export const shippingRouter = Router(); shippingRouter.get("/orders/options", requirePermissions([permissions.shippingRead]), async (_request, response) => { return ok(response, await listShipmentOrderOptions()); }); shippingRouter.get("/shipments", requirePermissions([permissions.shippingRead]), async (request, response) => { const parsed = shipmentListQuerySchema.safeParse(request.query); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Shipment filters are invalid."); } return ok(response, await listShipments(parsed.data)); }); shippingRouter.get("/shipments/:shipmentId", requirePermissions([permissions.shippingRead]), async (request, response) => { const shipmentId = getRouteParam(request.params.shipmentId); if (!shipmentId) { return fail(response, 400, "INVALID_INPUT", "Shipment id is invalid."); } const shipment = await getShipmentById(shipmentId); if (!shipment) { return fail(response, 404, "SHIPMENT_NOT_FOUND", "Shipment was not found."); } return ok(response, shipment); }); shippingRouter.post("/shipments", requirePermissions([permissions.shippingWrite]), async (request, response) => { const parsed = shipmentSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Shipment payload is invalid."); } const result = await createShipment(parsed.data, request.authUser?.id); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.shipment, 201); }); shippingRouter.put("/shipments/:shipmentId", requirePermissions([permissions.shippingWrite]), async (request, response) => { const shipmentId = getRouteParam(request.params.shipmentId); if (!shipmentId) { return fail(response, 400, "INVALID_INPUT", "Shipment id is invalid."); } const parsed = shipmentSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Shipment payload is invalid."); } const result = await updateShipment(shipmentId, parsed.data, request.authUser?.id); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.shipment); }); shippingRouter.patch("/shipments/:shipmentId/status", requirePermissions([permissions.shippingWrite]), async (request, response) => { const shipmentId = getRouteParam(request.params.shipmentId); if (!shipmentId) { return fail(response, 400, "INVALID_INPUT", "Shipment id is invalid."); } const parsed = shipmentStatusUpdateSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Shipment status payload is invalid."); } const result = await updateShipmentStatus(shipmentId, parsed.data.status, request.authUser?.id); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.shipment); }); shippingRouter.post("/shipments/:shipmentId/picks", requirePermissions([permissions.shippingWrite]), async (request, response) => { const shipmentId = getRouteParam(request.params.shipmentId); if (!shipmentId) { return fail(response, 400, "INVALID_INPUT", "Shipment id is invalid."); } const parsed = shipmentPickSchema.safeParse(request.body); if (!parsed.success) { return fail(response, 400, "INVALID_INPUT", "Shipment pick payload is invalid."); } const result = await postShipmentPick(shipmentId, parsed.data, request.authUser?.id); if (!result.ok) { return fail(response, 400, "INVALID_INPUT", result.reason); } return ok(response, result.shipment, 201); });