# 画面設計書 77-価格編集画面

## 概要

本ドキュメントは、Medusa Admin Dashboardにおける「価格編集画面」の画面設計書です。価格表内の既存商品の価格を編集するためのモーダル画面について記述します。

### 本画面の処理概要

この画面では、価格表に含まれる既存商品のバリアント価格を一括編集します。DataGridコンポーネントを使用したスプレッドシート形式のUIで、効率的な価格更新が可能です。

**業務上の目的・背景**：価格表の運用中に価格を変更する必要が生じることは頻繁にあります。セール価格の調整、競合状況に応じた価格変更、為替変動への対応など、様々なシナリオで使用されます。スプレッドシート形式のUIにより、複数商品・複数通貨の価格を一度に効率的に編集できます。

**画面へのアクセス方法**：
1. 価格表詳細画面のProducts Sectionの「Edit Prices」ボタンをクリック
2. 商品行のアクションメニューから「Edit Prices」を選択
3. URL `/price-lists/{id}/products/edit?ids[]={product_ids}` に直接アクセス

**主要な操作・処理内容**：
1. 既存価格の表示（商品・バリアント×通貨/リージョンのマトリクス）
2. 価格の編集（更新・新規追加・削除）
3. 変更内容の差分検出と一括保存

**画面遷移**：
- 遷移元：価格表詳細画面
- 遷移先：保存成功時・キャンセル時に価格表詳細画面に戻る

**権限による表示制御**：認証済みユーザーのみアクセス可能です。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 53 | 価格リスト管理 | 主機能 | 価格表内の価格編集 |

## 画面種別

モーダル形式（RouteFocusModal）

## URL/ルーティング

`/price-lists/:id/products/edit`

## 入出力項目

### パスパラメータ（入力）

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|------|------|
| id | string | Yes | 価格表のID |

### クエリパラメータ（入力）

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|------|------|
| ids[] | string | No | 編集対象の商品ID（カンマ区切り） |

### フォーム項目

| 項目名 | フィールド名 | 型 | 必須 | バリデーション | 説明 |
|--------|-------------|-----|------|---------------|------|
| 商品別価格 | products | object | Yes | - | 商品・バリアント別の価格設定 |
| 通貨別価格 | currency_prices | object | No | 数値 | 通貨コードごとの価格 |
| リージョン別価格 | region_prices | object | No | 数値 | リージョンIDごとの価格 |

## 表示項目

### ヘッダー部

| 項目名 | コンポーネント | 説明 |
|--------|--------------|------|
| タイトル | RouteFocusModal.Title | "Edit Prices for {price_list.title}" (sr-only) |

### メイン部

| 項目名 | コンポーネント | 説明 |
|--------|--------------|------|
| 価格入力グリッド | DataGrid | 商品/バリアント×通貨/リージョンのマトリクス |

### DataGrid列

| 列名 | データ型 | 説明 |
|------|---------|------|
| 商品/バリアント | string | 商品名・バリアント名（読み取り専用） |
| {通貨コード} | number | 各通貨での価格（編集可能） |
| {リージョン名} | number | 各リージョンでの価格（編集可能） |

## イベント仕様

### 1-価格入力

DataGridのセルをクリックして編集モードに入り、価格を入力します。入力値は自動的にフォーム状態に反映されます。

### 2-価格クリア

セルの値を空にすると、その価格は削除対象となります。

### 3-Saveボタン押下

フォームの現在値と初期値を比較し、以下の3種類の操作を検出して一括実行します：
- 新規作成（create）：初期値になく現在値にある価格
- 更新（update）：初期値と現在値で金額が異なる価格
- 削除（delete）：初期値にあり現在値で空になった価格

### 4-Cancelボタン押下

変更を破棄し、モーダルを閉じて詳細画面に戻ります。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示 | price_list | SELECT | 価格表・価格データの取得 |
| 画面表示 | product | SELECT | 商品・バリアントデータの取得 |
| Save押下 | price | INSERT/UPDATE/DELETE | 価格の一括更新 |

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

#### price（バッチ操作）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | amount | 入力された価格 | 新規価格 |
| INSERT | currency_code | 通貨コード | |
| INSERT | variant_id | バリアントID | |
| INSERT | rules | リージョン指定時は `{ region_id: xxx }` | |
| UPDATE | id | 既存価格のID | 更新対象 |
| UPDATE | amount | 入力された価格 | |
| DELETE | id | 削除対象の価格ID | 値がクリアされた価格 |

## メッセージ仕様

| 種別 | 条件 | メッセージ |
|------|------|-----------|
| 成功 | 価格編集成功時 | Prices successfully updated. |
| エラー | API呼び出し失敗時 | エラーメッセージをトースト表示 |

## 例外処理

