# 機能設計書 165-Widgetキャンペーン

## 概要

本ドキュメントは、Chatwoot WebウィジェットにおけるWidgetキャンペーン機能の設計を記載する。この機能はエンドユーザー（訪問者）がWebサイト上のチャットウィジェットでキャンペーンメッセージを受信するためのAPIを提供する。

### 本機能の処理概要

Widgetキャンペーン機能は、Webサイト訪問者に対してプロアクティブにメッセージを表示するためのキャンペーン一覧を取得する機能を提供する。キャンペーンはインボックスに紐づいており、トリガールールに基づいて訪問者に表示される。

**業務上の目的・背景**：Webサイト訪問者に対してプロアクティブなコミュニケーションを実現するため、キャンペーンメッセージの配信機能が必要である。これにより、訪問者の行動（特定ページの閲覧、滞在時間等）に応じた適切なタイミングでサポートを提供したり、プロモーション情報を伝達したりすることができる。

**機能の利用シーン**：
- 訪問者が特定のページに一定時間滞在した際にキャンペーンを表示する場面
- 訪問者がカート画面に到達した際にサポートオファーを表示する場面
- 営業時間内の訪問者にのみ特定のメッセージを表示する場面
- 新規訪問者に対してウェルカムキャンペーンを表示する場面

**主要な処理内容**：
1. インボックスに紐づくキャンペーン一覧の取得
2. 有効なキャンペーンのフィルタリング
3. キャンペーン機能の有効/無効チェック

**関連システム・外部連携**：
- アカウントのfeature_flags（campaigns機能の有効/無効）

**権限による制御**：
- website_tokenによるウィジェット認証が必要
- campaigns機能がアカウントで有効である必要がある

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 114 | ホーム画面 | 主画面 | キャンペーンメッセージ表示 |
| 118 | キャンペーン画面 | 主画面 | キャンペーンメッセージ表示 |

## 機能種別

データ取得（Read Only）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| website_token | String | Yes | ウェブウィジェットの識別トークン | 有効なトークンである必要がある |

### 入力データソース

- Webウィジェット（JavaScript SDK）からのHTTPリクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| (配列) | Array | キャンペーン一覧 |
| [].id | Integer | キャンペーンの表示ID（display_id） |
| [].trigger_rules | Object | トリガールール（URL条件、時間条件等） |
| [].trigger_only_during_business_hours | Boolean | 営業時間中のみトリガーするか |
| [].message | String | キャンペーンメッセージ内容 |
| [].sender | Object | 送信者情報（エージェント情報） |

### 出力先

- JSON形式のHTTPレスポンス

## 処理フロー

### 処理シーケンス

```
1. キャンペーン一覧取得（index）
   └─ campaigns機能の有効チェック
   └─ 有効の場合：インボックスのキャンペーン取得
      └─ enabled=trueのキャンペーンをフィルタ
      └─ senderをinclude
   └─ 無効の場合：空配列を返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{campaigns機能有効?}
    B -->|Yes| C[インボックスのキャンペーン取得]
    C --> D[enabled=trueでフィルタ]
    D --> E[senderをinclude]
    E --> F[レスポンス返却]
    B -->|No| G[空配列を返却]
    G --> F
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-165-01 | 機能フラグチェック | campaigns機能が有効な場合のみキャンペーンを返却 | 全リクエスト |
| BR-165-02 | 有効キャンペーンのみ | enabled=trueのキャンペーンのみを返却 | 機能有効時 |
| BR-165-03 | インボックス限定 | 対象ウィジェットのインボックスに紐づくキャンペーンのみ | 機能有効時 |
| BR-165-04 | アカウント限定 | 同一アカウントのキャンペーンのみ | 機能有効時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| index | campaigns | SELECT | キャンペーン一覧取得 |
| index | users | SELECT | sender情報取得（includes） |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | inbox_id = @web_widget.inbox.id | インボックス条件 |
| SELECT | - | enabled = true | 有効フラグ条件 |
| SELECT | - | account_id = account.id | アカウント条件 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | website_tokenが無効 | 正しいトークンを使用 |

### リトライ仕様

特になし（同期処理のため）

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

読み取り専用のため、トランザクション制御は不要

## パフォーマンス要件

- レスポンス時間: 200ms以内

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

- website_tokenによるウィジェット認証
- 連絡先情報のset_contactをスキップ（skip_before_action）

## 備考

- set_contactがスキップされているため、連絡先が不要なAPIである
- キャンペーンの表示判定（トリガールール評価）はクライアント側で行われる
- trigger_rulesにはURL条件、時間条件などが含まれる
- sender情報にはエージェントの名前、アバター等が含まれる

---

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

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

### 推奨読解順序

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

キャンペーンに関連するデータモデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/campaign.rb` | キャンペーンモデルの構造、trigger_rules、enabled |

**読解のコツ**: trigger_rulesのJSON構造とcampaign_typeの種類を確認すること。

#### Step 2: エントリーポイントを理解する

処理の起点となるコントローラーを確認する。

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

**主要処理フロー**:
1. **2行目**: skip_before_action :set_contact（連絡先設定をスキップ）
2. **4-15行目**: index処理
3. **5行目**: アカウント取得
4. **6-14行目**: campaigns機能の有効チェックと条件分岐
5. **7-11行目**: キャンペーン取得クエリ（enabled, account_id条件、sender include）

#### Step 3: ビュー（レスポンス形式）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.json.jbuilder | `app/views/api/v1/widget/campaigns/index.json.jbuilder` | キャンペーン一覧のレスポンス形式 |

**主要処理フロー**:
- **1-7行目**: 各キャンペーンのJSON構造（display_id, trigger_rules, message, sender）

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

```
Api::V1::Widget::CampaignsController
    │
    ├─ BaseController (認証・共通処理)
    │      └─ set_web_widget (set_contactはスキップ)
    │
    ├─ account.feature_enabled?('campaigns')
    │
    └─ inbox.campaigns.where(...).includes(:sender)
           └─ Campaign モデル
```

### データフロー図

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

website_token ────────▶ BaseController#set_web_widget ────▶ @web_widget
                              │
                        account.feature_enabled? ─────────▶ 判定結果
                              │
                        inbox.campaigns.where ────────────▶ @campaigns
                              │
                        JSON Response ────────────────────▶ クライアント
                              │
                        [trigger_rules評価] ──────────────▶ クライアント側で実行
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaigns_controller.rb | `app/controllers/api/v1/widget/campaigns_controller.rb` | ソース | Widgetキャンペーンのメインコントローラー |
| base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | ソース | Widget APIの基底コントローラー |
| campaign.rb | `app/models/campaign.rb` | ソース | キャンペーンモデル |
| index.json.jbuilder | `app/views/api/v1/widget/campaigns/index.json.jbuilder` | テンプレート | キャンペーン一覧レスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（392行目） |
