3.4 KiB
02 — Stock Ledger
Status: Not Started Depends On: 01 — Item Catalog
Goal
Introduce stock-per-location as a first-class, transaction-driven concept. Covers
mainPlan.md §8 in full,
plus the location-query half of §6 (getStockByLocation, putawayStock). Everything downstream —
loading events, delivery/sale ride execution, reconciliation — reads or writes through this ledger.
Background / Context
Nothing in the current schema tracks quantity of an Item anywhere. checkStockAvailability (§1)
and every warehouse function in §6 have no data source to query today. This step must land before
04_loading_event.md.
Data Model Changes
Stock Balance (per location)
A denormalized running balance per (ItemId, LocationId) pair, kept in sync by the movement
transaction table below — never edited directly by any service.
Stock Movement Transaction
One row per stock change: ItemId, LocationId, Quantity (signed: +load/return, -sale/damage),
MovementType (Load, Sale, Return, Damage, Adjustment), ReferenceId (the order/ride/GRN
that caused it), CreatedAt. This is the source of truth; the balance is its running sum.
Location Abstraction (Warehouse vs Vehicle)
A Location concept that can resolve to either a Warehouse or a Vehicle (once a ride is open —
see 03_ride_lifecycle.md), so the same movement/balance model serves both
getMultiLocationStock (§8) and the vehicle-as-temporary-warehouse pattern.
API Endpoints
| Function reference | Endpoint intent |
|---|---|
getMultiLocationStock(sku) (§8) |
GET /api/stock/items/{sku} — balance across every location |
getStockByLocation(warehouseId, sku) (§6) |
GET /api/stock/locations/{locationId}/items/{sku} |
setReorderPoint(sku, warehouseId, threshold) (§8) |
PUT /api/stock/locations/{locationId}/items/{sku}/reorder-point |
createStockTransfer(fromWarehouseId, toWarehouseId, items[]) (§8) |
POST /api/stock/transfers |
trackBatchExpiry(sku, batchNo, expiryDate) (§8) |
Deferred — see Open Questions |
calculateStockValuation(warehouseId, method) (§8) |
GET /api/stock/locations/{locationId}/valuation?method=fifo|weighted-average |
Business Rules
- Stock is never edited directly — every change is a
Stock Movement Transaction; the balance is always the running sum, recalculated or cached but never hand-edited. - A movement cannot take a balance negative unless explicitly flagged as an
Adjustment(physical count correction). - Every movement must carry a
ReferenceIdback to the business event that caused it — no freestanding movements outside of documentedAdjustments.
Fraud / Control Considerations
Adjustmentmovements should be restricted to Supervisor level and above (see role-permission mapping inData/Seed/RoleAndPermissionSeed.cs), and should require a reason code once 09_fraud_controls.md lands.
Open Questions
- Is batch/expiry tracking (
trackBatchExpiry) needed in v1, or can it be deferred until a regulated/perishable product line requires FEFO enforcement? - FIFO vs weighted-average valuation — does the business need both, or a single fixed method?
Implementation Notes
Fill this section in during planning discussion before writing any code.