# 機能設計書 37-プロモーションルール管理

## 概要

本ドキュメントは、Medusaのプロモーションモジュールにおけるプロモーションルール管理機能の設計を記載する。

### 本機能の処理概要

プロモーションルール管理機能は、プロモーションの適用条件を定義するルールの作成・更新・削除を行う機能である。ルールには属性（attribute）、演算子（operator）、値（values）の3要素があり、これらの組み合わせでプロモーションの適用条件を柔軟に設定できる。

**業務上の目的・背景**：プロモーションを特定の条件（顧客グループ、商品カテゴリ、購入金額など）に限定することで、ターゲティング精度の高いキャンペーンを実現する。また、Buy X Get Yプロモーションでは購入条件（buy_rules）と適用対象（target_rules）を分けて設定できる。

**機能の利用シーン**：
1. プロモーションに適用条件ルールを追加する場面
2. 既存ルールの条件を変更する場面
3. 不要なルールを削除する場面
4. Buy X Get Yプロモーションの購入条件・適用対象を設定する場面

**主要な処理内容**：
1. プロモーションルールの一括管理（作成・更新・削除）
2. 基本ルール（rules）の管理
3. 対象ルール（target_rules）の管理
4. 購入ルール（buy_rules）の管理

**関連システム・外部連携**：特になし（プロモーション計算時に内部で評価される）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 59 | プロモーション作成画面 | 補助機能 | 適用条件ルールの設定 |
| 60 | プロモーション詳細画面 | 補助機能 | ルール条件の表示 |
| 63 | ルール編集画面 | 主機能 | プロモーションルールの編集 |

## 機能種別

CRUD操作 / 条件設定

## 入力仕様

### 入力パラメータ

#### ルール作成（共通）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| attribute | string | Yes | ルールの属性（対象フィールド） | 空文字不可 |
| operator | enum | Yes | 比較演算子 | gte, lte, gt, lt, eq, ne, in |
| values | string[] | Yes | 比較する値の配列 | 空配列不可 |
| description | string | No | ルールの説明 | - |

#### ルール更新

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | ルールID | 存在するIDであること |
| attribute | string | No | 更新後の属性 | - |
| operator | enum | No | 更新後の演算子 | gte, lte, gt, lt, eq, ne, in |
| values | string[] | No | 更新後の値の配列 | - |
| description | string | No | 更新後の説明 | - |

#### バッチ操作

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| create | CreatePromotionRuleDTO[] | No | 作成するルール | - |
| update | UpdatePromotionRuleDTO[] | No | 更新するルール | - |
| delete | string[] | No | 削除するルールID | - |

### 入力データソース

- 画面入力：管理画面からの入力
- DBテーブル：promotion_rule, promotion_rule_value

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| promotion_rule | PromotionRuleDTO | ルール情報 |
| promotion_rule.id | string | ルールID（prefix: prorul） |
| promotion_rule.attribute | string | 属性 |
| promotion_rule.operator | enum | 演算子 |
| promotion_rule.description | string | 説明 |
| promotion_rule.values | PromotionRuleValueDTO[] | 値の配列 |
| promotion_rule.values[].id | string | 値ID（prefix: prorulval） |
| promotion_rule.values[].value | string | 値 |

### 出力先

- 画面表示：管理画面のルール編集
- DBテーブル：promotion_rule, promotion_rule_value

## 処理フロー

### 処理シーケンス

```
1. ルール一括管理（バッチ）
   ├─ batchPromotionRulesWorkflowの実行
   ├─ 並列処理
   │   ├─ createPromotionRulesWorkflow（作成）
   │   ├─ updatePromotionRulesWorkflow（更新）
   │   └─ deletePromotionRulesWorkflowStep（削除）
   └─ 結果を統合して返却

2. ルール作成
   ├─ ルールタイプに応じたメソッド選択
   │   ├─ rules → addPromotionRules
   │   ├─ target_rules → addPromotionTargetRules
   │   └─ buy_rules → addPromotionBuyRules
   ├─ バリデーション（validatePromotionRuleAttributes）
   ├─ PromotionRuleレコード作成
   ├─ PromotionRuleValueレコード作成
   └─ 作成したルールを返却

3. ルール更新
   ├─ 既存ルールの存在確認
   ├─ 古いvaluesの削除
   ├─ 新しいvaluesの作成
   ├─ ルール属性の更新
   └─ 更新したルールを返却

4. ルール削除
   ├─ プロモーションとの紐付け確認
   ├─ PromotionRuleの削除（cascadeでvaluesも削除）
   └─ 完了
```

