# 機能設計書 36-プロモーションCRUD

## 概要

本ドキュメントは、Medusaのプロモーションモジュールにおけるプロモーションの作成・取得・更新・削除機能の設計を記載する。

### 本機能の処理概要

プロモーションCRUD機能は、割引・クーポンなどのプロモーションを管理する機能である。プロモーションコード、割引タイプ、適用方法、ルールなどを設定し、カートや注文に対して割引を適用するための基盤を提供する。

**業務上の目的・背景**：ECサイトの売上向上や顧客獲得のため、様々なプロモーション施策が必要である。本機能により、パーセント割引、固定金額割引、Buy X Get Y（まとめ買い割引）など多様なプロモーションを柔軟に作成・管理できる。

**機能の利用シーン**：
1. マーケティング担当者が新しいプロモーションを作成する場面
2. 既存プロモーションの割引率や適用条件を変更する場面
3. 期間終了したプロモーションを削除または無効化する場面
4. プロモーションの一覧を確認・検索する場面

**主要な処理内容**：
1. プロモーションの新規作成（コード、タイプ、適用方法、ルール設定）
2. プロモーションの一覧取得・検索
3. プロモーションの更新（割引値、ステータス、ルール変更）
4. プロモーションの削除

**関連システム・外部連携**：キャンペーンモジュールとの連携（予算管理）

**権限による制御**：管理者（Admin）権限を持つユーザーのみがプロモーションの作成・更新・削除が可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 58 | プロモーション一覧画面 | 主機能 | プロモーション一覧の取得・表示 |
| 59 | プロモーション作成画面 | 主機能 | 新規プロモーションの作成 |
| 60 | プロモーション詳細画面 | 主機能 | プロモーション詳細の表示 |
| 61 | プロモーション編集画面 | 主機能 | プロモーション情報の編集 |
| 70 | プロモーション追加画面 | 参照画面 | キャンペーンへのプロモーション追加 |

## 機能種別

CRUD操作 / ビジネスロジック

## 入力仕様

### 入力パラメータ

#### プロモーション作成

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| code | string | Yes | プロモーションコード | 一意、空文字不可 |
| type | PromotionType | Yes | プロモーションタイプ（standard/buyget） | 有効な値であること |
| is_automatic | boolean | No | 自動適用フラグ（デフォルト: false） | - |
| is_tax_inclusive | boolean | No | 税込みフラグ（デフォルト: false） | - |
| status | PromotionStatus | No | ステータス（デフォルト: draft） | - |
| limit | number | No | 使用回数制限 | null or 正の整数 |
| application_method | object | Yes | 適用方法設定 | 詳細は適用方法仕様参照 |
| rules | array | No | プロモーションルール | - |
| campaign_id | string | No | 紐付けるキャンペーンID | 存在するIDであること |
| campaign | object | No | 新規作成するキャンペーン | campaign_idと同時指定不可 |

#### 適用方法（application_method）

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|-----|------|
| type | ApplicationMethodType | Yes | 割引タイプ（percentage/fixed） |
| target_type | TargetType | Yes | 適用対象（order/items/shipping_methods） |
| allocation | Allocation | No | 配分方法（each/across） |
| value | BigNumber | Yes | 割引値（パーセントまたは金額） |
| currency_code | string | Cond | 通貨コード（fixed時必須） |
| max_quantity | number | No | 最大適用数量 |
| target_rules | array | No | ターゲットルール |
| buy_rules | array | No | Buy条件ルール（buyget時必須） |

### 入力データソース

- 画面入力：管理画面からの入力
- DBテーブル：promotion、application_method、promotion_rule、campaign

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| promotion | PromotionDTO | プロモーション情報 |
| promotion.id | string | プロモーションID |
| promotion.code | string | プロモーションコード |
| promotion.type | PromotionType | プロモーションタイプ |
| promotion.status | PromotionStatus | ステータス |
| promotion.is_automatic | boolean | 自動適用フラグ |
| promotion.application_method | ApplicationMethodDTO | 適用方法 |
| promotion.rules | PromotionRuleDTO[] | ルール一覧 |
| promotion.campaign | CampaignDTO | 紐付けキャンペーン |

### 出力先

- 画面表示：管理画面のプロモーション一覧・詳細
- DBテーブル：promotion、application_method、promotion_rule、promotion_rule_value

## 処理フロー

### 処理シーケンス

