# Stock Tracking

## Overview

Stock Tracking owns the current inventory position for each item at each storage location and site-level demand reservations. It combines immutable physical stock movements, reservation balances, and blocked stock controls into queryable StockLevel and StockReservation caches.

Inventory records physical movement through InventoryLedger and maintains StockLevel for fast reads. StockLevel is scoped to an `(itemId, storageLocationId)` pair and exposes the current stock types used for availability decisions:

| Field | Meaning |
|---|---|
| `onHand` | Quantity physically present at the storage location |
| `reserved` | Open reservation quantity committed to outbound demand in StockReservation and unavailable for new site-level promises |
| `blocked` | Quantity physically present but unavailable due to damage, recall, quarantine, or other holds |
| `available` | Site-level derived quantity: sum of AVAILABLE stock type minus OPEN reservation quantity |

Future inbound supply is not part of current stock availability. Site-level supply planning and ATP projections are modeled separately by InventorySupplyPlan.

## Business Purpose

- Provides the current stock position for operational decisions such as picking, reservation, hold release, and stock inquiries
- Separates physical stock (`onHand`) from committed stock (`reserved`) and unusable stock (`blocked`)
- Makes available-to-promise checks deterministic with site-level `available = AVAILABLE stock type total - OPEN reservation quantity`
- Keeps a permanent InventoryLedger audit trail for physical stock movement
- Maintains StockLevel as a cache so stock inquiries do not need to scan the full ledger
- Supports externally triggered goods receipt and goods issue through the postInventoryLedger posting API, called by the inbound-shipment and outbound-shipment modules
- Supports operator-initiated correction, scrap, block, and unblock through StockAdjustment approval workflow

**InventoryLedger** is the immutable source of truth for physical stock movement. Each entry is insert-only and carries:

| Field | Description |
|---|---|
| `sourceType` / `sourceId` / `sourceLineId` | Generic reference to the cause document (INBOUND_SHIPMENT, OUTBOUND_SHIPMENT, TRANSFER_ORDER, or STOCK_ADJUSTMENT) and its line that produced the movement |
| `direction` | Movement direction: IN increases stock, OUT decreases it |
| `quantity` | Positive movement quantity; the sign is determined by `direction` |
| `itemId` | Item being moved |
| `storageLocationId` | Location where the movement is recorded |
| `executedAt` | Business timestamp of the movement |

**StockLevel** is the current physical/logical stock type cache. It is updated by stock commands and adjustment confirmation. StockLevel records persist even when all quantities reach zero so that location history remains queryable.

**StockReservation** is the current reservation slice cache. It is updated by reservation commands and is subtracted from site-level `StockLevel(stockType=AVAILABLE)` totals for availability calculations. Reservations are grouped by source document line but identified by `id`, so one source line can create multiple ATP-impacting slices. Open reservation quantity is derived as `max(reservedQuantity - consumedQuantity, 0)`.

Goods receipt and goods issue are owned by the inbound-shipment and outbound-shipment modules, which post their movements to stock through `postInventoryLedger`. Those modules already own their document lifecycle, such as purchase receipt, sales shipment, or manufacturing completion.

| Command | Ledger Effect | StockLevel Effect |
|---|---|---|
| `postInventoryLedger` (IN line) | IN | `onHand += quantity`, `available += quantity` |
| `postInventoryLedger` (OUT line) | OUT | `onHand -= quantity`, `available -= quantity` |
| `createStockReservation` | No ledger entry | Insert OPEN StockReservation with `reservedQuantity` |
| `updateStockReservation` | No ledger entry | Set StockReservation requirement fields such as `reservedQuantity` |

StockAdjustment provides an approval workflow for operator-initiated stock changes. Ledger and StockLevel are updated only when an adjustment reaches CONFIRMED.

| Adjustment Type | Ledger Effect On Confirm | StockLevel Effect |
|---|---|---|
| CORRECTION INCREASE | CORRECTION IN | `onHand += quantity`, `available += quantity` |
| CORRECTION DECREASE | CORRECTION OUT | `onHand -= quantity`, `available -= quantity` |
| SCRAP from available | SCRAP OUT | `onHand -= quantity`, `available -= quantity` |
| SCRAP from blocked | SCRAP OUT, UNBLOCK OUT | `onHand -= quantity`, `blocked -= quantity` |
| BLOCK | BLOCK IN | `blocked += quantity`, `available -= quantity` |
| UNBLOCK | UNBLOCK OUT | `blocked -= quantity`, `available += quantity` |

StockAdjustment lifecycle:

```mermaid
flowchart LR
    D[DRAFT] -->|submit| S[SUBMITTED]
    S -->|confirm| C[CONFIRMED]
    S -->|reject| R[REJECTED]
    R -->|update| D
    D -->|cancel| X[CANCELLED]
```

## Process Flow

