POs
This commit is contained in:
128
server/src/modules/purchasing/router.ts
Normal file
128
server/src/modules/purchasing/router.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { inventoryUnitsOfMeasure } from "@mrp/shared/dist/inventory/types.js";
|
||||
import { purchaseOrderStatuses } from "@mrp/shared";
|
||||
import { Router } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fail, ok } from "../../lib/http.js";
|
||||
import { requirePermissions } from "../../lib/rbac.js";
|
||||
import {
|
||||
createPurchaseOrder,
|
||||
getPurchaseOrderById,
|
||||
listPurchaseOrders,
|
||||
listPurchaseVendorOptions,
|
||||
updatePurchaseOrder,
|
||||
updatePurchaseOrderStatus,
|
||||
} from "./service.js";
|
||||
|
||||
const purchaseLineSchema = z.object({
|
||||
itemId: z.string().trim().min(1),
|
||||
description: z.string(),
|
||||
quantity: z.number().int().positive(),
|
||||
unitOfMeasure: z.enum(inventoryUnitsOfMeasure),
|
||||
unitCost: z.number().nonnegative(),
|
||||
position: z.number().int().nonnegative(),
|
||||
});
|
||||
|
||||
const purchaseOrderSchema = z.object({
|
||||
vendorId: z.string().trim().min(1),
|
||||
status: z.enum(purchaseOrderStatuses),
|
||||
issueDate: z.string().datetime(),
|
||||
taxPercent: z.number().min(0).max(100),
|
||||
freightAmount: z.number().nonnegative(),
|
||||
notes: z.string(),
|
||||
lines: z.array(purchaseLineSchema),
|
||||
});
|
||||
|
||||
const purchaseListQuerySchema = z.object({
|
||||
q: z.string().optional(),
|
||||
status: z.enum(purchaseOrderStatuses).optional(),
|
||||
});
|
||||
|
||||
const purchaseStatusUpdateSchema = z.object({
|
||||
status: z.enum(purchaseOrderStatuses),
|
||||
});
|
||||
|
||||
function getRouteParam(value: unknown) {
|
||||
return typeof value === "string" ? value : null;
|
||||
}
|
||||
|
||||
export const purchasingRouter = Router();
|
||||
|
||||
purchasingRouter.get("/vendors/options", requirePermissions(["purchasing.read"]), async (_request, response) => {
|
||||
return ok(response, await listPurchaseVendorOptions());
|
||||
});
|
||||
|
||||
purchasingRouter.get("/orders", requirePermissions(["purchasing.read"]), async (request, response) => {
|
||||
const parsed = purchaseListQuerySchema.safeParse(request.query);
|
||||
if (!parsed.success) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order filters are invalid.");
|
||||
}
|
||||
|
||||
return ok(response, await listPurchaseOrders(parsed.data));
|
||||
});
|
||||
|
||||
purchasingRouter.get("/orders/:orderId", requirePermissions(["purchasing.read"]), async (request, response) => {
|
||||
const orderId = getRouteParam(request.params.orderId);
|
||||
if (!orderId) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order id is invalid.");
|
||||
}
|
||||
|
||||
const order = await getPurchaseOrderById(orderId);
|
||||
if (!order) {
|
||||
return fail(response, 404, "PURCHASE_ORDER_NOT_FOUND", "Purchase order was not found.");
|
||||
}
|
||||
|
||||
return ok(response, order);
|
||||
});
|
||||
|
||||
purchasingRouter.post("/orders", requirePermissions(["purchasing.write"]), async (request, response) => {
|
||||
const parsed = purchaseOrderSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order payload is invalid.");
|
||||
}
|
||||
|
||||
const result = await createPurchaseOrder(parsed.data);
|
||||
if (!result.ok) {
|
||||
return fail(response, 400, "INVALID_INPUT", result.reason);
|
||||
}
|
||||
|
||||
return ok(response, result.document, 201);
|
||||
});
|
||||
|
||||
purchasingRouter.put("/orders/:orderId", requirePermissions(["purchasing.write"]), async (request, response) => {
|
||||
const orderId = getRouteParam(request.params.orderId);
|
||||
if (!orderId) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order id is invalid.");
|
||||
}
|
||||
|
||||
const parsed = purchaseOrderSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order payload is invalid.");
|
||||
}
|
||||
|
||||
const result = await updatePurchaseOrder(orderId, parsed.data);
|
||||
if (!result.ok) {
|
||||
return fail(response, 400, "INVALID_INPUT", result.reason);
|
||||
}
|
||||
|
||||
return ok(response, result.document);
|
||||
});
|
||||
|
||||
purchasingRouter.patch("/orders/:orderId/status", requirePermissions(["purchasing.write"]), async (request, response) => {
|
||||
const orderId = getRouteParam(request.params.orderId);
|
||||
if (!orderId) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order id is invalid.");
|
||||
}
|
||||
|
||||
const parsed = purchaseStatusUpdateSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return fail(response, 400, "INVALID_INPUT", "Purchase order status payload is invalid.");
|
||||
}
|
||||
|
||||
const result = await updatePurchaseOrderStatus(orderId, parsed.data.status);
|
||||
if (!result.ok) {
|
||||
return fail(response, 400, "INVALID_INPUT", result.reason);
|
||||
}
|
||||
|
||||
return ok(response, result.document);
|
||||
});
|
||||
Reference in New Issue
Block a user