PRD: Inventory Valuation
| Module | Inventory | PRD ID | PRD-VAL-001 |
| Status | No item | FEAT | VAL |
| Epic | — | Plane | — |
| Date | 2026-07-13 | Version | v1.0 |
| Packages | @nx/inventory | URD | VAL |
| Surface | BackendClient · Owner/Mgr | ||
| Owner | Phát Nguyễn | ||
What inventory valuation is
Cost is what one unit of an item sitting in a location is worth. On a sale, the selling price minus the cost is the gross margin.
Every item of a merchant has exactly one cost, shared across all inventory locations of that merchant. Several merchants sit under one organizer, and each merchant keeps its own cost. Moving stock from the central warehouse to the sales floor shifts quantity only; the money stays put.
The four numbers in stock
Lifted from Stock locations & levels so there is no need to open another PRD. Every item at every location carries four numbers:
| Number | What the system calls it | Meaning |
|---|---|---|
| On-hand | on-hand | Goods physically sitting in the location |
| Reserved | reserved | Already promised to another document, no longer sellable to anyone else |
| Available | available | The part still sellable |
| Incoming | incoming | Left the source location, not yet arrived at the destination |
The invariant, true at every moment:
Available = On-hand - ReservedThe denominator of the weighted moving average is the merchant's total on-hand, accumulated across every location.
Why a hand-typed box does not solve this
A merchant buys the same item in batches at different prices: a carton of milk was VND 22,000 last month and VND 24,000 this month. If the system does not roll those batches into one number, the owner does the arithmetic on paper, and every profit report is wrong from the ground up.
A single box for the owner to type a cost into does not solve it either. A hand-typed number sits still even when goods arrive at a completely different price, so the longer it is used the further it drifts from what was actually paid, and the margin computed at the point of sale drifts with it.
So the system keeps two numbers side by side: the weighted moving average the system computes itself, and the standard cost the owner types in. The owner picks which one each item uses.
One item, end to end
Fresh milk. The location is empty. Two receipts at two prices, sell it all, then receive again.
| What happens | On-hand | Stock value | Average cost |
|---|---|---|---|
| Receive 100 cartons at VND 20,000 | 100 | VND 2,000,000 | VND 20,000 |
| Sell 40 cartons | 60 | VND 1,200,000 | VND 20,000 |
| Receive 40 cartons at VND 25,000 | 100 | VND 2,200,000 | VND 22,000 |
| Sell the last 100 cartons, on-hand back to 0 | 0 | VND 0 | VND 22,000 (frozen) |
| Receive 50 cartons at VND 24,000 | 50 | VND 1,200,000 | VND 24,000 (replaced outright) |
- Selling does not change the cost. Cost only changes at receipt: (1,200,000 + 40 × 25,000) divided by 100 is VND 22,000.
- On-hand reaching 0 freezes the old cost at VND 22,000. The system does not divide by zero and does not return a cost of 0.
- The next receipt replaces the old cost outright with the unit price of the new lot, because there is no old stock left to blend into.
- If this item is running a standard cost of VND 21,000, the gross margin on a sale is computed at VND 21,000, while the average-cost column still ticks along exactly as in the table above.
The easiest thing to get wrong
Per-location costing is one-way and cannot be turned off
A merchant whose purchasing is spread across locations can turn on an advanced setting so that each item-and-location pair carries its own cost.
Turning it on splits one number into many. Merging them back has no correct rule: averaging by quantity, or picking one location as the reference, is wrong for some merchant. So the system does not allow turning it off. The form says so plainly before the switch.
By default the cost is a single number for the whole merchant, and that is the right choice for nearly every merchant.