# 機能設計書 68-キャンペーン作成・管理

## 概要

本ドキュメントは、Chatwootにおけるキャンペーン機能の作成・管理に関する設計を記述する。ウェブサイトウィジェット、SMS、WhatsAppを通じてプロアクティブなメッセージ配信を行うためのキャンペーン管理機能を提供する。

### 本機能の処理概要

キャンペーン管理機能は、顧客へのプロアクティブなメッセージ配信を管理するための機能を提供する。継続型キャンペーン（Webウィジェット）とワンオフキャンペーン（SMS/WhatsApp）の2種類をサポートする。

**業務上の目的・背景**：カスタマーサポートにおいて、受動的な問い合わせ対応だけでなく、プロアクティブに顧客にアプローチすることで、コンバージョン率向上や顧客満足度向上が期待できる。ウェブサイト訪問者への自動メッセージ表示、特定顧客セグメントへのSMS/WhatsApp配信など、様々なユースケースに対応する。

**機能の利用シーン**：
- ウェブサイト訪問者に特定ページでウェルカムメッセージを表示する場合
- 特定のラベルを持つ顧客にSMSで一斉通知を送信する場合
- WhatsAppテンプレートメッセージを使ったプロモーション配信
- キャンペーンの有効化/無効化の切り替え

**主要な処理内容**：
1. キャンペーンの作成（CRUD）
2. キャンペーンタイプの自動判定（ongoing/one_off）
3. ターゲットオーディエンス（ラベル）の設定
4. トリガールールの設定（URL、ページ滞在時間等）
5. キャンペーンの実行・配信

**関連システム・外部連携**：
- Twilio SMS：SMSキャンペーン送信
- WhatsApp Cloud API：WhatsAppキャンペーン送信
- Liquid テンプレート：メッセージのパーソナライズ

**権限による制御**：Campaign policyによるアクセス制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 99 | ライブチャットキャンペーン画面 | 主画面 | Webウィジェットキャンペーンの管理 |
| 100 | SMSキャンペーン画面 | 主画面 | SMSキャンペーンの管理 |
| 101 | WhatsAppキャンペーン画面 | 主画面 | WhatsAppキャンペーンの管理 |

## 機能種別

CRUD操作（Create/Read/Update/Delete）/ データ連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | String | Yes | キャンペーンタイトル | presence: true |
| message | Text | Yes | 送信メッセージ | presence: true |
| inbox_id | Integer | Yes | 対象インボックスID | presence: true, 対応チャネルのみ |
| description | Text | No | キャンペーン説明 | |
| enabled | Boolean | No | 有効/無効 | default: true |
| sender_id | Integer | No | 送信者（エージェント）ID | アカウントに属するユーザー |
| scheduled_at | DateTime | No | スケジュール日時 | one_off の場合 |
| audience | Array[Object] | No | ターゲットオーディエンス | type, id を含む |
| trigger_rules | Object | No | トリガールール | url, time_on_page 等 |
| trigger_only_during_business_hours | Boolean | No | 営業時間中のみ送信 | default: false |
| template_params | Object | No | WhatsAppテンプレートパラメータ | |

### 入力データソース

- 画面入力：キャンペーン管理画面
- APIパラメータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | キャンペーン表示ID（display_id） |
| title | String | タイトル |
| description | String | 説明 |
| account_id | Integer | アカウントID |
| inbox | Object | インボックス情報 |
| sender | Object | 送信者情報（任意） |
| message | Text | メッセージ内容 |
| template_params | Object | テンプレートパラメータ |
| campaign_status | String | ステータス（active/completed） |
| enabled | Boolean | 有効フラグ |
| campaign_type | String | タイプ（ongoing/one_off） |
| scheduled_at | Integer | スケジュール日時（one_off のみ） |
| audience | Array | オーディエンス（one_off のみ） |
| trigger_rules | Object | トリガールール |
| trigger_only_during_business_hours | Boolean | 営業時間制限フラグ |

### 出力先

- API レスポンス（JSON形式）
- 外部チャネル（SMS/WhatsApp）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ キャンペーンCRUD API

2. バリデーション
   └─ account_id, inbox_id, title, message の必須チェック
   └─ インボックスタイプの検証（Website, Twilio SMS, Sms, Whatsapp のみ）
   └─ sender の所属確認
   └─ inbox の所属確認

