# 機能設計書 1-商品CRUD

## 概要

本ドキュメントは、Medusaコマースプラットフォームにおける商品（Product）の作成・取得・更新・削除（CRUD）機能の設計を記述する。

### 本機能の処理概要

**業務上の目的・背景**：
ECサイト運営において、商品は販売の根幹を成すエンティティである。管理者が効率的に商品を登録・編集・管理できることで、カタログの迅速な更新、在庫状況への対応、マーケティング施策の実行が可能となる。本機能は、商品情報の一元管理を実現し、販売チャネル間での商品データ整合性を保証する。

**機能の利用シーン**：
- 新商品の発売に伴う商品情報の登録
- 既存商品の価格・説明文・画像の更新
- 季節商品やキャンペーン終了商品の削除・非公開化
- 商品一覧からの検索・フィルタリングによる特定商品の確認
- CSVインポートによる商品の一括登録

**主要な処理内容**：
1. 商品の新規作成（タイトル、説明、ステータス、オプション、バリアント、画像の同時登録）
2. 商品の一覧取得（フィルタリング、ページネーション、関連データの取得）
3. 商品の個別取得（ID指定による詳細情報の取得）
4. 商品の更新（基本情報、オプション、バリアント、カテゴリ・コレクションとの関連付け）
5. 商品の削除（論理削除による安全な削除処理）

**関連システム・外部連携**：
- 価格モジュール（Pricing Module）：バリアント価格の設定・取得
- 在庫モジュール（Inventory Module）：バリアントの在庫管理
- 販売チャネルモジュール（Sales Channel Module）：販売チャネルとの紐付け
- フルフィルメントモジュール（Fulfillment Module）：配送プロファイルとの紐付け
- インデックスエンジン：高速検索機能（オプション）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | 商品一覧画面 | 主画面 | 商品の一覧取得・検索・フィルタ処理 |
| 6 | 商品作成画面 | 主画面 | 新規商品の作成処理 |
| 7 | 商品詳細画面 | 主画面 | 商品詳細情報の取得・表示 |
| 8 | 商品編集画面 | 主画面 | 商品基本情報の更新処理 |
| 9 | 商品属性編集画面 | 主画面 | 商品属性の更新処理 |
| 19 | 商品インポート画面 | 主画面 | CSVからの商品一括インポート |
| 20 | 商品エクスポート画面 | 主画面 | 商品データのCSVエクスポート |
| 21 | 商品メタデータ編集画面 | 主画面 | 商品メタデータの編集 |

## 機能種別

CRUD操作（Create / Read / Update / Delete）

## 入力仕様

### 入力パラメータ

#### 商品作成（POST /admin/products）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | string | Yes | 商品名 | 必須、空文字不可 |
| handle | string | No | URLスラッグ | URL安全な文字列、ユニーク |
| subtitle | string | No | サブタイトル | - |
| description | string | No | 商品説明 | - |
| status | enum | No | ステータス | draft / published / proposed / rejected |
| is_giftcard | boolean | No | ギフトカードフラグ | デフォルト: false |
| discountable | boolean | No | 割引適用可否 | デフォルト: true |
| thumbnail | string | No | サムネイルURL | - |
| images | array | No | 画像URL配列 | url必須 |
| options | array | No | オプション配列 | title必須、values配列 |
| variants | array | No | バリアント配列 | title必須、options必須 |
| weight | number | No | 重量 | - |
| length | number | No | 長さ | - |
| height | number | No | 高さ | - |
| width | number | No | 幅 | - |
| origin_country | string | No | 原産国 | - |
| hs_code | string | No | HSコード | - |
| mid_code | string | No | MIDコード | - |
| material | string | No | 素材 | - |
| type_id | string | No | 商品タイプID | 存在するタイプID |
| collection_id | string | No | コレクションID | 存在するコレクションID |
| category_ids | array | No | カテゴリID配列 | 存在するカテゴリID |
| tag_ids | array | No | タグID配列 | 存在するタグID |
| sales_channels | array | No | 販売チャネル配列 | id必須 |
| shipping_profile_id | string | No | 配送プロファイルID | - |
| metadata | object | No | メタデータ | JSON形式 |

