# 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 InventoryExecution posting commands
- 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 |
|---|---|
| `inventoryExecutionId` | Reference to the InventoryExecution that owns execution type and source document context |
| `dcIndicator` | Debit or credit direction for quantity and accounting integration |
| `quantity` | Positive movement quantity; direction is determined by `dcIndicator` |
| `itemId` | Item being moved |
| `storageLocationId` | Location where the movement is recorded |
| `executedAt` | Business timestamp of the movement |
| `lotId` | Optional lot or batch reference |
| `serialNumberId` | Optional serial number reference |

**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 commands create draft InventoryExecution records and post them to stock. They are intended for external modules that already own their document lifecycle, such as purchase receipt, sales shipment, or manufacturing completion.

| Command | Ledger Effect | StockLevel Effect |
|---|---|---|
| `postGoodsReceipt` | GOODS_RECEIPT DR | `onHand += quantity`, `available += quantity` |
| `postGoodsIssue` | GOODS_ISSUE CR | `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 DR | `onHand += quantity`, `available += quantity` |
| CORRECTION DECREASE | CORRECTION CR | `onHand -= quantity`, `available -= quantity` |
| SCRAP from available | SCRAP CR | `onHand -= quantity`, `available -= quantity` |
| SCRAP from blocked | SCRAP CR, UNBLOCK CR | `onHand -= quantity`, `blocked -= quantity` |
| BLOCK | BLOCK DR | `blocked += quantity`, `available -= quantity` |
| UNBLOCK | UNBLOCK CR | `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[InventoryExecution posting command]
    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**: A caller records arrived goods with `createGoodsReceipt` and posts them with `postGoodsReceipt`; on-hand and available increase at the destination location.
- **Goods issue**: A caller ships or consumes goods with `createGoodsIssue` and posts them with `postGoodsIssue`; 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
- GOODS_RECEIPT increases onHand and available
- GOODS_ISSUE decreases onHand and available
- GOODS_ISSUE from blocked decreases onHand and blocked
- createStockReservation creates a StockReservation slice
- updateStockReservation sets StockReservation reserved quantity and requirement fields
- Issue execution 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
- [CreateGoodsReceipt](../command/CreateGoodsReceipt.md) / [PostGoodsReceipt](../command/PostGoodsReceipt.md) - goods receipt commands
- [CreateGoodsIssue](../command/CreateGoodsIssue.md) / [PostGoodsIssue](../command/PostGoodsIssue.md) - goods issue commands
- [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)
