# 画面設計書 52-在庫割当画面

## 概要

本ドキュメントは、Medusa Admin Dashboard における「在庫割当画面」の設計仕様を定義するものである。

### 本画面の処理概要

**業務上の目的・背景**：EC運営において、受注した商品を出荷するためには、在庫ロケーションから商品在庫を引き当てる（予約する）必要がある。本画面は、注文内の各商品アイテムに対して、どのロケーションからどれだけの数量を引き当てるかを指定するインターフェースを提供する。これにより、複数倉庫運営や在庫の最適配分、フルフィルメント準備の効率化を実現する。

**画面へのアクセス方法**：注文詳細画面から「在庫割当」アクションを選択することでアクセスする。在庫管理が有効でまだフルフィルメントされていない商品がある注文でのみ利用可能。

**主要な操作・処理内容**：
1. 在庫を割り当てるロケーションを選択する
2. 各商品アイテムに対して割当数量を入力する
3. インベントリキット（複数在庫アイテムで構成される商品）の場合、親数量または個別数量を入力
4. 割当ボタンを押下して在庫予約を作成する

**画面遷移**：
- 遷移元：注文詳細画面（`/orders/:id`）
- 遷移先：割当完了後、注文詳細画面に戻る

**権限による表示制御**：認証済みの管理者ユーザーのみアクセス可能。在庫管理権限を持つユーザーのみ操作可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 50 | 在庫予約管理 | 主機能 | 注文アイテムへの在庫割当 |
| 51 | 在庫確認 | API連携 | 利用可能在庫の確認 |

## 画面種別

編集（モーダル形式・フォーカスモーダル）

## URL/ルーティング

```
/orders/:id/allocate
```

## 入出力項目

| 項目名 | 項目ID | データ型 | 必須 | 入力形式 | バリデーション | 説明 |
|--------|--------|----------|------|----------|----------------|------|
| ロケーション | location_id | string | ○ | セレクト | 有効なロケーションID | 在庫を割り当てるロケーション |
| 数量 | quantity | Record<string, number> | ○ | 数値入力 | 0以上、利用可能在庫以下 | 各アイテム×在庫IDごとの割当数量 |
| 検索フィルター | filterTerm | string | - | テキスト | - | 商品名での絞り込み |

## 表示項目

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| 商品名 | テキスト | 商品バリアントのタイトル |
| 商品画像 | 画像 | 商品のサムネイル |
| SKU | テキスト | 在庫アイテムのSKU |
| 必要数量 | テキスト | フルフィルメント対象数量 |
| 利用可能在庫 | テキスト | 選択ロケーションでの利用可能在庫数 |
| 予約済み数量 | テキスト | 既に予約済みの数量 |

## イベント仕様

### 1-ロケーション選択変更

ロケーションセレクトボックスの値が変更された場合：
- 選択されたロケーションIDを状態に保存
- すべてのアイテムの割当数量をリセット（空文字に初期化）
- 各アイテムの利用可能在庫数を更新表示

### 2-数量入力

数量フィールドに入力があった場合：
- インベントリキットでない場合：直接数量を保存
- インベントリキットの場合：
  - 親数量を入力した場合、各構成アイテムの数量を自動計算（親数量 × required_quantity）
  - 構成アイテムの数量を個別に変更した場合、親数量をリセット
- 利用可能在庫を超過した場合、送信ボタンを無効化

### 3-検索フィルター入力

検索フィールドに入力があった場合：
- 商品名またはバリアント名で部分一致フィルタリング
- 表示アイテムリストを更新

### 4-割当ボタン押下

割当ボタンが押下された場合：
1. フォームバリデーションを実行（全アイテムに数量が入力されているか確認）
2. 各アイテムに対して`useCreateReservationItem`フックでAPI呼び出し
3. APIエンドポイント：`POST /admin/reservations`（各アイテムごと）
4. リクエストボディ：`{ location_id, inventory_item_id, line_item_id, quantity }`
5. 成功時：モーダルを閉じて注文詳細画面に戻る
6. 部分失敗時：失敗したアイテムのIDをエラートーストで表示
7. 完全失敗時：エラーメッセージをトーストで表示

### 5-キャンセルボタン押下

キャンセルボタンが押下された場合：
- フォームの変更を破棄
- モーダルを閉じて注文詳細画面に戻る

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 割当ボタン押下 | reservation | INSERT | 各アイテムの在庫予約レコード作成 |
| 割当ボタン押下 | inventory_level | UPDATE | 予約済み数量の更新 |

### テーブル別更新項目詳細

#### reservation

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUID自動生成 | - |
| INSERT | location_id | 選択されたロケーションID | - |
| INSERT | inventory_item_id | 対象在庫アイテムID | - |
| INSERT | line_item_id | 注文明細ID | - |
| INSERT | quantity | 入力された数量 | - |

