# ConfirmStockAdjustment

## Permission Scope

inventoryApproval

## Overview

confirmStockAdjustment approves and executes a submitted stock adjustment, creating InventoryLedger entries and updating StockLevel records for each adjustment line. This combines approval and execution into a single step.

The ledger entries and stock level effects depend on the adjustment type (and for SCRAP, the `fromStockCategory` on each line):

- **CORRECTION INCREASE**: single IN entry, increases onHand
- **CORRECTION DECREASE**: single OUT entry, decreases onHand
- **SCRAP (fromStockCategory=AVAILABLE, default)**: single OUT entry, decreases onHand
- **SCRAP (fromStockCategory=BLOCKED)**: single OUT entry against the BLOCKED type, removes blocked stock from inventory
- **BLOCK**: two entries (OUT on AVAILABLE + IN on BLOCKED), increases blocked (onHand unchanged)
- **UNBLOCK**: two entries (IN on AVAILABLE + OUT on BLOCKED), decreases blocked (onHand unchanged)

## Business Rules

- Stock adjustment must exist
- Stock adjustment header is locked while confirming to prevent duplicate execution for the same adjustment
- Stock adjustment must be in SUBMITTED status
- Stock effects are derived directly from each StockAdjustmentLine by adjustment type (and, for SCRAP, `fromStockCategory`) and posted through `postInventoryLedger`, which enforces stock sufficiency per line:
  - Every OUT effect on the AVAILABLE type (CORRECTION DECREASE, SCRAP with fromStockCategory=AVAILABLE, BLOCK) runs the availability (ATP) check: site availability net of open reservations must cover the quantity, so reserved stock cannot be adjusted away — resolve or close the reservation first, then adjust
  - Every other OUT effect (SCRAP or UNBLOCK on the BLOCKED type) must keep the location quantity non-negative
- Transitions status from SUBMITTED to CONFIRMED
- Posts the derived stock effects through `postInventoryLedger` with `sourceType=STOCK_ADJUSTMENT`, `sourceId` set to the StockAdjustment and `sourceLineId` set to each line
- For each line, creates ledger entries and updates stock levels as follows:
  - **CORRECTION INCREASE**: 1 IN ledger entry (`action=QUANTITY_CHANGE`) / StockLevel `onHand += qty` / Valuation: inventory gain against the adjustment account, at standard; when the line carries a unitCost it is the actual acquisition cost, so the standard-vs-actual difference is recognized as PPV
  - **CORRECTION DECREASE**: 1 OUT ledger entry (`action=QUANTITY_CHANGE`) / StockLevel `onHand -= qty` / Valuation: inventory loss against the adjustment account
  - **SCRAP (fromStockCategory=AVAILABLE)**: 1 OUT ledger entry (`action=QUANTITY_CHANGE`) / StockLevel `onHand -= qty` / Valuation: write-off against the adjustment account
  - **SCRAP (fromStockCategory=BLOCKED)**: 1 OUT ledger entry (`action=QUANTITY_CHANGE`) against the BLOCKED stockType / StockLevel `quantity -= qty` for the BLOCKED type / Valuation: write-off against the adjustment account
  - **BLOCK**: 2 ledger entries (OUT on AVAILABLE + IN on BLOCKED, `action=STOCK_TYPE_CHANGE`) / StockLevel `blocked += qty` (onHand unchanged) / Valuation: no effect (internal relocation)
  - **UNBLOCK**: 2 ledger entries (IN on AVAILABLE + OUT on BLOCKED, `action=STOCK_TYPE_CHANGE`) / StockLevel `blocked -= qty` (onHand unchanged) / Valuation: no effect (internal relocation)
- Ledger entries carry `sourceType=STOCK_ADJUSTMENT` and reference the StockAdjustment via `sourceId` and each line via `sourceLineId`
- When confirmed through module wiring, derives the stock effects from each StockAdjustmentLine and posts them through `postInventoryLedger`, which writes the InventoryLedger rows

## Process Flow

