# ReceiveStockWithInspection

## Permission Scope

inventoryOperation

## Overview

receiveStockWithInspection records a quality-inspection receipt (SAP QI Stock pattern) of goods into inventory. Stock is placed directly into the blocked (inspection-hold) state and is not available for use until the inspection outcome is resolved. The command creates three InventoryLedger entries in a single atomic transaction: GOODS_RECEIPT/DR + BLOCK/CR + BLOCK/DR. As a result, both `onHand` and `blocked` increase while `available` remains unchanged — inspection stock is never accidentally issued.

Resolution of the inspection is performed by subsequent commands:
- Pass → [StockAdjustment(UNBLOCK)](./ConfirmStockAdjustment.md) or per-line UNBLOCK flow
- Fail → discard via [StockAdjustment(SCRAP, fromStockCategory=BLOCKED)](./ConfirmStockAdjustment.md)
- Return to supplier → [issueStock](./IssueStock.md) with `fromBlocked=true`
- Hold indefinitely → do nothing

This command is intended to be called from the Purchase module (or any procurement flow) when the application's policy requires inspection before goods become usable. Applications that do not perform inspection should call [receiveStock](./ReceiveStock.md) instead.

## Business Rules

- Item must exist and be in ACTIVE status
- Storage location must exist and be in ACTIVE status
- Quantity must be greater than zero
- Unit cost must be greater than zero
- `lotId` is optional; when provided, the referenced lot must exist. The caller is responsible for supplying it when the item is lot-tracked.
- `serialNumberId` is optional; when provided, the referenced serial number must exist. The caller is responsible for supplying it when the item is serial-tracked.
- When `serialNumberId` is provided, quantity is expected to be 1 (enforced by the caller for serial-tracked items).
- Creates three InventoryLedger entries in one transaction: `{ GOODS_RECEIPT, DR }`, `{ BLOCK, CR }`, `{ BLOCK, DR }`
- Updates StockLevel: `onHand += quantity`, `blocked += quantity`, `incoming -= quantity`
- Available stock (`onHand - reserved - blocked`) never increases from this operation — inspection stock is not usable
- DR triggers CostLayer creation (FIFO) or recalculation (AVCO/STANDARD_COST). Inspection stock is still valued as an asset.
- Updates LotStockLevel when the item is lot-tracked: `onHand += quantity`, `blocked += quantity`
- Updates SerialNumber state for serial-tracked items: same transitions as receiveStock (ISSUED → RETURNED, RETURNED → AVAILABLE). The inspection hold is represented at the StockLevel layer only; it does not change the serial number state itself.

## Process Flow

```mermaid
flowchart TD
    A[Receive stock with inspection request] --> B{Item exists and ACTIVE?}
    B -->|No| C[Return error]
    B -->|Yes| D{Location exists and ACTIVE?}
    D -->|No| E[Return error]
    D -->|Yes| F{Quantity > 0 and unitCost > 0?}
    F -->|No| G[Return error]
    F -->|Yes| H{Lot/serial valid if provided?}
    H -->|No| I[Return error]
    H -->|Yes| J[Create 3 Ledger entries\nGOODS_RECEIPT/DR + BLOCK/CR + BLOCK/DR\nStockLevel: onHand += qty, blocked += qty, incoming -= qty]
    J --> K[Apply valuation DR]
    K --> L{Lot-tracked?}
    L -->|Yes| M[Update LotStockLevel\nonHand += qty, blocked += qty]
    L -->|No| N{Serial-tracked?}
    M --> N
    N -->|Yes| O[Update SerialNumber state]
    N -->|No| P[Return ledger entries]
    O --> P
```

## External Dependencies

- [item-management::Item](../../../item-management/docs/models/Item.md) - Validates that the referenced item exists and is ACTIVE
- [inventory::StorageLocation](../models/StorageLocation.md) - Validates that the storage location exists and is ACTIVE
- [inventory::InventoryLedger](../models/InventoryLedger.md) - Creates 3 ledger entries (GOODS_RECEIPT/DR + BLOCK/CR + BLOCK/DR)
- [inventory::StockLevel](../models/StockLevel.md) - Updates on-hand, blocked, and incoming quantities
- [inventory::LotStockLevel](../models/LotStockLevel.md) - Updates lot-level onHand and blocked for lot-tracked items
- [inventory::CostLayer](../models/CostLayer.md) - Creates or recalculates cost layer based on valuation method
- [inventory::Lot](../models/Lot.md) - Validates lot existence for lot-tracked items
- [inventory::SerialNumber](../models/SerialNumber.md) - Validates and updates serial number state for serial-tracked items

## Error Scenarios

- **ITEM_NOT_FOUND**: Referenced item does not exist
- **ITEM_NOT_ACTIVE**: Referenced item is not in ACTIVE status
- **STORAGE_LOCATION_NOT_FOUND**: Referenced storage location does not exist
- **STORAGE_LOCATION_NOT_ACTIVE**: Referenced storage location is not in ACTIVE status
- **INVALID_QUANTITY**: Quantity is zero or negative
- **INVALID_UNIT_COST**: Unit cost is zero or negative
- **LOT_NOT_FOUND**: Referenced lot does not exist
- **SERIAL_NOT_FOUND**: Referenced serial number does not exist

## Test Cases

- returns error when item does not exist
- returns error when item is not active
- returns error when storage location does not exist
- returns error when storage location is not active
- returns error when quantity is zero or negative
- returns error when unit cost is zero or negative
- returns error when lot does not exist
- returns error when serial number does not exist
- receives stock and creates 3 ledger entries (GOODS_RECEIPT/DR + BLOCK/CR + BLOCK/DR)
- updates onHand, blocked, and decreases incoming
- available stock does not increase from this operation
- creates new cost layer for FIFO valuation
- updates LotStockLevel onHand and blocked for lot-tracked items
- transitions serial number from ISSUED to RETURNED (customer return)
- creates new StockLevel when none exists
