138 lines
4.1 KiB
TypeScript
138 lines
4.1 KiB
TypeScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|