# 機能設計書 39-キャンペーン予算管理

## 概要

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

### 本機能の処理概要

キャンペーン予算管理機能は、キャンペーンに設定される予算の作成・更新・使用量追跡を行う機能である。予算タイプ（SPEND：金額ベース、USAGE：使用回数ベース、USE_BY_ATTRIBUTE：属性別使用回数）に応じた予算消費の管理と上限チェックを行う。

**業務上の目的・背景**：プロモーションの過剰な利用を防ぎ、マーケティング予算を適切に管理するため。キャンペーンごとに予算上限を設定し、超過を防止することで予算管理の精度を向上させる。

**機能の利用シーン**：
1. キャンペーン作成時に予算を設定する場面
2. 予算の上限を更新する場面
3. プロモーション使用時に予算消費を記録する場面
4. 予算超過をチェックしてエラーを返す場面
5. 顧客属性ごとの利用制限を設定する場面

**主要な処理内容**：
1. キャンペーン予算の作成（キャンペーン作成時）
2. 予算上限の更新（type/currency_codeは不変）
3. 予算使用量の加算（registerUsage）
4. 予算使用量の減算（revertUsage）
5. 属性別使用量の追跡（USE_BY_ATTRIBUTE）

**関連システム・外部連携**：特になし

**権限による制御**：予算の作成・更新は管理者権限。使用量の加算・減算はシステム内部で自動実行。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 65 | キャンペーン作成画面 | 補助機能 | 予算の設定 |
| 69 | 予算編集画面 | 主機能 | キャンペーン予算の編集 |

## 機能種別

設定管理 / 使用量追跡

## 入力仕様

### 入力パラメータ

#### 予算作成（キャンペーン作成時）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| type | enum | Yes | 予算タイプ | spend, usage, use_by_attribute, spend_by_attribute |
| limit | bigNumber | No | 予算上限 | nullable |
| currency_code | string | 条件付き必須 | 通貨コード | typeがspendの場合必須 |
| attribute | string | 条件付き必須 | 追跡属性 | typeがuse_by_attributeの場合必要（例：customer_id） |

#### 予算更新

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | 予算ID | 存在するIDであること |
| limit | bigNumber | No | 更新後の上限 | - |

**注意**: type、currency_codeは更新不可（immutable）

#### 使用量登録（内部API）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| computedActions | UsageComputedActions[] | Yes | 使用量計算結果 | - |
| registrationContext | object | Yes | 登録コンテキスト | 属性値を含む |

### 入力データソース

- 画面入力：管理画面からの予算設定
- システム入力：プロモーション適用時の自動計算
- DBテーブル：promotion_campaign_budget, promotion_campaign_budget_usage

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| campaign_budget | CampaignBudgetDTO | 予算情報 |
| campaign_budget.id | string | 予算ID（prefix: probudg） |
| campaign_budget.type | enum | 予算タイプ |
| campaign_budget.currency_code | string | 通貨コード（SPENDの場合） |
| campaign_budget.limit | bigNumber | 予算上限 |
| campaign_budget.used | bigNumber | 使用済み額/回数 |
| campaign_budget.attribute | string | 追跡属性（USE_BY_ATTRIBUTE） |
| campaign_budget.usages | CampaignBudgetUsageDTO[] | 属性別使用量 |

### 出力先

- 画面表示：管理画面の予算情報
- DBテーブル：promotion_campaign_budget, promotion_campaign_budget_usage

## 処理フロー

### 処理シーケンス

