# 機能設計書 22-テンプレート同期

## 概要

本ドキュメントは、Chatwootにおけるチャネル固有メッセージテンプレートの同期機能について記述する。この機能は、WhatsAppやTwilioなどのメッセージングプラットフォームから承認済みテンプレートを取得し、Chatwoot内で利用可能にする。

### 本機能の処理概要

**業務上の目的・背景**：WhatsAppやTwilioなどのメッセージングプラットフォームでは、事前に承認されたメッセージテンプレートを使用することが必須である。これらのプラットフォームでは、24時間以上経過した会話への送信や、マーケティング・通知目的のメッセージにテンプレートが必要となる。テンプレート同期機能により、外部プラットフォームで作成・承認されたテンプレートをChatwoot内で自動的に取得・更新し、エージェントがシームレスにテンプレートメッセージを送信できるようにする。

**機能の利用シーン**：
- WhatsAppチャネル作成時の初期テンプレート取得
- インボックス設定画面での手動テンプレート同期
- 定期バッチによる自動テンプレート更新（3時間ごと）
- キャンペーン作成時のテンプレート選択

**主要な処理内容**：
1. WhatsApp Cloud APIまたは360Dialog APIからメッセージテンプレートを取得
2. Twilio Content APIからコンテンツテンプレートを取得
3. 取得したテンプレートをJSONB形式でチャネルテーブルに保存
4. テンプレート最終更新日時を記録し、定期同期のスケジューリングに使用

**関連システム・外部連携**：
- WhatsApp Cloud API（Facebook Graph API）
- 360Dialog WABA API
- Twilio Content API
- Sidekiqによる非同期ジョブ実行

**権限による制御**：テンプレート同期はインボックス設定権限を持つユーザー（管理者）のみが手動実行可能。定期同期はシステムジョブとして自動実行される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | インボックス設定画面 | 主画面 | 手動テンプレート同期ボタン |
| 101 | WhatsAppキャンペーン画面 | 参照画面 | テンプレート選択時に同期済みテンプレートを表示 |

## 機能種別

データ連携 / 外部API同期

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| inbox_id | Integer | Yes | 同期対象のインボックスID | 存在チェック |

### 入力データソース

- APIエンドポイント: `POST /api/v1/accounts/:account_id/inboxes/:id/sync_templates`
- 外部API: WhatsApp Cloud API、360Dialog API、Twilio Content API

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| message | String | 同期結果メッセージ |
| message_templates | JSONB | 同期されたテンプレート一覧（WhatsApp） |
| content_templates | JSONB | 同期されたテンプレート一覧（Twilio） |
| message_templates_last_updated | DateTime | 最終同期日時（WhatsApp） |
| content_templates_last_updated | DateTime | 最終同期日時（Twilio） |

### 出力先

- DBテーブル: channel_whatsapp（WhatsApp）、channel_twilio_sms（Twilio）
- APIレスポンス: 同期結果メッセージ

## 処理フロー

### 処理シーケンス

```
1. 同期リクエスト受信
   └─ InboxesController#sync_templates

2. チャネルタイプ判定
   └─ WhatsAppチャネルかTwilioチャネルかを判定

3. [WhatsAppの場合]
   3-1. TemplatesSyncJobをキューに追加
   3-2. provider_serviceを通じてAPIにリクエスト
   3-3. ページネーション対応で全テンプレートを取得
   3-4. channel_whatsapp.message_templatesに保存

4. [Twilioの場合]
   4-1. TemplatesSyncJobをキューに追加
   4-2. Twilio Content APIからテンプレート取得
   4-3. テンプレート情報をフォーマット変換
   4-4. channel_twilio_sms.content_templatesに保存

5. 最終更新日時を記録
   └─ message_templates_last_updated / content_templates_last_updated
```

### フローチャート

