# transfer-order-lifecycle

## Overview

Manages the lifecycle of a Transfer Order from creation through closure, following the native erp-kit `DRAFT → OPEN → CLOSED` state machine. An Inventory Manager creates a TO in DRAFT status with source and destination `StorageLocation`s and line items (item + expected quantity). The Inventory Manager opens the TO to commit it to execution — `DRAFT → OPEN` is a single direct action that reserves source-site stock but does not ship it. While the TO is OPEN, the Inventory Manager ships each line (moving source AVAILABLE stock into IN_TRANSIT) and receives it at the destination (moving IN_TRANSIT into destination AVAILABLE); both are separate, explicit actions and the status stays OPEN throughout. Once in-transit stock is clear (`shippedQuantity == receivedQuantity` on every line), the Inventory Manager closes the OPEN TO. There is **no separate Cancel action and no `CANCELLED` state**; close serves both the happy-path settlement and the abandonment case, mirroring the native erp-kit `DRAFT → OPEN → CLOSED` state machine which provides no `cancel` command. The two-step submit / approve / reject flow used by the PurchaseOrder lifecycle does not apply to Transfer Orders. The unit on each line is pinned to the seed `Unit.symbol = "each"`.

The TO row carries two extension fields on top of the erp-kit native columns: `docNumber` (auto-generated `TO-%d` serial and shown on every screen for human disambiguation), and `notes` (free-form operator notes, patchable via `updateTransferOrder` while the TO is in DRAFT).

## Actors Involved

- [Inventory Manager](../../actor/inventory-manager.md) — creates, edits, opens, ships, receives, and closes transfer orders

See [action-matrix.md](./action-matrix.md) for models involved and the action visibility matrix.

## Flow Diagram

```mermaid
sequenceDiagram
    participant IM as Inventory Manager
    participant S as System

    Note over IM,S: Authoring (DRAFT)

    IM->>S: Create TO (source, destination, lines)
    S->>S: Validate self-transfer (source != destination)
    S->>S: Inject unitId = each on every line
    S-->>IM: TO created in DRAFT with auto-generated doc number

    IM->>S: Edit TO (header patch + replace line set)
    S->>S: Reject if not in DRAFT
    S-->>IM: TO updated

    Note over IM,S: Opening

    IM->>S: Open TO
    S->>S: Reserve source-site stock (shippedQuantity stays 0)
    S-->>IM: TO status → OPEN

    Note over IM,S: Shipping (while OPEN, any line under-shipped)

    IM->>S: Ship TO
    S->>S: Move source AVAILABLE → IN_TRANSIT, set shippedQuantity
    S-->>IM: TO stays OPEN

    Note over IM,S: Receiving (while OPEN, in-transit stock exists)

    IM->>S: Receive TO
    S->>S: Move IN_TRANSIT → destination AVAILABLE, set receivedQuantity
    S-->>IM: TO stays OPEN

    Note over IM,S: Closing (after in-transit is clear OR the order is abandoned)

    IM->>S: Close TO
    S-->>IM: TO status → CLOSED

    Note over S: Rejects with INVALID_TRANSFER_LOCATIONS when sourceSiteId === destinationSiteId (self-transfer)
    Note over S: Rejects with EMPTY_TRANSFER_LINES on create / update with no lines and INVALID_QUANTITY on non-positive expected quantities
    Note over S: Edit / update is refused on a TO that is no longer in DRAFT (lifecycle-gate guard)
```

## Stories

- [Create Transfer Order](./story/inventory-manager--create-transfer-order.md)
- [Edit Transfer Order](./story/inventory-manager--edit-transfer-order.md)
- [Open Transfer Order](./story/inventory-manager--open-transfer-order.md)
- [Ship Transfer Order](./story/inventory-manager--ship-transfer-order.md)
- [Receive Transfer Order](./story/inventory-manager--receive-transfer-order.md)
- [Close Transfer Order](./story/inventory-manager--close-transfer-order.md)
- [View Transfer Order List](./story/inventory-manager--view-transfer-order-list.md)
