# 機能設計書 25-会話CRUD

## 概要

本ドキュメントは、Chatwootにおける会話（Conversation）の作成・表示・更新・削除機能について記述する。会話はカスタマーサポートの中核的なエンティティであり、連絡先とエージェント間のコミュニケーションを管理する。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポートにおいて、顧客からの問い合わせを「会話」として管理し、エージェントが効率的に対応できるようにする。会話は連絡先、インボックス、担当者、チームなどと関連付けられ、ステータス管理、ラベル付け、優先度設定などの機能を通じて、組織的なサポート業務を実現する。

**機能の利用シーン**：
- 顧客からの問い合わせ受信時の新規会話作成
- ダッシュボードでの会話一覧表示と詳細確認
- エージェントによる会話のステータス更新（オープン/解決/保留/スヌーズ）
- 管理者による会話の削除

**主要な処理内容**：
1. 会話の作成（ConversationBuilder経由）
2. 会話一覧の取得（ConversationFinder経由）
3. 会話詳細の表示
4. 会話ステータスの切り替え（toggle_status）
5. 会話の優先度設定（toggle_priority）
6. カスタム属性の更新
7. 会話の削除（非同期ジョブ）

**関連システム・外部連携**：
- WebSocket（ActionCable）によるリアルタイム更新
- イベントディスパッチャーによる通知連携
- メール通知（トランスクリプト送信）

**権限による制御**：会話の表示・編集はインボックスへのアクセス権限を持つエージェントが実行可能。削除は管理者権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 9 | ダッシュボードホーム画面 | 主画面 | 会話一覧表示と選択 |
| 10 | インボックス別会話一覧 | 主画面 | 特定インボックスの会話一覧 |
| 11 | 会話詳細画面 | 主画面 | 会話の詳細情報表示 |
| 12 | ラベル別会話一覧 | 参照画面 | ラベルによるフィルタリング |
| 13 | チーム別会話一覧 | 参照画面 | チームによるフィルタリング |
| 14 | カスタムビュー会話一覧 | 参照画面 | カスタムフィルタによる一覧 |

## 機能種別

CRUD操作

## 入力仕様

### 入力パラメータ（作成時）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| inbox_id | Integer | Yes | インボックスID | 存在チェック |
| contact_id | Integer | Yes | 連絡先ID | 存在チェック |
| source_id | String | No | チャネル固有のソースID | ユニーク |
| status | String | No | 初期ステータス | open/resolved/pending/snoozed |
| assignee_id | Integer | No | 担当者ID | 存在チェック |
| team_id | Integer | No | チームID | 存在チェック |
| additional_attributes | JSON | No | 追加属性 | JSONフォーマット |
| custom_attributes | JSON | No | カスタム属性 | JSONフォーマット |
| message | Object | No | 初期メッセージ | - |

### 入力データソース

- 画面入力: ダッシュボードからの手動作成
- APIエンドポイント: チャネル経由での自動作成
- Widget: 顧客からの問い合わせ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | 会話ID |
| display_id | Integer | 表示用ID（アカウント内連番） |
| uuid | UUID | ユニークID |
| status | String | ステータス（open/resolved/pending/snoozed） |
| priority | String | 優先度（low/medium/high/urgent） |
| assignee | Object | 担当者情報 |
| contact | Object | 連絡先情報 |
| inbox | Object | インボックス情報 |
| messages | Array | メッセージ一覧 |
| labels | Array | ラベル一覧 |
| custom_attributes | JSON | カスタム属性 |
| additional_attributes | JSON | 追加属性 |
| created_at | DateTime | 作成日時 |
| last_activity_at | DateTime | 最終活動日時 |

### 出力先

- DBテーブル: conversations
- APIレスポンス: 会話情報JSON
- WebSocket: リアルタイム更新通知

## 処理フロー

### 処理シーケンス

```
1. 会話作成（Create）
   └─ POST /api/v1/accounts/:account_id/conversations
   └─ ConversationBuilder.new(params, contact_inbox).perform
   └─ 既存会話検索（lock_to_single_conversation時）
   └─ 新規会話作成
   └─ メッセージ作成（オプション）
   └─ CONVERSATION_CREATEDイベント発火

2. 会話一覧取得（Index）
   └─ GET /api/v1/accounts/:account_id/conversations
   └─ ConversationFinder.new(user, params).perform
   └─ フィルタリング・ソート・ページネーション

3. 会話詳細取得（Show）
   └─ GET /api/v1/accounts/:account_id/conversations/:id
   └─ 認可チェック（conversation_policy）

4. 会話更新（Update）
   └─ PATCH /api/v1/accounts/:account_id/conversations/:id
   └─ CONVERSATION_UPDATEDイベント発火

5. ステータス切り替え（toggle_status）
   └─ POST /api/v1/accounts/:account_id/conversations/:id/toggle_status
   └─ 自動担当者割り当て（オープン時）
   └─ CONVERSATION_OPENEDまたはCONVERSATION_RESOLVEDイベント発火

6. 会話削除（Destroy）
   └─ DELETE /api/v1/accounts/:account_id/conversations/:id
   └─ DeleteObjectJob で非同期削除
```