```mermaid
flowchart TD
    A[同期リクエスト] --> B{WhatsAppチャネル?}
    B -->|Yes| C[Whatsapp::TemplatesSyncJob]
    B -->|No| D{Twilio WhatsApp?}
    D -->|Yes| E[Twilio::TemplatesSyncJob]
    D -->|No| F[エラー: 非対応チャネル]

    C --> G{プロバイダー判定}
    G -->|whatsapp_cloud| H[WhatsappCloudService]
    G -->|default/360dialog| I[Whatsapp360DialogService]

    H --> J[Graph API呼び出し]
    I --> K[360Dialog API呼び出し]
    E --> L[Twilio Content API呼び出し]

    J --> M[テンプレート保存]
    K --> M
    L --> N[テンプレートフォーマット]
    N --> M

    M --> O[最終更新日時記録]
    O --> P[完了]
    F --> Q[エラーレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-22-01 | 対応チャネル制限 | WhatsAppまたはTwilio WhatsAppチャネルのみ同期可能 | 常時 |
| BR-22-02 | 自動同期スケジュール | 最終更新から3時間以上経過したチャネルを定期同期 | SchedulerJob実行時 |
| BR-22-03 | 同期上限 | 1回のスケジューラ実行で最大`BULK_EXTERNAL_HTTP_CALLS_LIMIT`件 | SchedulerJob実行時 |
| BR-22-04 | チャネル作成時同期 | WhatsAppチャネル作成後に自動でテンプレート同期を実行 | after_create |

### 計算ロジック

**テンプレートカテゴリ判定（Twilio）**:
- friendly_nameに "marketing/promo/offer/sale" を含む場合: "marketing"
- friendly_nameに "auth/otp/verify/code" を含む場合: "authentication"
- その他: "utility"

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| WhatsAppテンプレート同期 | channel_whatsapp | UPDATE | message_templates、message_templates_last_updatedを更新 |
| Twilioテンプレート同期 | channel_twilio_sms | UPDATE | content_templates、content_templates_last_updatedを更新 |

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

#### channel_whatsapp

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | message_templates | 外部APIから取得したテンプレート配列 | JSONB形式 |
| UPDATE | message_templates_last_updated | 現在日時（Time.now.utc） | 同期完了時に更新 |

#### channel_twilio_sms

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | content_templates | フォーマット変換済みテンプレート | JSONB形式 |
| UPDATE | content_templates_last_updated | 現在日時（Time.current） | 同期完了時に更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | unprocessable_entity | WhatsApp以外のチャネルで同期を試行 | エラーメッセージを返却 |
| 500 | internal_server_error | 外部API呼び出し失敗 | エラーメッセージをログ出力、例外メッセージを返却 |
| - | Twilio::REST::TwilioError | Twilio API呼び出し失敗 | エラーログ出力、falseを返却 |

### リトライ仕様

ジョブはSidekiqのデフォルトリトライ機構を使用。外部APIエラー時は自動リトライされる。

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

テンプレート保存はActiveRecordの`update`/`update!`メソッドで実行。トランザクションは暗黙的に適用。

## パフォーマンス要件

- 同期ジョブキュー: `low`（低優先度）
- 定期同期間隔: 3時間
- 1回のスケジューラ実行での処理上限: `Limits::BULK_EXTERNAL_HTTP_CALLS_LIMIT`件
- WhatsApp Cloud APIはページネーション対応で全テンプレートを取得

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

- APIキーはprovider_configにセキュアに保存
- 外部API呼び出し時はHTTPS通信を使用
- APIキーはログに出力しない
- Punditポリシーによる認可チェック

## 備考

- WhatsAppプロバイダーは "default"（360Dialog）と "whatsapp_cloud" の2種類をサポート
- Twilioテンプレートのステータスは現時点では全て "approved" として扱う
- テンプレート同期は非同期ジョブで実行されるため、即座に結果は反映されない

---

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

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

### 推奨読解順序

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

チャネルモデルのテンプレート関連カラムを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | whatsapp.rb | `app/models/channel/whatsapp.rb` | message_templates、message_templates_last_updatedカラム |

**読解のコツ**:
- **1-8行目**: スキーマ定義でJSONB型のmessage_templatesを確認
- **35行目**: `after_create :sync_templates`でチャネル作成時の自動同期
- **57-59行目**: `delegate :sync_templates, to: :provider_service`でプロバイダーサービスへの委譲

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

APIコントローラーのsync_templatesアクションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | sync_templatesアクション |

**主要処理フロー**:
1. **73-80行目**: `sync_templates`アクションでWhatsAppチャネル判定とジョブ起動
2. **204-206行目**: `whatsapp_channel?`でWhatsApp/Twilio WhatsApp判定
3. **208-213行目**: `trigger_template_sync`でプロバイダー別のジョブ起動

#### Step 3: 非同期ジョブを理解する

テンプレート同期ジョブの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | templates_sync_job.rb | `app/jobs/channels/whatsapp/templates_sync_job.rb` | WhatsApp用ジョブ |
| 3-2 | templates_sync_scheduler_job.rb | `app/jobs/channels/whatsapp/templates_sync_scheduler_job.rb` | 定期スケジューラ |
| 3-3 | templates_sync_job.rb | `app/jobs/channels/twilio/templates_sync_job.rb` | Twilio用ジョブ |

**主要処理フロー**:
- **4-6行目** (whatsapp job): `whatsapp_channel.sync_templates`を呼び出し
- **4-11行目** (scheduler): 3時間以上更新のないチャネルを取得し個別ジョブを起動

#### Step 4: プロバイダーサービスを理解する

各プロバイダーのテンプレート取得ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | whatsapp_cloud_service.rb | `app/services/whatsapp/providers/whatsapp_cloud_service.rb` | WhatsApp Cloud API連携 |
| 4-2 | whatsapp_360_dialog_service.rb | `app/services/whatsapp/providers/whatsapp_360_dialog_service.rb` | 360Dialog API連携 |
| 4-3 | template_sync_service.rb | `app/services/twilio/template_sync_service.rb` | Twilio Content API連携 |

**主要処理フロー**:
- **34-39行目** (cloud): `sync_templates`でGraph APIを呼び出しテンプレート取得
- **41-50行目** (cloud): `fetch_whatsapp_templates`でページネーション対応の再帰取得
- **27-31行目** (360dialog): `sync_templates`で360Dialog API呼び出し
- **4-11行目** (twilio): Twilio Content APIからテンプレート取得・フォーマット変換

#### Step 5: テンプレートフォーマット変換を理解する

Twilioテンプレートのフォーマット変換ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | template_sync_service.rb | `app/services/twilio/template_sync_service.rb` | フォーマット変換 |

**主要処理フロー**:
- **28-42行目**: `format_template`でTwilioテンプレートをChatwoot形式に変換
- **59-72行目**: `derive_template_type`でテンプレートタイプを判定
- **90-101行目**: `derive_category`でカテゴリを推測

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

```
InboxesController#sync_templates
    │
    ├─ [WhatsAppチャネル]
    │      │
    │      └─ Channels::Whatsapp::TemplatesSyncJob
    │             │
    │             └─ Channel::Whatsapp#sync_templates
    │                    │
    │                    ├─ [whatsapp_cloud]
    │                    │      └─ WhatsappCloudService#sync_templates
    │                    │             ├─ #fetch_whatsapp_templates (再帰)
    │                    │             └─ channel.update(message_templates: ...)
    │                    │
    │                    └─ [default/360dialog]
    │                           └─ Whatsapp360DialogService#sync_templates
    │                                  └─ channel.update(message_templates: ...)
    │
    └─ [Twilio WhatsApp]
           │
           └─ Channels::Twilio::TemplatesSyncJob
                  │
                  └─ Twilio::TemplateSyncService#call
                         ├─ #fetch_templates_from_twilio
                         ├─ #update_channel_templates
                         └─ #mark_templates_updated
