> For the complete documentation index, see [llms.txt](https://lmile.gitbook.io/lmile-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lmile.gitbook.io/lmile-docs/oversell-api/source-change.md).

# Source Change — 出荷元変更

[Units](/lmile-docs/fulfillment-api/units.md) の単位が、引当先（出荷元A）では加工できない（例: 刻印は流山でしかできない）ため、**別の出荷元B へ物理返送してから B で加工・出荷する**ケースの管理です。1 単位につき 1 件を起票し、単一の **advance エンドポイント**で「出荷元の切替 → 返送中 → 受領」を前進させます。

> **Base URL** `https://api.lmile.io` · **認証** `x-ingest-secret` ヘッダー または operator JWT（`Authorization: Bearer`）（[詳細](/lmile-docs/getting-started/authentication.md)） · `shop` はリクエストボディに含めます（API キーで呼ぶ場合は省略できます）

> `/api/routing/change-source` は機械間 secret（`x-ingest-secret`）と倉庫スタッフの operator JWT のどちらでも呼べます（系統はヘッダの有無で明示判別。[詳細](https://lmile.gitbook.io/lmile-docs/oversell-api/pages/7cB70I8yeRnMpvcdLkNu#routing-write-の-2-系統認証)）。operator JWT 経由は操作スタッフを記録します（起票 = `opened_by_staff_user_id` / 前進・中止 = `updated_by_staff_user_id`）。

> [Oversell / Reallocate](/lmile-docs/oversell-api/oversell-reallocate.md)（同一出荷元内の振替）や [Fulfillment](/lmile-docs/fulfillment-api/fulfillment.md) の `in_transit_to_mfc`（出荷元は変えず現在在庫のみ MFC へ移動）とは**別経路**です。本 API は**出荷元（fulfillment source）そのものを A→B に変え**、A→B の返送レグを追跡します。在庫は状態のみ更新し `inventory_levels` は変更しません。

## ステップ（status）

| status              | ステップ                | 自動記録 / 副作用                                                                                                                                                                                                 |
| ------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `change_requested`  | 1: 出荷元を A→B に切替（起票） | 旧 decision を `superseded`・新 decision（出荷元=B）を `applied`／unit を `source_change_pending`・出荷元=B・引当=B在庫・**現在在庫=A のまま**／B のヘッダを作成し relink／**旧出荷元A へ `cancel` dispatch**／**返送便ラベルを自動生成**（A の `label`+`return` 設定） |
| `return_in_transit` | 2: A が出庫し返送中        | `in_transit_at` / unit → `source_change_in_transit`（`returnTrackingNumber` 上書き可）                                                                                                                           |
| `return_received`   | 3: B が返送を受領         | `received_at` / 現在在庫を B へ確定・unit → `allocated`（通常フロー再開）／**新出荷元B へ `create` dispatch**                                                                                                                      |
| `cancelled`         | 中止（任意の非終端から）        | `cancelled_at` / `cancel_reason`／SoT を起票前の出荷元A へ巻き戻し（WMS への再連絡は運用側 best-effort）                                                                                                                            |

遷移は **1 ステップ前進**が基本。`cancelled` は `change_requested` / `return_in_transit` からのみ。`return_received` / `cancelled` は終端。それ以外のジャンプは `409 invalid_transition`。

`return_received` で unit が `allocated` に戻るため、以降は B 側で [在庫確保](/lmile-docs/fulfillment-api/units.md)（`/api/routing/secure`：加工後の確保→**最終出荷ラベル生成**）→ [Fulfillment](/lmile-docs/fulfillment-api/fulfillment.md)（梱包→出荷完了）の既存フローに合流します。

## エンドポイント

| Method | Path                         | 用途                        |
| ------ | ---------------------------- | ------------------------- |
| `POST` | `/api/routing/change-source` | 出荷元変更の起票 + 前進（単一 advance） |

起票済みケースの一覧（送り側の梱包キュー / 受け側の入荷待ちキュー）は [Operator Queues](/lmile-docs/operator-api/queues.md) の `GET /api/operator/source-changes` を参照してください。複数の単位を**箱単位でまとめて返送**する場合は [Transfer API](/lmile-docs/transfer-api/batches.md)（倉庫間移動バルク）を使います — バルクの dispatch / receive は本 API の `return_in_transit` / `return_received` と同一の遷移を一括実行します（バルク所属の単位は返送ラベルの夜間バッチ生成から除外されます）。

***

## `POST` /api/routing/change-source

対象単位の変更が無ければ**起票**（`change_requested`）し、あれば指定 status へ前進します。`toStatus` 省略時は 1 ステップ前進。起票時のみ `toSourceLocationId` が必須で、対象 unit は **`secured`** である必要があります（LM-913: 店舗で確保してから流山転送を起票する物理オペに合わせ、`secured` のみに一本化。確保前の `allocated` 起票は廃止）。前回の変更が**中止（`cancelled`）済み**の単位は、`secured` であれば同一行を**再オープン**して再度起票できます（LM-956。中止後に再び転送が必要になったケース）。

### リクエスト

```http
POST https://api.lmile.io/api/routing/change-source
x-ingest-secret: {INGEST_SECRET}
Content-Type: application/json
```

```json
{
  "shop": "demo-store.myshopify.com",
  "sourceSystem": "shopify",
  "externalOrderRef": "1001",
  "barcode": "1234567890123",
  "lineInstanceNo": 1,
  "toSourceLocationId": "33333333-3333-3333-3333-333333333333",
  "returnTag": "return",
  "fields": { "reason": "刻印は流山のみ" }
}
```

### ボディパラメータ

| パラメータ                               | 型               |  必須 | 説明                                                                                                  |
| ----------------------------------- | --------------- | :-: | --------------------------------------------------------------------------------------------------- |
| `shop`                              | string          |  ✓  | 店舗ドメイン（API キーで呼ぶ場合は省略できます）                                                                          |
| `routingOrderId`                    | string (uuid)   |  △  | 対象注文。無ければ `sourceSystem` + `externalOrderRef` で特定                                                   |
| `sourceSystem` / `externalOrderRef` | string          |  △  | `routingOrderId` 未指定時に必須                                                                            |
| `fulfillmentUnitId`                 | string (uuid)   |  △  | 対象単位。無ければ `barcode` + `lineInstanceNo` で特定                                                          |
| `barcode` / `lineInstanceNo`        | string / number |  △  | `fulfillmentUnitId` 未指定時に必須                                                                         |
| `toSourceLocationId`                | string (uuid)   |  △  | 新出荷元。**起票時必須**。有効メンバーを持ち、MFC または `return_destination_enabled` であること                                 |
| `toInventoryLocationId`             | string (uuid)   |     | 新出荷元配下の引当先在庫ロケーション（省略時は最優先メンバー）                                                                     |
| `mode`                              | string          |     | 省略 / `transfer` = 返送フロー（本ページの既定動作）。`switch` = **即時切替**（下記「mode: switch」参照。同一レグのまま出荷元だけを更新）          |
| `toStatus`                          | string          |     | 目標 status（省略 = 1 ステップ前進）。`change_requested` / `return_in_transit` / `return_received` / `cancelled` |
| `tag`                               | string          |     | 顧客出荷 dispatch（A への cancel / B への create）の tag                                                       |
| `returnTag`                         | string          |     | 返送便ラベルの tag（既定 `return`）。A 拠点の `action=label` 設定を解決                                                 |
| `fields.reason`                     | string          |     | 変更理由（例: 刻印は流山のみ）                                                                                    |
| `fields.returnTrackingNumber`       | string          |     | 返送便の追跡番号（自動生成より優先。手動上書き）                                                                            |
| `fields.returnCarrierCode`          | string          |     | 返送便のキャリアコード                                                                                         |
| `fields.externalShipmentRef`        | string          |     | 返送便の外部出荷 ID                                                                                         |
| `fields.cancelReason`               | string          |     | 中止理由（`cancelled` 用）                                                                                 |
| `fields.note`                       | string          |     | 任意メモ                                                                                                |

### レスポンス

```json
{
  "ok": true,
  "changeId": "d4e5f6a7-b8c9-0123-def4-56789012345a",
  "status": "change_requested",
  "unitStatus": "source_change_pending",
  "fromSource": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "toSource": "33333333-3333-3333-3333-333333333333",
  "toInventoryLocationId": "44444444-4444-4444-4444-444444444444",
  "returnTrackingNumber": "RTN-9999-8888",
  "returnLabelStatus": "generated",
  "returnLabelPrintStatus": "printed",
  "cancelDispatches": [ { "locationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "sent", "attempts": 1 } ],
  "releasedShelfLinks": 0,
  "opened": true,
  "changed": true
}
```

| フィールド                                        | 説明                                                                                                                                                                                                                          |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `changeId`                                   | 出荷元変更 id（`sot.lm_fulfillment_source_changes.id`）                                                                                                                                                                            |
| `status`                                     | 更新後の変更 status                                                                                                                                                                                                               |
| `unitStatus`                                 | 連動して更新された単位の status                                                                                                                                                                                                         |
| `fromSource` / `toSource`                    | 旧 / 新 出荷元ロケーション                                                                                                                                                                                                             |
| `toInventoryLocationId`                      | 新出荷元配下の引当先在庫ロケーション                                                                                                                                                                                                          |
| `returnTrackingNumber` / `returnLabelStatus` | 返送便の追跡番号 / ラベル生成結果（`generated` / `skipped` / `failed`）                                                                                                                                                                      |
| `returnLabelPrintStatus`                     | 返送ラベルの物理印刷結果（`printed` / `skipped` / `failed` / `none`）。旧出荷元 A の `action=print` 設定で印刷（[Dispatch Config](https://lmile.gitbook.io/lmile-docs/oversell-api/pages/bGOYICmcj4SdTpefqrDg#actionprint-ラベル物理印刷)）。レスポンスのみで行には保存しません |
| `cancelDispatches` / `createDispatches`      | A への cancel（起票時）/ B への create（受領時）の dispatch 結果                                                                                                                                                                             |
| `releasedShelfLinks`                         | 起票時に解放した旧出荷元 A の棚紐付け数（`secured` からの起票で旧ヘッダが空になった場合のみ非 0）                                                                                                                                                                    |
| `opened`                                     | この呼び出しで起票したか（再オープン含む）                                                                                                                                                                                                       |
| `reopened`                                   | 前回 `cancelled` だった単位を再オープンして起票したか（LM-956）                                                                                                                                                                                   |
| `changed`                                    | 状態が変化したか（同一 status への再呼び出しは `false`）                                                                                                                                                                                        |

### 挙動・冪等性

* **解決キー**: 1 単位につき変更 1 件（`shop + fulfillment_unit_id` で一意）。初回呼び出しで起票、以降は前進。前回が `cancelled`（終端）の単位は前進できないため、起票相当として**同一行を再オープン**します（`reopened: true`。`secured` 必須。新 decision / 新ヘッダ / 旧ヘッダ cancel / 返送便ラベル生成を起票と同一に再実行し、前回の進行・バルク・チェック・返送ラベル列は初期化）。LM-956。
* **起票（`change_requested`）**: 出荷元を B へ切替（旧 decision を `superseded`・新 decision を `applied`）、unit の 3 ロケーション列を更新（現在在庫は返送前=A のまま）、B のヘッダを `allocated` で作成し unit を relink、A へ `cancel` を dispatch、A の `label`+`returnTag` 設定で返送便ラベルを自動生成します。対象 unit は **`secured` のみ**（それ以外は `409 unit_not_changeable`）。
* **`secured` からの起票**: 確保を解除し（unit の `secured` / `secured_at` をクリア。B 受領で `allocated` に復帰後、B で再確保）、unit が抜けて旧出荷元 A のヘッダにアクティブ unit が残らない場合は、A の顧客出荷ヘッダを `cancelled` に遷移・発行済み配送ラベルを運用破棄マーク（`label_voided_at`。キャリア void は呼ばず tracking / label 列は監査用に温存）・[棚紐付け](/lmile-docs/shelf-api/link.md)を `released_reason="source_changed"` で解放します（解放数はレスポンスの `releasedShelfLinks`）。旧拠点でピック済みの事実（棚在庫減算済）は温存され、バルク発送時に二重減算しません。
* **返送便ラベル**は A 拠点に `action=label` の dispatch 設定が必要（送付先 B の住所はテンプレートに定義）。未設定なら `returnLabelStatus: "skipped"`。`fields.returnTrackingNumber` を渡すと自動生成より優先します。
* **受領（`return_received`）**: 現在在庫を B へ確定し unit を `allocated` に戻して B へ `create` を dispatch。以降の確保・最終出荷ラベルは `/api/routing/secure`（B の設定）で生成します。
* **中止（`cancelled`）**: SoT（出荷元・引当・現在在庫・decision・unit）を起票前の A へ巻き戻します。WMS への再連絡（A への再開・B への取消）は運用側 best-effort（dispatch しません）。
* 単位の `properties`（明細単位の任意キー/値 = [Routing](/lmile-docs/routing-api/orders.md) の `lines[].properties`）は新 decision へ引き継がれ、A への cancel / B への create の dispatch payload と返送指示の明細にも含まれます。
* 同一 status への再呼び出しは `changed: false` の no-op。在庫は状態のみで `inventory_levels` は不変。

### mode: "switch"（即時切替・同一レグ）

物理的な返送を伴わず、**データ上の出荷元だけを付け替えたい**場合の動作です。変更ケース（`lm_fulfillment_source_changes`）を起票せず、旧レグの取消も新レグの作成もしません — **出荷レグ（shipment）と単位（unit）は同じ id のまま**、出荷元・引当ロケーションが新拠点へ更新されます。現物の移動は運用側の対応です。

```json
{
  "shop": "demo-store.myshopify.com",
  "routingOrderId": "b1c2d3e4-...",
  "fulfillmentUnitId": "e2864b95-...",
  "toSourceLocationId": "33333333-3333-3333-3333-333333333333",
  "mode": "switch",
  "fields": { "reason": "大阪の佐川契約では代引不可のため" }
}
```

* **対象単位**: `allocated` または `secured`、かつ未ピック（それ以外は `409 unit_not_switchable`）。進行中の返送ケースを持つ単位は対象外（`409 transfer_in_progress` — 返送フロー側で前進/中止してください）。
* **対象レグ**: 切替はレグ（shipment）のヘッダごと新拠点へ移すため、**同一レグに他の active な単位がある場合**（複数明細・同一商品×複数点）は `409 shipment_has_siblings`、**MFC 集約グループ（1箱=1注文）に属するレグ**は `409 consolidation_group_member` になります。いずれも従来の返送フロー（`transfer`）を使ってください。
* **新出荷元の適格性**は、有効な在庫メンバーを持つ出荷元であること。店舗に引き当てられた単位（出荷元だけが変わる）は、`transfer` と違い MFC / `return_destination_enabled` でなくても選べます（店舗を兼ねる出荷元など）。自己在庫の MFC に引き当てられた単位（引当ごと移る）は `transfer` と同じく MFC / `return_destination_enabled` に限ります。
* **起きること**: 新 decision の記録（旧は `superseded`）→ unit の出荷元 / 引当 / 現在在庫ロケーションを新拠点へ更新（`secured` は解除し `allocated` へ）→ 同一レグの引当拠点を更新・集約棚を解放 → 旧拠点の棚紐付けを解放 → 旧出荷元 WMS へ `cancel`・**新出荷元 WMS へ `create` を dispatch**（いずれも best-effort。返送フローの受領と同じ）→ 新拠点が自己在庫なら割当保管棚を再分配 → **新旧両拠点の自己在庫 available を再計算** → **切替先の確保待ち通知イベント（`store_secure_pending_assigned`）を発火** → タイムライン（メモ）に変更履歴を記録。
* 発行済みの配送ラベルが載っている場合はレスポンスに `requiresLabelReissue: true` が返ります（旧出荷元の契約で発行済みのため、[ラベル再発行](/lmile-docs/fulfillment-api/print-label.md)が必要）。
* レスポンス例: `{ "ok": true, "mode": "switch", "shipmentId": "...", "fromSourceLocationId": "...", "toSourceLocationId": "...", "toInventoryLocationId": "...", "unsecured": true, "requiresLabelReissue": false, ... }`
* **再実行**: 切替完了後に同じボディを再送すると、新旧が同一になるため `422 same_source` になります（重複適用はされません）。

## エラー

エンドポイント固有:

| Status | 条件                                                                                       | レスポンス例                                                                                                                                                                                          |
| :----: | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|  `400` | 注文・単位の特定情報なし / 起票で `toSourceLocationId` 欠落                                               | `{ "ok": false, "error_kind": "invalid_input", "error": "toSourceLocationId required to open a source change" }`                                                                                |
|  `404` | 注文・単位が存在しない                                                                              | `{ "ok": false, "error_kind": "unit_not_found", "error": "fulfillment unit not found for order" }`                                                                                              |
|  `409` | 単位が `secured` でない（確保が先）/ 不正なステップ遷移                                                       | `{ "ok": false, "error_kind": "unit_not_changeable", "error": "unit must be 'secured' to change source — secure it first (current: allocated)" }`                                               |
|  `409` | `mode=switch`: 単位が `allocated` / `secured` 以外・ピック済み                                      | `{ "ok": false, "error_kind": "unit_not_switchable", "error": "unit is already picked — switch is only allowed before pick" }`                                                                  |
|  `409` | `mode=switch`: 進行中の返送ケースがある                                                              | `{ "ok": false, "error_kind": "transfer_in_progress", "error": "unit has an open source-change case (change_requested) — advance or cancel it instead" }`                                       |
|  `409` | `mode=switch`: 同一レグに他の active 単位がある                                                      | `{ "ok": false, "error_kind": "shipment_has_siblings", "error": "shipment has 1 other active unit(s) — in-place switch supports single-unit legs only (use the transfer flow)" }`               |
|  `409` | `mode=switch`: MFC 集約グループのレグ                                                             | `{ "ok": false, "error_kind": "consolidation_group_member", "error": "shipment belongs to an MFC consolidation group — use the transfer flow (in-place switch would break the box grouping)" }` |
|  `422` | 新出荷元が同一 / 未解決 / メンバーなし / 返送先でない（`transfer`、または `switch` で自己在庫の MFC に引き当てた単位）/ 指定在庫がメンバー外 | `{ "ok": false, "error_kind": "not_a_return_destination", "error": "target source is neither an MFC nor return_destination_enabled" }`                                                          |

共通エラー（`401` / `400` / `403` / `405` / `500` / `503`）は [Error Codes](/lmile-docs/reference/error-codes.md) を参照。

> A への cancel / B への create dispatch は WMS 設定が無ければ `skipped`（`cancelDispatches` / `createDispatches` 配列で返却。silent skip しません）。

## データモデル

* `sot.lm_fulfillment_source_changes` — 1 件 = 1 単位（`UNIQUE shop + fulfillment_unit_id`）。`status`（3 ステップ + `cancelled`）/ `from_source_location_id` / `to_source_location_id` / `from_inventory_location_id` / `to_inventory_location_id` / `return_tracking_number` / `return_label_pdf_path` / `reason` + 各ステップ timestamp。`fulfillment_unit_id`（[Units](/lmile-docs/fulfillment-api/units.md)）と `routing_decision_id` にリンク。読み取り view: `public.lm_v_fulfillment_source_changes`
* 単位の状態は `sot.lm_fulfillment_units.status`（`source_change_pending` / `source_change_in_transit`、受領で `allocated` に復帰）と 3 ロケーション列（引当 / 出荷元 / 現在在庫）に反映。注文集計 view `public.lm_v_routing_order_unit_fulfillment` は `source_changing` で分類


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://lmile.gitbook.io/lmile-docs/oversell-api/source-change.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