```
1. 予算作成（キャンペーン作成時）
   ├─ validateCampaignBudgetDataでバリデーション
   │   ├─ type必須チェック
   │   └─ SPEND時のcurrency_code必須チェック
   └─ CampaignBudgetレコード作成

2. 予算使用量加算（registerUsage）
   ├─ 予算タイプ判定
   │   ├─ SPEND: 金額を加算
   │   │   └─ limit超過チェック
   │   ├─ USAGE: 回数を1加算
   │   │   └─ limit超過チェック
   │   └─ USE_BY_ATTRIBUTE: 属性別回数を1加算
   │       ├─ CampaignBudgetUsageを取得/作成
   │       └─ 属性別limit超過チェック
   └─ CampaignBudget.usedを更新

3. 予算使用量減算（revertUsage）
   ├─ 予算タイプ判定
   │   ├─ SPEND: 金額を減算
   │   ├─ USAGE: 回数を1減算
   │   └─ USE_BY_ATTRIBUTE: 属性別回数を1減算
   │       ├─ CampaignBudgetUsageを取得
   │       └─ used <= 1ならレコード削除、そうでなければ減算
   └─ CampaignBudget.usedを更新
```

### フローチャート

```mermaid
flowchart TD
    A[開始: 予算使用量登録] --> B{予算タイプ}

    B -->|SPEND| C[金額加算]
    C --> C1{limit超過?}
    C1 -->|Yes| C2[MedusaError: budget exceeded]
    C1 -->|No| C3[used更新]

    B -->|USAGE| D[回数加算（+1）]
    D --> D1{limit超過?}
    D1 -->|Yes| D2[MedusaError: budget exceeded]
    D1 -->|No| D3[used更新]

    B -->|USE_BY_ATTRIBUTE| E[属性別処理]
    E --> E1{属性値あり?}
    E1 -->|No| E5[スキップ]
    E1 -->|Yes| E2[registerCampaignBudgetUsageByAttribute_]
    E2 --> E3{既存レコード?}
    E3 -->|No| E4[CampaignBudgetUsage作成]
    E3 -->|Yes| E6{属性別limit超過?}
    E6 -->|Yes| E7[MedusaError: budget exceeded]
    E6 -->|No| E8[属性別used更新]
    E4 --> E9[グローバルused更新]
    E8 --> E9

    C3 --> F[campaignBudgetService_.update]
    D3 --> F
    E9 --> F
    F --> G[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-39-001 | 予算タイプ必須 | typeは必須項目 | 作成時 |
| BR-39-002 | SPEND通貨必須 | typeがSPENDの場合、currency_codeは必須 | 作成時 |
| BR-39-003 | タイプ不変 | 作成後のtypeは変更不可 | 更新時 |
| BR-39-004 | 通貨コード不変 | 作成後のcurrency_codeは変更不可 | 更新時 |
| BR-39-005 | 上限超過エラー | used > limitの場合はエラー | 使用量登録時 |
| BR-39-006 | 使用量非負数 | revert時にusedが0未満にならないよう調整 | 使用量減算時 |
| BR-39-007 | 属性別使用量削除 | USE_BY_ATTRIBUTEで属性別used <= 1の場合、revert時にレコード削除 | 使用量減算時 |
| BR-39-008 | カスケード削除 | 予算削除時はusagesも自動削除 | 予算削除時 |

### 計算ロジック

#### SPEND（金額ベース）
```
新使用額 = 現在の使用額 + プロモーション適用金額
if (limit && 新使用額 > limit) → エラー
```

#### USAGE（回数ベース）
```
新使用回数 = 現在の使用回数 + 1（プロモーションコードごとに1回のみ加算）
if (limit && 新使用回数 > limit) → エラー
```

#### USE_BY_ATTRIBUTE（属性別回数）
```
属性別使用回数 = 属性値ごとの使用回数 + 1
if (limit && 属性別使用回数 > limit) → エラー
グローバル使用回数も同時に更新
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | promotion_campaign_budget | INSERT | 予算作成 |
| 更新 | promotion_campaign_budget | UPDATE | 上限更新 |
| 使用量加算 | promotion_campaign_budget | UPDATE | used更新 |
| 属性別追跡 | promotion_campaign_budget_usage | INSERT/UPDATE | 属性別使用量 |
| 使用量減算 | promotion_campaign_budget | UPDATE | used減算 |
| 属性別削除 | promotion_campaign_budget_usage | DELETE | used <= 1時 |

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

