# 画面設計書 100-SMSキャンペーン画面

## 概要

本ドキュメントは、Chatwootのキャンペーン機能における「SMSキャンペーン画面」の設計仕様を記述したものである。

### 本画面の処理概要

SMSキャンペーン画面は、SMS/Twilioチャネル向けの一括配信キャンペーンを管理する画面である。特定のラベルが付与された顧客に対して、指定した日時にSMSメッセージを一括送信するキャンペーンを作成・削除できる。

**業務上の目的・背景**：マーケティングやお知らせ配信のために、顧客セグメントに対してSMSメッセージを一括送信する機能が必要である。ラベルベースのオーディエンスセグメンテーションと予約配信機能により、効果的なSMSマーケティングキャンペーンを実施できる。

**画面へのアクセス方法**：サイドバーの「Campaigns」メニューから「SMS」タブをクリック、または直接URL `/app/accounts/:accountId/campaigns/sms` にアクセスする。

**主要な操作・処理内容**：
1. SMSキャンペーン一覧の表示
2. 新規キャンペーンの作成（予約配信）
3. キャンペーンの削除

**画面遷移**：
- 遷移元：サイドナビゲーション、キャンペーントップ
- 遷移先：なし（本画面で操作完結）

**権限による表示制御**：administrator権限を持つユーザーのみアクセス可能。CAMPAIGNSフィーチャーフラグが有効である必要がある。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 161 | キャンペーン管理 | 主機能 | SMSキャンペーンの管理 |

## 画面種別

一覧

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| パス | `/app/accounts/:accountId/campaigns/sms` |
| ルート名 | `campaigns_sms_index` |
| コンポーネント | `SMSCampaignsPage.vue` |

## 入出力項目

### 入力項目（URLパラメータ）

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| accountId | Number | Yes | アカウントID |

### 出力項目

| 項目名 | 型 | 説明 |
|--------|-----|------|
| SMSCampaigns | Array | SMSキャンペーン一覧 |
| isFetchingCampaigns | Boolean | データ取得中フラグ |

## 表示項目

### ヘッダー

| 項目名 | 型 | 説明 |
|--------|-----|------|
| タイトル | Text | "SMS campaigns" |
| 新規作成ボタン | Button | "Create campaign" |

### キャンペーンカード（CampaignCard）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| title | String | キャンペーン名 |
| message | String | キャンペーンメッセージ |
| campaign_status | String | active/completed |
| inbox | Object | 関連インボックス（SMS/Twilio） |
| scheduled_at | DateTime | 配信予定日時 |
| アクションメニュー | Dropdown | Delete |

### キャンペーン作成フォーム（SMSCampaignForm）

| 項目名 | 型 | バリデーション | 説明 |
|--------|-----|--------------|------|
| タイトル | Input | 必須、1文字以上 | キャンペーン名 |
| メッセージ | TextArea | 必須、1文字以上 | 送信メッセージ（文字数カウント付き） |
| インボックス | ComboBox | 必須 | SMS/Twilio Inboxのみ |
| オーディエンス | TagMultiSelectComboBox | 必須 | ラベルベースの顧客セグメント |
| 配信予定日時 | Input(datetime-local) | 必須、現在以降 | UTC変換して保存 |

## イベント仕様

### 1-画面初期表示

**トリガー**: コンポーネントのマウント時

**処理フロー**:
1. CampaignLayoutでキャンペーン取得（親コンポーネントで処理済み）
2. Vuexゲッター `campaigns/getSMSCampaigns` でSMSキャンペーンをフィルタ
3. campaign_type=one_off かつ inbox.channel_type=SMS/Twilio のみ表示
4. CampaignListコンポーネントで一覧表示

### 2-新規キャンペーン作成

**トリガー**: 「Create campaign」ボタンクリック

**処理フロー**:
1. toggleSMSCampaignDialog関数でダイアログを表示
2. SMSCampaignDialogがオーバーレイ表示
3. フォーム入力・バリデーション（Vuelidate）
4. 送信ボタンクリックでhandleSubmit関数を実行
5. addCampaign関数でキャンペーン情報を構築
6. prepareCampaignDetails関数で:
   - scheduled_atをUTC形式に変換
   - audienceをLabel形式のオブジェクト配列に変換
