# ShipTransferOrder

## Permission Scope

transferOrder

## Overview

shipTransferOrder posts a transfer shipment for an OPEN TransferOrder. It moves stock from source-site AVAILABLE stock into IN_TRANSIT stock and increments TransferOrderLine `shippedQuantity`.

The caller supplies the transit storage location used for the IN_TRANSIT side of the shipment. erp-kit records and validates that execution fact, but does not choose the transit location. Applications can pass a global default in-transit location or a site-specific in-transit location when users do not need to distinguish transit locations.

## Business Rules

- The TransferOrder must exist and be OPEN
- Each transfer line must belong to the TransferOrder
- Shipment quantity must be greater than zero
- Shipment quantity cannot exceed `orderedQuantity - shippedQuantity`
- Source storage location must belong to the TransferOrder source site
- Transit storage location must exist
- Shipment is posted through `postInventoryLedger` as one posting event with `sourceType=TRANSFER_ORDER`, `sourceId` set to the transfer order and `sourceLineId` set to each transfer line, and `action=TRANSFER` (no costing)
- The posting event carries a paired set of movement lines per transfer line: an OUT line on source AVAILABLE stock and an IN line on transit IN_TRANSIT stock
- The posting writes two InventoryLedger rows per transfer line (source AVAILABLE OUT and transit IN_TRANSIT IN), consumes matching TransferOrder StockReservation quantity for the source AVAILABLE line, and updates source and transit StockLevel balances
- Being an internal relocation, the shipment does not affect valuation
- Shipment increments TransferOrderLine `shippedQuantity`

## Process Flow

```mermaid
flowchart TD
    A[Receive ship request] --> B[Lock TransferOrder]
    B --> C{TransferOrder is OPEN?}
    C -->|No| D[Return not found or state error]
    C -->|Yes| E[Validate each line and location]
    E --> F[Build paired source AVAILABLE OUT and transit IN_TRANSIT IN movement lines]
    F --> G[Post through postInventoryLedger sourceType=TRANSFER_ORDER action=TRANSFER]
    G --> H[Consume source reservation, write ledger rows, update StockLevel]
    H --> I[Increment shippedQuantity]
    I --> J[Return ledger entries and updated lines]
```

## External Dependencies

- [inventory::TransferOrder](../model/TransferOrder.md) - Must be OPEN
- [inventory::TransferOrderLine](../model/TransferOrderLine.md) - Provides line item and shipment progress
- [inventory::StockReservation](../model/StockReservation.md) - Source reservation is consumed
- [inventory::StockLevel](../model/StockLevel.md) - Source AVAILABLE and transit IN_TRANSIT balances are updated
- [inventory::postInventoryLedger](./PostInventoryLedger.md) - Posts the paired shipment movement lines to the InventoryLedger
- [inventory::InventoryLedger](../model/InventoryLedger.md) - Records immutable stock movement entries

## Error Scenarios

- **TRANSFER_ORDER_NOT_FOUND**: Transfer order does not exist
- **TRANSFER_ORDER_LINE_NOT_FOUND**: Transfer order line does not exist or does not belong to the order
- **INVALID_STATE_TRANSITION**: Transfer order lifecycle transition is not allowed
- **INVALID_QUANTITY**: Quantity is zero or negative
- **INVALID_TRANSFER_LOCATIONS**: Transfer location or site is invalid
- **STORAGE_LOCATION_NOT_FOUND**: Referenced storage location does not exist
- **INSUFFICIENT_STOCK**: Available stock cannot cover the requested quantity

## Test Cases

- ships a transfer line into in-transit stock
- returns error when shipping more than remaining ordered quantity
- returns error when no lines are provided
