# 機能設計書 4-商品カテゴリ管理

## 概要

本ドキュメントは、Medusaコマースプラットフォームにおける商品カテゴリ（Product Category）の階層構造管理機能の設計を記述する。

### 本機能の処理概要

**業務上の目的・背景**：
商品カテゴリは、商品を論理的にグループ化し、顧客が商品を探しやすくするための階層構造を提供する。親子関係を持つツリー構造により、「メンズ > トップス > シャツ」のような多階層のナビゲーションを実現できる。カテゴリは公開/非公開、内部/外部の設定により、柔軟な表示制御が可能。

**機能の利用シーン**：
- ECサイトのナビゲーション構造の構築
- 親カテゴリの下に子カテゴリを追加
- カテゴリの表示順序（rank）の変更
- 季節限定カテゴリの公開/非公開切り替え
- 内部管理用カテゴリの設定

**主要な処理内容**：
1. カテゴリの新規作成（親カテゴリ指定、名前、ハンドル設定）
2. カテゴリの一覧取得（階層構造を含む取得オプション）
3. カテゴリの個別取得（子孫・祖先ツリーの取得）
4. カテゴリの更新（名前変更、親カテゴリ変更、表示設定変更）
5. カテゴリの削除（子カテゴリのカスケード削除）

**関連システム・外部連携**：
- 商品モジュール（Product Module）：商品とカテゴリの多対多関連
- ストアフロント：カテゴリ別商品一覧の表示

**権限による制御**：
- Admin API経由のアクセスには認証が必要（AuthenticatedMedusaRequest）
- カテゴリの作成・更新・削除は管理者権限を持つユーザーのみ実行可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | カテゴリ一覧画面 | 主画面 | カテゴリ階層の一覧表示 |
| 15 | カテゴリ作成画面 | 主画面 | カテゴリの新規作成 |
| 16 | カテゴリ編集画面 | 主画面 | カテゴリ情報の更新 |

## 機能種別

CRUD操作（Create / Read / Update / Delete）+ 階層構造管理

## 入力仕様

### 入力パラメータ

#### カテゴリ作成（POST /admin/product-categories）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | カテゴリ名 | 必須 |
| handle | string | No | URLスラッグ | ユニーク（deleted_at IS NULL） |
| description | string | No | カテゴリ説明 | - |
| parent_category_id | string | No | 親カテゴリID | 存在するカテゴリID |
| is_active | boolean | No | 公開フラグ | デフォルト: false |
| is_internal | boolean | No | 内部フラグ | デフォルト: false |
| rank | number | No | 表示順序 | デフォルト: 0 |
| metadata | object | No | メタデータ | JSON形式 |

#### カテゴリ取得（GET /admin/product-categories）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| q | string | No | 検索クエリ | - |
| id | string/array | No | カテゴリID | - |
| parent_category_id | string | No | 親カテゴリID | - |
| is_active | boolean | No | 公開フラグフィルタ | - |
| is_internal | boolean | No | 内部フラグフィルタ | - |
| include_descendants_tree | boolean | No | 子孫ツリー取得 | - |
| include_ancestors_tree | boolean | No | 祖先ツリー取得 | - |
| offset | number | No | オフセット | - |
| limit | number | No | 取得件数 | - |

### 入力データソース

- 管理画面（Admin Dashboard）からのフォーム入力
- REST API経由のJSON/リクエストボディ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | カテゴリID（pcat_xxx形式） |
| name | string | カテゴリ名 |
| handle | string | URLスラッグ |
| description | string | カテゴリ説明 |
| mpath | string | 階層パス（Materialized Path） |
| is_active | boolean | 公開フラグ |
| is_internal | boolean | 内部フラグ |
| rank | number | 表示順序 |
| parent_category_id | string | 親カテゴリID |
| parent_category | object | 親カテゴリオブジェクト |
| category_children | array | 子カテゴリ配列 |
| products | array | 関連商品配列 |
| metadata | object | メタデータ |
| created_at | datetime | 作成日時 |
| updated_at | datetime | 更新日時 |
| deleted_at | datetime | 削除日時 |

### 出力先

- REST APIレスポンス（JSON形式）
- 管理画面への表示

## 処理フロー

### 処理シーケンス

```
1. カテゴリ作成フロー
   ├─ 親カテゴリの存在確認（指定時）
   ├─ handle自動生成（未指定時）
   ├─ mpath（階層パス）の計算
   ├─ カテゴリ作成（createProductCategoriesStep）
   ├─ フック実行（categoriesCreated）
   └─ イベント発行（ProductCategoryWorkflowEvents.CREATED）

2. カテゴリ取得フロー
   ├─ フィルタ条件の適用
   ├─ フリーテキスト検索（q指定時）
   ├─ 子孫ツリー取得（include_descendants_tree）
   ├─ 祖先ツリー取得（include_ancestors_tree）
   └─ レスポンス整形

3. カテゴリ更新フロー
   ├─ セレクタによるカテゴリ特定
   ├─ 親カテゴリ変更時のmpath再計算
   ├─ カテゴリ更新（updateProductCategoriesStep）
   ├─ フック実行（categoriesUpdated）
   └─ イベント発行（ProductCategoryWorkflowEvents.UPDATED）

4. カテゴリ削除フロー
   ├─ 削除対象カテゴリの取得
   ├─ 子カテゴリのカスケード削除
   ├─ 商品リンクの削除（removeRemoteLinkStep）
   ├─ カテゴリ削除（deleteProductCategoriesStep）
   ├─ フック実行（categoriesDeleted）
   └─ イベント発行（ProductCategoryWorkflowEvents.DELETED）
```

