# 機能設計書 72-キャンペーン閲覧

## 概要

本ドキュメントは、Chatwootにおけるインボックス毎のキャンペーン一覧表示機能の仕様を定義する機能設計書である。

### 本機能の処理概要

特定のインボックスに紐づくキャンペーンの一覧を取得・表示する機能。インボックス詳細画面からキャンペーン一覧へのアクセスを提供し、管理者がインボックス毎のキャンペーン状況を確認できるようにする。

**業務上の目的・背景**：Chatwootでは複数のインボックス（チャネル）を運用することが一般的であり、各インボックスにどのようなキャンペーンが設定されているかを効率的に把握する必要がある。本機能により、インボックス管理画面からワンクリックで関連キャンペーンを確認でき、チャネル別のマーケティング活動を管理しやすくなる。

**機能の利用シーン**：
- インボックスの設定確認時に関連キャンペーンを確認する
- 特定チャネル向けのキャンペーン運用状況を把握する
- インボックス削除前に影響を受けるキャンペーンを確認する

**主要な処理内容**：
1. 指定されたインボックスIDでキャンペーンテーブルを検索
2. 該当インボックスに紐づくすべてのキャンペーンを取得
3. キャンペーン情報をJSON形式でレスポンス

**関連システム・外部連携**：インボックス管理機能と連携

**権限による制御**：インボックスの閲覧権限を持つユーザーが利用可能（Administrator、Agent）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 50 | インボックス設定画面 | 主画面 | インボックスからキャンペーン一覧へのナビゲーション |

## 機能種別

データ参照（READ） / 一覧表示

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | インボックスID | 存在確認、アカウント所属確認 |

### 入力データソース

- URLパス経由（/api/v1/accounts/:account_id/inboxes/:id/campaigns）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | キャンペーンのdisplay_id |
| title | String | キャンペーンタイトル |
| description | String | キャンペーン説明 |
| account_id | Integer | アカウントID |
| inbox | Object | インボックス情報 |
| sender | Object | 送信者情報（設定時） |
| message | String | キャンペーンメッセージ |
| template_params | JSON | テンプレートパラメータ |
| campaign_status | String | ステータス（active/completed） |
| enabled | Boolean | 有効/無効フラグ |
| campaign_type | String | タイプ（ongoing/one_off） |
| scheduled_at | Integer | スケジュール日時（one_offの場合） |
| audience | JSON | 対象者（one_offの場合） |
| trigger_rules | JSON | トリガールール |
| trigger_only_during_business_hours | Boolean | 営業時間中のみ送信 |
| created_at | DateTime | 作成日時 |
| updated_at | DateTime | 更新日時 |

### 出力先

- APIレスポンス（JSON形式）
- フロントエンドでの一覧表示

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ GET /api/v1/accounts/:account_id/inboxes/:id/campaigns
2. インボックス検索
   └─ Current.account.inboxes.find(params[:id])
3. 権限チェック
   └─ authorize @inbox, :show?
4. キャンペーン取得
   └─ @campaigns = @inbox.campaigns
5. レスポンス生成
   └─ JSON形式でキャンペーン一覧を返却
```

### フローチャート

```mermaid
flowchart TD
    A[GET /inboxes/:id/campaigns] --> B[インボックス取得]
    B --> C{インボックス存在?}
    C -->|No| D[404 Not Found]
    C -->|Yes| E[権限チェック]
    E --> F{権限あり?}
    F -->|No| G[403 Forbidden]
    F -->|Yes| H[キャンペーン一覧取得]
    H --> I[JSONレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-72-01 | インボックス所属確認 | 指定インボックスが現在のアカウントに所属していること | 常時 |
| BR-72-02 | 全キャンペーン取得 | active/completedに関わらずすべてのキャンペーンを返却 | キャンペーン一覧取得時 |
| BR-72-03 | 関連データ含む | インボックス情報、送信者情報を含めて返却 | レスポンス生成時 |

### 計算ロジック

特になし（単純な参照のみ）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キャンペーン取得 | campaigns | SELECT | インボックスIDで絞り込み |
| インボックス取得 | inboxes | SELECT | IDでインボックス取得 |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | inbox_id = :inbox_id | インボックスに紐づくキャンペーン |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定インボックスが存在しない | 正しいIDを指定 |
| 403 | Forbidden | インボックス閲覧権限がない | 権限のあるユーザーでアクセス |
| 401 | Unauthorized | 未認証 | ログイン後に再試行 |

### リトライ仕様

読み取り専用APIのため、リトライは不要

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

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

## パフォーマンス要件

- レスポンス時間：200ms以内
- キャンペーン数が多い場合もN+1クエリを避ける設計

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

- 認証済みユーザーのみアクセス可能
- アカウント内のインボックスのみアクセス可能
- Pundit Policyによる権限チェック

## 備考

- インボックスモデルには`has_many :campaigns`のアソシエーションが定義されている
- キャンペーン一覧はフィルタリングやソートなしで全件取得される

---

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

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

### 推奨読解順序

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

インボックスとキャンペーンのアソシエーションを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | inbox.rb | `app/models/inbox.rb` | Inboxモデルの定義、campaignsとのアソシエーション |
| 1-2 | campaign.rb | `app/models/campaign.rb` | Campaignモデルの定義、belongs_to :inbox |

**読解のコツ**:
- Inboxモデルで`has_many :campaigns`を探す
- Campaignモデルの45行目で`belongs_to :inbox`が定義されている

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

コントローラーのcampaignsアクションを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | campaignsメソッドの実装 |

**主要処理フロー**:
1. **21-23行目**: campaignsアクションで`@campaigns = @inbox.campaigns`を実行
2. **92-95行目**: fetch_inboxメソッドでインボックスを取得し権限チェック

#### Step 3: レスポンス生成を理解する

JSONビューテンプレートを確認する。

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

**主要処理フロー**:
- **1-24行目**: display_id, title, description, inbox, sender, message等の属性をJSON化
- **16-19行目**: one_off型の場合はscheduled_atとaudienceを追加

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

```
GET /api/v1/accounts/:account_id/inboxes/:id/campaigns
    │
    ├─ before_action :fetch_inbox
    │      └─ Current.account.inboxes.find(params[:id])
    │      └─ authorize @inbox, :show?
    │
    └─ campaigns アクション
           └─ @inbox.campaigns
                  └─ campaigns.json.jbuilder
                         └─ _campaign.json.jbuilder (partial)
```

### データフロー図

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

URLパラメータ           コントローラー                    JSONレスポンス
(inbox_id) ──────▶ InboxesController#campaigns ──────▶ キャンペーン一覧
                        │
                        ├─ Inbox.find(id)
                        └─ @inbox.campaigns
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | コントローラー | campaignsアクションの定義 |
| inbox.rb | `app/models/inbox.rb` | モデル | Inboxモデル、campaignsアソシエーション |
| campaign.rb | `app/models/campaign.rb` | モデル | Campaignモデル定義 |
| _campaign.json.jbuilder | `app/views/api/v1/models/_campaign.json.jbuilder` | ビュー | キャンペーンJSON出力 |
| campaigns.json.jbuilder | `app/views/api/v1/accounts/inboxes/campaigns.json.jbuilder` | ビュー | 一覧レスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（215行目: get :campaigns, on: :member） |
