# 画面設計書 25-バリアント在庫管理画面

## 概要

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

### 本画面の処理概要

バリアント在庫管理画面は、商品バリアントに紐付ける在庫アイテムを管理するためのフルスクリーンモーダル形式の画面である。単一の在庫アイテムの設定だけでなく、複数の在庫アイテムを組み合わせた「インベントリキット」の構成も可能。

**業務上の目的・背景**：ECサイトの在庫管理において、バリアントと在庫アイテムの紐付けは重要な役割を果たす。単純な1対1の紐付けだけでなく、セット商品やバンドル商品では複数の在庫アイテムを1つのバリアントに紐付ける必要がある（インベントリキット）。この画面により、バリアントの在庫構成を柔軟に設定でき、必要数量の管理も行える。

**画面へのアクセス方法**：バリアント詳細画面の在庫セクションにあるアクションメニューから「在庫アイテムを管理」または「インベントリキットを管理」を選択することでモーダル形式で表示される。URLは `/products/:id/variants/:variant_id/manage-items` の形式となる。

**主要な操作・処理内容**：
1. 紐付ける在庫アイテムの選択（Comboboxで検索・選択）
2. 各在庫アイテムの必要数量の設定
3. 複数の在庫アイテムの追加（インベントリキット構成）
4. 在庫アイテムの削除
5. 変更内容の保存

**画面遷移**：バリアント詳細画面からモーダルとして開き、保存またはキャンセル後にバリアント詳細画面に戻る。

**権限による表示制御**：商品の編集権限を持つユーザーのみが操作可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 48 | 在庫アイテム管理 | 主機能 | バリアントの在庫アイテム管理 |
| 49 | 在庫レベル管理 | 補助機能 | 在庫数量の設定 |

## 画面種別

編集（フルスクリーンモーダル形式）

## URL/ルーティング

`/products/:id/variants/:variant_id/manage-items`

## 入出力項目

| 項目名 | 項目ID | データ型 | 必須 | 入力/出力 | 説明 |
|--------|--------|----------|------|-----------|------|
| 商品ID | id | string | 必須 | 入力（URLパラメータ） | 親商品のID |
| バリアントID | variant_id | string | 必須 | 入力（URLパラメータ） | 対象バリアントのID |
| 在庫アイテムID | inventory[].inventory_item_id | string | 必須 | 入出力 | 紐付ける在庫アイテムのID |
| 必要数量 | inventory[].required_quantity | number | 必須 | 入出力 | 1バリアントあたりの必要在庫数 |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| ヘッダー | Heading | 「在庫アイテム」または「インベントリキット」 |
| バリアントタイトル | Label | 対象バリアントの名前 |
| 在庫アイテムカード | card[] | 各在庫アイテムの入力カード |
| アイテム選択 | Combobox | 在庫アイテムの検索・選択 |
| 数量入力 | Input | 必要数量の数値入力 |
| 追加ボタン | Button | 新しい在庫アイテム行を追加 |
| 削除ボタン | IconButton | 在庫アイテム行を削除 |

## イベント仕様

### 1-在庫アイテム選択

Comboboxで在庫アイテムを検索・選択。既存の在庫アイテム一覧から選択する形式で、ページネーション付きの検索が可能。既に選択済みの在庫アイテムはdisabled表示となる。

### 2-必要数量入力

数値入力フィールドで必要数量を設定。1以上の整数が必要（バリデーションエラー）。

### 3-追加ボタン押下

新しい在庫アイテム入力行が追加される。アイテム選択と数量入力の空のフォームが表示される。

### 4-削除ボタン押下

該当行の在庫アイテム設定を削除する。

### 5-保存ボタン押下

既存の設定と比較して、新規追加・更新・削除を判定し、バッチAPIを呼び出す。成功時はトースト通知を表示し、モーダルを閉じる。

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

変更を破棄し、モーダルを閉じてバリアント詳細画面に戻る。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | product_variant_inventory_item | INSERT | 新規在庫アイテム紐付けを追加 |
| 保存ボタン押下 | product_variant_inventory_item | UPDATE | 既存紐付けの必要数量を更新 |
| 保存ボタン押下 | product_variant_inventory_item | DELETE | 削除された紐付けを削除 |

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

#### product_variant_inventory_item

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | variant_id | 対象バリアントID | 新規紐付け |
| INSERT | inventory_item_id | 選択した在庫アイテムID | 新規紐付け |
| INSERT | required_quantity | フォーム入力値（数値） | 新規紐付け |
| UPDATE | required_quantity | フォーム入力値（数値） | 既存紐付けで数量変更時 |
| DELETE | - | variant_id + inventory_item_idで特定 | 紐付け解除時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| products.variant.inventory.toast.itemsManageSuccess | 成功 | 在庫アイテムが正常に更新されました | 保存成功時 |
| - | エラー | {エラーメッセージ} | API呼び出し失敗時 |
| products.variant.inventory.validation.itemId | バリデーション | 在庫アイテムを選択してください | アイテム未選択時 |
| products.variant.inventory.validation.quantity | バリデーション | 数量は1以上を入力してください | 数量が1未満の場合 |

## 例外処理

| 例外条件 | 処理内容 |
|----------|----------|
| バリアントが見つからない | エラーをthrowし、エラーバウンダリで処理 |
| API呼び出し失敗 | トースト通知でエラーメッセージを表示 |
| バリデーションエラー | フォームフィールドにエラーメッセージを表示 |

## 備考

- useProductVariantsInventoryItemsBatchフックでバッチ更新を実行
- useComboboxDataで在庫アイテムの検索・ページネーションを実現
- 複数在庫アイテム（インベントリキット）の場合、ヘッダーが「インベントリキット」に変わる
- 同一在庫アイテムの重複選択は防止される（isItemOptionDisabled関数）
- castNumber関数で文字列の数値変換を行う
- zodスキーマでsuperRefineを使用した複合バリデーション