#### 商品取得（GET /admin/products）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string/array | No | 商品ID | - |
| q | string | No | 検索クエリ | - |
| status | array | No | ステータスフィルタ | - |
| title | string | No | タイトルフィルタ | - |
| handle | string | No | ハンドルフィルタ | - |
| is_giftcard | boolean | No | ギフトカードフィルタ | - |
| type_id | array | No | タイプIDフィルタ | - |
| collection_id | array | No | コレクションIDフィルタ | - |
| tags | array | No | タグフィルタ | - |
| categories | array | No | カテゴリフィルタ | - |
| sales_channel_id | array | No | 販売チャネルフィルタ | - |
| offset | number | No | オフセット | デフォルト: 0 |
| limit | number | No | 取得件数 | デフォルト: 50 |
| fields | string | No | 取得フィールド | カンマ区切り |
| expand | string | No | リレーション展開 | カンマ区切り |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | 商品ID（prod_xxx形式） |
| title | string | 商品名 |
| handle | string | URLスラッグ |
| subtitle | string | サブタイトル |
| description | string | 商品説明 |
| status | enum | ステータス |
| is_giftcard | boolean | ギフトカードフラグ |
| discountable | boolean | 割引適用可否 |
| thumbnail | string | サムネイルURL |
| images | array | 画像配列 |
| options | array | オプション配列 |
| variants | array | バリアント配列 |
| weight | string | 重量 |
| length | string | 長さ |
| height | string | 高さ |
| width | string | 幅 |
| origin_country | string | 原産国 |
| hs_code | string | HSコード |
| mid_code | string | MIDコード |
| material | string | 素材 |
| type | object | 商品タイプ |
| collection | object | コレクション |
| categories | array | カテゴリ配列 |
| tags | array | タグ配列 |
| external_id | string | 外部ID |
| metadata | object | メタデータ |
| created_at | datetime | 作成日時 |
| updated_at | datetime | 更新日時 |
| deleted_at | datetime | 削除日時 |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. 商品作成フロー
   ├─ バリデーション実行（validateProductInputStep）
   │     └─ オプションの存在チェック
   ├─ 商品データ作成（createProductsStep）
   │     └─ handle自動生成（未指定時）
   │     └─ status初期値設定（draft）
   │     └─ thumbnail自動設定（最初の画像）
   ├─ 販売チャネルリンク作成
   ├─ 配送プロファイルリンク作成
   ├─ バリアント作成（createProductVariantsWorkflow）
   │     └─ オプション値との紐付け
   │     └─ 価格設定
   └─ イベント発行（ProductWorkflowEvents.CREATED）

2. 商品取得フロー
   ├─ フィルタ条件の適用
   ├─ インデックスエンジン判定
   │     └─ 有効時：インデックス検索
   │     └─ 無効時：通常クエリ
   ├─ リレーションの取得
   └─ レスポンス整形

3. 商品更新フロー
   ├─ 既存商品の存在確認
   ├─ バリアント更新処理
   ├─ 販売チャネルリンク更新
   ├─ 配送プロファイルリンク更新
   ├─ 価格更新（upsertVariantPricesWorkflow）
   └─ イベント発行（ProductWorkflowEvents.UPDATED）

4. 商品削除フロー
   ├─ 削除対象商品の取得
   ├─ 関連バリアントの取得
   ├─ 在庫アイテムの削除判定・実行
   ├─ リモートリンクの削除
   ├─ 商品の削除（論理削除）
   └─ イベント発行（ProductWorkflowEvents.DELETED）
