# 画面設計書 9-商品属性編集画面

## 概要

本ドキュメントは、Medusa Admin Dashboard の商品属性編集画面の設計仕様を定義するものです。商品の物理的属性（サイズ、重量）および関税関連情報（HSコード、原産国など）を編集するためのDrawer形式の画面です。

### 本画面の処理概要

本画面は、商品の属性情報を編集するためのフォーム画面です。幅、高さ、長さ、重量といった物理的な寸法情報と、MIDコード、HSコード、原産国といった関税・輸出入関連の情報を管理します。

**業務上の目的・背景**：国際的なECサイト運営において、商品の物理的属性は配送料計算に、HSコードや原産国は関税計算や輸出入手続きに必要です。本画面では、これらの属性情報を一箇所で管理できるようにしています。

**画面へのアクセス方法**：
- 商品詳細画面の属性セクションの「編集」ボタンから
- ブラウザで直接 `/products/{id}/attributes` へアクセス

**主要な操作・処理内容**：
1. 幅の設定
2. 高さの設定
3. 長さの設定
4. 重量の設定
5. MIDコードの設定
6. HSコードの設定
7. 原産国の設定

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

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | 商品CRUD | 主機能 | 商品属性情報の更新 |

## 画面種別

編集画面（Drawer形式）

## URL/ルーティング

`/products/:id/attributes`

## 入出力項目

| 項目名 | 項目ID | データ型 | 必須 | バリデーション | 説明 |
|--------|--------|----------|------|----------------|------|
| 幅 | width | number | 任意 | 0以上の数値 | 商品の幅 |
| 高さ | height | number | 任意 | 0以上の数値 | 商品の高さ |
| 長さ | length | number | 任意 | 0以上の数値 | 商品の長さ |
| 重量 | weight | number | 任意 | 0以上の数値 | 商品の重量 |
| MIDコード | mid_code | string | 任意 | - | Manufacturer ID コード |
| HSコード | hs_code | string | 任意 | - | Harmonized System コード |
| 原産国 | origin_country | string | 任意 | 有効な国コード | 原産国 |

## 表示項目

### ヘッダー

| 項目名 | 表示位置 | データソース | 説明 |
|--------|----------|--------------|------|
| タイトル | ヘッダー | i18n (products.editAttributes) | 「属性を編集」 |

### フォーム本体

| 項目名 | 表示位置 | データソース | 説明 |
|--------|----------|--------------|------|
| 幅入力 | 本体 | product.width | 数値入力（min=0, step=any） |
| 高さ入力 | 本体 | product.height | 数値入力（min=0, step=any） |
| 長さ入力 | 本体 | product.length | 数値入力（min=0, step=any） |
| 重量入力 | 本体 | product.weight | 数値入力（min=0, step=any） |
| MIDコード入力 | 本体 | product.mid_code | テキスト入力 |
| HSコード入力 | 本体 | product.hs_code | テキスト入力 |
| 原産国選択 | 本体 | product.origin_country | 国選択ドロップダウン（CountrySelect） |
| 拡張フィールドゾーン | 本体下部 | FormExtensionZone | カスタムフィールド表示エリア |

### フッター

| 項目名 | 表示位置 | データソース | 説明 |
|--------|----------|--------------|------|
| キャンセルボタン | フッター右 | i18n (actions.cancel) | キャンセルボタン |
| 保存ボタン | フッター右 | i18n (actions.save) | 保存ボタン |

## イベント仕様

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

**トリガー**: 「保存」ボタンのクリックまたは Cmd/Ctrl + Enter

**処理フロー**:
1. フォームのバリデーションを実行
2. バリデーション成功時、`useUpdateProduct` フックで商品を更新
3. 数値項目は空の場合nullに変換
4. 成功時:
   - `handleSuccess()` でDrawerを閉じる
5. 失敗時:
   - デフォルトのエラーハンドリング

**遷移先**: 商品詳細画面（Drawerが閉じる）

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

**トリガー**: 「キャンセル」ボタンのクリック

**処理フロー**:
1. RouteDrawer.Close によりDrawerを閉じる
2. フォームの変更は破棄される

**遷移先**: 商品詳細画面（Drawerが閉じる）

### 3-数値入力の変換

**トリガー**: 数値入力フィールドの変更

**処理フロー**:
1. 空文字列の場合はnullに変換
2. それ以外はparseFloat/Numberで数値に変換
3. フォーム状態を更新

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 属性更新 | product | UPDATE | 商品レコードの属性更新 |

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

#### product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | width | 入力値またはnull | 空の場合null |
| UPDATE | height | 入力値またはnull | 空の場合null |
| UPDATE | length | 入力値またはnull | 空の場合null |
| UPDATE | weight | 入力値またはnull | 空の場合null |
| UPDATE | mid_code | 入力値 | 任意 |
| UPDATE | hs_code | 入力値 | 任意 |
| UPDATE | origin_country | 入力値 | 任意 |
| UPDATE | updated_at | 現在日時 | 自動設定 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| validation.number | エラー | 有効な数値を入力してください | 数値形式が不正な場合 |

