# 機能設計書 67-通話機能

## 概要

本ドキュメントは、Chatwoot Enterprise版における連絡先への音声通話発信機能の設計を記述する。エージェントがダッシュボードから直接顧客に電話をかけることができる機能を提供する。

### 本機能の処理概要

通話機能は、Enterprise版限定で提供されるアウトバウンド音声通話機能である。Voice チャネル（Twilioベース）を使用して、連絡先に対して直接電話を発信し、会話として記録する。

**業務上の目的・背景**：カスタマーサポートにおいて、テキストベースのやり取りだけでなく、音声通話によるリアルタイムコミュニケーションが必要な場面は多い。複雑な問題の解決、緊急対応、シニア顧客への対応など、電話での対応が求められるケースに対応する。Chatwootのダッシュボードから直接発信することで、コンテキストを維持したまま通話に移行できる。

**機能の利用シーン**：
- 連絡先詳細画面から電話を発信したい場合
- チャットでは解決できない複雑な問題を電話で対応する場合
- 緊急の連絡が必要な場合
- 通話履歴を会話として記録・管理したい場合

**主要な処理内容**：
1. 連絡先の電話番号の検証
2. Voice インボックスの選択と権限確認
3. アウトバウンド通話の発信
4. 会話の作成と通話情報の記録
5. 通話メッセージの作成

**関連システム・外部連携**：
- Twilio Voice API：音声通話の発信・管理
- Voice チャネル：Channel::Voice
- Conference機能：通話のカンファレンス管理

**権限による制御**：連絡先とVoiceインボックスへのアクセス権限を持つエージェントのみが通話発信可能。Enterprise版限定。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | 連絡先編集画面 | 主画面 | 通話発信ボタン |

## 機能種別

データ連携 / 外部API連携 / Enterprise機能

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 対象連絡先ID | URLパラメータ、存在確認 |
| inbox_id | Integer | Yes | Voice インボックスID | リクエストボディ、権限確認 |

### 入力データソース

- URLパラメータ：連絡先ID
- リクエストボディ：inbox_id

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| conversation_id | Integer | 作成された会話のdisplay_id |
| inbox_id | Integer | 使用されたインボックスID |
| call_sid | String | Twilio通話SID |
| conference_sid | String | Twilioカンファレンス SID |

### 出力先

- API レスポンス（JSON形式）
- Twilio（外部通話発信）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POST /api/v1/accounts/:account_id/contacts/:id/call

2. 認証・認可チェック
   └─ 連絡先へのshow権限確認
   └─ Voice インボックスへのshow権限確認
   └─ Enterprise版チェック

3. 通話発信準備
   └─ 連絡先の電話番号確認
   └─ エージェントの存在確認
   └─ ContactInbox の作成または取得

4. 会話作成
   └─ Conversation を open ステータスで作成

5. 通話発信
   └─ Voice チャネル経由で Twilio API 呼び出し
   └─ call_sid を取得

6. 会話情報更新
   └─ 通話方向、ステータス、conference_sid を設定
   └─ identifier に call_sid を設定

7. 通話メッセージ作成
   └─ Voice::CallMessageBuilder で通話メッセージを作成

