@zenuinebit/stock-ledger
v0.1.0
Published
Inventory movements with FIFO or weighted-average costing, stock on hand and valuation.
Readme
@zenuinebit/stock-ledger
Inventory from a list of movements: units on hand, stock value, cost of goods sold, low-stock and stock ageing, with FIFO or weighted-average costing. Costs are integers in minor units (see @zenuinebit/money); quantities may be fractional; dates are "YYYY-MM-DD". No dependencies.
import { replay, onHand, lowStock, ageing, type Movement } from "@zenuinebit/stock-ledger";
const movements: Movement[] = [
{ date: "2025-01-01", sku: "TEA", direction: "in", quantity: 10, unitCost: 100, ref: "PO-1" },
{ date: "2025-01-05", sku: "TEA", direction: "in", quantity: 10, unitCost: 120, ref: "PO-2" },
{ date: "2025-01-10", sku: "TEA", direction: "out", quantity: 15, ref: "INV-1" },
];
const fifo = replay(movements); // method: "fifo" is the default
fifo.cogs; // [{ date, sku, quantity: 15, cost: 1600, ref: "INV-1" }] (10 x 100 + 5 x 120)
fifo.items.TEA; // { onHand: 5, value: 600, averageCost: 120, layers: [...] }
replay(movements, { method: "average" }).cogs[0].cost; // 1650 (15 x the 110 average)
onHand(movements, "TEA", "2025-01-09"); // 20 - stock on a past date
lowStock(fifo, { TEA: 10 }); // [{ sku: "TEA", onHand: 5, level: 10 }]
ageing(fifo, "2025-03-01"); // units and value in 0-30 / 31-60 / 61-90 / 90+ day bucketsHow it works
- Store movements, not balances. Everything is worked out by replaying them in date order (receipts before issues on the same day), so a back-dated entry is handled correctly.
- FIFO: issues take the oldest receipt first and cost what it cost. Average: issues cost the running weighted average. Pick the method your accounting uses.
- The books always tie out: cost received = cost of goods sold + value of stock on hand, to the minor unit, even with fractional quantities.
- Issuing more than is in stock on that date throws, rather than creating negative stock.
- For
unitCost, use the landed cost per unit (price plus freight and duty) if your accounting capitalises those.
Not included: multiple warehouses (use a SKU like TEA@store1, or call replay per location), batches and expiry dates, stock transfers, and returns to supplier (post an "out" at the original cost).