### フローチャート

```mermaid
flowchart TD
    A[カテゴリ作成リクエスト] --> B{親カテゴリ指定?}
    B -->|Yes| C[親カテゴリ存在確認]
    B -->|No| D[ルートカテゴリとして作成]
    C -->|存在| E[mpath計算]
    C -->|不存在| ERR[エラー]
    D --> F[handle生成]
    E --> F
    F --> G[カテゴリ作成]
    G --> H[イベント発行]
    H --> I[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-C01 | ハンドル一意性 | handleは削除されていないカテゴリ間でユニーク | カテゴリ作成・更新時 |
| BR-C02 | ハンドル自動生成 | handle未指定時、nameからkebab-case形式で自動生成 | カテゴリ作成時 |
| BR-C03 | カスケード削除 | カテゴリ削除時に子カテゴリもカスケード削除 | カテゴリ削除時 |
| BR-C04 | mpathの自動計算 | 親カテゴリ指定時、mpathは自動計算される | カテゴリ作成・更新時 |
| BR-C05 | is_activeデフォルト | is_active未指定時はfalse（非公開） | カテゴリ作成時 |
| BR-C06 | is_internalデフォルト | is_internal未指定時はfalse（外部公開） | カテゴリ作成時 |
| BR-C07 | 親カテゴリ制約 | 自身を親カテゴリに設定不可 | カテゴリ更新時 |
| BR-C08 | 循環参照禁止 | 子孫を親カテゴリに設定不可 | カテゴリ更新時 |

### 計算ロジック

- **mpath計算**: 親カテゴリのmpath + 自身のID + "."

## データベース操作仕様

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | product_category | INSERT | カテゴリレコードの挿入 |
| 取得 | product_category | SELECT | カテゴリの取得（階層構造含む） |
| 更新 | product_category | UPDATE | カテゴリの更新 |
| 削除 | product_category | UPDATE | deleted_atの設定（論理削除） |
| 削除 | product_category_product | DELETE | 商品リンクの削除 |

### テーブル別操作詳細

#### product_category

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（pcat_xxx形式） | generateEntityId使用 |
| INSERT | name | リクエストの値 | 必須 |
| INSERT | handle | リクエストの値 or 自動生成 | ユニーク |
| INSERT | mpath | 計算値 | 階層パス |
| UPDATE | parent_category_id | リクエストの値 | mpathも再計算 |
| DELETE | deleted_at | 現在日時 | 論理削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NOT_FOUND | 404 | 指定IDのカテゴリが存在しない | 正しいIDを指定 |
| NOT_FOUND | 404 | 存在しない親カテゴリIDを指定 | 正しい親カテゴリIDを指定 |
| INVALID_DATA | 400 | handleが重複 | ユニークな値を指定 |
| INVALID_DATA | 400 | 自身を親カテゴリに指定 | 別のカテゴリを指定 |
| INVALID_DATA | 400 | 子孫を親カテゴリに指定 | 別のカテゴリを指定 |

### リトライ仕様

トランザクション失敗時は自動ロールバック。明示的なリトライ機構はなし。

## トランザクション仕様

- カテゴリ作成・更新・削除は`@InjectTransactionManager()`デコレータによりトランザクション管理
- 子カテゴリのカスケード削除は同一トランザクション内で処理
- ワークフロー内の各ステップは補償関数を持ち、失敗時にロールバック

## パフォーマンス要件

- カテゴリ一覧取得：ページネーション対応
- handleにユニークインデックス（IDX_category_handle_unique）
- mpathにインデックス（IDX_product_category_path）
- フリーテキスト検索対応（name, description, handle）

## セキュリティ考慮事項

- Admin API経由のアクセスには認証必須（AuthenticatedMedusaRequest）
- name・handleの入力値サニタイズ

## 備考

- カテゴリのIDプレフィックスは「pcat」
- Materialized Pathパターンによる階層構造管理
- 子孫・祖先ツリーの取得はinclude_descendants_tree/include_ancestors_treeオプションで制御

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | product-category.ts | `packages/modules/product/src/models/product-category.ts` | カテゴリモデル定義 |

**主要処理フロー（product-category.ts）**:
- **4-45行目**: ProductCategoryモデル定義
- **6行目**: IDプレフィックス「pcat」
- **7行目**: nameフィールド（searchable, translatable）
- **8行目**: descriptionフィールド（searchable, translatable）
- **9行目**: handleフィールド（searchable）
- **10行目**: mpathフィールド（Materialized Path）
- **11-12行目**: is_active, is_internalのデフォルト値
- **13行目**: rankのデフォルト値（0）
- **15-19行目**: 親カテゴリとの自己参照リレーション
- **20-22行目**: 子カテゴリとのリレーション
- **23-25行目**: 商品との多対多リレーション
- **27-29行目**: カスケード削除設定（category_children）
- **30-43行目**: インデックス定義

#### Step 2: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | product-category.ts | `packages/modules/product/src/services/product-category.ts` | カテゴリサービス |

**主要処理フロー（product-category.ts）**:
- **46-87行目**: retrieve - 単一カテゴリ取得（子孫ツリー含む）
- **89-121行目**: list - カテゴリ一覧取得（フィルタ、子孫/祖先ツリー）
- **123-155行目**: listAndCount - カテゴリ一覧取得（件数付き）
- **157-165行目**: create - カテゴリ作成
- **167-176行目**: update - カテゴリ更新
- **178-214行目**: delete - カテゴリ削除（イベント発行含む）
- **216-225行目**: softDelete - 論理削除
- **227-236行目**: restore - 復元

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | route.ts | `packages/medusa/src/api/admin/product-categories/route.ts` | 一覧・作成エンドポイント |
| 3-2 | [id]/route.ts | `packages/medusa/src/api/admin/product-categories/[id]/route.ts` | 詳細・更新・削除 |

**主要処理フロー（route.ts）**:
- **9-27行目**: GET - カテゴリ一覧取得
- **29-51行目**: POST - カテゴリ作成

**主要処理フロー（[id]/route.ts）**:
- **16-40行目**: GET - カテゴリ詳細取得
- **42-66行目**: POST - カテゴリ更新
- **68-83行目**: DELETE - カテゴリ削除

#### Step 4: ワークフローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create-product-categories.ts | `packages/core/core-flows/src/product-category/workflows/create-product-categories.ts` | 作成ワークフロー |
| 4-2 | update-product-categories.ts | `packages/core/core-flows/src/product-category/workflows/update-product-categories.ts` | 更新ワークフロー |
| 4-3 | delete-product-categories.ts | `packages/core/core-flows/src/product-category/workflows/delete-product-categories.ts` | 削除ワークフロー |

**主要処理フロー（create-product-categories.ts）**:
- **45-75行目**: createProductCategoriesWorkflow定義
- **50行目**: createProductCategoriesStep呼び出し
- **61-64行目**: イベント発行（ProductCategoryWorkflowEvents.CREATED）
- **66-69行目**: フック定義（categoriesCreated）

**主要処理フロー（delete-product-categories.ts）**:
- **39-70行目**: deleteProductCategoriesWorkflow定義
- **42行目**: deleteProductCategoriesStep呼び出し
- **50-60行目**: リモートリンク削除とイベント発行の並列実行

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

```
Admin API Route
    │
    ├─ GET /admin/product-categories
    │      └─ refetchEntities()
    │             └─ ProductCategoryService.list()
    │                    └─ ProductCategoryRepository.find()
    │
    ├─ POST /admin/product-categories
    │      └─ createProductCategoriesWorkflow.run()
    │             ├─ createProductCategoriesStep
    │             │      └─ ProductCategoryService.create()
    │             └─ emitEventStep
    │
    ├─ POST /admin/product-categories/:id
    │      └─ updateProductCategoriesWorkflow.run()
    │             ├─ updateProductCategoriesStep
    │             │      └─ ProductCategoryService.update()
    │             └─ emitEventStep
    │
    └─ DELETE /admin/product-categories/:id
           └─ deleteProductCategoriesWorkflow.run()
                  ├─ deleteProductCategoriesStep
                  │      └─ ProductCategoryService.delete()
                  ├─ removeRemoteLinkStep
                  └─ emitEventStep
