# 機能設計書 2-商品バリアント管理

## 概要

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

### 本機能の処理概要

**業務上の目的・背景**：
ECサイトにおいて、商品は複数のバリエーション（サイズ、色、素材など）を持つことが一般的である。バリアントは、商品のオプション値の組み合わせごとに作成され、それぞれ固有のSKU、価格、在庫を持つ。本機能により、管理者は商品の各バリエーションを個別に管理し、適切な価格設定と在庫管理を行うことができる。

**機能の利用シーン**：
- 新しいサイズ・カラーバリエーションの追加
- バリアント固有の価格・SKUの設定
- 在庫管理設定（manage_inventory）の変更
- 販売終了バリアントの削除
- バリアントへの在庫アイテムの紐付け

**主要な処理内容**：
1. バリアントの新規作成（オプション値、価格、在庫アイテムの同時設定）
2. バリアントの一覧取得（商品IDによるフィルタリング、在庫数量の取得）
3. バリアントの個別取得
4. バリアントの更新（価格、SKU、在庫管理設定の変更）
5. バリアントの削除（関連する在庫アイテムの削除判定を含む）

**関連システム・外部連携**：
- 価格モジュール（Pricing Module）：バリアント価格の設定・更新
- 在庫モジュール（Inventory Module）：在庫アイテムの管理
- 商品モジュール（Product Module）：親商品との関連管理

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | 商品詳細画面 | 主画面 | バリアント一覧の表示 |
| 10 | バリアント編集画面 | 主画面 | バリアントの作成・更新処理 |
| 11 | バリアント一覧画面 | 主画面 | バリアントの一覧表示・検索 |
| 12 | バリアント詳細画面 | 主画面 | バリアント詳細情報の表示 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

#### バリアント作成（POST /admin/products/:id/variants）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | string | Yes | バリアント名 | 必須 |
| sku | string | No | SKU（在庫管理単位） | ユニーク（deleted_at IS NULL） |
| barcode | string | No | バーコード | ユニーク（deleted_at IS NULL） |
| ean | string | No | EANコード | ユニーク（deleted_at IS NULL） |
| upc | string | No | UPCコード | ユニーク（deleted_at IS NULL） |
| allow_backorder | boolean | No | バックオーダー許可 | デフォルト: false |
| manage_inventory | boolean | No | 在庫管理有効化 | デフォルト: true |
| hs_code | string | No | HSコード | - |
| origin_country | string | No | 原産国 | - |
| mid_code | string | No | MIDコード | - |
| material | string | No | 素材 | - |
| weight | number | No | 重量 | - |
| length | number | No | 長さ | - |
| height | number | No | 高さ | - |
| width | number | No | 幅 | - |
| thumbnail | string | No | サムネイルURL | - |
| options | object | Yes | オプション値マップ | 商品オプション全ての値が必要 |
| prices | array | No | 価格配列 | amount必須、currency_code必須 |
| inventory_items | array | No | 在庫アイテム配列 | inventory_item_id必須 |
| metadata | object | No | メタデータ | JSON形式 |