### フローチャート

```mermaid
flowchart TD
    A[開始: ルールバッチ管理] --> B{操作振り分け}
    B --> C[createPromotionRulesWorkflow]
    B --> D[updatePromotionRulesWorkflow]
    B --> E[deletePromotionRulesWorkflowStep]

    C --> C1{rule_type判定}
    C1 -->|RULES| C2[addPromotionRules]
    C1 -->|TARGET_RULES| C3[addPromotionTargetRules]
    C1 -->|BUY_RULES| C4[addPromotionBuyRules]

    C2 --> C5[validatePromotionRuleAttributes]
    C3 --> C5
    C4 --> C5
    C5 --> C6{BUY_RULES + STANDARDチェック}
    C6 -->|エラー| C7[MedusaError]
    C6 -->|OK| C8[createPromotionRulesAndValues_]
    C8 --> F[結果返却]

    D --> D1[updatePromotionRules_]
    D1 --> D2[既存ルール取得]
    D2 --> D3{存在チェック}
    D3 -->|NG| D4[MedusaError]
    D3 -->|OK| D5[values削除・作成]
    D5 --> D6[ルール更新]
    D6 --> F

    E --> E1[removePromotionRules_]
    E1 --> E2[プロモーション取得]
    E2 --> E3[紐付きルールIDフィルタ]
    E3 --> E4[PromotionRule削除]
    E4 --> F

    F --> G[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-37-001 | 演算子制約 | operatorはPromotionRuleOperatorのいずれか（gte, lte, gt, lt, eq, ne, in） | 作成・更新時 |
| BR-37-002 | 属性必須 | attributeは必須項目 | 作成時 |
| BR-37-003 | BUYGETルール制約 | STANDARDタイプのプロモーションにはbuy_rulesを追加できない | buy_rules作成時 |
| BR-37-004 | 値の置換 | 更新時にvaluesが指定された場合、既存の値をすべて削除して新しい値で置き換える | 更新時 |
| BR-37-005 | カスケード削除 | ルール削除時は関連するPromotionRuleValueも自動削除 | 削除時 |

### 計算ロジック

#### ルール評価ロジック（参考：プロモーション計算時に使用）

| 演算子 | 説明 | 評価方法 |
|--------|------|---------|
| eq | 等しい | すべての対象値がルール値のいずれかに含まれる |
| ne | 等しくない | すべての対象値がルール値に含まれない |
| in | 含まれる | いずれかの対象値がルール値に含まれる |
| gt | より大きい | すべての対象値がルール値のいずれかより大きい |
| gte | 以上 | すべての対象値がルール値のいずれか以上 |
| lt | より小さい | すべての対象値がルール値のいずれかより小さい |
| lte | 以下 | すべての対象値がルール値のいずれか以下 |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ルール作成 | promotion_rule | INSERT | ルールレコード作成 |
| ルール作成 | promotion_rule_value | INSERT | ルール値レコード作成 |
| ルール更新 | promotion_rule | UPDATE | ルール属性更新 |
| ルール更新 | promotion_rule_value | DELETE | 既存値削除 |
| ルール更新 | promotion_rule_value | INSERT | 新規値作成 |
| ルール削除 | promotion_rule | DELETE | ルール削除（カスケード） |

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

#### promotion_rule

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prefix: prorul） | - |
| INSERT | attribute | 属性文字列 | インデックス付き |
| INSERT | operator | 演算子enum | インデックス付き |
| INSERT | description | 説明文字列 | nullable |
| UPDATE | attribute | 更新後の属性 | - |
| UPDATE | operator | 更新後の演算子 | - |
| UPDATE | description | 更新後の説明 | - |

#### promotion_rule_value

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prefix: prorulval） | - |
| INSERT | value | 値文字列 | インデックス付き |
| INSERT | promotion_rule_id | 親ルールID | FK |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INVALID_DATA | MedusaError | attribute未指定 | attributeを指定 |
| INVALID_DATA | MedusaError | operator未指定または不正 | 有効な演算子を指定 |
| INVALID_DATA | MedusaError | 存在しないルールIDの更新 | 正しいルールIDを指定 |
| INVALID_DATA | MedusaError | STANDARDプロモーションへのbuy_rules追加 | BUYGETプロモーションを使用 |
| INVALID_DATA | MedusaError | application_methodが未設定 | 先にapplication_methodを設定 |

### リトライ仕様

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

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

- ワークフローによるトランザクション管理
- バッチ操作（create, update, delete）は並列実行
- 各操作は@InjectTransactionManagerで個別トランザクション

## パフォーマンス要件

- バッチ操作：500ms以内
- インデックス：attribute、operator、value、promotion_rule_id + valueに設定

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

- 管理者認証必須（AuthenticatedMedusaRequest）
- ルール条件はプロモーション計算時のみ評価される

## 備考

- ルールタイプは3種類：RULES（基本ルール）、TARGET_RULES（対象ルール）、BUY_RULES（購入ルール）
- BUY_RULESはBUYGET（Buy X Get Y）タイプのプロモーションでのみ使用可能
- TARGET_RULESはtarget_typeがITEMSまたはSHIPPING_METHODSの場合に使用

---

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

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

### 推奨読解順序

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

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

**読解のコツ**:
- **promotion-rule.ts 13行目**: idプレフィックスは`prorul`
- **promotion-rule.ts 15-18行目**: attribute（対象属性）とoperator（演算子）がインデックス付き
- **promotion-rule.ts 19-21行目**: valuesとのhasMany関連（1対多）
- **promotion-rule.ts 22-30行目**: promotions、method_target_rules、method_buy_rulesとのmanyToMany関連
- **promotion-rule.ts 46-48行目**: valuesへのカスケード削除
- **promotion-rule-value.ts 8行目**: idプレフィックスは`prorulval`
- **promotion-rule-value.ts 10-12行目**: promotion_ruleへのbelongsTo関連

#### Step 2: バリデーションロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | promotion-rule.ts | `packages/modules/promotion/src/utils/validations/promotion-rule.ts` | ルールバリデーション |

**主要処理フロー**:
- **19-53行目**: validatePromotionRuleAttributes
  - **25-27行目**: attribute必須チェック
  - **29-47行目**: operator必須・有効値チェック
- **55-105行目**: areRulesValidForContext（プロモーション計算時の評価）
- **111-161行目**: evaluateRuleValueCondition（演算子ごとの評価ロジック）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | route.ts | `packages/medusa/src/api/admin/promotions/[id]/rules/batch/route.ts` | 基本ルールバッチAPI |
| 3-2 | route.ts | `packages/medusa/src/api/admin/promotions/[id]/target-rules/batch/route.ts` | 対象ルールバッチAPI |
| 3-3 | route.ts | `packages/medusa/src/api/admin/promotions/[id]/buy-rules/batch/route.ts` | 購入ルールバッチAPI |

**共通処理フロー**:
- **10-38行目**: POST（バッチ操作）
  - **21-29行目**: batchPromotionRulesWorkflowの実行
  - **24行目**: rule_typeでルールタイプを指定（RULES, TARGET_RULES, BUY_RULES）
  - **31-35行目**: refetchBatchRulesで結果を再取得

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

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

**読解のコツ**: batch-promotion-rules.tsがメインのワークフロー。
- **89-101行目**: create, update, delete用の入力データをtransformで整形
- **103-111行目**: parallelizeで3つのサブワークフローを並列実行
- **113-115行目**: 結果をcreated, updated, deletedにまとめて返却

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | promotion-module.ts | `packages/modules/promotion/src/services/promotion-module.ts` | PromotionModuleService |

**主要メソッド**:
- **1377-1391行目**: updatePromotionRules（ルール更新）
- **1394-1459行目**: updatePromotionRules_（更新の実装）
  - **1415-1425行目**: 存在チェックとエラー処理
  - **1436-1446行目**: valuesの置換処理
  - **1449-1456行目**: 並列で更新・削除・作成
- **1463-1482行目**: addPromotionRules（基本ルール追加）
- **1486-1516行目**: addPromotionTargetRules（対象ルール追加）
- **1520-1552行目**: addPromotionBuyRules（購入ルール追加）
- **1554-1617行目**: createPromotionRulesAndValues_（ルール・値の作成）
  - **1574-1582行目**: BUYGETでないプロモーションへのbuy_rules追加エラー
  - **1584行目**: validatePromotionRuleAttributesの呼び出し
- **1621-1645行目**: removePromotionRules（ルール削除）
- **1649-1708行目**: removePromotionTargetRules, removePromotionBuyRules

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

```
POST /admin/promotions/:id/rules/batch (route.ts)
    │
    └─ batchPromotionRulesWorkflow
           │
           ├─ createPromotionRulesWorkflow (並列)
           │      │
           │      └─ addRulesToPromotionsStep
           │             │
           │             └─ [rule_type分岐]
           │                    ├─ RULES: addPromotionRules()
           │                    ├─ TARGET_RULES: addPromotionTargetRules()
           │                    └─ BUY_RULES: addPromotionBuyRules()
           │                           │
           │                           └─ createPromotionRulesAndValues_()
           │                                  ├─ validatePromotionRuleAttributes()
           │                                  ├─ promotionRuleService_.create()
           │                                  └─ promotionRuleValueService_.create()
           │
           ├─ updatePromotionRulesWorkflow (並列)
           │      │
           │      └─ updatePromotionRulesStep
           │             │
           │             └─ updatePromotionRules()
           │                    │
           │                    └─ updatePromotionRules_()
           │                           ├─ promotionRuleService_.update()
           │                           ├─ promotionRuleValueService_.delete()
           │                           └─ promotionRuleValueService_.create()
           │
           └─ deletePromotionRulesWorkflowStep (並列)
                  │
                  └─ removeRulesFromPromotionsStep
                         │
                         └─ [rule_type分岐]
                                ├─ RULES: removePromotionRules()
                                ├─ TARGET_RULES: removePromotionTargetRules()
                                └─ BUY_RULES: removePromotionBuyRules()
                                       │
                                       └─ promotionRuleService_.delete()
