# 機能設計書 69-キャンペーンスケジューリング

## 概要

本ドキュメントは、Chatwootにおけるキャンペーンのスケジューリング機能の設計を記述する。ワンオフキャンペーン（SMS/WhatsApp）の送信日時を指定し、予約配信を行う機能を提供する。

### 本機能の処理概要

キャンペーンスケジューリング機能は、キャンペーンの送信日時を指定して予約配信を設定する機能を提供する。scheduled_at カラムに送信予定日時を設定し、定期的なジョブで配信を実行する仕組み。

**業務上の目的・背景**：マーケティングキャンペーンにおいて、適切なタイミングでメッセージを配信することは開封率やコンバージョン率に大きく影響する。顧客のタイムゾーンやアクティブな時間帯を考慮したスケジューリングにより、効果的なキャンペーン配信が可能になる。また、キャンペーン準備後に即時配信ではなく、特定の日時（セール開始時刻、イベント前など）に合わせた配信ニーズにも対応する。

**機能の利用シーン**：
- 特定の日時にSMSキャンペーンを配信したい場合
- WhatsAppプロモーションメッセージを特定時刻に一斉送信したい場合
- 事前にキャンペーンを準備し、後日配信したい場合
- タイムゾーンを考慮した時刻指定

**主要な処理内容**：
1. キャンペーン作成時の scheduled_at 設定
2. 未設定の場合は現在時刻を自動設定
3. スケジュールされたキャンペーンのインデックス管理
4. 配信時刻到達時のキャンペーン実行

**関連システム・外部連携**：
- バックグラウンドジョブシステム（Sidekiq）
- SMS/WhatsApp配信サービス

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 99 | ライブチャットキャンペーン画面 | 参照画面 | スケジュール設定UI |
| 100 | SMSキャンペーン画面 | 主画面 | キャンペーン送信時刻の設定 |
| 101 | WhatsAppキャンペーン画面 | 主画面 | キャンペーン送信時刻の設定 |

## 機能種別

CRUD操作（Update）/ スケジューリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| scheduled_at | DateTime | No | 送信予定日時 | ISO8601形式 |

### 入力データソース

- 画面入力：キャンペーン作成・編集画面の日時ピッカー
- APIパラメータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| scheduled_at | Integer | 送信予定日時（Unix timestamp）※one_offのみ |

### 出力先

- API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. キャンペーン作成/更新
   └─ scheduled_at パラメータ受信

2. タイプ判定
   └─ SMS/WhatsApp の場合のみ scheduled_at 有効

3. 自動設定
   └─ scheduled_at が未設定の場合、現在時刻を設定

4. データ保存
   └─ campaigns テーブルに保存

5. スケジュール実行（バックグラウンド）
   └─ 定期ジョブが scheduled_at 到達のキャンペーンを検出
   └─ trigger! メソッドで配信実行
```

### フローチャート

```mermaid
flowchart TD
    A[キャンペーン作成/更新] --> B{インボックスタイプ}
    B -->|Website| C[scheduled_at = nil]
    B -->|SMS/WhatsApp| D{scheduled_at指定あり?}
    D -->|Yes| E[scheduled_atを保存]
    D -->|No| F[scheduled_at = Time.now.utc]
    C --> G[キャンペーン保存]
    E --> G
    F --> G
    G --> H[バックグラウンドジョブ待ち]
    H --> I{scheduled_at到達?}
    I -->|No| H
    I -->|Yes| J[campaign.trigger!]
    J --> K[配信実行]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | one_off専用 | scheduled_at は one_off タイプのみ有効 | SMS/WhatsApp キャンペーン |
| BR-02 | 自動設定 | one_off で scheduled_at 未指定の場合、現在時刻を自動設定 | キャンペーン作成時 |
| BR-03 | ongoing無効 | ongoing タイプの場合、scheduled_at は nil に設定 | Website キャンペーン |
| BR-04 | 完了済み不変 | completed 状態のキャンペーンは scheduled_at 変更不可 | 更新時 |
| BR-05 | インデックス | scheduled_at にインデックスが設定されている | データベース |

### 計算ロジック