#### promotion_campaign_budget

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prefix: probudg） | - |
| INSERT | type | 予算タイプenum | インデックス付き |
| INSERT | currency_code | 通貨コード | nullable |
| INSERT | limit | 上限値 | bigNumber、nullable |
| INSERT | used | 0（初期値） | bigNumber |
| INSERT | attribute | 追跡属性 | nullable |
| UPDATE | limit | 更新後の上限 | - |
| UPDATE | used | 更新後の使用量 | - |

#### promotion_campaign_budget_usage

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（prefix: probudgus） | - |
| INSERT | attribute_value | 属性値（例：cus_123） | - |
| INSERT | used | 1（初期値） | bigNumber |
| INSERT | budget_id | 親予算ID | FK |
| UPDATE | used | used + 1 または used - 1 | - |
| DELETE | - | used <= 1のレコード | revert時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INVALID_DATA | MedusaError | type未指定 | typeを指定 |
| INVALID_DATA | MedusaError | SPEND予算でcurrency_code未指定 | currency_codeを指定 |
| INVALID_DATA | MedusaError | type/currency_codeを変更しようとした | 新しい予算を作成 |
| NOT_ALLOWED | MedusaError | 予算上限超過 | 予算を増額または別キャンペーンを使用 |

### リトライ仕様

- 使用量登録・減算は同一トランザクション内で実行
- 上限超過エラー時は即座に失敗（リトライなし）

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

- 予算作成：キャンペーン作成と同一トランザクション
- 使用量登録/減算：@InjectTransactionManagerで保護
- 複数プロモーション適用時は一括更新

## パフォーマンス要件

- 使用量更新：50ms以内
- インデックス：type、(attribute_value + budget_id)の一意制約

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

- 使用量の直接更新は内部APIのみ許可
- 予算上限の変更は管理者権限が必要
- 属性値（customer_idなど）の扱いに注意

## 備考

- 予算タイプは4種類：SPEND（金額）、USAGE（回数）、USE_BY_ATTRIBUTE（属性別回数）、SPEND_BY_ATTRIBUTE（属性別金額）
- USE_BY_ATTRIBUTEは顧客ごとの利用制限（例：1顧客あたり3回まで）に使用
- グローバルのused値は属性別予算でも更新されるが、主に統計用途
- 予算がnull（未設定）の場合は無制限

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign-budget.ts | `packages/modules/promotion/src/models/campaign-budget.ts` | CampaignBudgetエンティティの構造 |
| 1-2 | campaign-budget-usage.ts | `packages/modules/promotion/src/models/campaign-budget-usage.ts` | 属性別使用量の構造 |
| 1-3 | index.ts | `packages/core/utils/src/promotion/index.ts` | CampaignBudgetType enum定義 |

**読解のコツ**:
- **campaign-budget.ts 9行目**: idプレフィックスは`probudg`
- **campaign-budget.ts 10-12行目**: typeはCampaignBudgetType enum、インデックス付き
- **campaign-budget.ts 13行目**: currency_codeはnullable（SPENDの場合のみ使用）
- **campaign-budget.ts 14行目**: limitはbigNumber、nullable
- **campaign-budget.ts 15行目**: usedはbigNumber、デフォルト0
- **campaign-budget.ts 23行目**: attributeは追跡属性（v2.11.0以降）
- **campaign-budget.ts 29-31行目**: usagesとのhasMany関連（属性別使用量）
- **campaign-budget.ts 34-36行目**: usagesへのカスケード削除
- **campaign-budget-usage.ts 14行目**: idプレフィックスは`probudgus`
- **campaign-budget-usage.ts 15行目**: attribute_valueは属性の値（例：cus_123）
- **campaign-budget-usage.ts 22-28行目**: (attribute_value + budget_id)の一意制約
- **index.ts 39-44行目**: CampaignBudgetType: SPEND, USAGE, USE_BY_ATTRIBUTE, SPEND_BY_ATTRIBUTE

#### Step 2: 予算バリデーションを理解する

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

**主要処理**:
- **1800-1820行目**: validateCampaignBudgetData
  - **1804-1809行目**: type必須チェック
  - **1811-1819行目**: SPEND時のcurrency_code必須チェック