### フローチャート

```mermaid
flowchart TD
    A[会話操作] --> B{操作種別}
    B -->|作成| C[inbox_id, contact_id取得]
    B -->|一覧| D[ConversationFinder]
    B -->|詳細| E[認可チェック]
    B -->|更新| F[permitted_params適用]
    B -->|削除| G[管理者権限チェック]

    C --> H{lock_to_single_conversation?}
    H -->|Yes| I[既存会話検索]
    H -->|No| J[新規会話作成]
    I --> K{既存あり?}
    K -->|Yes| L[既存会話返却]
    K -->|No| J
    J --> M[イベント発火]

    D --> N[フィルタ/ソート]
    N --> O[結果返却]

    E --> P[会話情報返却]

    F --> Q[DB更新]
    Q --> R[イベント発火]

    G --> S{権限OK?}
    S -->|Yes| T[DeleteObjectJob]
    S -->|No| U[403エラー]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-25-01 | display_id自動採番 | display_idはアカウント内で連番を自動採番 | 作成時 |
| BR-25-02 | uuid自動生成 | uuidはDBトリガーで自動生成 | 作成時 |
| BR-25-03 | lock_to_single_conversation | インボックス設定で有効な場合、連絡先毎に1会話のみ | 作成時 |
| BR-25-04 | ステータス初期値 | ボットアクティブ時はpending、ブロック連絡先はresolved | 作成時 |
| BR-25-05 | 自動担当者割り当て | オープン時にエージェントが操作した場合、自動で担当者に | toggle_status時 |
| BR-25-06 | スヌーズリセット | スヌーズ以外のステータスに変更時、snoozed_untilをクリア | 更新時 |

### 計算ロジック

**display_id生成**:
- DBトリガーで実装: `NEW.display_id := nextval('conv_dpid_seq_' || NEW.account_id);`
- アカウント毎に独立したシーケンスを使用

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 会話作成 | conversations | INSERT | 新規会話レコード作成 |
| 会話更新 | conversations | UPDATE | ステータス、担当者等の更新 |
| 会話削除 | conversations | DELETE | 会話レコード削除 |
| 会話削除 | messages | DELETE | 関連メッセージ削除（destroy_async） |
| 会話削除 | conversation_participants | DELETE | 関連参加者削除 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | contact_inbox経由で取得 | 必須 |
| INSERT | inbox_id | contact_inbox経由で取得 | 必須 |
| INSERT | contact_id | contact_inbox経由で取得 | 必須 |
| INSERT | display_id | DBトリガーで自動生成 | アカウント内ユニーク |
| INSERT | uuid | DBトリガーで自動生成 | グローバルユニーク |
| INSERT/UPDATE | status | open/resolved/pending/snoozed | enum |
| INSERT/UPDATE | priority | low/medium/high/urgent | enum、null可 |
| UPDATE | assignee_id | 担当者のuser_id | null可 |
| UPDATE | team_id | チームID | null可 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | RecordNotFound | 指定された会話が存在しない | 404エラーを返却 |
| 403 | Pundit::NotAuthorizedError | 権限不足 | 403エラーを返却 |
| 422 | RecordInvalid | バリデーションエラー | エラー詳細を返却 |
| 422 | InvalidAttribute | フィルタ条件不正 | エラーメッセージを返却 |

### リトライ仕様

削除はDeleteObjectJobで非同期実行。Sidekiqのデフォルトリトライ機構を使用。

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

会話作成時はActiveRecord::Base.transactionブロック内でConversationBuilderとMessageBuilderを実行。メッセージ作成失敗時は会話作成もロールバック。

## パフォーマンス要件

- 複合インデックス: (account_id, inbox_id, status, assignee_id)
- ページネーション: 25件/ページ（デフォルト）
- 添付ファイル一覧: 100件/ページ
- last_seen更新スロットリング: 1時間（未読メッセージなし時）

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

- Punditポリシーによる認可チェック
- インボックスアクセス権限の確認
- source_idのユニーク性検証
- referer URLのバリデーション

## 備考

- display_idとuuidはDBトリガーで生成されるため、after_create_commitで再読み込み
- Enterprise版ではSLAポリシー、監査ログなどの追加機能あり

---

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

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

### 推奨読解順序

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

Conversationモデルのスキーマとアソシエーションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversation.rb | `app/models/conversation.rb` | スキーマ、enum、スコープ、アソシエーション |

**読解のコツ**:
- **1-52行目**: スキーマ定義とインデックス情報
- **54-63行目**: includeされるConcern群（Labelable, AssignmentHandler等）
- **75-76行目**: status、priorityのenum定義
- **78-98行目**: 各種スコープ定義（unassigned, assigned等）
- **100-115行目**: アソシエーション定義

**主要処理フロー**:
1. **149-154行目**: `toggle_status`でステータス切り替えロジック
2. **156-159行目**: `toggle_priority`で優先度設定
3. **268-269行目**: `notify_conversation_creation`でイベント発火
4. **340-342行目**: DBトリガーでdisplay_id生成

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

APIコントローラーの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | conversations_controller.rb | `app/controllers/api/v1/accounts/conversations_controller.rb` | CRUD、ステータス操作 |

**主要処理フロー**:
- **11-15行目**: index でConversationFinder使用
- **39-44行目**: create でConversationBuilder使用、トランザクション内でメッセージも作成
- **78-89行目**: toggle_status でステータス切り替えと自動割り当て

#### Step 3: ビルダーパターンを理解する

会話作成のビルダークラスを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | conversation_builder.rb | `app/builders/conversation_builder.rb` | 会話作成ロジック |

**主要処理フロー**:
- **4-6行目**: `perform`で既存会話検索または新規作成
- **10-14行目**: `look_up_exising_conversation`でlock_to_single_conversation対応
- **20-39行目**: `conversation_params`でパラメータ構築

#### Step 4: 検索・フィルタリングを理解する

会話検索のFinderクラスを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | conversation_finder.rb | `app/finders/conversation_finder.rb` | 検索・フィルタロジック |

**主要処理フロー**:
- Finderパターンで複雑な検索条件を構築

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

```
ConversationsController
    │
    ├─ #index
    │      └─ ConversationFinder#perform
    │             └─ フィルタ/ソート/ページネーション
    │
    ├─ #create
    │      │
    │      └─ [transaction]
    │             ├─ ConversationBuilder#perform
    │             │      ├─ look_up_exising_conversation
    │             │      └─ create_new_conversation
    │             │             └─ Conversation.create!
    │             │                    └─ [DBトリガー: display_id, uuid]
    │             │
    │             └─ Messages::MessageBuilder#perform (オプション)
    │
    ├─ #show
    │      └─ authorize @conversation
    │
    ├─ #update
    │      └─ @conversation.update!
    │             └─ execute_after_update_commit_callbacks
    │
    ├─ #toggle_status
    │      ├─ @conversation.toggle_status
    │      └─ assign_conversation (自動割り当て)
    │
    └─ #destroy
           └─ DeleteObjectJob.perform_later