```ruby
# ensure_correct_campaign_attributes メソッド内
if ['Twilio SMS', 'Sms', 'Whatsapp'].include?(inbox.inbox_type)
  self.campaign_type = 'one_off'
  self.scheduled_at ||= Time.now.utc
else
  self.campaign_type = 'ongoing'
  self.scheduled_at = nil
end
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成/更新 | campaigns | INSERT/UPDATE | scheduled_at 保存 |
| スケジュール検索 | campaigns | SELECT | scheduled_at でフィルタ |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | scheduled_at | パラメータ値 or Time.now.utc | DateTime型 |
| SELECT | scheduled_at | <= 現在時刻 AND status = active | スケジュール実行対象 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | Unprocessable Entity | 完了済みキャンペーンの更新 | 新規キャンペーンを作成 |

### リトライ仕様

- スケジュールジョブの失敗時はジョブシステムのリトライ機構に依存

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

- キャンペーン保存は暗黙的なトランザクション管理

## パフォーマンス要件

- scheduled_at カラムにインデックス（index_campaigns_on_scheduled_at）
- スケジュール検索時の効率的なクエリ実行

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

- 過去日時の設定は許可（即時実行として扱われる）
- タイムゾーン処理はUTCベース

## 備考

- Webサイトウィジェット（ongoing）キャンペーンは scheduled_at を使用しない
- JSON出力では scheduled_at.to_i で Unix timestamp に変換

---

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

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

### 推奨読解順序

#### Step 1: モデル定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/campaign.rb` | scheduled_at の自動設定ロジック |

**読解のコツ**: ensure_correct_campaign_attributes の before_validation フックが重要。

**主要処理フロー**:
- **12行目（スキーマ）**: `scheduled_at :datetime` - スケジュール日時カラム
- **30行目（インデックス）**: `index_campaigns_on_scheduled_at` - 検索用インデックス
- **54行目**: `before_validation :ensure_correct_campaign_attributes`
- **88-98行目**: `ensure_correct_campaign_attributes`
  - SMS/WhatsApp → one_off に設定、scheduled_at ||= Time.now.utc
  - Website → ongoing に設定、scheduled_at = nil

#### Step 2: JSON出力を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | _campaign.json.jbuilder | `app/views/api/v1/models/_campaign.json.jbuilder` | scheduled_at の出力形式 |

**主要処理フロー**:
- **16-19行目**: one_off の場合のみ scheduled_at を出力
  - `json.scheduled_at resource.scheduled_at.to_i` - Unix timestamp に変換

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | scheduled_at パラメータ許可 |

**主要処理フロー**:
- **31行目**: `campaign_params` で `:scheduled_at` を許可

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

```
CampaignsController#create/update
    │
    ├─ campaign_params[:scheduled_at] 受信
    │
    └─ Campaign#save
           │
           └─ before_validation :ensure_correct_campaign_attributes
                  │
                  ├─ SMS/WhatsApp の場合
                  │      ├─ campaign_type = 'one_off'
                  │      └─ scheduled_at ||= Time.now.utc
                  │
                  └─ Website の場合
                         ├─ campaign_type = 'ongoing'
                         └─ scheduled_at = nil

[バックグラウンドジョブ]

ScheduledCampaignJob（想定）
    │
    └─ Campaign.where(campaign_type: 'one_off')
              .where(campaign_status: 'active')
              .where('scheduled_at <= ?', Time.now)
                  │
                  └─ campaign.trigger!
```

### データフロー図

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

scheduled_at (ISO8601) ─▶ campaign_params ────────────▶ DateTime パース
                                │
                    ensure_correct_campaign_attributes
                                │
                         ┌──────┴──────┐
                         │             │
                    SMS/WhatsApp    Website
                         │             │
                    scheduled_at   scheduled_at
                    ||= Time.now      = nil
                         │             │
                         └──────┬──────┘
                                │
                                ▼
                    campaigns テーブル ────────────▶ scheduled_at 保存
                                │
                                ▼
                    JSON レスポンス
                    (scheduled_at.to_i for one_off)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaign.rb | `app/models/campaign.rb` | モデル | scheduled_at 自動設定ロジック |
| campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | コントローラー | scheduled_at パラメータ受信 |
| _campaign.json.jbuilder | `app/views/api/v1/models/_campaign.json.jbuilder` | ビュー | scheduled_at 出力（Unix timestamp） |