```

### フローチャート

```mermaid
flowchart TD
    A[商品作成リクエスト] --> B{オプションあり?}
    B -->|Yes| C[商品データ作成]
    B -->|No| ERR1[エラー: オプション必須]
    C --> D[販売チャネルリンク作成]
    D --> E[配送プロファイルリンク作成]
    E --> F{バリアントあり?}
    F -->|Yes| G[バリアント作成]
    F -->|No| H[完了]
    G --> I[価格設定]
    I --> J[イベント発行]
    J --> H
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | ハンドル自動生成 | handleが未指定の場合、titleからkebab-case形式で自動生成 | 商品作成時 |
| BR-002 | ハンドル一意性 | handleは削除されていない商品間でユニーク | 商品作成・更新時 |
| BR-003 | オプション必須 | バリアントを持つ商品はオプションが必須 | 商品作成時 |
| BR-004 | バリアントオプション整合性 | バリアントは商品のオプション全てに対する値を持つ必要あり | バリアント作成時 |
| BR-005 | ギフトカード割引不可 | is_giftcard=trueの場合、discountable=false | 商品作成・更新時 |
| BR-006 | サムネイル自動設定 | thumbnail未指定で画像がある場合、最初の画像をサムネイルに設定 | 商品作成時 |
| BR-007 | ステータス初期値 | status未指定時はdraft（下書き）が設定される | 商品作成時 |
| BR-008 | タグ事前登録必須 | 商品に紐付けるタグは事前に作成されている必要あり | 商品作成・更新時 |
| BR-009 | 論理削除 | 商品削除は物理削除ではなく論理削除（deleted_atを設定） | 商品削除時 |
| BR-010 | カスケード削除 | 商品削除時にバリアント、オプション、画像も削除 | 商品削除時 |

### 計算ロジック

特になし（価格計算はPricingモジュールで実施）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 商品作成 | product | INSERT | 商品レコードの挿入 |
| 商品作成 | product_option | INSERT | オプションレコードの挿入 |
| 商品作成 | product_option_value | INSERT | オプション値レコードの挿入 |
| 商品作成 | image | INSERT | 画像レコードの挿入 |
| 商品作成 | product_variant | INSERT | バリアントレコードの挿入 |
| 商品作成 | product_tags | INSERT | 商品-タグ中間テーブルの挿入 |
| 商品作成 | product_category_product | INSERT | 商品-カテゴリ中間テーブルの挿入 |
| 商品取得 | product | SELECT | 商品レコードの取得 |
| 商品更新 | product | UPDATE | 商品レコードの更新 |
| 商品削除 | product | UPDATE | deleted_atの設定（論理削除） |

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

#### product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prod_xxx形式） | generateEntityId使用 |
| INSERT | title | リクエストの値 | 必須 |
| INSERT | handle | リクエストの値 or タイトルから生成 | ユニーク |
| INSERT | status | リクエストの値 or 'draft' | デフォルト値あり |
| UPDATE | title, description等 | リクエストの値 | 指定フィールドのみ |
| DELETE | deleted_at | 現在日時 | 論理削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NOT_FOUND | 404 | 指定IDの商品が存在しない | 正しいIDを指定 |
| INVALID_DATA | 400 | オプション未設定で商品作成 | オプションを設定 |
| INVALID_DATA | 400 | handle値がURL安全でない | URL安全な文字列を使用 |
| INVALID_DATA | 400 | 存在しないタグIDを指定 | 事前にタグを作成 |
| INVALID_DATA | 400 | バリアントにオプション値が不足 | 全オプションの値を指定 |
| INVALID_DATA | 400 | 同一オプション組み合わせのバリアントが存在 | 異なるオプション組み合わせを使用 |

### リトライ仕様

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

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

- 商品作成・更新・削除は`@InjectTransactionManager()`デコレータによりトランザクション管理
- 関連エンティティ（オプション、バリアント、リンク）は同一トランザクション内で処理
- ワークフロー内の各ステップは補償（compensation）関数を持ち、失敗時にロールバック

