# 機能設計書 63-利用可能インボックス表示

## 概要

本ドキュメントは、Chatwootにおける連絡先（Contact）が利用可能なインボックス一覧を表示する機能の設計を記述する。連絡先の属性（メールアドレス、電話番号等）に基づいて、その連絡先と会話を開始できるインボックスを特定し表示する機能である。

### 本機能の処理概要

利用可能インボックス表示機能は、特定の連絡先に対してメッセージを送信できるチャネル（インボックス）を特定し、一覧表示する機能を提供する。各チャネルタイプに応じて、連絡先の属性を基に利用可否を判定する。

**業務上の目的・背景**：カスタマーサポート業務において、エージェントが顧客にプロアクティブにコンタクトを取りたい場合、どのチャネルを使えるかを知ることが重要である。例えば、電話番号を持つ顧客にはSMSやWhatsAppで連絡できるが、メールアドレスのみの顧客にはEmailチャネルのみが利用可能となる。この機能により、エージェントは適切なチャネルを選択して会話を開始できる。

**機能の利用シーン**：
- 連絡先詳細画面から新規会話を開始する際のチャネル選択
- エージェントがプロアクティブにアウトバウンドメッセージを送信したい場合
- 連絡先の連絡可能手段を確認したい場合

**主要な処理内容**：
1. 連絡先IDを受け取り、連絡先情報を取得
2. アカウント内の全インボックスをループ処理
3. 各インボックスのチャネルタイプに応じて利用可否を判定
4. 利用可能なインボックスとソースID（連絡先識別子）をリストアップ
5. ユーザーの権限に基づいてフィルタリング

**関連システム・外部連携**：
- 各チャネルプロバイダー（Twilio、WhatsApp、Email等）との連携情報を返却
- 権限管理システム（Pundit）との連携

**権限による制御**：ユーザーがアクセス権限を持つインボックスのみが結果に含まれる。policy(inbox).show? でフィルタリングされる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | 連絡先編集画面 | 主画面 | 利用可能インボックスの表示 |

## 機能種別

データ連携 / 判定処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 対象連絡先ID | URLパラメータ、存在確認 |

### 入力データソース

- URLパラメータ：連絡先ID
- データベース：contacts テーブル、inboxes テーブル、contact_inboxes テーブル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| payload | Array[Object] | 利用可能インボックス情報の配列 |
| payload[].source_id | String | チャネル固有の連絡先識別子 |
| payload[].inbox | Object | インボックス情報 |

### 出力先

- API レスポンス（JSON形式）
- フロントエンド連絡先詳細画面

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ API エンドポイントで受信（GET /api/v1/accounts/:account_id/contacts/:id/contactable_inboxes）

2. 認証・認可チェック
   └─ BaseController で認証確認
   └─ 連絡先の存在確認と所属確認

3. 利用可能インボックス判定
   └─ ContactableInboxesService を呼び出し
   └─ アカウント内全インボックスをループ
   └─ チャネルタイプ別に利用可否を判定

4. 権限フィルタリング
   └─ policy(inbox).show? でアクセス権限チェック
   └─ 権限のあるインボックスのみ返却

5. レスポンス生成
   └─ JBuilder テンプレートでJSON形式に整形