3. キャンペーンタイプ自動判定
   └─ SMS/WhatsApp → one_off
   └─ Website → ongoing

4. データ保存
   └─ display_id の自動採番（トリガー）

5. キャンペーン実行（one_off の場合）
   └─ trigger! メソッドで配信サービス呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[キャンペーン作成/更新] --> B[バリデーション]
    B --> C{インボックスタイプ}
    C -->|Website| D[ongoing に設定]
    C -->|SMS/WhatsApp| E[one_off に設定]
    D --> F[scheduled_at = nil]
    E --> G[scheduled_at設定]
    F --> H[キャンペーン保存]
    G --> H
    H --> I{one_off かつ trigger?}
    I -->|Yes| J[配信サービス実行]
    I -->|No| K[完了]
    J --> L{チャネルタイプ}
    L -->|Twilio SMS| M[Twilio::OneoffSmsCampaignService]
    L -->|Sms| N[Sms::OneoffSmsCampaignService]
    L -->|Whatsapp| O[Whatsapp::OneoffCampaignService]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | インボックス制限 | Website, Twilio SMS, Sms, Whatsapp のみ対応 | 作成・更新時 |
| BR-02 | タイプ自動判定 | SMS/WhatsApp → one_off、Website → ongoing | インボックス設定時 |
| BR-03 | スケジュール自動設定 | one_off でスケジュール未設定の場合は現在時刻 | one_off 作成時 |
| BR-04 | URL形式検証 | Websiteの場合、URLはhttp/httpsで始まること | trigger_rules.url 設定時 |
| BR-05 | 完了済み更新禁止 | campaign_status が completed の場合は更新不可 | 更新時 |
| BR-06 | WhatsApp機能フラグ | WhatsAppキャンペーンは feature_enabled?(:whatsapp_campaign) が必要 | WhatsApp実行時 |
| BR-07 | display_id採番 | アカウント毎のシーケンスで自動採番 | 作成時 |

### 計算ロジック

特に計算ロジックはなし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| index | campaigns | SELECT | キャンペーン一覧取得 |
| show | campaigns | SELECT | キャンペーン詳細取得 |
| create | campaigns | INSERT | キャンペーン作成 |
| update | campaigns | UPDATE | キャンペーン更新 |
| destroy | campaigns | DELETE | キャンペーン削除 |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | title | パラメータ値 | 必須 |
| INSERT | message | パラメータ値 | 必須 |
| INSERT | inbox_id | パラメータ値 | 必須 |
| INSERT | account_id | Current.account.id | 自動設定 |
| INSERT | campaign_type | ongoing/one_off | 自動判定 |
| INSERT | campaign_status | active | デフォルト |
| INSERT | display_id | トリガーで自動採番 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 認証エラー | 再認証 |
| 404 | Not Found | キャンペーンが存在しない | 正しいIDを指定 |
| 422 | Unprocessable Entity | バリデーションエラー | 入力内容を修正 |
| 422 | Unprocessable Entity | Unsupported Inbox type | 対応インボックスを使用 |

### リトライ仕様

- SMS/WhatsApp送信エラーはログ出力して次の連絡先に継続

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

- 作成・更新は暗黙的なトランザクション管理

## パフォーマンス要件

- campaign_status, campaign_type, inbox_id, scheduled_at にインデックス
- one_off キャンペーンは対象連絡先数に応じた処理時間

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

- Campaign policy によるアクセス制御
- アカウント単位でのスコープ制限
- 送信者・インボックスの所属確認

## 備考