#### inventory_level

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | reserved_quantity | 現在値 + 予約数量 | - |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| quantityNotAllocated | エラー | 「すべてのアイテムに数量を入力してください」 | 数量未入力のアイテムがある場合 |
| error | エラー | 「アイテムの割当に失敗しました: {失敗したアイテムID}」 | 一部または全部の割当失敗時 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 注文が見つからない | エラーをスローし、エラーページを表示 |
| ロケーションが選択されていない | 割当ボタンが無効化される |
| 利用可能在庫を超過 | 割当ボタンが無効化される |
| 在庫予約の競合 | APIエラーとして処理、エラートースト表示 |
| ネットワークエラー | エラートーストを表示 |

## 備考

- 在庫管理が有効でないバリアント、または既にフルフィルメント済みのアイテムは割当対象外
- 複数の在庫アイテムで構成されるインベントリキットに対応
- 各アイテムの割当は個別のAPIコールで実行（現状、一括エンドポイントは未実装）
- 部分的な成功・失敗が発生する可能性あり

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、在庫割当で使用されるデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AllocateItemsSchema | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/constants.ts` | Zodスキーマによる入力データの型定義 |
| 1-2 | OrderAllocateItemsFormProps | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/order-allocate-items-form.tsx` | AdminOrderの型（25-27行目） |

**読解のコツ**: quantityフィールドが`Record<string, number>`で、キーは`${lineItemId}-${inventoryItemId}`の形式。

#### Step 2: エントリーポイントを理解する

処理の起点となるファイル・関数を特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | OrderAllocateItems | `packages/admin/dashboard/src/routes/orders/order-allocate-items/order-allocate-items.tsx` | 画面コンポーネントのエントリーポイント |

**主要処理フロー**:
1. **8行目**: useParamsでパスパラメータからIDを取得
2. **10-13行目**: useOrderフックで注文データを取得（在庫関連フィールドを含む）
3. **23行目**: OrderAllocateItemsFormコンポーネントをレンダリング

#### Step 3: フォームコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | OrderAllocateItemsForm | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/order-allocate-items-form.tsx` | 割当フォームの実装 |

**主要処理フロー**:
- **36-37行目**: useCreateReservationItemフックの取得
- **39-48行目**: 割当対象アイテムのフィルタリング
- **61-67行目**: useFormによるフォーム初期化
- **69行目**: useStockLocationsでロケーション一覧取得
- **71-128行目**: フォーム送信ハンドラ
- **130-191行目**: 数量変更ハンドラ（インベントリキット対応）

#### Step 4: ユーティリティ関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | utils.ts | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/utils.ts` | checkInventoryKit関数 |

**主要処理フロー**:
- インベントリキット判定ロジック

#### Step 5: APIフックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | useCreateReservationItem | `packages/admin/dashboard/src/hooks/api/reservations.tsx` | 予約作成APIミューテーションフック（96-120行目） |

**主要処理フロー**:
- **104行目**: sdk.admin.reservation.create呼び出し
- **106-111行目**: キャッシュの無効化処理

### プログラム呼び出し階層図

```
OrderAllocateItems (order-allocate-items.tsx)
    │
    ├─ useOrder (注文データ取得、在庫関連フィールド含む)
    │
    └─ OrderAllocateItemsForm (order-allocate-items-form.tsx)
           │
           ├─ useStockLocations (ロケーション一覧取得)
           │
           ├─ checkInventoryKit (インベントリキット判定)
           │
           └─ useCreateReservationItem (reservations.tsx)
                  └─ sdk.admin.reservation.create
                         └─ POST /admin/reservations
```

### データフロー図

```
[入力]                    [処理]                         [出力]

ロケーション選択 ─────┐
                      │
数量入力 ────────────┼───▶ OrderAllocateItemsForm ───▶ Promise.all ───▶ 各予約API
                      │         │                          │
検索フィルター ──────┘         │                          ├─▶ reservation (DB)
                               ▼                          └─▶ inventory_level (DB)
                    アイテムフィルタリング
                               │
                               ▼
                    インベントリキット判定
                               │
                               ▼
                         バリデーション
                               │
                               ▼
                       トースト通知 ───▶ 注文詳細画面
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| order-allocate-items.tsx | `packages/admin/dashboard/src/routes/orders/order-allocate-items/order-allocate-items.tsx` | ソース | 画面コンポーネント |
| order-allocate-items-form.tsx | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/order-allocate-items-form.tsx` | ソース | 割当フォーム |
| order-allocate-items-item.tsx | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/order-allocate-items-item.tsx` | ソース | 個別アイテム行 |
| constants.ts | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/constants.ts` | ソース | スキーマ定義 |
| utils.ts | `packages/admin/dashboard/src/routes/orders/order-allocate-items/components/order-create-fulfillment-form/utils.ts` | ソース | ユーティリティ関数 |
| reservations.tsx | `packages/admin/dashboard/src/hooks/api/reservations.tsx` | ソース | 予約APIフック |
| stock-locations.tsx | `packages/admin/dashboard/src/hooks/api/stock-locations.tsx` | ソース | ロケーションAPIフック |