7. `campaigns/create` アクションをディスパッチ
8. POST `/api/v1/accounts/:accountId/campaigns` を呼び出し
9. 成功時：成功メッセージを表示、ダイアログを閉じる
10. 失敗時：エラーメッセージを表示
11. イベントトラッキング（CAMPAIGNS_EVENTS.CREATE_CAMPAIGN、type: ONE_OFF）

### 3-キャンペーン削除

**トリガー**: キャンペーンカードの「Delete」ボタンクリック

**処理フロー**:
1. handleDelete関数を実行
2. 選択キャンペーンをselectedCampaignに設定
3. ConfirmDeleteCampaignDialogを開く
4. 確認後handleDialogConfirm関数を実行
5. deleteCampaign関数を実行
6. `campaigns/delete` アクションをディスパッチ
7. DELETE `/api/v1/accounts/:accountId/campaigns/:id` を呼び出し
8. 成功時：成功メッセージを表示、ダイアログを閉じる
9. 失敗時：エラーメッセージを表示
10. イベントトラッキング（CAMPAIGNS_EVENTS.DELETE_CAMPAIGN）

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| キャンペーン取得 | campaigns | SELECT | account_idに紐づくキャンペーン取得 |
| キャンペーン作成 | campaigns | INSERT | 新規キャンペーン作成 |
| キャンペーン削除 | campaigns | DELETE | キャンペーン削除 |
| キャンペーン削除 | conversations | UPDATE | campaign_idをnullに更新 |

### テーブル別更新項目詳細

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | title | 入力値 | 必須 |
| INSERT | message | 入力値 | 必須 |
| INSERT | inbox_id | 入力値 | SMS/Twilio Inboxのみ |
| INSERT | scheduled_at | 入力値（UTC変換） | 必須 |
| INSERT | audience | [{"id": n, "type": "Label"}, ...] | JSONB |
| INSERT | campaign_type | 'one_off' | SMS固定（自動設定） |
| INSERT | campaign_status | 'active' | デフォルト |
| DELETE | - | id = campaignId | カスケード影響あり |

## メッセージ仕様

| 種別 | メッセージキー | メッセージ内容 | 表示条件 |
|------|--------------|--------------|----------|
| ヘッダータイトル | CAMPAIGN.SMS.HEADER_TITLE | "SMS campaigns" | 常時 |
| 新規作成ボタン | CAMPAIGN.SMS.NEW_CAMPAIGN | "Create campaign" | 常時 |
| 空状態タイトル | CAMPAIGN.SMS.EMPTY_STATE.TITLE | "No SMS campaigns are available" | キャンペーンなし時 |
| 空状態サブタイトル | CAMPAIGN.SMS.EMPTY_STATE.SUBTITLE | "Launch an SMS campaign to reach your customers directly..." | キャンペーンなし時 |
| 作成成功 | CAMPAIGN.SMS.CREATE.FORM.API.SUCCESS_MESSAGE | "SMS campaign created successfully" | 作成成功時 |
| 作成失敗 | CAMPAIGN.SMS.CREATE.FORM.API.ERROR_MESSAGE | "There was an error. Please try again." | 作成失敗時 |
| 削除確認タイトル | CAMPAIGN.CONFIRM_DELETE.TITLE | "Are you sure to delete?" | 削除確認時 |
| 削除確認説明 | CAMPAIGN.CONFIRM_DELETE.DESCRIPTION | "The delete action is permanent and cannot be reversed." | 削除確認時 |
| 削除成功 | CAMPAIGN.CONFIRM_DELETE.API.SUCCESS_MESSAGE | "Campaign deleted successfully" | 削除成功時 |
| 削除失敗 | CAMPAIGN.CONFIRM_DELETE.API.ERROR_MESSAGE | "There was an error. Please try again." | 削除失敗時 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| キャンペーン取得失敗 | 空のリストを表示 |
| キャンペーン作成失敗 | useAlertでエラーメッセージを表示 |
| キャンペーン削除失敗 | useAlertでエラーメッセージを表示 |
| SMS Inboxなし | インボックス選択肢が空になる |
| ラベルなし | オーディエンス選択肢が空になる |
| 権限不足 | ルートガードによりアクセス拒否 |