```mermaid
flowchart TD
    A[Confirm stock adjustment request] --> B{Adjustment exists?}
    B -->|No| C[Return error: STOCK_ADJUSTMENT_NOT_FOUND]
    B -->|Yes| D{Status is SUBMITTED?}
    D -->|No| E[Return error: INVALID_STATUS]
    D -->|Yes| F[Process each line]
    F --> G{Adjustment type?}
    G -->|CORRECTION INCREASE| H[Create Ledger IN\nonHand += qty\nValuation gain]
    G -->|CORRECTION DECREASE| J{onHand >= qty?}
    J -->|No| K[Return error: INSUFFICIENT_STOCK]
    J -->|Yes| L[Create Ledger OUT\nonHand -= qty\nValuation loss]
    G -->|SCRAP + AVAILABLE| M{onHand - blocked >= qty?}
    M -->|No| N[Return error: INSUFFICIENT_STOCK]
    M -->|Yes| O[Create 1 Ledger OUT\nonHand -= qty\nValuation loss]
    G -->|SCRAP + BLOCKED| M2{blocked >= qty?}
    M2 -->|No| N2[Return error: INSUFFICIENT_STOCK]
    M2 -->|Yes| O2[Create 1 Ledger entry\nOUT from BLOCKED\nBLOCKED quantity -= qty\nValuation loss]
    G -->|BLOCK| P{available >= qty?}
    P -->|No| Q[Return error: INSUFFICIENT_STOCK]
    P -->|Yes| R[Create 2 Ledger entries\nOUT on AVAILABLE + IN on BLOCKED\nblocked += qty]
    G -->|UNBLOCK| S{blocked >= qty?}
    S -->|No| T[Return error: INSUFFICIENT_STOCK]
    S -->|Yes| U[Create 2 Ledger entries\nIN on AVAILABLE + OUT on BLOCKED\nblocked -= qty]
    H --> W[Transition to CONFIRMED]
    L --> W
    O --> W
    O2 --> W
    R --> W
    U --> W
    W --> X[Return confirmed adjustment]
```

## External Dependencies

- [inventory::StockAdjustment](../model/StockAdjustment.md) - Validates existence and current status, updates status to CONFIRMED
- [inventory::StockAdjustmentLine](../model/StockAdjustmentLine.md) - Reads source line details such as cost metadata
- [inventory::postInventoryLedger](./PostInventoryLedger.md) - Posts the derived stock effects to the InventoryLedger with `sourceType=STOCK_ADJUSTMENT`
- [inventory::InventoryLedger](../model/InventoryLedger.md) - Creates ledger entries per adjustment type and fromStockCategory
- [inventory::StockLevel](../model/StockLevel.md) - Validates stock sufficiency in the selected category and updates quantities

## Error Scenarios

- **STOCK_ADJUSTMENT_NOT_FOUND**: Referenced stock adjustment does not exist
- **INVALID_STATUS**: Target entity is not in a valid status for this operation
- **INSUFFICIENT_STOCK**: Available stock cannot cover the requested quantity

## Test Cases

- returns error when stock adjustment does not exist
- locks the StockAdjustment row before validating status
- returns error when stock adjustment is not in SUBMITTED status
- returns error when stock adjustment is not in SUBMITTED status (CONFIRMED)
- confirms CORRECTION INCREASE and creates IN ledger entry (increases onHand)
- stamps STOCK_ADJUSTMENT source references on posted ledger entries
- creates new StockLevel when none exists for CORRECTION INCREASE
- confirms CORRECTION DECREASE and creates OUT ledger entry (decreases onHand)
- returns error when on-hand is insufficient for CORRECTION DECREASE
- confirms SCRAP (AVAILABLE) and creates 1 OUT ledger entry (decreases onHand)
- returns error when available portion is insufficient for SCRAP (AVAILABLE)
- returns error when SCRAP would draw down reserved stock
- confirms SCRAP (BLOCKED) and creates a single OUT ledger entry
- confirms SCRAP (BLOCKED) and decreases the BLOCKED StockLevel type
- returns error when blocked is insufficient for SCRAP (BLOCKED)
- confirms BLOCK and creates two ledger entries (OUT + IN)
- confirms BLOCK and increases blocked without changing onHand
- returns error when available stock is insufficient for BLOCK
- confirms UNBLOCK and creates two ledger entries (IN + OUT)
- confirms UNBLOCK and decreases blocked without changing onHand
- returns error when blocked stock is insufficient for UNBLOCK
- triggers valuation gain for CORRECTION INCREASE
- triggers valuation loss for CORRECTION DECREASE and SCRAP (both categories)
- does not trigger valuation for BLOCK and UNBLOCK
- links ledger entries to the stock adjustment via source references
- posts each CORRECTION INCREASE line against the same stock key
- transitions status from SUBMITTED to CONFIRMED
- processes multiple lines in a single confirmation