#### バリアント更新（POST /admin/products/:id/variants/:variant_id）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | string | No | バリアント名 | - |
| sku | string | No | SKU | ユニーク |
| barcode | string | No | バーコード | ユニーク |
| ean | string | No | EANコード | ユニーク |
| upc | string | No | UPCコード | ユニーク |
| allow_backorder | boolean | No | バックオーダー許可 | - |
| manage_inventory | boolean | No | 在庫管理有効化 | - |
| weight | number | No | 重量 | - |
| length | number | No | 長さ | - |
| height | number | No | 高さ | - |
| width | number | No | 幅 | - |
| thumbnail | string | No | サムネイルURL | - |
| options | object | No | オプション値マップ | - |
| prices | array | No | 価格配列 | - |
| metadata | object | No | メタデータ | - |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | バリアントID（variant_xxx形式） |
| title | string | バリアント名 |
| sku | string | SKU |
| barcode | string | バーコード |
| ean | string | EANコード |
| upc | string | UPCコード |
| allow_backorder | boolean | バックオーダー許可 |
| manage_inventory | boolean | 在庫管理有効化 |
| hs_code | string | HSコード |
| origin_country | string | 原産国 |
| mid_code | string | MIDコード |
| material | string | 素材 |
| weight | number | 重量 |
| length | number | 長さ |
| height | number | 高さ |
| width | number | 幅 |
| thumbnail | string | サムネイルURL |
| variant_rank | number | 表示順序 |
| product_id | string | 親商品ID |
| options | array | オプション値配列 |
| prices | array | 価格配列 |
| inventory_quantity | number | 在庫数量（オプション） |
| metadata | object | メタデータ |
| created_at | datetime | 作成日時 |
| updated_at | datetime | 更新日時 |
| deleted_at | datetime | 削除日時 |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. バリアント作成フロー
   ├─ 価格データの分離（prices除去）
   ├─ バリアント作成（createProductVariantsStep）
   │     └─ オプション値との紐付け
   ├─ 在庫アイテムID検証（validateInventoryItems）
   ├─ 在庫アイテム作成判定
   │     └─ manage_inventory=true かつ inventory_items未指定の場合
   │         └─ 新規在庫アイテム作成（createInventoryItemsWorkflow）
   ├─ バリアント-在庫リンク作成（createLinksWorkflow）
   ├─ 価格セット作成（createPriceSetsStep）
   ├─ バリアント-価格リンク作成（createVariantPricingLinkStep）
   └─ イベント発行（ProductVariantWorkflowEvents.CREATED）

2. バリアント取得フロー
   ├─ 商品IDによるフィルタリング
   ├─ refetchEntitiesによるデータ取得
   ├─ 在庫数量取得判定
   │     └─ inventory_quantity指定時
   │         └─ wrapVariantsWithTotalInventoryQuantity
   └─ レスポンス整形

3. バリアント更新フロー
   ├─ 価格データの分離
   ├─ バリアント更新（updateProductVariantsStep）
   ├─ 在庫リンク解除判定
   │     └─ manage_inventory=false変更時
   │         └─ dismissProductVariantsInventoryStep
   ├─ 価格セット更新（updatePriceSetsStep）
   └─ イベント発行（ProductVariantWorkflowEvents.UPDATED）

4. バリアント削除フロー
   ├─ バリアントの在庫情報取得（useQueryGraphStep）
   ├─ リモートリンク削除（removeRemoteLinkStep）
   ├─ 在庫アイテム削除判定
   │     └─ 他バリアントと共有されていない場合のみ削除
   ├─ バリアント削除（deleteProductVariantsStep）
   └─ イベント発行（ProductVariantWorkflowEvents.DELETED）