## 備考

- SMSキャンペーンはcampaign_type=one_off（単発型）として作成される
- インボックスはSMS（Channel::Sms）またはTwilio SMS（Channel::TwilioSms）タイプのみ選択可能
- オーディエンスはラベルベースで顧客をセグメント（複数選択可）
- scheduled_atはローカル時間で入力し、UTC形式に変換して保存
- 配信予定日時は現在時刻以降のみ選択可能
- キャンペーン配信はCampaignTriggerJobで実行される
- 配信完了後、campaign_statusがcompletedに更新される
- SMSキャンペーンは編集機能がない（削除のみ）
- フィーチャーフラグ `CAMPAIGNS` が有効である必要がある

---

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

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

### 推奨読解順序

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

キャンペーンのデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Campaign (Model) | `app/models/campaign.rb` | スキーマ定義、campaign_type enum、audience JSONB |
| 1-2 | campaigns.js (Store) | `app/javascript/dashboard/store/modules/campaigns.js` | Vuexストアの状態、getSMSCampaignsゲッター |

**読解のコツ**: Campaign モデルの campaign_type が one_off で、inbox_type が Twilio SMS/Sms の場合がSMSキャンペーンであることを確認する。

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

画面のエントリーポイントとコンポーネント構成を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SMSCampaignsPage.vue | `app/javascript/dashboard/routes/dashboard/campaigns/pages/SMSCampaignsPage.vue` | 画面コンポーネントのメイン処理 |
| 2-2 | campaigns.routes.js | `app/javascript/dashboard/routes/dashboard/campaigns/campaigns.routes.js` | ルート定義（48-53行目） |

**主要処理フロー**:
1. **20-21行目**: uiFlagsとisFetchingCampaignsを取得
2. **25行目**: getSMSCampaignsゲッターでフィルタリング
3. **31-34行目**: handleDelete関数で削除ダイアログを開く

#### Step 3: 子コンポーネントを理解する

キャンペーンの作成UIを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CampaignList.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/CampaignList.vue` | キャンペーンカードリスト |
| 3-2 | SMSCampaignDialog.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignDialog.vue` | 新規作成ダイアログ |
| 3-3 | SMSCampaignForm.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue` | 作成フォーム |
| 3-4 | ConfirmDeleteCampaignDialog.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/ConfirmDeleteCampaignDialog.vue` | 削除確認ダイアログ |

**主要処理フロー**:
- **SMSCampaignForm.vue 91-100行目**: prepareCampaignDetails関数でキャンペーンオブジェクト構築
- **SMSCampaignForm.vue 82-83行目**: formatToUTCString関数でscheduled_atをUTC変換
- **SMSCampaignForm.vue 34-40行目**: バリデーションルール定義

#### Step 4: Vuex Actionsを理解する

キャンペーン操作のAPI呼び出し処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | campaigns.js (Store) | `app/javascript/dashboard/store/modules/campaigns.js` | get, create, deleteアクション、getSMSCampaignsゲッター |
| 4-2 | campaigns.js (API) | `app/javascript/dashboard/api/campaigns.js` | APIクライアント（ApiClient継承） |

**主要処理フロー**:
- **39-42行目**: getSMSCampaignsゲッター（SMS/Twilioチャネルフィルタ）
- **57-67行目**: getアクション - キャンペーン一覧取得
- **68-78行目**: createアクション - キャンペーン作成
- **91-102行目**: deleteアクション - キャンペーン削除

#### Step 5: バックエンドAPIを理解する