8. レスポンス生成
```

### フローチャート

```mermaid
flowchart TD
    A[通話発信リクエスト] --> B{Enterprise版?}
    B -->|No| C[機能利用不可]
    B -->|Yes| D{連絡先権限OK?}
    D -->|No| E[401 Unauthorized]
    D -->|Yes| F{インボックス権限OK?}
    F -->|No| E
    F -->|Yes| G{電話番号あり?}
    G -->|No| H[ArgumentError]
    G -->|Yes| I[ContactInbox作成/取得]
    I --> J[Conversation作成]
    J --> K[Twilio API呼び出し]
    K --> L[call_sid取得]
    L --> M[Conversation更新]
    M --> N[CallMessage作成]
    N --> O[JSONレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | Enterprise限定 | Enterprise版でのみ利用可能（ルート定義にif条件） | 全アクション |
| BR-02 | 電話番号必須 | 連絡先に電話番号が設定されていること | 通話発信時 |
| BR-03 | エージェント必須 | 通話発信者（エージェント）が必須 | 通話発信時 |
| BR-04 | Voice インボックス | channel_type が 'Channel::Voice' のインボックスのみ使用可能 | 通話発信時 |
| BR-05 | 割り当て済みインボックス | ユーザーに割り当てられたインボックスのみ選択可能 | インボックス選択時 |
| BR-06 | 通話方向記録 | call_direction は 'outbound' として記録 | 会話作成時 |
| BR-07 | 初期ステータス | 通話開始時のcall_statusは 'ringing' | 会話作成時 |

### 計算ロジック

#### Conference SID 生成

```ruby
Voice::Conference::Name.for(conversation)
```

会話IDを基にカンファレンス名を生成し、一意のカンファレンスSIDとして使用。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 通話発信 | contact_inboxes | SELECT/INSERT | ContactInbox 取得または作成 |
| 通話発信 | conversations | INSERT | 会話作成 |
| 通話発信 | conversations | UPDATE | 通話情報設定 |
| 通話発信 | messages | INSERT | 通話メッセージ作成 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | status | 'open' | |
| INSERT | contact_inbox_id | ContactInbox.id | |
| INSERT | inbox_id | Voice インボックスID | |
| INSERT | contact_id | 連絡先ID | |
| UPDATE | identifier | call_sid | Twilio通話ID |
| UPDATE | additional_attributes | call_direction, call_status, agent_id, conference_sid, meta | JSON形式 |
| UPDATE | last_activity_at | 現在時刻 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 権限がない | 権限を確認 |
| 404 | Not Found | 連絡先またはインボックスが存在しない | 正しいIDを指定 |
| ArgumentError | 引数エラー | 電話番号がない、エージェントがない | 必要な情報を設定 |

### リトライ仕様

- Twilio API呼び出し失敗時はトランザクションロールバック

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

- 全処理（ContactInbox作成、会話作成、通話発信、会話更新、メッセージ作成）は単一トランザクション内で実行
- 途中で失敗した場合は全てロールバック

## パフォーマンス要件

- Twilio API呼び出しによる外部通信が発生するため、レスポンス時間は外部依存

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

- Enterprise版チェック
- 連絡先とインボックスへの権限確認（Pundit policy）
- ユーザーに割り当てられたインボックスのみ使用可能

## 備考

- Enterprise Edition 限定機能
- ルート定義: `post :call, on: :member, to: 'calls#create' if ChatwootApp.enterprise?`
- Voice チャネルの詳細実装は Twilio 統合に依存

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | calls_controller.rb | `enterprise/app/controllers/api/v1/accounts/contacts/calls_controller.rb` | 通話発信エンドポイント |

**読解のコツ**: Enterprise版のコントローラーは enterprise/ ディレクトリ配下に配置される。

**主要処理フロー**:
- **2-3行目**: before_action で contact と voice_inbox を取得
- **5-6行目**: 権限チェック（contact と inbox の両方）
- **8-14行目**: Voice::OutboundCallBuilder でアウトバウンド通話実行
- **16-21行目**: レスポンス生成（conversation_id, inbox_id, call_sid, conference_sid）
- **28-30行目**: `contact` - アカウント内の連絡先取得
- **32-37行目**: `voice_inbox` - ユーザーに割り当てられたVoiceインボックス取得

#### Step 2: ビルダーサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | outbound_call_builder.rb | `enterprise/app/services/voice/outbound_call_builder.rb` | 通話発信ロジック |

**主要処理フロー**:
- **15-31行目**: `perform!` - メイン処理（トランザクション内）
  - 電話番号とエージェントの存在チェック
  - ContactInbox 作成/取得
  - 会話作成
  - 通話発信（Twilio API）
  - 会話情報更新
  - 通話メッセージ作成
- **35-42行目**: `ensure_contact_inbox!` - ContactInbox の find_or_create
- **44-51行目**: `create_conversation!` - 会話作成（open ステータス）
- **53-57行目**: `initiate_call!` - Voice チャネル経由で通話発信
- **59-73行目**: `update_conversation!` - 通話情報（direction, status, conference_sid等）を設定
- **75-89行目**: `build_voice_message!` - 通話メッセージの作成

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

```
Api::V1::Accounts::Contacts::CallsController#create
    │
    ├─ authorize contact, :show?
    ├─ authorize voice_inbox, :show?
    │
    └─ Voice::OutboundCallBuilder.perform!
           │
           ├─ ensure_contact_inbox!
           │      └─ ContactInbox.find_or_create_by!
           │
           ├─ create_conversation!
           │      └─ account.conversations.create!(status: :open, ...)
           │
           ├─ initiate_call!
           │      └─ inbox.channel.initiate_call(to: phone_number)
           │             └─ Twilio API
           │
           ├─ update_conversation!
           │      └─ conversation.update!(identifier: call_sid, additional_attributes: {...})
           │
           └─ build_voice_message!
                  └─ Voice::CallMessageBuilder.perform!
```

### データフロー図

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

contact_id (URL) ───▶ 連絡先取得 ──────────────────▶ @contact
                              │
inbox_id (Body) ────▶ Voice インボックス取得 ────────▶ @voice_inbox
                              │
                    OutboundCallBuilder.perform!
                              │
                    ├─ ContactInbox 作成/取得
                    ├─ Conversation 作成
                    ├─ Twilio API 呼び出し ───────────▶ 電話発信
                    │         │
                    │         └─ call_sid 取得
                    │
                    ├─ Conversation 更新 ────────────▶ conversations テーブル
                    │    (call_sid, conference_sid)
                    │
                    └─ CallMessage 作成 ─────────────▶ messages テーブル
                              │
                              ▼
                    JSON レスポンス
                    {conversation_id, inbox_id, call_sid, conference_sid}
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| calls_controller.rb | `enterprise/app/controllers/api/v1/accounts/contacts/calls_controller.rb` | コントローラー | 通話発信API |
| outbound_call_builder.rb | `enterprise/app/services/voice/outbound_call_builder.rb` | サービス | 通話発信ロジック |
| call_message_builder.rb | `enterprise/app/services/voice/call_message_builder.rb` | サービス | 通話メッセージ作成 |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先（phone_number属性） |
| inbox.rb | `app/models/inbox.rb` | モデル | インボックス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（Enterprise条件付き） |
