# Transfer Order — Models & Action Visibility Matrix

## Models Involved

Source of truth: native `inventory.TransferOrder` + the local extension that adds fields (`docNumber`, `notes`).

| Model | erp-kit module | Role in this flow |
|---|---|---|
| TransferOrder | (erp-kit `inventory`, native) | Document header; owns `status` (`DRAFT` / `OPEN` / `CLOSED`), per-line `orderedQuantity` / `shippedQuantity` / `receivedQuantity` rollups. Extension layers `docNumber`, `notes`. |
| TransferOrderLine | (erp-kit `inventory`, native) | Per-line item, unit, expected qty, shipped / received qty (mutated by `shipTransferOrder` / `receiveTransferOrder`) |
| StorageLocation | (erp-kit `inventory`) | Source / destination references |
| Site | (erp-kit `organization`) | `Site.type` (WAREHOUSE / STORE) labels the source / destination in the UI |
| Unit | (erp-kit `item-management`) | The resolver pins every line's `unitId` to the seed `Unit.symbol = "each"` |
| Item | (erp-kit `item-management`) | Line item reference |

### Status Axes

| Field | Values |
|---|---|
| `status` (Document Status) | `DRAFT` → `OPEN` → `CLOSED`. Native erp-kit provides no `cancel` command and no `CANCELLED` state; abandonment is expressed via `close` on an OPEN TO. The status is displayed verbatim on every screen (`DRAFT` / `OPEN` / `CLOSED`) — there is no presentation overlay or status remapping. The two-step submit / approve / reject flow does not apply to Transfer Orders. |

The Document Status state machine is owned by native erp-kit `inventory` (`DRAFT → OPEN → CLOSED`). Open, Ship, Receive, and Close are separate, explicit commands — Open never ships, and receiving the last in-transit qty never closes the order automatically. Shipping and receiving happen while the TO stays `OPEN`; the terminal `CLOSED` state is only reached via an explicit Close once in-transit stock is clear.

## Action Visibility Matrix

The `transfer-order-detail` screen's action panel gates each button on `status`.

Legend: ✓ visible+enabled · ◐ visible but disabled · blank = hidden.

| Action | Resolver / target | DRAFT | OPEN | CLOSED | Extra condition | Role |
|---|---|:-:|:-:|:-:|---|---|
| Edit | _navigate to edit form_ | ✓ |  |  | — | inventory-manager |
| Open | `openTransferOrder` | ✓ |  |  | reserves source-site stock; does not ship (`shippedQuantity` stays 0) | inventory-manager |
| Ship | `shipTransferOrder` |  | ✓ |  | shown while any line is under-shipped; moves source AVAILABLE → IN_TRANSIT and sets `shippedQuantity`; status stays OPEN | inventory-manager |
| Receive | `receiveTransferOrder` |  | ✓ |  | shown only when in-transit stock exists (`shipped > received`); moves IN_TRANSIT → destination AVAILABLE and sets `receivedQuantity`; status stays OPEN | inventory-manager |
| Close | `closeTransferOrder` |  | ✓ |  | only when in-transit is clear (`shipped == received` on every line) | inventory-manager |
| Back | _navigate to list_ | ✓ | ✓ | ✓ | — | — |

## Source / Destination Validation

The module command rejects self-transfers (`source = destination`) before persisting. Rejections surface as `INVALID_TRANSFER_LOCATIONS`.

## Notes on Stock Movement

`shipTransferOrder` and `receiveTransferOrder` are owned by erp-kit `inventory` native and are driven directly from the `transfer-order-detail` action panel as separate, explicit operator actions. Ship moves source AVAILABLE stock into IN_TRANSIT; Receive moves IN_TRANSIT into destination AVAILABLE. Both leave the TO `OPEN`; closing is a separate, later step.