## パフォーマンス要件

- 商品一覧取得：ページネーション対応、デフォルト50件/リクエスト
- インデックスエンジン有効時：高速検索が可能
- 画像ソートのためのインデックス（IDX_product_image_rank）

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

- Admin API経由のアクセスには認証必須（AuthenticatedMedusaRequest）
- handle値のサニタイズ（URL安全性の検証）
- メタデータはJSON形式で保存、スクリプトインジェクション対策が必要

## 備考

- 商品のIDプレフィックスは「prod」
- バリアントのIDプレフィックスは「variant」
- オプションのIDプレフィックスは「opt」
- 画像のIDプレフィックスは「img」

---

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

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

### 推奨読解順序

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

まず、商品エンティティの構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | product.ts | `packages/modules/product/src/models/product.ts` | 商品モデルの定義、リレーション、インデックス |
| 1-2 | product-variant.ts | `packages/modules/product/src/models/product-variant.ts` | バリアントモデル、オプション値との関連 |
| 1-3 | product-option.ts | `packages/modules/product/src/models/product-option.ts` | オプションモデル、値の管理 |
| 1-4 | product-image.ts | `packages/modules/product/src/models/product-image.ts` | 画像モデル、ランキング |

**読解のコツ**: Medusaのモデルは`@medusajs/framework/utils`の`model`ビルダーを使用。`model.define()`でエンティティを定義し、`model.belongsTo()`/`model.hasMany()`/`model.manyToMany()`でリレーションを定義する。

**主要処理フロー（product.ts）**:
- **11-61行目**: Productモデルの定義。フィールド、リレーション、状態の定義
- **34-36行目**: バリアントとの1対多リレーション
- **42-45行目**: タグとの多対多リレーション（product_tagsピボットテーブル）
- **57-60行目**: カテゴリとの多対多リレーション
- **62-64行目**: カスケード削除の設定（variants, options, images）
- **65-90行目**: インデックス定義（handle一意性、type_id、collection_id、status）

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

APIルートから処理の流れを追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/admin/products/route.ts` | GET/POST エンドポイント |
| 2-2 | [id]/route.ts | `packages/medusa/src/api/admin/products/[id]/route.ts` | GET/POST/DELETE エンドポイント |

**主要処理フロー（route.ts）**:
- **17-36行目**: GET - 商品一覧取得。インデックスエンジンの有効/無効で処理分岐
- **38-59行目**: getProducts関数 - refetchEntitiesによる通常クエリ
- **61-92行目**: getProductsWithIndexEngine関数 - インデックス検索
- **94-115行目**: POST - 商品作成。createProductsWorkflowを呼び出し

**主要処理フロー（[id]/route.ts）**:
- **14-31行目**: GET - 商品詳細取得
- **33-74行目**: POST - 商品更新。updateProductsWorkflowを呼び出し
- **76-91行目**: DELETE - 商品削除。deleteProductsWorkflowを呼び出し

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

ビジネスロジックの中核となるワークフロー。

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

**主要処理フロー（create-products.ts）**:
- **73-91行目**: validateProductInputStep - オプション存在チェック
- **163-290行目**: createProductsWorkflow定義
- **167-181行目**: 外部リレーション（販売チャネル、配送プロファイル、バリアント）の分離
- **183行目**: バリデーション実行
- **185行目**: createProductsStep呼び出し
- **187-201行目**: 販売チャネルリンク作成
- **203-221行目**: 配送プロファイルリンク作成
- **223-247行目**: バリアント作成
- **276-279行目**: イベント発行

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

モジュールサービスの実装。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | product-module-service.ts | `packages/modules/product/src/services/product-module-service.ts` | メインサービス |

**主要処理フロー（product-module-service.ts）**:
- **81-121行目**: MedusaService継承、DTO型定義
- **196-228行目**: retrieveProduct - 単一商品取得
- **230-266行目**: listProducts - 商品一覧取得
- **1516-1542行目**: createProducts - 商品作成（公開メソッド）
- **1634-1724行目**: createProducts_ - 商品作成（内部実装）
- **1726-1775行目**: updateProducts_ - 商品更新（内部実装）
- **1882-1928行目**: validateProductCreatePayload - 作成時バリデーション

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

```
Admin API Route (route.ts)
    │
    ├─ GET /admin/products
    │      └─ refetchEntities() or query.index()
    │             └─ ProductModuleService.listProducts()
    │
    ├─ POST /admin/products
    │      └─ createProductsWorkflow.run()
    │             ├─ validateProductInputStep
    │             ├─ createProductsStep
    │             │      └─ ProductModuleService.createProducts()
    │             ├─ associateProductsWithSalesChannelsStep
    │             ├─ createRemoteLinkStep (shipping profile)
    │             └─ createProductVariantsWorkflow.runAsStep()
    │
    ├─ POST /admin/products/:id
    │      └─ updateProductsWorkflow.run()
    │             ├─ updateProductsStep
    │             │      └─ ProductModuleService.updateProducts()
    │             ├─ upsertVariantPricesWorkflow.runAsStep()
    │             └─ createRemoteLinkStep (sales channels, shipping profile)
    │
    └─ DELETE /admin/products/:id
           └─ deleteProductsWorkflow.run()
                  ├─ getProductsStep
                  ├─ deleteInventoryItemWorkflow.runAsStep()
                  ├─ removeRemoteLinkStep
                  └─ deleteProductsStep
                         └─ ProductModuleService.deleteProducts()