```

### データフロー図

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

inbox_id, contact_id ───▶ ContactInboxBuilder ───▶ contact_inbox
         │
         ▼
    ConversationBuilder ───▶ Conversation.create! ───▶ conversations
         │                                                  │
         ▼                                                  ▼
    [DBトリガー] ───▶ display_id, uuid生成          CONVERSATION_CREATED
                                                          │
                                                          ▼
                                                    WebSocket通知
                                                    メール通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| conversation.rb | `app/models/conversation.rb` | モデル | 会話モデル |
| conversations_controller.rb | `app/controllers/api/v1/accounts/conversations_controller.rb` | コントローラー | CRUD API |
| conversation_builder.rb | `app/builders/conversation_builder.rb` | ビルダー | 会話作成ロジック |
| conversation_finder.rb | `app/finders/conversation_finder.rb` | Finder | 検索ロジック |
| contact_inbox_builder.rb | `app/builders/contact_inbox_builder.rb` | ビルダー | ContactInbox作成 |
| assignment_handler.rb | `app/models/concerns/assignment_handler.rb` | Concern | 割り当て処理 |
| auto_assignment_handler.rb | `app/models/concerns/auto_assignment_handler.rb` | Concern | 自動割り当て |
| activity_message_handler.rb | `app/models/concerns/activity_message_handler.rb` | Concern | アクティビティ記録 |
| conversation_mute_helpers.rb | `app/models/concerns/conversation_mute_helpers.rb` | Concern | ミュート機能 |
| delete_object_job.rb | `app/jobs/delete_object_job.rb` | ジョブ | 非同期削除 |