```mermaid
flowchart TD
    A[External module or operator action] --> B{Operation type}
    B -->|Receipt / issue| C[postInventoryLedger posting API]
    B -->|Reservation| R[Reservation command]
    B -->|Correction / scrap / block / unblock| D[StockAdjustment workflow]
    D --> E{Confirmed?}
    E -->|No| F[No stock movement]
    E -->|Yes| G[Create InventoryLedger entries]
    C --> G
    G --> H[Update StockLevel]
    R --> I[Compute available = AVAILABLE stock type - open reserved]
    H --> I
```

## Scenario Patterns

- **Stock inquiry**: An operator checks on-hand, reserved, blocked, and available quantity for an item at a storage location before picking or replenishment.
- **Available-to-promise check**: Sales or planning checks available quantity across locations. Future supply is not counted as available current stock.
- **Goods receipt**: The inbound-shipment module posts arrived goods through `postInventoryLedger` with IN lines; on-hand and available increase at the destination location.
- **Goods issue**: The outbound-shipment module posts shipped or consumed goods through `postInventoryLedger` with OUT lines; on-hand and available decrease after validating sufficient stock.
- **Reservation lifecycle**: A caller creates or updates reservation slices, reducing available without creating ledger entries. Shipment or issue execution consumes the reservation slice internally by increasing consumed quantity. Source modules close each slice when it no longer contributes reservation demand.
- **Correction**: An operator records count variance through StockAdjustment(CORRECTION), then confirms it to create ledger entries and update on-hand.
- **Scrap**: An operator records damaged or expired stock through StockAdjustment(SCRAP), reducing on-hand and valuation when confirmed.
- **Block and unblock**: Operators quarantine or release stock through StockAdjustment(BLOCK/UNBLOCK), moving quantity between available and blocked.

- StockLevel is unique per item, storage location, and stock type
- StockReservation is unique by `id`; `(sourceType, sourceLineId)` is a non-unique grouping key
- Site-level `available = sum StockLevel(AVAILABLE).quantity in active site locations - OPEN reservation quantity`
- Future inbound supply is represented by InventorySupplyPlan, not by current availability
- Reservations cannot make available quantity negative
- Block operations cannot exceed available quantity
- Unblock operations cannot exceed blocked quantity
- Issue operations cannot exceed available quantity unless explicitly issuing from blocked stock
- Physical stock movements create immutable InventoryLedger entries
- Reservation commands do not create InventoryLedger entries or mutate StockLevel because they do not move physical stock
- StockLevel quantities are stored in the item's base unit of measure
- StockLevel records persist with zero quantities instead of being deleted

## Test Cases

- StockLevel is created for a new item-location pair on first stock movement
- Inbound posting increases onHand and available
- Outbound posting decreases onHand and available
- Outbound posting from blocked stock decreases onHand and blocked
- createStockReservation creates a StockReservation slice
- updateStockReservation sets StockReservation reserved quantity and requirement fields
- Issue posting internally increases StockReservation consumed quantity
- closeStockReservation excludes StockReservation from open reservation calculations
- StockAdjustment follows DRAFT -> SUBMITTED -> CONFIRMED, with reject and cancel paths
- Confirming CORRECTION adjusts onHand according to the correction direction
- Confirming SCRAP decreases onHand and available, or decreases blocked when scrapping blocked stock
- Confirming BLOCK increases blocked and decreases available
- Confirming UNBLOCK decreases blocked and increases available
- InventoryLedger entries are immutable and can be used to verify StockLevel balances
- Stock inquiry available quantity always equals AVAILABLE stock type minus open reservation quantity
- Concurrent stock movements to the same item-location produce correct final quantities

## Reference Links

- [InventoryLedger](../model/InventoryLedger.md) - immutable physical stock movement record
- [StockLevel](../model/StockLevel.md) - current stock position cache
- [StockReservation](../model/StockReservation.md) - current reservation balance cache
- [PostInventoryLedger](../command/PostInventoryLedger.md) - posting API used by inbound/outbound shipment modules
- [Inventory Adjustment](./inventory-adjustment.md) - operator-initiated corrections and status workflow
- [Scrap Management](./scrap-management.md) - scrap-specific stock and valuation behavior
- [Supply Planning](./supply-planning.md) - future inbound supply projections
- [SAP Inventory Management - Stock Overview](https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE/f18f0acdc5f04a35badc70e96e8d4549/4f36a7addfae4f32e10000000a11466f.html)
- [Oracle Inventory - On-Hand Quantity](https://docs.oracle.com/en/cloud/saas/supply-chain-and-manufacturing/25a/famoh/inventory-on-hand-balances.html)
- [Oracle Inventory Transactions](https://docs.oracle.com/en/cloud/saas/supply-chain-and-manufacturing/25a/fammi/inventory-transactions.html)