```

### データフロー図

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

Admin Dashboard   ──▶  API Route                    ──▶  JSON Response
     │                     │                                   │
     │                     ▼                                   │
     │              Workflow Layer                             │
     │              (core-flows)                               │
     │                     │                                   │
     │                     ▼                                   │
     │             Module Service                              │
     │         (ProductModuleService)                          │
     │                     │                                   │
     │                     ▼                                   │
     │              Repository                                 │
     │          (ProductRepository)                            │
     │                     │                                   │
     │                     ▼                                   │
     │               Database                                  │
     │            (PostgreSQL)                                 │
     │                     │                                   │
     └─────────────────────┴───────────────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product.ts | `packages/modules/product/src/models/product.ts` | モデル | 商品エンティティ定義 |
| product-variant.ts | `packages/modules/product/src/models/product-variant.ts` | モデル | バリアントエンティティ定義 |
| product-option.ts | `packages/modules/product/src/models/product-option.ts` | モデル | オプションエンティティ定義 |
| product-image.ts | `packages/modules/product/src/models/product-image.ts` | モデル | 画像エンティティ定義 |
| product-module-service.ts | `packages/modules/product/src/services/product-module-service.ts` | サービス | 商品モジュールサービス |
| route.ts | `packages/medusa/src/api/admin/products/route.ts` | API | 一覧・作成エンドポイント |
| [id]/route.ts | `packages/medusa/src/api/admin/products/[id]/route.ts` | API | 詳細・更新・削除エンドポイント |
| create-products.ts | `packages/core/core-flows/src/product/workflows/create-products.ts` | ワークフロー | 商品作成ワークフロー |
| update-products.ts | `packages/core/core-flows/src/product/workflows/update-products.ts` | ワークフロー | 商品更新ワークフロー |
| delete-products.ts | `packages/core/core-flows/src/product/workflows/delete-products.ts` | ワークフロー | 商品削除ワークフロー |
| validators.ts | `packages/medusa/src/api/admin/products/validators.ts` | バリデーション | 入力検証スキーマ |
| helpers.ts | `packages/medusa/src/api/admin/products/helpers.ts` | ヘルパー | レスポンス整形 |