```
1. プロモーション作成
   ├─ 入力データの検証
   │   ├─ campaign_idとcampaignの同時指定チェック
   │   ├─ キャンペーンの存在確認（campaign_id指定時）
   │   └─ 通貨コードの整合性チェック（SPEND予算時）
   ├─ Promotionレコードの作成
   ├─ ApplicationMethodレコードの作成
   │   ├─ target_type=ORDERでtarget_rulesは不可
   │   ├─ type=BUYGETでbuy_rules必須チェック
   │   └─ type=BUYGETでtarget_rules必須チェック
   ├─ PromotionRulesの作成
   ├─ Campaignの作成（campaign指定時）
   └─ 結果を返却

2. プロモーション更新
   ├─ 既存プロモーションの取得
   ├─ limitと現在のused値の整合性チェック
   ├─ キャンペーンの存在・通貨コード整合性チェック
   ├─ Promotionレコードの更新
   ├─ ApplicationMethodレコードの更新
   └─ 結果を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: createPromotions] --> B[入力検証]
    B --> C{campaign_id AND campaign?}
    C -->|Yes| D[エラー: 同時指定不可]
    C -->|No| E{campaign_id指定?}
    E -->|Yes| F[キャンペーン存在確認]
    F --> G{存在する?}
    G -->|No| H[エラー: キャンペーン未発見]
    G -->|Yes| I[通貨コード整合性チェック]
    E -->|No| J[Promotionレコード作成]
    I --> J
    J --> K[ApplicationMethod作成]
    K --> L{type=BUYGET?}
    L -->|Yes| M[buy_rules/target_rules必須チェック]
    L -->|No| N[PromotionRules作成]
    M --> N
    N --> O{campaign指定?}
    O -->|Yes| P[Campaign作成]
    O -->|No| Q[結果返却]
    P --> Q
    Q --> R[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-36-001 | コード一意性 | プロモーションコードは一意（deleted_at IS NULLの範囲） | 作成時 |
| BR-36-002 | キャンペーン排他指定 | campaign_idとcampaignは同時指定不可 | 作成時 |
| BR-36-003 | BUYGETルール必須 | type=BUYGETの場合、buy_rulesとtarget_rulesは必須 | 作成時 |
| BR-36-004 | ORDERターゲットルール不可 | target_type=ORDERの場合、target_rulesは指定不可 | 作成時 |
| BR-36-005 | SPEND予算通貨整合性 | キャンペーン予算タイプがSPENDの場合、通貨コードが一致必須 | 作成・更新時 |
| BR-36-006 | limit下限チェック | limitはused以上である必要がある | 更新時 |
| BR-36-007 | 自動適用プロモーション | is_automatic=trueのプロモーションはコード入力なしで自動適用 | 計算時 |

### 計算ロジック

**ステータス判定**:
- DRAFT: 下書き状態、適用不可
- ACTIVE: 有効状態、適用可能
- キャンペーン期間外のACTIVEプロモーションも適用不可（計算時にチェック）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | promotion | INSERT | プロモーションレコード作成 |
| 作成 | application_method | INSERT | 適用方法レコード作成 |
| 作成 | promotion_rule | INSERT | ルールレコード作成 |
| 作成 | promotion_rule_value | INSERT | ルール値レコード作成 |
| 更新 | promotion | UPDATE | プロモーションレコード更新 |
| 更新 | application_method | UPDATE | 適用方法レコード更新 |
| 削除 | promotion | DELETE | プロモーションレコード削除（カスケード削除） |

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

#### promotion

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prefix: promo） | - |
| INSERT | code | プロモーションコード | searchable、一意 |
| INSERT | type | standard/buyget | enum |
| INSERT | status | draft/active | enum、デフォルトdraft |
| INSERT | is_automatic | true/false | デフォルトfalse |
| INSERT | is_tax_inclusive | true/false | デフォルトfalse |
| INSERT | limit | 使用回数上限 | nullable |
| INSERT | used | 使用回数 | デフォルト0 |
| INSERT | campaign_id | キャンペーンID | nullable、外部キー |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INVALID_DATA | MedusaError | campaign_idとcampaignの同時指定 | どちらか一方のみ指定 |
| NOT_FOUND | MedusaError | 存在しないcampaign_id | 正しいIDを指定 |
| INVALID_DATA | MedusaError | 通貨コード不一致 | キャンペーンと同じ通貨を使用 |
| INVALID_DATA | MedusaError | BUYGETでルール未指定 | buy_rulesとtarget_rulesを指定 |
| INVALID_DATA | MedusaError | ORDERでtarget_rules指定 | target_rulesを削除 |
| INVALID_DATA | MedusaError | limit < used | limitをused以上に設定 |

### リトライ仕様

- 通常のCRUD操作のため、特別なリトライ仕様なし

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

- `@InjectTransactionManager`デコレータによりトランザクション管理
- 複数テーブルへの作成・更新は単一トランザクションで実行
- カスケード削除によりapplication_methodも自動削除

## パフォーマンス要件

- 一覧取得：500ms以内（ページネーション使用）
- 作成・更新：1秒以内
- インデックス：code、is_automatic、status、type

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

- 管理者認証必須（AuthenticatedMedusaRequest）
- プロモーションコードの推測困難性を推奨（ランダム文字列等）
- 自動適用プロモーション（is_automatic=true）の悪用防止

## 備考

- プロモーションの論理削除はdeleted_atカラムで管理
- 同じコードの再利用は削除後のみ可能（deleted_at IS NULLの一意制約）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | promotion.ts | `packages/modules/promotion/src/models/promotion.ts` | Promotionエンティティの構造 |
| 1-2 | application-method.ts | `packages/modules/promotion/src/models/application-method.ts` | ApplicationMethodエンティティ |
| 1-3 | promotion-rule.ts | `packages/modules/promotion/src/models/promotion-rule.ts` | PromotionRuleエンティティ |

**読解のコツ**:
- **promotion.ts 8行目**: idプレフィックスは`promo`
- **promotion.ts 20行目**: typeはenum（PromotionType）
- **promotion.ts 30行目**: application_methodはhasOne関連
- **promotion.ts 35行目**: rulesはmanyToMany関連

#### Step 2: APIルートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/admin/promotions/route.ts` | 一覧取得・作成エンドポイント |

