@zenuinebit/purchase-order
v0.1.0
Published
Purchase orders and goods receipts: status flow, partial receiving, three-way match, stock movements.
Readme
@zenuinebit/purchase-order
Purchase orders and goods receipts: status flow, partial receiving, over-receipt limits, a three-way match of the supplier's bill, and stock movements for @zenuinebit/stock-ledger. Immutable: every function returns a new order. Costs are integers in minor units, quantities may be fractional, dates are "YYYY-MM-DD".
import { createOrder, approve, receive, close, outstanding, orderTotals, matchBill, toStockMovements } from "@zenuinebit/purchase-order";
import { replay } from "@zenuinebit/stock-ledger";
let po = createOrder({
number: "PO-1", date: "2025-04-01",
supplier: { name: "Tea Estates", gstin: "27AAPFU0939F1ZV" },
lines: [{ sku: "TEA", hsn: "0902", quantity: 100, unitCost: 8_000, taxRate: 5 }],
}); // status: "draft"
po = approve(po); // "approved"
po = receive(po, { id: "GRN-1", date: "2025-04-05", lines: [{ sku: "TEA", quantity: 60 }] });
po.status; // "partially-received"
outstanding(po); // { TEA: 40 }
orderTotals(po, "27"); // GST on the ordered quantities (your GSTIN or state decides CGST+SGST or IGST)
// Is the supplier's bill right? Compare it with the order and with what actually arrived
matchBill(po, [{ sku: "TEA", quantity: 60, unitCost: 8_000 }]); // { ok: true, issues: [] }
// Goods in -> stock
const stock = replay(toStockMovements(po)); // FIFO / average costing, valuation, ageing
po = close(po, "Supplier cannot deliver the rest"); // short closeStatus flow: draft -> approved -> partially-received -> received -> closed. A draft or approved order with nothing received can be cancelled. Closing a partly received order is a short close.
Rules it enforces
- Goods can only be received against an approved or partly received order, for SKUs on the order, with unique receipt ids.
- Receiving more than ordered is refused unless you allow a tolerance:
receive(po, grn, { tolerancePercent: 5 }). - The three-way match flags a SKU that is not on the order, a bill for more than has been received (pass earlier bills as
alreadyBilled), and a price that differs beyondpriceTolerancePercent. - The stock value from
toStockMovementsequalsreceivedValue(po)exactly.
Not included: storage, approval workflows and limits, returns to supplier, landed costs (freight, duty), multiple delivery dates per line, and currencies other than your accounting one.