```

### データフロー図

```
[カテゴリ作成]

Input                    Processing                         Output
──────────────────────────────────────────────────────────────────────
name              ──▶   handle生成                   ──▶   category_id
parent_category_id             │
                               ▼
                        mpath計算
                               │
                               ▼
               createProductCategoriesStep    ──▶   カテゴリ作成
                               │
                               ▼
                      emitEventStep           ──▶   イベント発行
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-category.ts | `packages/modules/product/src/models/product-category.ts` | モデル | カテゴリエンティティ定義 |
| product-category.ts | `packages/modules/product/src/services/product-category.ts` | サービス | カテゴリ操作メソッド |
| product-category.ts | `packages/modules/product/src/repositories/product-category.ts` | リポジトリ | ツリー構造操作 |
| route.ts | `packages/medusa/src/api/admin/product-categories/route.ts` | API | 一覧・作成エンドポイント |
| [id]/route.ts | `packages/medusa/src/api/admin/product-categories/[id]/route.ts` | API | 詳細・更新・削除 |
| create-product-categories.ts | `packages/core/core-flows/src/product-category/workflows/create-product-categories.ts` | ワークフロー | 作成処理 |
| update-product-categories.ts | `packages/core/core-flows/src/product-category/workflows/update-product-categories.ts` | ワークフロー | 更新処理 |
| delete-product-categories.ts | `packages/core/core-flows/src/product-category/workflows/delete-product-categories.ts` | ワークフロー | 削除処理 |
