The Inventory Model
Inventory is where quantity and money meet. It is also the most common place for an ERP to be “wrong” in the eyes of the business.
The stock key
A stock balance is not just “material X, quantity 500”. The full key:
(material, plant, storage_location, batch, stock_type, special_stock_indicator, owner)
Every dimension matters:
| Dimension | Why |
|---|---|
| Material | What it is |
| Plant | Which site owns it |
| Storage location | Where inside the site |
| Batch / lot | Which production run — different expiry, different characteristics |
| Stock type | Unrestricted / quality inspection / blocked / in transit |
| Special stock | Consignment / subcontracting / customer-owned / project stock |
| Valuation type | Split valuation (e.g. domestic vs imported, grade A vs B) |
Stock types
| Type | Available to MRP? | Available to sell? | On balance sheet? |
|---|---|---|---|
| Unrestricted | Yes | Yes | Yes |
| Quality inspection | Usually yes | No | Yes |
| Blocked | No | No | Yes |
| In transit | Yes (planned) | No | Yes |
| Returns | No | No | Yes |
| Consignment (vendor’s) | Yes | Only on consumption | No |
| Consignment (at customer) | No | Already shipped | Yes — still yours |
| Subcontractor stock | Yes | No | Yes — still yours |
| Customer-owned material | No | No | No |
The rule that catches people out: physical possession ≠ ownership. Vendor consignment sits in your warehouse and is not yours. Customer consignment sits at the customer’s site and is yours. Subcontractor stock is at someone else’s factory and is yours. Get this wrong and your balance sheet is wrong.
Movement types
Every stock change has a movement type — a code that determines everything about the posting.
| Code (SAP convention) | Movement |
|---|---|
| 101 / 102 | Goods receipt for PO / reversal |
| 122 | Return delivery to vendor |
| 201 / 202 | Goods issue to cost centre / reversal |
| 261 / 262 | Goods issue to production order / reversal |
| 301 | Transfer plant to plant, one step |
| 311 | Transfer storage location to storage location |
| 321 | Quality inspection → unrestricted |
| 343 | Blocked → unrestricted |
| 501 | Receipt without PO |
| 551 | Scrapping |
| 561 | Initial stock upload |
| 601 | Goods issue for delivery (sales) |
| 641 | Goods issue for stock transport order |
| 701/702 | Physical inventory adjustment |
The movement type controls:
- Quantity sign (+/−)
- Which stock type
- Which GL accounts (via account determination)
- Which fields are required (cost centre, order, reason code)
- Whether it’s reversible and by which counter-movement
If you’re building an ERP, implement movement types. The alternative — bespoke logic per transaction — becomes unmaintainable within a year.
The material document
Every stock movement creates a material document, separate from the accounting document:
material_document_header(id, doc_number, doc_year, posting_date, document_date,
movement_reason, created_by, reference)
material_document_item(doc_id, line, material_id, plant_id, storage_location_id,
batch, stock_type, movement_type,
quantity, uom, amount_local,
cost_object, reference_doc, reference_line,
accounting_document_id)
Append-only. A wrong movement is corrected with a reversal movement, never edited.
Your stock balance is SUM(quantity) over these lines for a stock key — with the same “derived vs materialised” trade-off as GL balances. See General Ledger.
Stock quantities you must distinguish
On hand physically present
+ On order open POs not yet received
+ Planned receipts planned/production orders
− Allocated/reserved committed to sales orders or production
− Safety stock buffer you refuse to plan into
────────────────────────────────────────────────
= Available to promise (ATP)
Different questions need different numbers:
- “Can I sell it?” → ATP
- “Is it on the shelf?” → unrestricted on hand
- “What’s it worth?” → valuated stock (includes QI and blocked, excludes vendor consignment)
- “What should I reorder?” → on hand + on order − requirements
Never expose a single “stock” number in a UI without saying which one it is. Half of all “the system is wrong” tickets are actually this.
Reservations and allocation
A reservation earmarks stock:
reservation(id, material, plant, storage_location, quantity_required,
quantity_withdrawn, requirement_date,
source_type, -- SALES_ORDER | PRODUCTION_ORDER | MAINTENANCE | MANUAL
source_document, status)
Types:
- Hard allocation — this exact stock/batch is for this order, nobody else can take it
- Soft allocation — a claim on quantity, satisfied by any matching stock
- Time-phased — the claim exists on a date, not now
Design decision: allocate at order entry (guarantees the customer, may sit on stock for weeks) or at picking (maximises flexibility, risks disappointing the customer). Most systems allow both, configured by order type and customer priority.
Physical inventory
Reality drifts from the system. Counting fixes it.
| Method | How |
|---|---|
| Annual/periodic count | Everything, once a year, warehouse stopped. Disruptive |
| Cycle counting | Continuous rolling count; A items monthly, B quarterly, C annually |
| Perpetual with sampling | Statistical sampling, extrapolate |
| Zero-count / opportunity count | Count a bin when it hits zero during picking. Cheap and effective |
Process:
1. Create count documents (freeze the book quantity at that moment)
2. Block movements for the counted stock, or record book quantity at freeze time
3. Count (paper, RF scanner, mobile app)
4. Enter results
5. Recount differences over tolerance
6. Approve, then post the adjustment:
Shortage: Inventory Cr, Inventory adjustment expense Dr
Overage: Inventory Dr, Inventory adjustment income Cr
Inventory accuracy = items with zero variance / items counted. World-class is >99%. Below 95% means MRP is planning on fiction and every promise date is a guess.
Adjustment postings need reason codes — theft, damage, mis-pick, receiving error, data entry, expiry. The reason distribution tells you what to fix; a lump of “adjustment” tells you nothing.
Inventory analysis
ABC classification
By annual consumption value:
- A — ~20% of items, ~80% of value → tight control, frequent counts, low safety stock
- B — ~30% of items, ~15% → moderate
- C — ~50% of items, ~5% → loose control, bulk ordering
XYZ classification
By demand variability:
- X — steady, predictable → easy to forecast
- Y — variable with trend/season
- Z — erratic → don’t forecast, buffer or make to order
Combined ABC-XYZ gives nine strategies. AX items get tight JIT; CZ items get a big buffer and no planner attention.
Key metrics
| Metric | Formula | Meaning |
|---|---|---|
| Inventory turns | COGS / average inventory | How many times you cycle stock a year |
| DOH / DSI | 365 / turns | Days of inventory |
| Fill rate | Lines shipped complete / lines ordered | Service level |
| Stockout rate | Stockout events / opportunities | Failure rate |
| Obsolete/slow-moving % | No movement in N months | Dead capital |
| Inventory accuracy | Count matches / counts | Data quality |
| Carrying cost | 15–30% of value/year | Capital, storage, insurance, obsolescence, shrinkage |
Slow-moving and obsolete (SLOB)
Inventory that isn’t turning is a write-down waiting to happen. Standard policy:
No movement 6–12 months → provision 25%
No movement 12–24 months → provision 50%
No movement > 24 months → provision 100%
Expired / superseded → write off
IAS 2 requires inventory at the lower of cost and net realisable value. The provision calculation is a standard report you will be asked to build; the ageing must be based on last movement date (or last issue date), not creation date.
Design rules
- Movement types, not bespoke logic per transaction.
- Material documents are append-only. Reverse, never edit.
- Balances derived from movements, with an optional materialised projection that is reconciled nightly.
- Ownership is a separate concept from location. Model special stock explicitly.
- Quantity is meaningless without UoM — see Item Master and UoM.
- Negative stock should be blocked by default. Allowing it is a deliberate, per-material, per-plant configuration decision, and it always causes valuation problems.
- Every adjustment needs a reason code.
- Reconcile inventory subledger to the GL inventory accounts every period.