```

### フローチャート

```mermaid
flowchart TD
    A[バリアント作成リクエスト] --> B[価格データ分離]
    B --> C[バリアント作成]
    C --> D{manage_inventory?}
    D -->|Yes| E{inventory_items指定?}
    D -->|No| H[価格セット作成]
    E -->|Yes| F[在庫リンク作成]
    E -->|No| G[在庫アイテム自動作成]
    G --> F
    F --> H
    H --> I[価格リンク作成]
    I --> J[イベント発行]
    J --> K[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-V01 | SKUユニーク性 | SKUは削除されていないバリアント間でユニーク | バリアント作成・更新時 |
| BR-V02 | バーコードユニーク性 | barcodeは削除されていないバリアント間でユニーク | バリアント作成・更新時 |
| BR-V03 | EANユニーク性 | EANは削除されていないバリアント間でユニーク | バリアント作成・更新時 |
| BR-V04 | UPCユニーク性 | UPCは削除されていないバリアント間でユニーク | バリアント作成・更新時 |
| BR-V05 | オプション完全性 | バリアント作成時、商品の全オプションに対する値が必要 | バリアント作成時 |
| BR-V06 | オプション組み合わせ一意性 | 同一オプション値の組み合わせを持つバリアントは作成不可 | バリアント作成時 |
| BR-V07 | 在庫アイテム自動作成 | manage_inventory=trueでinventory_items未指定の場合、在庫アイテムを自動作成 | バリアント作成時 |
| BR-V08 | 重複在庫アイテム禁止 | 同一バリアントに同じ在庫アイテムを複数回紐付け不可 | バリアント作成時 |
| BR-V09 | 在庫リンク解除 | manage_inventory=false変更時、在庫リンクを解除 | バリアント更新時 |
| BR-V10 | 在庫アイテム削除判定 | バリアント削除時、他バリアントと共有されていない在庫アイテムのみ削除 | バリアント削除時 |

### 計算ロジック

- `inventory_quantity`: 全ロケーションの在庫数量合計（stocked_quantity - reserved_quantity）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | product_variant | INSERT | バリアントレコードの挿入 |
| 作成 | product_variant_option | INSERT | オプション値との関連挿入 |
| 作成 | price_set | INSERT | 価格セットの作成 |
| 作成 | money_amount | INSERT | 価格の作成 |
| 作成 | product_variant_price_set | INSERT | バリアント-価格リンク |
| 作成 | inventory_item | INSERT | 在庫アイテム作成（自動時） |
| 作成 | product_variant_inventory_item | INSERT | バリアント-在庫リンク |
| 取得 | product_variant | SELECT | バリアントの取得 |
| 更新 | product_variant | UPDATE | バリアントの更新 |
| 更新 | price_set | UPDATE | 価格セットの更新 |
| 削除 | product_variant | UPDATE | deleted_atの設定（論理削除） |
| 削除 | product_variant_inventory_item | DELETE | バリアント-在庫リンク削除 |
| 削除 | inventory_item | UPDATE | 在庫アイテム論理削除（条件付き） |

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

#### product_variant

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（variant_xxx形式） | generateEntityId使用 |
| INSERT | title | リクエストの値 | 必須 |
| INSERT | product_id | URLパラメータの値 | 必須 |
| UPDATE | manage_inventory | リクエストの値 | 在庫リンク処理に影響 |
| DELETE | deleted_at | 現在日時 | 論理削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NOT_FOUND | 404 | 指定IDのバリアントが存在しない | 正しいIDを指定 |
| INVALID_DATA | 400 | 重複するSKU/バーコード/EAN/UPCを指定 | ユニークな値を指定 |
| INVALID_DATA | 400 | 必須オプション値が不足 | 全オプションの値を指定 |
| INVALID_DATA | 400 | 同一オプション組み合わせが既存 | 異なる組み合わせを使用 |
| INVALID_DATA | 400 | 重複する在庫アイテムIDを指定 | 一意の在庫アイテムIDを指定 |
| NOT_FOUND | 404 | 存在しない在庫アイテムIDを指定 | 正しい在庫アイテムIDを指定 |

### リトライ仕様

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

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

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

## パフォーマンス要件

- バリアント一覧取得：ページネーション対応
- SKU/バーコード/EAN/UPCにはユニークインデックス
- product_idにはインデックス（IDX_product_variant_product_id）

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

- Admin API経由のアクセスには認証必須（AuthenticatedMedusaRequest）
- SKU・バーコード等の識別子は適切なフォーマットチェックが必要

## 備考

- バリアントのIDプレフィックスは「variant」
- バリアントは商品削除時にカスケード削除される
- バージョン2.11.2でthumbnail、imagesフィールドが追加

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | product-variant.ts | `packages/modules/product/src/models/product-variant.ts` | バリアントモデル定義 |
| 1-2 | product-option-value.ts | `packages/modules/product/src/models/product-option-value.ts` | オプション値モデル |

**主要処理フロー（product-variant.ts）**:
- **5-48行目**: ProductVariantモデル定義
- **7行目**: IDプレフィックス「variant」
- **13-14行目**: allow_backorder、manage_inventoryのデフォルト値
- **29-34行目**: 商品との紐付け（belongsTo）
- **38-41行目**: 画像との多対多リレーション（v2.11.2追加）
- **42-47行目**: オプション値との多対多リレーション
- **49-84行目**: ユニークインデックス定義（SKU、barcode、EAN、UPC）

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

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

**主要処理フロー（route.ts）**:
- **16-50行目**: GET - バリアント一覧取得
- **22-30行目**: inventory_quantityフィールドの特別処理
- **40-42行目**: 在庫数量ラッピング処理
- **52-81行目**: POST - バリアント作成

**主要処理フロー（[variant_id]/route.ts）**:
- **19-35行目**: GET - バリアント詳細取得
- **37-64行目**: POST - バリアント更新
- **66-91行目**: DELETE - バリアント削除

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

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

**主要処理フロー（create-product-variants.ts）**:
- **78-110行目**: 重複在庫アイテムIDバリデーション
- **164-199行目**: 在庫アイテム自動作成マップ構築
- **249-373行目**: createProductVariantsWorkflow定義
- **253-258行目**: 価格データ分離
- **260行目**: バリアント作成ステップ
- **271行目**: 在庫アイテムバリデーション
- **278-282行目**: 在庫アイテム作成
- **315行目**: 価格セット作成
- **339行目**: 価格リンク作成

**主要処理フロー（update-product-variants.ts）**:
- **130-289行目**: updateProductVariantsWorkflow定義
- **155行目**: バリアント更新ステップ
- **179-181行目**: 在庫リンク解除処理
- **245行目**: 価格セット更新

**主要処理フロー（delete-product-variants.ts）**:
- **54-123行目**: deleteProductVariantsWorkflow定義
- **57-68行目**: 在庫情報取得
- **70-72行目**: リモートリンク削除
- **74-96行目**: 削除対象在庫アイテムID決定ロジック
- **102行目**: バリアント削除ステップ

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

```
Admin API Route
    │
    ├─ GET /admin/products/:id/variants
    │      └─ refetchEntities()
    │             └─ Query.graph()
    │                    └─ ProductVariant取得
    │
    ├─ POST /admin/products/:id/variants
    │      └─ createProductVariantsWorkflow.run()
    │             ├─ createProductVariantsStep
    │             │      └─ ProductModuleService.createProductVariants()
    │             ├─ validateInventoryItems
    │             ├─ createInventoryItemsWorkflow.runAsStep()
    │             ├─ createLinksWorkflow.runAsStep()
    │             ├─ createPriceSetsStep
    │             │      └─ PricingModuleService.createPriceSets()
    │             └─ createVariantPricingLinkStep
    │
    ├─ POST /admin/products/:id/variants/:variant_id
    │      └─ updateProductVariantsWorkflow.run()
    │             ├─ updateProductVariantsStep
    │             │      └─ ProductModuleService.updateProductVariants()
    │             ├─ dismissProductVariantsInventoryStep
    │             ├─ getVariantPricingLinkStep
    │             └─ updatePriceSetsStep
    │
    └─ DELETE /admin/products/:id/variants/:variant_id
           └─ deleteProductVariantsWorkflow.run()
                  ├─ useQueryGraphStep (在庫情報取得)
                  ├─ removeRemoteLinkStep
                  ├─ deleteInventoryItemWorkflow.runAsStep()
                  └─ deleteProductVariantsStep
```

### データフロー図

```
[バリアント作成]

Input                     Processing                        Output
──────────────────────────────────────────────────────────────────────
title, sku        ──▶  createProductVariantsStep  ──▶  variant_id
options                        │
prices                         ▼
inventory_items        createPriceSetsStep        ──▶  price_set_id
                               │
                               ▼
                    createVariantPricingLinkStep  ──▶  リンク作成
                               │
                               ▼
                    createLinksWorkflow           ──▶  在庫リンク
                               │
                               ▼
                    emitEventStep                 ──▶  イベント発行
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-variant.ts | `packages/modules/product/src/models/product-variant.ts` | モデル | バリアントエンティティ定義 |
| product-option-value.ts | `packages/modules/product/src/models/product-option-value.ts` | モデル | オプション値エンティティ |
| product-module-service.ts | `packages/modules/product/src/services/product-module-service.ts` | サービス | バリアント操作メソッド |
| route.ts | `packages/medusa/src/api/admin/products/[id]/variants/route.ts` | API | 一覧・作成エンドポイント |
| [variant_id]/route.ts | `packages/medusa/src/api/admin/products/[id]/variants/[variant_id]/route.ts` | API | 詳細・更新・削除 |
| create-product-variants.ts | `packages/core/core-flows/src/product/workflows/create-product-variants.ts` | ワークフロー | 作成処理 |
| update-product-variants.ts | `packages/core/core-flows/src/product/workflows/update-product-variants.ts` | ワークフロー | 更新処理 |
| delete-product-variants.ts | `packages/core/core-flows/src/product/workflows/delete-product-variants.ts` | ワークフロー | 削除処理 |
| helpers.ts | `packages/medusa/src/api/admin/products/helpers.ts` | ヘルパー | レスポンス整形 |