```

### データフロー図

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

API Request ──────▶ InboxesController ────────▶ TemplatesSyncJob
                          │                            │
                          ▼                            ▼
                   チャネルタイプ判定           プロバイダーサービス
                                                       │
                          ┌────────────────────────────┤
                          ▼                            ▼
                   WhatsApp Cloud API          360Dialog API
                   Twilio Content API
                          │                            │
                          └────────────┬───────────────┘
                                       ▼
                               テンプレート配列
                                       │
                                       ▼
                            channel_whatsapp /
                            channel_twilio_sms
                            (message_templates)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| whatsapp.rb | `app/models/channel/whatsapp.rb` | モデル | WhatsAppチャネル、テンプレート保持 |
| inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | コントローラー | sync_templatesエンドポイント |
| templates_sync_job.rb | `app/jobs/channels/whatsapp/templates_sync_job.rb` | ジョブ | WhatsApp同期ジョブ |
| templates_sync_scheduler_job.rb | `app/jobs/channels/whatsapp/templates_sync_scheduler_job.rb` | ジョブ | 定期スケジューラ |
| templates_sync_job.rb | `app/jobs/channels/twilio/templates_sync_job.rb` | ジョブ | Twilio同期ジョブ |
| whatsapp_cloud_service.rb | `app/services/whatsapp/providers/whatsapp_cloud_service.rb` | サービス | WhatsApp Cloud API連携 |
| whatsapp_360_dialog_service.rb | `app/services/whatsapp/providers/whatsapp_360_dialog_service.rb` | サービス | 360Dialog API連携 |
| template_sync_service.rb | `app/services/twilio/template_sync_service.rb` | サービス | Twilio Content API連携 |
| base_service.rb | `app/services/whatsapp/providers/base_service.rb` | サービス | プロバイダー基底クラス |