- display_id はアカウント毎のシーケンス（camp_dpid_seq_{account_id}）で管理
- conversations テーブルとの関連（campaign_id）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/campaign.rb` | キャンペーンモデル定義、バリデーション、タイプ自動判定 |

**読解のコツ**: enum 定義（campaign_type, campaign_status）とバリデーションの関係を理解する。

**主要処理フロー**:
- **32-50行目**: バリデーション定義
  - validate :validate_campaign_inbox - インボックスタイプ制限
  - validate :validate_url - URL形式チェック
  - validate :prevent_completed_campaign_from_update - 完了済み更新禁止
  - validate :sender_must_belong_to_account
  - validate :inbox_must_belong_to_account
- **48行目**: `enum campaign_type: { ongoing: 0, one_off: 1 }`
- **50行目**: `enum campaign_status: { active: 0, completed: 1 }`
- **57-63行目**: `trigger!` - キャンペーン実行メソッド
- **88-98行目**: `ensure_correct_campaign_attributes` - タイプ自動判定ロジック
- **128-130行目**: DBトリガーでdisplay_id自動採番

#### Step 2: コントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | キャンペーンAPI実装 |

**主要処理フロー**:
- **5-7行目**: `index` - 全キャンペーン一覧
- **11-13行目**: `create` - キャンペーン作成（create!使用）
- **15-17行目**: `update` - キャンペーン更新（update!使用）
- **30-33行目**: `campaign_params` - 許可パラメータ

#### Step 3: 配信サービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | oneoff_sms_campaign_service.rb | `app/services/twilio/oneoff_sms_campaign_service.rb` | Twilio SMS配信 |
| 3-2 | oneoff_sms_campaign_service.rb | `app/services/sms/oneoff_sms_campaign_service.rb` | SMS配信 |
| 3-3 | oneoff_campaign_service.rb | `app/services/whatsapp/oneoff_campaign_service.rb` | WhatsApp配信 |

**主要処理フロー（共通パターン）**:
1. キャンペーンタイプと状態の検証
2. campaign.completed! でステータス更新
3. audience からラベルIDを抽出
4. ラベルに紐づく連絡先をループ
5. Liquidテンプレートでメッセージをパーソナライズ
6. チャネル経由でメッセージ送信

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

```
Api::V1::Accounts::CampaignsController
    │
    ├─ create アクション
    │      └─ Current.account.campaigns.create!(campaign_params)
    │             │
    │             └─ Campaign#ensure_correct_campaign_attributes (before_validation)
    │                    └─ campaign_type 自動判定
    │
    └─ Campaign#trigger! （実行時）
           │
           ├─ one_off? チェック
           │
           └─ execute_campaign
                  │
                  ├─ Twilio::OneoffSmsCampaignService.new.perform
                  │      ├─ campaign.completed!
                  │      ├─ audience ラベル抽出
                  │      ├─ contacts.tagged_with(labels) ループ
                  │      ├─ Liquid::CampaignTemplateService でメッセージ生成
                  │      └─ channel.send_message
                  │
                  ├─ Sms::OneoffSmsCampaignService.new.perform
                  │
                  └─ Whatsapp::OneoffCampaignService.new.perform
                         ├─ feature_enabled?(:whatsapp_campaign) チェック
                         ├─ provider = 'whatsapp_cloud' チェック
                         └─ Whatsapp::TemplateProcessorService でテンプレート処理
```

### データフロー図

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

campaign_params ─────▶ バリデーション ───────────────▶ campaigns テーブル
 (POST body)                  │                          (INSERT)
                              │
                    ensure_correct_campaign_attributes
                              │
                    campaign_type 自動判定
                              │
                    (ongoing / one_off)

[one_off 実行]

campaign.trigger! ───▶ execute_campaign ─────────────▶ SMS/WhatsApp送信
                              │
                    audience からラベル取得
                              │
                    contacts.tagged_with(labels)
                              │
                    Liquid テンプレート処理
                              │
                    channel.send_message ────────────▶ 外部API（Twilio/WhatsApp）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaign.rb | `app/models/campaign.rb` | モデル | キャンペーンモデル定義 |
| campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | コントローラー | キャンペーンAPI |
| oneoff_sms_campaign_service.rb | `app/services/twilio/oneoff_sms_campaign_service.rb` | サービス | Twilio SMS配信 |
| oneoff_sms_campaign_service.rb | `app/services/sms/oneoff_sms_campaign_service.rb` | サービス | SMS配信 |
| oneoff_campaign_service.rb | `app/services/whatsapp/oneoff_campaign_service.rb` | サービス | WhatsApp配信 |
| campaign_template_service.rb | `app/services/liquid/campaign_template_service.rb` | サービス | Liquidテンプレート処理 |
| _campaign.json.jbuilder | `app/views/api/v1/models/_campaign.json.jbuilder` | ビュー | キャンペーンJSON出力 |
| campaign_policy.rb | `app/policies/campaign_policy.rb` | ポリシー | キャンペーン権限管理 |