#### Step 3: 使用量登録を理解する

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

**主要処理フロー**:
- **355-387行目**: SPEND予算の処理
  - **361行目**: typeチェック（CampaignBudgetType.SPEND）
  - **369-372行目**: 金額加算
  - **374-382行目**: limit超過チェックとエラー
- **389-415行目**: USAGE予算の処理
  - **389行目**: typeチェック（CampaignBudgetType.USAGE）
  - **397行目**: 回数+1
  - **399-407行目**: limit超過チェックとエラー
  - **414行目**: プロモーションコードごとに1回のみカウント
- **417-447行目**: USE_BY_ATTRIBUTE予算の処理
  - **417行目**: typeチェック
  - **425-426行目**: attribute取得と値チェック
  - **432-436行目**: registerCampaignBudgetUsageByAttribute_呼び出し
  - **438-444行目**: グローバルused更新
- **457-474行目**: 一括更新

#### Step 4: 属性別使用量管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | promotion-module.ts | `packages/modules/promotion/src/services/promotion-module.ts` | 属性別使用量メソッド |

**主要処理**:
- **208-246行目**: registerCampaignBudgetUsageByAttribute_（属性別使用量登録）
  - **208-217行目**: 既存レコード検索
  - **218-227行目**: 新規レコード作成（存在しない場合）
  - **228-245行目**: 既存レコード更新（limit超過チェック含む）
- **257-285行目**: revertCampaignBudgetUsageByAttribute_（属性別使用量減算）
  - **271-276行目**: used <= 1ならレコード削除
  - **277-282行目**: そうでなければused - 1

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

```
registerUsage() (promotion-module.ts)
    │
    ├─ [SPEND予算]
    │      │
    │      └─ MathBN.add(used, amount)
    │             │
    │             └─ limit超過チェック → MedusaError
    │
    ├─ [USAGE予算]
    │      │
    │      └─ MathBN.add(used, 1)
    │             │
    │             └─ limit超過チェック → MedusaError
    │
    └─ [USE_BY_ATTRIBUTE予算]
           │
           └─ registerCampaignBudgetUsageByAttribute_()
                  │
                  ├─ campaignBudgetUsageService_.list()
                  ├─ campaignBudgetUsageService_.create() (新規)
                  └─ campaignBudgetUsageService_.update() (既存)
                         │
                         └─ limit超過チェック → MedusaError
    │
    └─ campaignBudgetService_.update() (一括)
```

### データフロー図

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

computedActions ──────────▶ registerUsage()
(code, amount)                    │
                                  ▼ (タイプ別分岐)
                    ┌─────────────┴─────────────┐
                    │                           │
               [SPEND]                     [USAGE]
                    │                           │
                    ▼                           ▼
              金額加算                      回数+1
                    │                           │
                    ▼                           ▼
              limit超過チェック            limit超過チェック
                    │                           │
                    └───────────┬───────────────┘
                                │
                    ┌───────────▼───────────────┐
                    │ promotion_campaign_budget │
                    │ (UPDATE used)             │
                    └───────────────────────────┘

[USE_BY_ATTRIBUTE]
        │
        ▼
属性値取得（例：customer_id）
        │
        ▼
┌───────────────────────────────────┐
│ promotion_campaign_budget_usage   │
│ (INSERT or UPDATE)                │
└───────────────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaign-budget.ts | `packages/modules/promotion/src/models/campaign-budget.ts` | モデル | CampaignBudgetエンティティ定義 |
| campaign-budget-usage.ts | `packages/modules/promotion/src/models/campaign-budget-usage.ts` | モデル | 属性別使用量エンティティ定義 |
| index.ts | `packages/core/utils/src/promotion/index.ts` | ユーティリティ | CampaignBudgetType enum定義 |
| promotion-module.ts | `packages/modules/promotion/src/services/promotion-module.ts` | サービス | 予算管理・使用量追跡メソッド |
| campaign.ts | `packages/modules/promotion/src/models/campaign.ts` | モデル | Campaignとの関連定義 |