## 例外処理

| 例外条件 | 処理内容 | ユーザーへの表示 |
|---------|---------|-----------------|
| 商品データ取得失敗 | エラーをスロー | エラー境界でキャッチして表示 |
| バリデーションエラー | フィールドにエラー表示 | フィールド下にエラーメッセージ |
| 商品更新API失敗 | デフォルトエラーハンドリング | エラーメッセージを表示 |

## 備考

- 本画面は認証済みユーザーのみアクセス可能なプロテクト画面です
- RouteDrawer形式で商品詳細画面上にオーバーレイ表示されます
- 数値入力フィールドは type="number" で min=0, step="any" が設定されています
- 空の数値入力はnullとしてAPIに送信されます
- CountrySelectコンポーネントで国の選択が可能です
- useExtendableFormにより拡張フォームフィールドに対応しています
- FormExtensionZoneによりカスタムフィールドの追加が可能です
- KeyboundFormコンポーネントにより、Cmd/Ctrl + Enter での送信に対応しています
- dimensionスキーマにより、文字列と数値の両方を受け付け、空文字列はnullに変換されます

---

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

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

### 推奨読解順序

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

まず、属性フォームのスキーマ定義を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | product-attributes-form.tsx | `packages/admin/dashboard/src/routes/products/product-attributes/components/product-attributes-form/product-attributes-form.tsx` | 20-39行目: スキーマ定義 |

**スキーマ定義**:
- dimension: string|numberを受け付け、空文字列はnullに変換
- width, height, length, weight: dimension型（オプション・nullable）
- mid_code, hs_code, origin_country: オプション文字列

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

ProductAttributesコンポーネントから処理の流れを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | product-attributes.tsx | `packages/admin/dashboard/src/routes/products/product-attributes/product-attributes.tsx` | メインコンポーネント |

**主要処理フロー**:
1. **10行目**: useParamsでIDを取得
2. **13-17行目**: useProductでデータ取得
3. **19-21行目**: エラー時のスロー
4. **23-31行目**: RouteDrawerとProductAttributesFormの表示

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

ProductAttributesFormコンポーネントの詳細を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | product-attributes-form.tsx | `packages/admin/dashboard/src/routes/products/product-attributes/components/product-attributes-form/product-attributes-form.tsx` | フォーム実装 |

**主要処理フロー**:
1. **44-49行目**: useExtension呼び出し
2. **51-64行目**: useExtendableFormでフォーム初期化
3. **66行目**: useUpdateProductフック取得
4. **68-85行目**: handleSubmit関数（null変換と更新処理）
5. **87-276行目**: UIレンダリング

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

```
ProductAttributes (product-attributes.tsx)
    │
    ├─ useParams ─▶ { id }
    ├─ useProduct
    │
    └─ RouteDrawer
           │
           └─ ProductAttributesForm (product-attributes-form.tsx)
                  │
                  ├─ useRouteModal ─▶ handleSuccess
                  ├─ useExtension ─▶ getFormConfigs, getFormFields
                  ├─ useExtendableForm
                  │      └─ ProductAttributesSchema
                  │
                  ├─ useUpdateProduct
                  │      │
                  │      └─ mutateAsync
                  │            └─ [成功] → handleSuccess
                  │
                  └─ KeyboundForm
                         ├─ Form.Field (width)
                         ├─ Form.Field (height)
                         ├─ Form.Field (length)
                         ├─ Form.Field (weight)
                         ├─ Form.Field (mid_code)
                         ├─ Form.Field (hs_code)
                         ├─ Form.Field (origin_country) ─▶ CountrySelect
                         └─ FormExtensionZone
```

### データフロー図

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

URL /products/:id/attributes
        │
        ▼
   useProduct
        │
        ▼
  product データ
        │
        ▼
useExtendableForm (初期値設定)
        │
        ▼
   ユーザー入力
        │
        ▼
  数値入力変換
   (空 → null)
   (数字 → Number)
        │
        ▼
   handleSubmit
        │
        ├─ null変換処理
        │   (data.width ? data.width : null)
        │
        ▼
  useUpdateProduct.mutateAsync
        │
        └─ [成功] → handleSuccess (Drawer閉じる)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-attributes.tsx | `packages/admin/dashboard/src/routes/products/product-attributes/product-attributes.tsx` | ソース | メインコンポーネント |
| product-attributes-form.tsx | `packages/admin/dashboard/src/routes/products/product-attributes/components/product-attributes-form/product-attributes-form.tsx` | ソース | フォームコンポーネント |
| index.ts | `packages/admin/dashboard/src/routes/products/product-attributes/index.ts` | ソース | エクスポート定義 |
