# 画面設計書 10-商品組織編集画面

## 概要

本ドキュメントは、Medusa Admin Dashboard の商品組織編集画面の設計仕様を定義するものです。商品の組織情報（タイプ、コレクション、カテゴリ、タグ）を編集するためのDrawer形式の画面です。

### 本画面の処理概要

本画面は、商品の組織・分類情報を編集するためのフォーム画面です。商品タイプ、所属コレクション、カテゴリ、タグの設定を行います。これらの情報は商品の検索、フィルタリング、グループ化に使用されます。

**業務上の目的・背景**：ECサイトにおいて、商品の適切な分類は顧客の商品発見性を高め、在庫管理や販売分析を効率化します。本画面では、商品タイプによる商品種別の定義、コレクションによるマーケティング用グループ化、カテゴリによる階層的な分類、タグによる柔軟なラベル付けが可能です。

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

**主要な操作・処理内容**：
1. 商品タイプの選択
2. コレクションの選択
3. カテゴリの選択（複数選択可）
4. タグの選択（複数選択可）

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

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | 商品CRUD | 主機能 | 商品組織情報の更新 |
| 6 | 商品タイプ管理 | API連携 | タイプ一覧の取得 |
| 7 | 商品タグ管理 | API連携 | タグ一覧の取得 |
| 4 | コレクションCRUD | API連携 | コレクション一覧の取得 |
| 5 | カテゴリCRUD | API連携 | カテゴリ一覧の取得 |

## 画面種別

編集画面（Drawer形式）

## URL/ルーティング

`/products/:id/organization`

## 入出力項目

| 項目名 | 項目ID | データ型 | 必須 | バリデーション | 説明 |
|--------|--------|----------|------|----------------|------|
| タイプ | type_id | string | 任意 | nullable | 商品タイプID |
| コレクション | collection_id | string | 任意 | nullable | コレクションID |
| カテゴリ | category_ids | string[] | 任意 | 配列 | カテゴリIDの配列 |
| タグ | tag_ids | string[] | 任意 | 配列 | タグIDの配列 |

## 表示項目

### ヘッダー

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

### フォーム本体

| 項目名 | 表示位置 | データソース | 説明 |
|--------|----------|--------------|------|
| タイプ選択 | 本体 | product_types API | Comboboxコンポーネント |
| コレクション選択 | 本体 | product_collections API | Comboboxコンポーネント |
| カテゴリ選択 | 本体 | categories API | CategoryCombobox（複数選択） |
| タグ選択 | 本体 | product_tags API | Combobox（複数選択） |
| 拡張フィールドゾーン | 本体下部 | FormExtensionZone | カスタムフィールド表示エリア |

### フッター

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

## イベント仕様

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

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

**処理フロー**:
1. フォームのバリデーションを実行
2. バリデーション成功時、`useUpdateProduct` フックで商品を更新
3. type_id, collection_idは空文字列の場合nullに変換
4. categories, tagsはIDの配列からオブジェクト配列に変換
5. 成功時:
   - `toast.success` で成功メッセージを表示
   - `handleSuccess()` でDrawerを閉じる
6. 失敗時:
   - `toast.error` でエラーメッセージを表示

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

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

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

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

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

### 3-Comboboxの検索

**トリガー**: Comboboxの検索入力

**処理フロー**:
1. useComboboxDataフックが検索クエリを受け取る
2. APIにリクエストを送信
3. 結果をオプションリストに反映
4. ページネーション対応（fetchNextPage）

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 組織更新 | product | UPDATE | 商品レコードの組織情報更新 |
| カテゴリ紐付け | product_category | UPSERT | カテゴリ紐付けの更新 |
| タグ紐付け | product_tag | UPSERT | タグ紐付けの更新 |

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

#### product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | type_id | 入力値またはnull | 空の場合null |
| UPDATE | collection_id | 入力値またはnull | 空の場合null |
| UPDATE | updated_at | 現在日時 | 自動設定 |

#### product_category（中間テーブル）

| 操作 | 項目 | 更新値・取得条件 | 備考 |
|-----|------|-----------------|------|
| UPSERT | product_id | 対象商品ID | - |
| UPSERT | category_id | 選択されたカテゴリID | 複数 |

#### product_tag（中間テーブル）

| 操作 | 項目 | 更新値・取得条件 | 備考 |
|-----|------|-----------------|------|
| UPSERT | product_id | 対象商品ID | - |
| UPSERT | tag_id | 選択されたタグID | 複数 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| products.organization.edit.toasts.success | 成功 | {title}の組織を更新しました | 組織更新成功時 |

## 例外処理

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