**主要処理フロー**:
- **13-36行目**: GET（一覧取得）
- **38-60行目**: POST（作成）
  - **46-47行目**: createPromotionsWorkflowの実行

#### Step 3: モジュールサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | promotion-module.ts | `packages/modules/promotion/src/services/promotion-module.ts` | createPromotions, updatePromotionsメソッド |

**主要処理フロー**:
- **931-976行目**: createPromotions（公開インターフェース）
- **978-1219行目**: createPromotions_（内部処理）
  - **1038-1043行目**: campaign_idとcampaignの排他チェック
  - **1054-1058行目**: キャンペーン存在チェック
  - **1116-1125行目**: target_type=ORDERでのtarget_rulesチェック
  - **1127-1132行目**: BUYGETでのbuy_rules必須チェック
  - **1134-1142行目**: BUYGETでのtarget_rules必須チェック
- **1221-1262行目**: updatePromotions（公開インターフェース）
- **1264-1372行目**: updatePromotions_（内部処理）
  - **1302-1309行目**: limit < usedのチェック

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

```
POST /admin/promotions (route.ts)
    │
    └─ createPromotionsWorkflow
           │
           └─ PromotionModuleService.createPromotions()
                  │
                  ├─ createPromotions_()
                  │      │
                  │      ├─ 入力検証
                  │      ├─ promotionService_.create()
                  │      ├─ applicationMethodService_.create()
                  │      ├─ createPromotionRulesAndValues_()
                  │      └─ createCampaigns() (campaign指定時)
                  │
                  └─ listPromotions() (結果取得)
```

### データフロー図

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

code ──────────────────▶ createPromotionsWorkflow
type                            │
application_method              │
rules                           │
campaign_id/campaign            │
                                ▼
                    ┌───────────────────────────┐
                    │ promotion                 │
                    │ (INSERT)                  │
                    └──────────┬────────────────┘
                               │
        ┌──────────────────────┼──────────────────────┐
        │                      │                      │
        ▼                      ▼                      ▼
  ┌─────────────┐    ┌─────────────────┐    ┌───────────────┐
  │ application │    │ promotion_rule  │    │ campaign      │
  │ _method     │    │ (INSERT)        │    │ (INSERT)      │
  │ (INSERT)    │    │                 │    │ (条件付き)     │
  └─────────────┘    └─────────────────┘    └───────────────┘
                               │
                               ▼
                          PromotionDTO
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| promotion.ts | `packages/modules/promotion/src/models/promotion.ts` | モデル | Promotionエンティティ定義 |
| application-method.ts | `packages/modules/promotion/src/models/application-method.ts` | モデル | ApplicationMethodエンティティ定義 |
| promotion-rule.ts | `packages/modules/promotion/src/models/promotion-rule.ts` | モデル | PromotionRuleエンティティ定義 |
| route.ts | `packages/medusa/src/api/admin/promotions/route.ts` | APIルート | 一覧取得・作成エンドポイント |
| promotion-module.ts | `packages/modules/promotion/src/services/promotion-module.ts` | サービス | プロモーションCRUDロジック |
| validators.ts | `packages/medusa/src/api/admin/promotions/validators.ts` | バリデータ | 入力値検証 |