```

### データフロー図

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

create[] ─────────────────▶ createPromotionRulesWorkflow
update[] ─────────────────▶ updatePromotionRulesWorkflow
delete[] ─────────────────▶ deletePromotionRulesWorkflowStep
                                      │
                                      ▼ (並列実行)
                    ┌─────────────────────────────────────┐
                    │ promotion_rule                      │
                    │ (INSERT/UPDATE/DELETE)              │
                    │                                     │
                    │ promotion_rule_value                │
                    │ (INSERT/DELETE)                     │
                    └─────────────────┬───────────────────┘
                                      │
                                      ▼
                                BatchResult
                                {created, updated, deleted}
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| promotion-rule.ts | `packages/modules/promotion/src/models/promotion-rule.ts` | モデル | PromotionRuleエンティティ定義 |
| promotion-rule-value.ts | `packages/modules/promotion/src/models/promotion-rule-value.ts` | モデル | PromotionRuleValueエンティティ定義 |
| promotion-rule.ts | `packages/modules/promotion/src/utils/validations/promotion-rule.ts` | バリデーション | ルールバリデーション・評価 |
| route.ts | `packages/medusa/src/api/admin/promotions/[id]/rules/batch/route.ts` | APIルート | 基本ルールバッチAPI |
| route.ts | `packages/medusa/src/api/admin/promotions/[id]/target-rules/batch/route.ts` | APIルート | 対象ルールバッチAPI |
| route.ts | `packages/medusa/src/api/admin/promotions/[id]/buy-rules/batch/route.ts` | APIルート | 購入ルールバッチAPI |
| batch-promotion-rules.ts | `packages/core/core-flows/src/promotion/workflows/batch-promotion-rules.ts` | ワークフロー | バッチ管理ワークフロー |
| create-promotion-rules.ts | `packages/core/core-flows/src/promotion/workflows/create-promotion-rules.ts` | ワークフロー | 作成ワークフロー |
| update-promotion-rules.ts | `packages/core/core-flows/src/promotion/workflows/update-promotion-rules.ts` | ワークフロー | 更新ワークフロー |
| delete-promotion-rules.ts | `packages/core/core-flows/src/promotion/workflows/delete-promotion-rules.ts` | ワークフロー | 削除ワークフロー |
| promotion-module.ts | `packages/modules/promotion/src/services/promotion-module.ts` | サービス | ルールCRUDメソッド |
| index.ts | `packages/core/utils/src/promotion/index.ts` | ユーティリティ | PromotionRuleOperator enum定義 |