## 備考

- 本画面は認証済みユーザーのみアクセス可能なプロテクト画面です
- RouteDrawer形式で商品詳細画面上にオーバーレイ表示されます
- タイプ、コレクションは単一選択、カテゴリ、タグは複数選択可能です
- useComboboxDataフックにより、各選択肢は検索可能でページネーション対応しています
- CategoryComboboxは階層的なカテゴリ選択に対応しています
- useExtendableFormにより拡張フォームフィールドに対応しています
- FormExtensionZoneによりカスタムフィールドの追加が可能です
- KeyboundFormコンポーネントにより、Cmd/Ctrl + Enter での送信に対応しています
- 商品取得時は「*categories」フィールドで全カテゴリを取得しています

---

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

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

### 推奨読解順序

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

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

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

**スキーマ定義**:
- type_id: nullable文字列
- collection_id: nullable文字列
- category_ids: 文字列配列
- tag_ids: 文字列配列

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

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

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

**主要処理フロー**:
1. **10行目**: useParamsでIDを取得
2. **13-16行目**: useProductでデータ取得（*categoriesフィールド付き）
3. **18-20行目**: エラー時のスロー
4. **22-31行目**: RouteDrawerとProductOrganizationFormの表示

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

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

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

**主要処理フロー**:
1. **36-39行目**: useExtension呼び出し
2. **41-69行目**: useComboboxDataで各選択肢を取得
3. **71-81行目**: useExtendableFormでフォーム初期化
4. **83行目**: useUpdateProductフック取得
5. **85-107行目**: handleSubmit関数（データ変換と更新処理）
6. **109-217行目**: UIレンダリング

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

```
ProductOrganization (product-organization.tsx)
    │
    ├─ useParams ─▶ { id }
    ├─ useProduct
    │
    └─ RouteDrawer
           │
           └─ ProductOrganizationForm (product-organization-form.tsx)
                  │
                  ├─ useRouteModal ─▶ handleSuccess
                  ├─ useExtension ─▶ getFormConfigs, getFormFields
                  │
                  ├─ useComboboxData (collections)
                  │      └─ sdk.admin.productCollection.list
                  │
                  ├─ useComboboxData (types)
                  │      └─ sdk.admin.productType.list
                  │
                  ├─ useComboboxData (tags)
                  │      └─ sdk.admin.productTag.list
                  │
                  ├─ useExtendableForm
                  │      └─ ProductOrganizationSchema
                  │
                  ├─ useUpdateProduct
                  │      │
                  │      └─ mutateAsync
                  │            ├─ [成功] → toast.success → handleSuccess
                  │            └─ [失敗] → toast.error
                  │
                  └─ KeyboundForm
                         ├─ Form.Field (type_id) ─▶ Combobox
                         ├─ Form.Field (collection_id) ─▶ Combobox
                         ├─ Form.Field (category_ids) ─▶ CategoryCombobox
                         ├─ Form.Field (tag_ids) ─▶ Combobox (multiple)
                         └─ FormExtensionZone
```

### データフロー図

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

URL /products/:id/organization
        │
        ▼
   useProduct
        │
        ▼
  product データ
  (*categories含む)
        │
        ├────────────────────────┐
        │                        │
        ▼                        ▼
useExtendableForm       useComboboxData
(初期値設定)            (選択肢取得)
        │                        │
        │  ┌─ collections API ◀──┤
        │  ├─ types API ◀────────┤
        │  ├─ tags API ◀─────────┘
        │  │
        ▼  ▼
   ユーザー入力
        │
        ▼
   handleSubmit
        │
        ├─ type_id: "" → null
        ├─ collection_id: "" → null
        ├─ categories: [id] → [{id}]
        ├─ tags: [id] → [{id}]
        │
        ▼
  useUpdateProduct.mutateAsync
        │
        ├─ [成功]
        │     ├─ toast.success
        │     └─ handleSuccess (Drawer閉じる)
        │
        └─ [失敗]
              └─ toast.error
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-organization.tsx | `packages/admin/dashboard/src/routes/products/product-organization/product-organization.tsx` | ソース | メインコンポーネント |
| product-organization-form.tsx | `packages/admin/dashboard/src/routes/products/product-organization/components/product-organization-form/product-organization-form.tsx` | ソース | フォームコンポーネント |
| index.ts | `packages/admin/dashboard/src/routes/products/product-organization/index.ts` | ソース | エクスポート定義 |
| category-combobox.tsx | `packages/admin/dashboard/src/routes/products/common/components/category-combobox/category-combobox.tsx` | ソース | カテゴリ選択コンポーネント |
