# 画面設計書 13-商品在庫編集画面

## 概要

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

### 本画面の処理概要

商品在庫編集画面は、商品バリアントの在庫情報をロケーション別に一括管理するための画面である。複数のロケーションにまたがる在庫数量を効率的に設定・更新することができる。

**業務上の目的・背景**：在庫管理はECビジネスの根幹をなす重要な機能である。本画面は、複数倉庫・店舗を持つビジネスにおいて、各ロケーションの在庫数量を一元管理し、在庫切れや過剰在庫を防止するために必要である。在庫予約や入荷予定との整合性も考慮した運用が可能となる。

**画面へのアクセス方法**：商品詳細画面の在庫セクションから「Edit Stock」ボタンをクリックすることでモーダル形式で表示される。URLパスは `/products/:id/stock` となる。

**主要な操作・処理内容**：
1. バリアントごとのロケーション別在庫数量の表示
2. 在庫数量の編集（数値入力）
3. ロケーションとの紐付けの有効化/無効化（チェックボックス）
4. インベントリキット（複数在庫アイテム構成）の管理
5. 在庫情報の一括保存

**画面遷移**：
- 遷移元：商品詳細画面（No.7）
- 遷移先：商品詳細画面（保存/キャンセル後）

**権限による表示制御**：本画面へのアクセスには商品管理権限および在庫管理権限が必要。権限がない場合は画面にアクセスできない。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 48 | 在庫アイテム管理 | 主機能 | 商品在庫の管理 |
| 49 | 在庫レベル管理 | 補助機能 | ロケーション別在庫数量の設定 |

## 画面種別

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

## URL/ルーティング

```
/products/:id/stock
```

パラメータ：
- `id`：商品ID（必須）

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | バリデーション | 備考 |
|--------|--------|-----|------|---------------|------|
| 在庫数量 | quantity | number \| string | - | 数値または空文字 | ロケーション別 |
| 有効化フラグ | checked | boolean | - | - | ロケーションとの紐付け状態 |

### 出力項目

| 項目名 | 項目ID | 型 | 備考 |
|--------|--------|-----|------|
| バリアント一覧 | variants | ProductVariant[] | インベントリアイテムを含む |
| ロケーション一覧 | locations | StockLocation[] | 在庫レベルを含む |

## 表示項目

### 在庫編集グリッド（DataGrid）

| 項目名 | 表示形式 | 備考 |
|--------|---------|------|
| バリアントタイトル | テキスト | 階層表示（バリアント > インベントリアイテム） |
| SKU | テキスト | サブ行にインベントリアイテムSKUを表示 |
| ロケーション別在庫数量 | 数値入力/チェックボックス | 各ロケーションがカラムとして表示 |

### セル状態

| 状態 | 表示 | 備考 |
|------|------|------|
| 有効 | 数値入力フィールド | 在庫数量を入力可能 |
| 無効 | チェックボックスのみ | クリックで有効化 |
| 無効化不可 | 数値入力（トグル無効） | 予約・入荷予定がある場合 |

## イベント仕様

### 1-在庫数量入力

**トリガー**：在庫数量セルへの入力

**処理フロー**：
1. DataGridコンポーネントで在庫グリッドを表示
2. セル編集時にreact-hook-formの状態を更新
3. variants[variantId].inventory_items[inventoryItemId].locations[locationId].quantityを更新

### 2-ロケーション有効化/無効化

**トリガー**：ロケーションセルのチェックボックス操作

**処理フロー**：
1. checkedフラグを切り替え
2. 無効化時は確認プロンプトを表示（削除対象がある場合）
3. 有効化時は数量入力欄を表示

### 3-保存

**トリガー**：「Save」ボタン押下

**処理フロー**：
1. フォームデータを処理
2. 各ロケーションレベルについて：
   - 既存IDあり + チェック解除 → delete配列に追加
   - 既存IDあり + 数量変更 → update配列に追加
   - 新規 + 数量入力あり → create配列に追加
3. delete配列に要素がある場合、確認プロンプトを表示
4. `useBatchInventoryItemsLocationLevels`ミューテーションで一括処理
5. 成功時にトースト表示、モーダルを閉じる

**ペイロード構造**：
```typescript
{
  create: [{ inventory_item_id, location_id, stocked_quantity }],
  update: [{ inventory_item_id, location_id, stocked_quantity }],
  delete: [location_level_id],
  force: true
}
```

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | inventory_level | INSERT | 新規ロケーションレベルの作成 |
| 保存ボタン押下 | inventory_level | UPDATE | 既存ロケーションレベルの更新 |
| 保存ボタン押下 | inventory_level | DELETE | ロケーションレベルの削除 |

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

#### inventory_level

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | inventory_item_id | インベントリアイテムID | 必須 |
| INSERT | location_id | ロケーションID | 必須 |
| INSERT | stocked_quantity | 在庫数量 | castNumber関数で変換 |
| UPDATE | stocked_quantity | 在庫数量 | 数量変更時のみ |
| DELETE | id | ロケーションレベルID | チェック解除時 |

## メッセージ仕様