| 状態 | 処理 |
|------|------|
| 価格表が存在しない場合 | エラーをスローし、エラーバウンダリーで捕捉 |
| 商品が存在しない場合 | エラーをスローし、エラーバウンダリーで捕捉 |
| データ取得中 | フォームを表示しない（ready判定） |
| API エラー発生時 | エラーメッセージをトースト表示 |

## 備考

- DataGridコンポーネントによるスプレッドシート風UI
- 初期値と現在値の差分比較による効率的な更新
- 編集中はEscキーでモーダルを閉じないよう制御（setCloseOnEscape）
- usePriceListGridColumnsで動的に列を生成
- 商品はツリー構造で表示（getSubRowsでバリアントを子行として展開）

---

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

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

### 推奨読解順序

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

ページコンポーネントとデータ取得処理を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | price-list-prices-edit.tsx | `packages/admin/dashboard/src/routes/price-lists/price-list-prices-edit/price-list-prices-edit.tsx` | RouteFocusModal構成、データ取得 |

**主要処理フロー**:
1. **9行目**: useParamsでURLからid取得
2. **10-11行目**: useSearchParamsでids[]パラメータ取得
3. **13-15行目**: usePriceListで価格表データ取得（価格・バリアント情報含む）
4. **18-30行目**: useProductsで商品データ取得（price_list_idでフィルタ）
5. **35-36行目**: ready判定

#### Step 2: 編集フォームを理解する

メインの編集フォームロジックを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | price-list-prices-edit-form.tsx | `packages/admin/dashboard/src/routes/price-lists/price-list-prices-edit/components/price-list-prices-edit-form/price-list-prices-edit-form.tsx` | フォーム構成、差分検出、API呼び出し |

**主要処理フロー**:
- **46行目**: useRefで初期値を保持
- **48-53行目**: useFormでフォーム初期化
- **55行目**: useBatchPriceListPricesでバッチ操作
- **57-83行目**: handleSubmit - sortPricesで差分検出、バッチ実行
- **125-175行目**: initRecord - 既存価格を初期値オブジェクトに変換
- **247-314行目**: comparePrices - 初期値と新値を比較して操作を分類

#### Step 3: グリッド列定義を理解する

DataGridの列構成を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | use-price-list-grid-columns.tsx | `packages/admin/dashboard/src/routes/price-lists/common/hooks/use-price-list-grid-columns.tsx` | 動的列生成 |

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

```
PriceListPricesEdit (price-list-prices-edit.tsx)
    │
    ├─ usePriceList() - 価格表・価格取得
    │
    ├─ useProducts() - 商品・バリアント取得
    │
    ├─ usePriceListCurrencyData() - 通貨/リージョン取得
    │
    └─ PriceListPricesEditForm
           │
           ├─ initRecord() - 初期値構築
           │
           ├─ useForm()
           │
           ├─ useBatchPriceListPrices()
           │
           ├─ usePriceListGridColumns() - 列定義
           │
           ├─ handleSubmit()
           │      │
           │      ├─ sortPrices()
           │      │      │
           │      │      ├─ convertToPriceArray()
           │      │      │
           │      │      └─ comparePrices()
           │      │
           │      └─ mutateAsync({ delete, update, create })
           │
           └─ DataGrid
                  └─ getSubRows() - バリアント展開
```

### データフロー図

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

URL param (id) ──────▶ usePriceList() ──────▶ price_list (with prices)

query param (ids[]) ──────▶ useProducts() ──────▶ products (with variants)

price_list.prices ──────▶ initRecord() ──────▶ initialValue (useRef)
products                         │
                                 ▼
                        useForm({ defaultValues: { products: initialValue } })

グリッド編集 ──────▶ form state更新

Save押下 ──────▶ handleSubmit()
                   │
                   ├─▶ sortPrices(current, initial, regions)
                   │      │
                   │      ├─▶ convertToPriceArray() × 2
                   │      │
                   │      └─▶ comparePrices()
                   │             │
                   │             └─▶ { pricesToDelete, pricesToUpdate, pricesToCreate }
                   │
                   └─▶ useBatchPriceListPrices()
                          │
                          └─▶ { delete, update, create }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| price-list-prices-edit.tsx | `packages/admin/dashboard/src/routes/price-lists/price-list-prices-edit/price-list-prices-edit.tsx` | ソース | ページコンポーネント |
| price-list-prices-edit-form.tsx | `packages/admin/dashboard/src/routes/price-lists/price-list-prices-edit/components/price-list-prices-edit-form/price-list-prices-edit-form.tsx` | ソース | 編集フォーム |
| use-price-list-grid-columns.tsx | `packages/admin/dashboard/src/routes/price-lists/common/hooks/use-price-list-grid-columns.tsx` | ソース | グリッド列定義 |
| schemas.ts | `packages/admin/dashboard/src/routes/price-lists/common/schemas.ts` | ソース | バリデーションスキーマ |
| price-lists.tsx | `packages/admin/dashboard/src/hooks/api/price-lists.tsx` | ソース | API呼び出しフック |
