Files
mrp/server/tests/sales-planning.test.ts

138 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-03-15 15:45:29 -05:00
import { describe, expect, it } from "vitest";
import { buildSalesOrderPlanning, type SalesOrderPlanningSnapshot } from "../src/modules/sales/planning.js";
describe("sales order planning", () => {
it("nets stock and open supply before cascading build demand into child components", () => {
const snapshot: SalesOrderPlanningSnapshot = {
orderId: "order-1",
documentNumber: "SO-00001",
status: "APPROVED",
lines: [
{
id: "line-1",
itemId: "assembly-1",
itemSku: "ASSY-100",
itemName: "Assembly",
quantity: 5,
unitOfMeasure: "EA",
},
],
itemsById: {
"assembly-1": {
id: "assembly-1",
sku: "ASSY-100",
name: "Assembly",
type: "ASSEMBLY",
unitOfMeasure: "EA",
isPurchasable: false,
bomLines: [
{
componentItemId: "mfg-1",
quantity: 2,
unitOfMeasure: "EA",
},
{
componentItemId: "buy-1",
quantity: 3,
unitOfMeasure: "EA",
},
],
},
"mfg-1": {
id: "mfg-1",
sku: "MFG-200",
name: "Manufactured Child",
type: "MANUFACTURED",
unitOfMeasure: "EA",
isPurchasable: false,
bomLines: [
{
componentItemId: "buy-2",
quantity: 4,
unitOfMeasure: "EA",
},
],
},
"buy-1": {
id: "buy-1",
sku: "BUY-300",
name: "Purchased Child",
type: "PURCHASED",
unitOfMeasure: "EA",
isPurchasable: true,
bomLines: [],
},
"buy-2": {
id: "buy-2",
sku: "BUY-400",
name: "Raw Material",
type: "PURCHASED",
unitOfMeasure: "EA",
isPurchasable: true,
bomLines: [],
},
},
supplyByItemId: {
"assembly-1": {
onHandQuantity: 1,
reservedQuantity: 0,
availableQuantity: 1,
openWorkOrderSupply: 1,
openPurchaseSupply: 0,
},
"mfg-1": {
onHandQuantity: 2,
reservedQuantity: 0,
availableQuantity: 2,
openWorkOrderSupply: 1,
openPurchaseSupply: 0,
},
"buy-1": {
onHandQuantity: 4,
reservedQuantity: 1,
availableQuantity: 3,
openWorkOrderSupply: 0,
openPurchaseSupply: 5,
},
"buy-2": {
onHandQuantity: 1,
reservedQuantity: 0,
availableQuantity: 1,
openWorkOrderSupply: 0,
openPurchaseSupply: 2,
},
},
};
const planning = buildSalesOrderPlanning(snapshot);
expect(planning.summary.totalBuildQuantity).toBe(6);
expect(planning.summary.totalPurchaseQuantity).toBe(10);
const assembly = planning.items.find((item) => item.itemId === "assembly-1");
const manufacturedChild = planning.items.find((item) => item.itemId === "mfg-1");
const purchasedChild = planning.items.find((item) => item.itemId === "buy-1");
const rawMaterial = planning.items.find((item) => item.itemId === "buy-2");
expect(assembly?.recommendedBuildQuantity).toBe(3);
expect(assembly?.supplyFromStock).toBe(1);
expect(assembly?.supplyFromOpenWorkOrders).toBe(1);
expect(manufacturedChild?.grossDemand).toBe(6);
expect(manufacturedChild?.recommendedBuildQuantity).toBe(3);
expect(manufacturedChild?.supplyFromStock).toBe(2);
expect(manufacturedChild?.supplyFromOpenWorkOrders).toBe(1);
expect(purchasedChild?.grossDemand).toBe(9);
expect(purchasedChild?.recommendedPurchaseQuantity).toBe(1);
expect(purchasedChild?.supplyFromStock).toBe(3);
expect(purchasedChild?.supplyFromOpenPurchaseOrders).toBe(5);
expect(rawMaterial?.grossDemand).toBe(12);
expect(rawMaterial?.recommendedPurchaseQuantity).toBe(9);
expect(rawMaterial?.supplyFromStock).toBe(1);
expect(rawMaterial?.supplyFromOpenPurchaseOrders).toBe(2);
});
});