```

### フローチャート

```mermaid
flowchart TD
    A[APIリクエスト受信] --> B{認証済み?}
    B -->|No| C[401 Unauthorized]
    B -->|Yes| D{連絡先存在?}
    D -->|No| E[404 Not Found]
    D -->|Yes| F[ContactableInboxesService実行]
    F --> G[全インボックスループ]
    G --> H{チャネルタイプ判定}
    H -->|TwilioSms| I[電話番号チェック]
    H -->|WhatsApp| J[電話番号チェック]
    H -->|Sms| K[電話番号チェック]
    H -->|Email| L[メールアドレスチェック]
    H -->|Api| M[既存source_id or UUID生成]
    H -->|WebWidget| N[既存ContactInboxチェック]
    I --> O{利用可能?}
    J --> O
    K --> O
    L --> O
    M --> O
    N --> O
    O -->|Yes| P[結果に追加]
    O -->|No| Q[スキップ]
    P --> R{次のインボックス}
    Q --> R
    R -->|あり| G
    R -->|なし| S[権限フィルタリング]
    S --> T[JSONレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | TwilioSms判定 | 電話番号必須、mediumに応じてsource_id形式が変わる（whatsapp:prefix） | Channel::TwilioSms |
| BR-02 | WhatsApp判定 | 電話番号必須、+を除去した番号がsource_id | Channel::Whatsapp |
| BR-03 | Sms判定 | 電話番号必須、電話番号がそのままsource_id | Channel::Sms |
| BR-04 | Email判定 | メールアドレス必須、メールアドレスがsource_id | Channel::Email |
| BR-05 | Api判定 | 既存ContactInboxがあればそのsource_id、なければUUID生成 | Channel::Api |
| BR-06 | WebWidget判定 | 既存ContactInboxがあり、会話がない場合のみ利用可能 | Channel::WebWidget |
| BR-07 | 権限フィルタ | ユーザーがshow権限を持つインボックスのみ返却 | 全チャネル |

### 計算ロジック

#### チャネルタイプ別source_id生成

| チャネルタイプ | source_id生成ロジック |
|--------------|---------------------|
| TwilioSms (sms) | contact.phone_number |
| TwilioSms (whatsapp) | "whatsapp:#{contact.phone_number}" |
| WhatsApp | contact.phone_number.delete('+') |
| Sms | contact.phone_number |
| Email | contact.email |
| Api | contact_inbox.source_id or SecureRandom.uuid |
| WebWidget | contact_inbox.source_id |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| contactable_inboxes | contacts | SELECT | 連絡先情報取得 |
| contactable_inboxes | inboxes | SELECT | アカウント内全インボックス取得 |
| contactable_inboxes | contact_inboxes | SELECT | 既存連絡先-インボックス関連取得 |
| contactable_inboxes | channels | SELECT | チャネル情報取得 |

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

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | phone_number | id = :id | TwilioSms, WhatsApp, Sms判定に使用 |
| SELECT | email | id = :id | Email判定に使用 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 認証トークンが無効または期限切れ | 再認証を促す |
| 404 | Not Found | 連絡先が存在しない | 正しいIDを指定 |

### リトライ仕様

特にリトライ仕様はなし。

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

- 参照のみの処理のため、特別なトランザクション管理は不要

## パフォーマンス要件

- アカウント内の全インボックスをループ処理するため、インボックス数に比例した処理時間
- contact_inboxes の取得は都度クエリ発行（N+1問題の可能性あり）

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

- アカウント単位でのアクセス制御
- Pundit policy によるインボックス権限フィルタリング
- 連絡先の所属確認

## 備考

- Enterprise版ではContactableInboxesServiceがprepend_mod_withで拡張される可能性がある
- WebWidgetは既に会話がある場合は利用不可（1連絡先1会話の制約）

---

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

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

### 推奨読解順序

#### Step 1: サービスクラスを理解する

メインロジックはContactableInboxesServiceに集約されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | contactable_inboxes_service.rb | `app/services/contacts/contactable_inboxes_service.rb` | チャネルタイプ別の判定ロジック |

**読解のコツ**: pattr_initialize はattr_readerとinitializeを自動生成するマクロ。`:contact!` の `!` は必須パラメータを意味する。

**主要処理フロー**:
- **4-7行目**: `get` - メインメソッド、全インボックスをfilter_mapでループ
- **11-26行目**: `get_contactable_inbox` - チャネルタイプ別にディスパッチ
- **28-35行目**: `website_contactable_inbox` - WebWidget判定（既存ContactInbox必須、会話なし）
- **37-42行目**: `api_contactable_inbox` - API判定（source_id取得 or UUID生成）
- **44-48行目**: `email_contactable_inbox` - Email判定（メールアドレス必須）
- **50-55行目**: `whatsapp_contactable_inbox` - WhatsApp判定（電話番号必須、+除去）
- **57-61行目**: `sms_contactable_inbox` - SMS判定（電話番号必須）
- **63-72行目**: `twilio_contactable_inbox` - TwilioSMS判定（medium別に分岐）

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

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

**主要処理フロー**:
- **74-77行目**: `contactable_inboxes` アクション
  - ContactableInboxesService で全候補を取得
  - policy(inbox).show? で権限フィルタリング

#### Step 3: ルーティングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | routes.rb | `config/routes.rb` | エンドポイント定義 |

**主要処理フロー**:
- **183行目**: `get :contactable_inboxes` - メンバールートとして定義

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

```
Api::V1::Accounts::ContactsController#contactable_inboxes
    │
    ├─ Contacts::ContactableInboxesService.new(contact: @contact).get
    │      │
    │      ├─ account.inboxes.filter_map
    │      │      │
    │      │      └─ get_contactable_inbox(inbox)
    │      │              │
    │      │              ├─ twilio_contactable_inbox (Channel::TwilioSms)
    │      │              ├─ whatsapp_contactable_inbox (Channel::Whatsapp)
    │      │              ├─ sms_contactable_inbox (Channel::Sms)
    │      │              ├─ email_contactable_inbox (Channel::Email)
    │      │              ├─ api_contactable_inbox (Channel::Api)
    │      │              └─ website_contactable_inbox (Channel::WebWidget)
    │      │
    │      └─ { source_id: ..., inbox: inbox }
    │
    └─ @all_contactable_inboxes.select { policy(inbox).show? }
           │
           └─ @contactable_inboxes (フィルタ済み結果)
```

### データフロー図

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

contact_id (URL) ───▶ fetch_contact ────────────────▶ @contact
                              │
                              ▼
                    ContactableInboxesService
                              │
                    account.inboxes ループ
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
        ▼                     ▼                     ▼
  TwilioSms判定         WhatsApp判定          Email判定
  (phone_number)        (phone_number)        (email)
        │                     │                     │
        └─────────────────────┼─────────────────────┘
                              │
                              ▼
                    { source_id, inbox }[]
                              │
                              ▼
                    権限フィルタリング
                    policy(inbox).show?
                              │
                              ▼
                    JSON レスポンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| contactable_inboxes_service.rb | `app/services/contacts/contactable_inboxes_service.rb` | サービス | チャネル別利用可否判定ロジック |
| contacts_controller.rb | `app/controllers/api/v1/accounts/contacts_controller.rb` | コントローラー | contactable_inboxes エンドポイント |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先モデル（email, phone_number属性） |
| inbox.rb | `app/models/inbox.rb` | モデル | インボックスモデル |
| contact_inbox.rb | `app/models/contact_inbox.rb` | モデル | 連絡先-インボックス関連 |
| inbox_policy.rb | `app/policies/inbox_policy.rb` | ポリシー | インボックス権限管理 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