| 種別 | メッセージキー | 表示条件 |
|------|--------------|---------|
| 成功 | inventory.stock.successToast | 保存成功時 |
| 確認 | inventory.stock.disablePrompt | ロケーション無効化時 |
| エラー | (API error message) | API呼び出し失敗時 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 商品が見つからない | データローダーでエラーをスロー |
| 予約/入荷予定がある場合 | トグル無効化（disabledToggle: true） |
| API呼び出し失敗 | エラートースト表示 |

## 備考

- 本画面はデータのチャンク読み込みを行い、ローカルProgressBarで進捗を表示
- DataGridコンポーネントでマルチカラム選択が可能（multiColumnSelection: true）
- インベントリキット（複数在庫アイテムで構成されるバリアント）の場合、getSubRows関数で階層表示
- 予約数量(reserved_quantity)または入荷予定数量(incoming_quantity)がある場合、ロケーションの無効化は不可

---

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

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

### 推奨読解順序

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

まず、在庫管理のデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.ts | `packages/admin/dashboard/src/routes/products/product-stock/schema.ts` | ProductStockSchema、ProductStockVariantSchema等の定義 |

**読解のコツ**: 階層構造として variants > inventory_items > locations の3階層でデータが管理される。各locationには id, quantity, checked, disabledToggle が含まれる。

#### Step 2: データローダーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | loader.ts | `packages/admin/dashboard/src/routes/products/product-stock/loader.ts` | データのプリフェッチとチャンク処理 |

**読解のコツ**: React Router の defer 機能を使用してデータを非同期で読み込む。variants と locations を並列でフェッチ。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | product-stock.tsx | `packages/admin/dashboard/src/routes/products/product-stock/product-stock.tsx` | ルートコンポーネント、ローディング状態管理 |

**主要処理フロー**:
- **17行目**: useLoaderDataでプリフェッチデータを取得
- **30-50行目**: ローカルProgressBarのタイマー管理
- **66-81行目**: Awaitコンポーネントでデータ解決待ち

#### Step 4: 在庫フォームを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | product-stock-form.tsx | `packages/admin/dashboard/src/routes/products/product-stock/components/product-stock-form/product-stock-form.tsx` | メインのフォームロジック |

**主要処理フロー**:
- **49-52行目**: useFormフックでフォーム初期化
- **56-57行目**: 無効化行とカラムの計算
- **61-138行目**: onSubmit - create/update/delete配列の生成
- **111-128行目**: delete配列がある場合の確認プロンプト
- **183-219行目**: getDefaultValue - 初期値生成ロジック

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

```
ProductStock (product-stock.tsx)
    │
    ├─ useLoaderData
    │      └─ productStockLoader (loader.ts)
    │             ├─ sdk.admin.product.listVariants
    │             └─ sdk.admin.stockLocation.list
    │
    └─ ProductStockForm (product-stock-form.tsx)
           │
           ├─ useForm (react-hook-form)
           │      └─ zodResolver(ProductStockSchema)
           │
           ├─ useProductStockColumns (hooks)
           │      └─ DataGridカラム定義
           │
           ├─ DataGrid
           │      └─ 在庫グリッドUI
           │
           ├─ usePrompt
           │      └─ 確認ダイアログ
           │
           └─ useBatchInventoryItemsLocationLevels
                  └─ sdk.admin.inventoryItem.batchLocationLevels
```

### データフロー図

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

variants, locations ────────▶ getDefaultValue ─────────────▶ フォーム初期状態
(loader.ts)                        │
                                   ├─ variant → variants[variantId]
                                   ├─ inventory_item → inventory_items[itemId]
                                   └─ location_level → locations[locationId]

セル編集 ──────────────────▶ DataGrid/useForm ─────────────▶ フォーム状態更新
                                   │
保存ボタン ────────────────▶ onSubmit ──────────────────────▶ API更新
                                   │
                                   ├─ wasChecked && !level.checked → delete
                                   ├─ level.id && quantityChanged → update
                                   ├─ !level.id && quantity !== "" → create
                                   │
                                   └─ useBatchInventoryItemsLocationLevels
                                          └─ { create, update, delete, force }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-stock.tsx | `packages/admin/dashboard/src/routes/products/product-stock/product-stock.tsx` | ソース | ルートコンポーネント |
| product-stock-form.tsx | `packages/admin/dashboard/src/routes/products/product-stock/components/product-stock-form/product-stock-form.tsx` | ソース | 在庫フォーム本体 |
| schema.ts | `packages/admin/dashboard/src/routes/products/product-stock/schema.ts` | ソース | Zodスキーマ定義 |
| loader.ts | `packages/admin/dashboard/src/routes/products/product-stock/loader.ts` | ソース | データローダー |
| utils.ts | `packages/admin/dashboard/src/routes/products/product-stock/utils.ts` | ソース | ユーティリティ関数 |
| use-product-stock-columns.tsx | `packages/admin/dashboard/src/routes/products/product-stock/hooks/use-product-stock-columns.tsx` | ソース | DataGridカラム定義 |
| index.ts | `packages/admin/dashboard/src/routes/products/product-stock/index.ts` | ソース | エクスポート定義 |
