# ReceiveStock

## Permission Scope

inventoryOperation

## Overview

receiveStock records the standard (trust-on-receipt) receipt of goods into inventory, creating a single InventoryLedger entry and updating the StockLevel in one atomic operation. The received stock is immediately available for use. For goods that require a quality-inspection hold before becoming available, use [receiveStockWithInspection](./ReceiveStockWithInspection.md) instead.

The command also triggers cost layer creation or recalculation depending on the item's valuation method, and updates LotStockLevel for lot-tracked items and SerialNumber state for serial-tracked items.

## 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 (application layer) is responsible for supplying `lotId` when the item is lot-tracked — the inventory module does not infer tracking mode from the item.
- `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 one InventoryLedger entry `{ GOODS_RECEIPT, DR, amount }`
- Updates StockLevel: `onHand += quantity`, `incoming -= quantity`
- DR triggers CostLayer creation (FIFO) or recalculation (AVCO/STANDARD_COST)
- Updates LotStockLevel when the item is lot-tracked: `onHand += quantity`
- Updates SerialNumber state for serial-tracked items:
  - If the serial is currently ISSUED, transitions to RETURNED (customer return scenario)
  - If the serial is currently RETURNED, transitions to AVAILABLE (restock after inspection)
  - For a newly received serial not yet tracked, assignSerialNumber should be called separately to create the SerialNumber record in AVAILABLE state

## Process Flow

```mermaid
flowchart TD
    A[Receive stock 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 1 Ledger entry\nGOODS_RECEIPT / DR\nStockLevel: onHand += qty, incoming -= qty]
    J --> K[Apply valuation DR]
    K --> L{Lot-tracked?}
    L -->|Yes| M[Update LotStockLevel\nonHand += qty]
    L -->|No| N{Serial-tracked?}
    M --> N
    N -->|Yes| O[Update SerialNumber state]
    N -->|No| P[Return ledger entry]
    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 the GOODS_RECEIPT/DR ledger entry
- [inventory::StockLevel](../models/StockLevel.md) - Updates on-hand and incoming quantities
- [inventory::LotStockLevel](../models/LotStockLevel.md) - Updates lot-level onHand 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 1 ledger entry with GOODS_RECEIPT/DR
- updates onHand and decreases incoming
- creates new cost layer for FIFO valuation
- updates LotStockLevel onHand for lot-tracked items
- transitions serial number from ISSUED to RETURNED (customer return)
- creates new StockLevel when none exists