RailsコントローラーでのAPI処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | CRUD操作のエンドポイント |
| 5-2 | campaign.rb (Model) | `app/models/campaign.rb` | ensure_correct_campaign_attributes、execute_campaign |

**主要処理フロー**:
- **Model 57-63行目**: trigger!メソッド - キャンペーン配信実行
- **Model 66-75行目**: execute_campaign - inbox_typeに応じたサービス呼び出し
- **Model 88-98行目**: ensure_correct_campaign_attributes - campaign_type自動設定

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

```
SMSCampaignsPage.vue
    │
    ├─ CampaignLayout.vue (親から提供)
    │      └─ campaigns/get (マウント時)
    │
    ├─ Vuexゲッター
    │      └─ campaigns/getSMSCampaigns
    │             └─ フィルタ: campaign_type=one_off, channel_type=SMS/Twilio
    │
    ├─ @click (Create campaign)
    │      └─ toggleSMSCampaignDialog()
    │             └─ SMSCampaignDialog.vue
    │                    ├─ SMSCampaignForm.vue
    │                    │      ├─ labels/getLabels → audienceList
    │                    │      ├─ inboxes/getSMSInboxes → inboxOptions
    │                    │      └─ handleSubmit()
    │                    │             └─ prepareCampaignDetails()
    │                    │                    ├─ formatToUTCString()
    │                    │                    └─ audience変換
    │                    │
    │                    └─ addCampaign()
    │                           └─ campaigns/create
    │                                  └─ POST /api/v1/.../campaigns
    │
    ├─ CampaignList.vue
    │      └─ CampaignCard[forEach]
    │             └─ @delete → handleDelete()
    │                    └─ ConfirmDeleteCampaignDialog
    │                           └─ campaigns/delete
    │
    └─ SMSCampaignEmptyState.vue (空状態)
```

### データフロー図

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

URLパラメータ ────────▶ CampaignLayout.vue ─────────────▶ campaigns[]
  - accountId              │
                           ▼
                      campaigns/get action
                           │
                           ▼
                      CampaignsAPI.get()
                           │
                           ▼
                      campaigns (state.records)
                           │
                           ▼
                      getSMSCampaigns getter
                           │  (フィルタ: one_off + SMS/Twilio)
                           ▼
                      SMSCampaigns[]
                           │
                           ▼
                      CampaignList.vue
                           │
                           └─▶ CampaignCard[forEach]
                                  │
                                  ├─▶ title, message
                                  ├─▶ campaign_status (Scheduled/Completed)
                                  ├─▶ inbox name
                                  ├─▶ scheduled_at
                                  └─▶ delete button
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SMSCampaignsPage.vue | `app/javascript/dashboard/routes/dashboard/campaigns/pages/SMSCampaignsPage.vue` | ソース | 画面コンポーネント |
| CampaignLayout.vue | `app/javascript/dashboard/components-next/Campaigns/CampaignLayout.vue` | ソース | レイアウト共通 |
| CampaignList.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/CampaignList.vue` | ソース | キャンペーンリスト |
| CampaignCard.vue | `app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue` | ソース | キャンペーンカード |
| SMSCampaignDetails.vue | `app/javascript/dashboard/components-next/Campaigns/CampaignCard/SMSCampaignDetails.vue` | ソース | SMS詳細表示 |
| SMSCampaignDialog.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignDialog.vue` | ソース | 新規作成ダイアログ |
| SMSCampaignForm.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue` | ソース | フォーム |
| ConfirmDeleteCampaignDialog.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/ConfirmDeleteCampaignDialog.vue` | ソース | 削除確認ダイアログ |
| campaigns.routes.js | `app/javascript/dashboard/routes/dashboard/campaigns/campaigns.routes.js` | ソース | ルート定義 |
| campaigns.js | `app/javascript/dashboard/store/modules/campaigns.js` | ソース | Vuexモジュール |
| campaigns.js | `app/javascript/dashboard/api/campaigns.js` | ソース | APIクライアント |
| campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | ソース | コントローラー |
| campaign.rb | `app/models/campaign.rb` | ソース | モデル定義 |
