# 機能設計書 31-参加者管理

## 概要

本ドキュメントは、Chatwootにおける会話参加者管理機能の設計仕様を記載する。会話に対してエージェント（ユーザー）を参加者として追加・削除・更新する機能を提供し、複数のエージェントが同じ会話を共同で対応できるようにする。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポート業務において、1つの会話を複数のエージェントで共同対応する必要がある場合がある。例えば、専門知識を持つエージェントへの相談、シフト引き継ぎ、チームでの協力対応などのケースで、会話参加者機能により、担当者（assignee）以外のエージェントも会話を閲覧・参加できるようになる。

**機能の利用シーン**：
- 専門的な問い合わせで他のエージェントに相談したい場合
- シフト交代時に引き継ぎのため複数エージェントで会話を共有する場合
- チームで協力して顧客対応を行う場合
- 上司やマネージャーが会話を監視・サポートする場合

**主要な処理内容**：
1. 会話参加者の一覧表示（show）：現在の会話に参加しているエージェントの一覧を取得
2. 会話参加者の追加（create）：新しいエージェントを会話に参加者として追加
3. 会話参加者の更新（update）：参加者リストを指定されたユーザーIDリストで同期（追加・削除を一括処理）
4. 会話参加者の削除（destroy）：指定されたエージェントを会話から削除

**関連システム・外部連携**：
- 内部システムとしてUser（エージェント）モデル、Conversation（会話）モデルと連携
- WebSocket経由でリアルタイムに参加者変更を通知（ActionCable）

**権限による制御**：
- 会話へのアクセス権限（ConversationPolicy#show?）を持つユーザーのみが参加者を管理可能
- 参加者として追加されるユーザーは、該当インボックスへのアクセス権限（assignable_agents）を持っている必要がある

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | 会話参加者の追加・削除操作 |
| 17 | 参加中会話一覧 | 参照画面 | 参加中の会話を一覧表示 |

## 機能種別

CRUD操作（会話参加者の作成・読取・更新・削除）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| account_id | Integer | Yes | アカウントID（URLパラメータ） | 存在チェック |
| conversation_id | Integer | Yes | 会話のdisplay_id（URLパラメータ） | 存在チェック |
| user_ids | Array[Integer] | Yes（create/update/destroy） | 追加・更新・削除対象のユーザーIDリスト | 各ユーザーのインボックスアクセス権チェック |

### 入力データソース

- URLパラメータ: account_id, conversation_id
- リクエストボディ: user_ids配列

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| participants | Array | 参加者（エージェント）情報の配列 |
| participants[].id | Integer | ユーザーID |
| participants[].name | String | ユーザー名 |
| participants[].email | String | メールアドレス |
| participants[].avatar_url | String | アバター画像URL |

### 出力先

- JSON形式でHTTPレスポンスとして返却
- WebSocket経由でリアルタイム通知

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ account_id, conversation_idの検証

2. 会話取得・認可チェック
   └─ Current.account.conversations.find_by!(display_id: conversation_id)
   └─ ConversationPolicy#show? による認可

3. 各アクションの実行
   ├─ show: conversation_participantsを取得
   ├─ create: user_idsからfind_or_create_byで参加者作成
   ├─ update: 追加対象と削除対象を計算し、トランザクション内で処理
   └─ destroy: user_idsの参加者をトランザクション内で削除

4. レスポンス生成
   └─ JSON形式で参加者リストを返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[会話取得]
    B --> C{認可チェック}
    C -->|失敗| D[403 Forbidden]
    C -->|成功| E{アクション判定}

    E -->|show| F[参加者一覧取得]
    E -->|create| G[参加者追加処理]
    E -->|update| H[参加者同期処理]
    E -->|destroy| I[参加者削除処理]

    G --> J{インボックスアクセス権チェック}
    H --> J
    J -->|失敗| K[422 Unprocessable Entity]
    J -->|成功| L[トランザクション実行]

    F --> M[JSONレスポンス返却]
    L --> M
    I --> N[200 OK]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-1 | インボックスアクセス権限 | 参加者として追加されるユーザーは、該当会話のインボックスのassignable_agentsに含まれている必要がある | create, update時 |
| BR-31-2 | 重複防止 | 同一ユーザーを同一会話に重複して追加することはできない（find_or_create_by） | create, update時 |
| BR-31-3 | アカウント自動設定 | 参加者のaccount_idは会話のaccount_idから自動設定される | create時 |

### 計算ロジック

- 追加対象ユーザーID: `params[:user_ids] - current_participant_ids`
- 削除対象ユーザーID: `current_participant_ids - params[:user_ids]`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| show | conversation_participants | SELECT | 会話の参加者一覧取得 |
| create | conversation_participants | INSERT | 新規参加者追加 |
| update | conversation_participants | INSERT/DELETE | 参加者の追加・削除 |
| destroy | conversation_participants | DELETE | 参加者削除 |

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