---

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

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

### 推奨読解順序

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

フォームスキーマと在庫アイテム紐付けの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | manage-variant-inventory-items-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/components/manage-variant-inventory-items-form/manage-variant-inventory-items-form.tsx` | ManageVariantInventoryItemsSchema（37-62行目）でフォーム構造とバリデーションを確認 |

**読解のコツ**: zodのsuperRefineで数量のカスタムバリデーションを実装。required_quantityが1未満の場合にエラー。

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

ルートコンポーネントの処理を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | product-variant-manage-inventory-items.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/product-variant-manage-inventory-items.tsx` | useParamsでパラメータ取得（9行目） |
| 2-2 | product-variant-manage-inventory-items.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/product-variant-manage-inventory-items.tsx` | useProductVariantでデータ取得（11-18行目） |
| 2-3 | product-variant-manage-inventory-items.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/product-variant-manage-inventory-items.tsx` | RouteFocusModalでフルスクリーンモーダル（25行目） |

**主要処理フロー**:
1. **9行目**: useParamsでURLパラメータを取得
2. **11-18行目**: useProductVariantでバリアントと在庫アイテム情報を取得
3. **25-30行目**: RouteFocusModalでラップし、データ取得完了後にフォームを描画

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

在庫アイテム管理フォームの実装詳細を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | manage-variant-inventory-items-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/components/manage-variant-inventory-items-form/manage-variant-inventory-items-form.tsx` | defaultValuesの設定（208-223行目） |
| 3-2 | manage-variant-inventory-items-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/components/manage-variant-inventory-items-form/manage-variant-inventory-items-form.tsx` | handleSubmitの差分計算ロジック（254-308行目） |
| 3-3 | manage-variant-inventory-items-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/components/manage-variant-inventory-items-form/manage-variant-inventory-items-form.tsx` | VariantInventoryItemRow（83-199行目）で在庫アイテム選択UIを確認 |

**主要処理フロー**:
- **208-223行目**: 既存の在庫アイテム紐付けをフォーム初期値に変換
- **225-228行目**: useFieldArrayで動的な行の追加・削除を管理
- **238-246行目**: isItemOptionDisabledで重複選択を防止
- **254-297行目**: handleSubmitで既存・新規・削除の差分を計算
- **299-307行目**: mutateAsyncでバッチAPI呼び出し

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

```
ProductVariantManageInventoryItems (product-variant-manage-inventory-items.tsx:8-31)
    │
    ├─ useParams() → URLパラメータ取得
    │
    ├─ useProductVariant(id, variant_id) → バリアント・在庫アイテムデータ取得
    │      └─ sdk.admin.productVariant.retrieve()
    │
    └─ ManageVariantInventoryItemsForm (manage-variant-inventory-items-form.tsx:202-376)
           │
           ├─ useForm() → フォーム状態管理
           │
           ├─ useFieldArray() → 動的行管理
           │
           ├─ useProductVariantsInventoryItemsBatch() → バッチ更新
           │      └─ sdk.admin.productVariant.batchInventoryItems()
           │
           ├─ VariantInventoryItemRow × N
           │      └─ useComboboxData() → 在庫アイテム検索
           │             └─ sdk.admin.inventoryItem.list()
           │
           └─ handleSubmit()
                  ├─ 差分計算(create/update/delete)
                  └─ mutateAsync(payload)
```

### データフロー図

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

URLパラメータ(id, variant_id) ───▶ useProductVariant ───▶ バリアント・在庫データ取得
                                          │
                                          ▼
                              variant.inventory_items
                                          │
                                          ▼
                              defaultValuesに変換
                                          │
                                          ▼
                              useForm + useFieldArray
                                          │
在庫アイテム選択 ────────────────────────▶ VariantInventoryItemRow
                                          │
数量入力 ─────────────────────────────────┘
                                          │
追加ボタン ────────────────────────────▶ inventory.append()
                                          │
削除ボタン ────────────────────────────▶ inventory.remove()
                                          │
保存ボタン押下 ───────────────────────────▶ handleSubmit()
                                          │
                                          ▼
                              差分計算(existingItems vs selectedItems)
                                          │
                                          ▼
                              payload {create, update, delete}
                                          │
                                          ▼
                              mutateAsync ───▶ バッチAPI更新
                                          │
                                          ▼
                              handleSuccess() ───▶ モーダルを閉じる
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-variant-manage-inventory-items.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/product-variant-manage-inventory-items.tsx` | ソース | 画面のエントリーポイント |
| manage-variant-inventory-items-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-manage-inventory-items/components/manage-variant-inventory-items-form/manage-variant-inventory-items-form.tsx` | ソース | 在庫管理フォームコンポーネント |
| constants.ts | `packages/admin/dashboard/src/routes/product-variants/product-variant-detail/constants.ts` | ソース | VARIANT_DETAIL_FIELDSの定義 |
| useProductVariant | `packages/admin/dashboard/src/hooks/api/products.ts` | ソース | バリアント取得フック |
| useProductVariantsInventoryItemsBatch | `packages/admin/dashboard/src/hooks/api/products.ts` | ソース | 在庫アイテムバッチ更新フック |
| useComboboxData | `packages/admin/dashboard/src/hooks/use-combobox-data.ts` | ソース | Combobox用データ取得フック |
| RouteFocusModal | `packages/admin/dashboard/src/components/modals/` | ソース | フルスクリーンモーダルコンポーネント |
| Combobox | `packages/admin/dashboard/src/components/inputs/combobox/` | ソース | 検索選択コンポーネント |
