# IssueStock

## Permission Scope

inventoryOperation

## Overview

issueStock records the issuance of goods out of inventory, creating InventoryLedger entries and updating the StockLevel in a single atomic operation. The command supports two modes controlled by the `fromBlocked` flag:

- **fromBlocked=false (default)**: Standard issue from available stock. Creates one GOODS_ISSUE/CR ledger entry. Decrements onHand.
- **fromBlocked=true**: Issue from blocked (quarantined) stock — typically used for returning inspection-failed goods to the supplier. Creates two ledger entries: GOODS_ISSUE/CR + UNBLOCK/CR. Decrements both onHand and blocked.

The command validates that sufficient stock exists in the selected category (available for fromBlocked=false, blocked for fromBlocked=true), creates credit ledger entries, consumes from cost layers, and transitions serial numbers to ISSUED state when applicable.

## 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
- `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 `fromBlocked=false` (default):
  - Available stock must be sufficient: `onHand - reserved - blocked >= quantity`
  - Serial number must be in AVAILABLE state (for serial-tracked items)
  - Creates one InventoryLedger entry `{ GOODS_ISSUE, CR, amount }`
  - Updates StockLevel: `onHand -= quantity`
- When `fromBlocked=true`:
  - Blocked stock must be sufficient: `blocked >= quantity`
  - Creates two InventoryLedger entries in one transaction: `{ GOODS_ISSUE, CR, amount }` + `{ UNBLOCK, CR, quantity }`
  - Updates StockLevel: `onHand -= quantity`, `blocked -= quantity`
- Valuation is the same in both modes: CR triggers CostLayer consumption (FIFO) or recalculation (AVCO/STANDARD_COST)
- Updates LotStockLevel when the item is lot-tracked:
  - fromBlocked=false: `onHand -= quantity`
  - fromBlocked=true: `onHand -= quantity`, `blocked -= quantity`
- Transitions SerialNumber from AVAILABLE to ISSUED if the item is serial-tracked (both modes)

## Process Flow

```mermaid
flowchart TD
    A[Issue 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?}
    F -->|No| G[Return error: INVALID_QUANTITY]
    F -->|Yes| H{Lot/serial tracking valid?}
    H -->|No| I[Return error]
    H -->|Yes| J{fromBlocked?}
    J -->|No| K{Available stock >= qty?}
    K -->|No| L[Return error: INSUFFICIENT_STOCK]
    K -->|Yes| M{Serial in AVAILABLE state?}
    M -->|No| N[Return error: SERIAL_NOT_AVAILABLE]
    M -->|Yes or N/A| O[Create 1 Ledger entry\nGOODS_ISSUE / CR\nStockLevel: onHand -= qty]
    J -->|Yes| P{Blocked stock >= qty?}
    P -->|No| Q[Return error: INSUFFICIENT_BLOCKED_STOCK]
    P -->|Yes| R[Create 2 Ledger entries\nGOODS_ISSUE/CR + UNBLOCK/CR\nStockLevel: onHand -= qty, blocked -= qty]
    O --> S[Apply valuation CR]
    R --> S
    S --> T{Lot-tracked?}
    T -->|Yes| U[Update LotStockLevel]
    T -->|No| V{Serial-tracked?}
    U --> V
    V -->|Yes| W[Update SerialNumber\nAVAILABLE -> ISSUED]
    V -->|No| X[Return ledger entries]
    W --> X
```

## 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 ledger entries (1 for available issue, 2 for blocked issue)
- [inventory::StockLevel](../models/StockLevel.md) - Validates sufficient stock in the selected category and updates quantities
- [inventory::LotStockLevel](../models/LotStockLevel.md) - Updates lot-level onHand and blocked for lot-tracked items
- [inventory::CostLayer](../models/CostLayer.md) - Consumes from or recalculates cost layers based on valuation method
- [inventory::Lot](../models/Lot.md) - Validates lot existence for lot-tracked items
- [inventory::SerialNumber](../models/SerialNumber.md) - Validates AVAILABLE state and transitions to ISSUED 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
- **INSUFFICIENT_STOCK**: Available stock (onHand - reserved - blocked) is less than the requested quantity (fromBlocked=false)
- **INSUFFICIENT_BLOCKED_STOCK**: Blocked stock is less than the requested quantity
- **INVALID_QUANTITY**: Quantity is zero or negative
- **LOT_NOT_FOUND**: Referenced lot does not exist
- **SERIAL_NOT_FOUND**: Referenced serial number does not exist
- **SERIAL_NOT_AVAILABLE**: Serial number is not in AVAILABLE state

## 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 insufficient available stock (fromBlocked=false)
- returns error when insufficient blocked stock (fromBlocked=true)
- returns error when lot does not exist
- returns error when serial number does not exist
- returns error when serial number is not in AVAILABLE state
- issues stock (fromBlocked=false) and creates 1 ledger entry GOODS_ISSUE/CR
- updates onHand when fromBlocked=false
- issues stock (fromBlocked=true) and creates 2 ledger entries (GOODS_ISSUE/CR + UNBLOCK/CR)
- updates onHand and blocked when fromBlocked=true
- updates LotStockLevel onHand (and blocked if fromBlocked) for lot-tracked items
- transitions serial number from AVAILABLE to ISSUED