#### conversation_participants

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | conversation.account_id | 自動設定 |
| INSERT | conversation_id | 対象会話のID | |
| INSERT | user_id | リクエストのuser_ids | |
| INSERT | created_at, updated_at | 現在時刻 | |
| DELETE | - | user_id AND conversation_id で特定 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | NotFound | 会話が存在しない | 正しいconversation_idを指定 |
| 403 | Forbidden | 会話へのアクセス権限がない | 権限を持つユーザーでアクセス |
| 422 | UnprocessableEntity | インボックスアクセス権のないユーザーを追加しようとした | assignable_agentsに含まれるユーザーを指定 |

### リトライ仕様

特になし。エラー発生時は即座にエラーレスポンスを返却。

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

- create, update, destroyアクションはActiveRecord::Base.transaction内で実行
- 途中でエラーが発生した場合は全ての変更がロールバックされる

## パフォーマンス要件

- 参加者数は通常少数（10名以下）を想定
- 特別なパフォーマンス要件なし

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

- ConversationPolicy#show?による会話アクセス権限チェック
- ensure_inbox_accessによるインボックスアクセス権限チェック
- Current.accountによるアカウントスコープの制限

## 備考

- 参加者（participant）と担当者（assignee）は別の概念。担当者は会話に1名のみ設定可能だが、参加者は複数設定可能
- 参加者として追加されたユーザーは、「参加中会話一覧」で該当会話を確認できる

---

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

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

### 推奨読解順序

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

まず、会話参加者のデータモデルを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversation_participant.rb | `app/models/conversation_participant.rb` | 参加者モデルの構造、バリデーション、リレーション |
| 1-2 | conversation.rb | `app/models/conversation.rb` | 会話モデルとの関連（has_many :conversation_participants） |

**読解のコツ**: ConversationParticipantモデルはシンプルな中間テーブル的な役割。`ensure_inbox_access`バリデーションが重要なビジネスルールを実装している。

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

処理の起点となるコントローラーを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | participants_controller.rb | `app/controllers/api/v1/accounts/conversations/participants_controller.rb` | 各アクション（show, create, update, destroy）の処理フロー |
| 2-2 | base_controller.rb | `app/controllers/api/v1/accounts/conversations/base_controller.rb` | 共通の会話取得・認可ロジック |

**主要処理フロー**:
1. **7-9行目**: create - トランザクション内でfind_or_create_byで参加者追加
2. **12-18行目**: update - 追加対象と削除対象を分けて処理
3. **21-25行目**: destroy - 指定されたユーザーの参加者レコードを削除
4. **30-31行目**: participants_to_be_added_ids - 追加対象の計算ロジック
5. **34-35行目**: participants_to_be_removed_ids - 削除対象の計算ロジック

#### Step 3: 認可・ポリシーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | conversation_policy.rb | `app/policies/conversation_policy.rb` | show?メソッドによる会話アクセス権限チェック |

**主要処理フロー**:
- **10-12行目**: show?メソッド - 管理者、エージェントボット、またはエージェントがアクセス可能か判定
- **42-44行目**: participant?メソッド - ユーザーが参加者かどうかの判定（他の機能で使用）

#### Step 4: ビューを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | show.json.jbuilder | `app/views/api/v1/accounts/conversations/participants/show.json.jbuilder` | 参加者一覧のJSON形式 |
| 4-2 | create.json.jbuilder | `app/views/api/v1/accounts/conversations/participants/create.json.jbuilder` | 作成結果のJSON形式 |

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

```
Api::V1::Accounts::Conversations::ParticipantsController
    │
    ├─ BaseController#conversation
    │      └─ Current.account.conversations.find_by!
    │      └─ authorize @conversation, :show?
    │             └─ ConversationPolicy#show?
    │
    ├─ show
    │      └─ @conversation.conversation_participants
    │
    ├─ create
    │      └─ ActiveRecord::Base.transaction
    │             └─ ConversationParticipant.find_or_create_by
    │                    └─ ensure_inbox_access (validation)
    │
    ├─ update
    │      └─ ActiveRecord::Base.transaction
    │             ├─ find_or_create_by (追加)
    │             └─ find_by.destroy (削除)
    │
    └─ destroy
           └─ ActiveRecord::Base.transaction
                  └─ find_by.destroy
```

### データフロー図

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

user_ids (Array)  ───▶ ParticipantsController  ───▶ JSON Response
                            │
                            ▼
                  ConversationParticipant
                            │
                            ▼
                  conversation_participants
                        (DB Table)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| participants_controller.rb | `app/controllers/api/v1/accounts/conversations/participants_controller.rb` | コントローラー | 参加者のCRUD処理 |
| base_controller.rb | `app/controllers/api/v1/accounts/conversations/base_controller.rb` | コントローラー | 共通処理（会話取得・認可） |
| conversation_participant.rb | `app/models/conversation_participant.rb` | モデル | 参加者データモデル |
| conversation.rb | `app/models/conversation.rb` | モデル | 会話モデル（参加者との関連定義） |
| conversation_policy.rb | `app/policies/conversation_policy.rb` | ポリシー | アクセス権限制御 |
| show.json.jbuilder | `app/views/api/v1/accounts/conversations/participants/show.json.jbuilder` | ビュー | 参加者一覧JSON生成 |
| create.json.jbuilder | `app/views/api/v1/accounts/conversations/participants/create.json.jbuilder` | ビュー | 作成結果JSON生成 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（140行目） |
