# 機能設計書 34-ミュート・アンミュート

## 概要

本ドキュメントは、Chatwootにおける会話ミュート・アンミュート機能の設計仕様を記載する。迷惑な連絡先からの会話をミュート（ブロック）し、会話を自動的に解決済みにする機能、およびミュートを解除する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポート業務において、スパムや悪意のあるユーザー、迷惑な連絡からの問い合わせを受けることがある。このような連絡先からの会話に対応し続けることは、エージェントの時間とリソースの無駄となる。ミュート機能により、特定の連絡先をブロックし、その連絡先からの全ての会話を自動的にクローズすることで、サポートチームの効率を向上させる。

**機能の利用シーン**：
- スパムメッセージを送信してくる連絡先をブロックする場合
- 悪意のある・攻撃的なメッセージを送信する連絡先を制御する場合
- 間違ってミュートした連絡先を復元する場合
- 以前ブロックした連絡先を再度受け入れる場合

**主要な処理内容**：
1. 会話のミュート（mute）：連絡先をブロック状態にし、会話を解決済みに変更、アクティビティメッセージを作成
2. 会話のアンミュート（unmute）：連絡先のブロックを解除、アクティビティメッセージを作成

**関連システム・外部連携**：
- Contact（連絡先）モデルのblocked属性を更新
- アクティビティメッセージをJobとして非同期作成

**権限による制御**：
- 会話へのアクセス権限（ConversationPolicy#show?）を持つユーザーのみがミュート・アンミュート操作が可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | ミュート・アンミュートボタンの操作 |

## 機能種別

データ更新 / ステータス管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| account_id | Integer | Yes | アカウントID（URLパラメータ） | 存在チェック |
| id | Integer | Yes | 会話のdisplay_id（URLパラメータ） | 存在チェック |

### 入力データソース

- URLパラメータ: account_id, id (conversation display_id)

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | - | 200 OK（ボディなし） |

### 出力先

- HTTPレスポンス（head :ok）
- アクティビティメッセージがJobとしてDBに保存される

## 処理フロー

### 処理シーケンス

```
【mute - ミュート】
1. リクエスト受信
   └─ account_id, conversation_idの検証

2. 会話取得・認可チェック
   └─ ConversationPolicy#show?

3. ミュート処理
   └─ conversation.mute!
       ├─ 連絡先のブロック: contact.update(blocked: true)
       ├─ 会話を解決済みに: resolved!
       └─ アクティビティメッセージ作成: create_muted_message

4. レスポンス生成
   └─ head :ok

【unmute - アンミュート】
1. リクエスト受信
   └─ account_id, conversation_idの検証

2. 会話取得・認可チェック
   └─ ConversationPolicy#show?

3. アンミュート処理
   └─ conversation.unmute!
       ├─ 連絡先のブロック解除: contact.update(blocked: false)
       └─ アクティビティメッセージ作成: create_unmuted_message

4. レスポンス生成
   └─ head :ok
```

### フローチャート

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

    E -->|mute| F{連絡先存在?}
    F -->|No| G[処理終了]
    F -->|Yes| H[連絡先をブロック]
    H --> I[会話を解決済みに変更]
    I --> J[ミュートアクティビティ作成]
    J --> K[200 OK]

    E -->|unmute| L{連絡先存在?}
    L -->|No| G
    L -->|Yes| M[連絡先のブロック解除]
    M --> N[アンミュートアクティビティ作成]
    N --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-34-1 | 連絡先ブロック | ミュート時に連絡先のblocked属性がtrueに設定される | mute実行時 |
| BR-34-2 | 会話自動解決 | ミュート時に会話ステータスが「resolved」に変更される | mute実行時 |
| BR-34-3 | 新規会話自動解決 | ブロックされた連絡先からの新規会話は自動的に解決済みで作成される | 新規会話作成時 |
| BR-34-4 | アクティビティ記録 | ミュート・アンミュート操作はアクティビティメッセージとして記録される | 操作実行時 |
| BR-34-5 | 連絡先必須 | 連絡先が存在しない場合は処理をスキップ | 常時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| mute | contacts | UPDATE | blocked = true |
| mute | conversations | UPDATE | status = 'resolved' |
| mute | messages | INSERT | アクティビティメッセージ（非同期Job） |
| unmute | contacts | UPDATE | blocked = false |
| unmute | messages | INSERT | アクティビティメッセージ（非同期Job） |

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

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | blocked | true（mute時）/ false（unmute時） | |
| UPDATE | updated_at | 現在時刻 | |

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | status | 1 (resolved) | mute時のみ |
| UPDATE | updated_at | 現在時刻 | |

#### messages（アクティビティ）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | 会話のaccount_id | |
| INSERT | inbox_id | 会話のinbox_id | |
| INSERT | message_type | 'activity' | |
| INSERT | content | I18n翻訳されたメッセージ | ミュート/アンミュート通知 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | NotFound | 会話が存在しない | 正しいconversation_idを指定 |
| 403 | Forbidden | 会話へのアクセス権限がない | 権限を持つユーザーでアクセス |

### リトライ仕様

- アクティビティメッセージ作成はJobとして非同期実行されるため、Job失敗時はSidekiqのリトライ機構に従う

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

- mute!/unmute!内の処理は個別のActiveRecord操作
- アクティビティメッセージはJobとして非同期で作成されるため、メイン処理とは別トランザクション

## パフォーマンス要件

- 単純なDB更新操作のため、特別なパフォーマンス要件なし
- アクティビティメッセージ作成は非同期で行われるため、レスポンス時間に影響しない

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

- ConversationPolicy#show?による会話アクセス権限チェック
- 連絡先のブロック状態は他のエージェントにも影響する（アカウント全体でのブロック）

## 備考

- ミュートは連絡先単位で適用されるため、同じ連絡先からの他の会話にも影響する
- muted?メソッドで現在のミュート状態を確認可能（contact&.blocked?）
- ブロックされた連絡先からの新規会話は、Conversation#determine_conversation_statusで自動的にresolvedとして作成される

---

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

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

### 推奨読解順序

#### Step 1: ミュートヘルパーを理解する

まず、ミュート・アンミュートのコアロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversation_mute_helpers.rb | `app/models/concerns/conversation_mute_helpers.rb` | mute!, unmute!, muted?メソッドの実装 |

**読解のコツ**: このモジュールはConversationモデルにincludeされている。mute!では連絡先ブロックと会話解決の両方を行う。

**主要処理フロー**:
- **4-10行目**: mute! - ミュート処理
  - contact存在チェック
  - resolved!で会話を解決済みに
  - contact.update(blocked: true)で連絡先をブロック
  - create_muted_messageでアクティビティ作成
- **12-17行目**: unmute! - アンミュート処理
  - contact存在チェック
  - contact.update(blocked: false)でブロック解除
  - create_unmuted_messageでアクティビティ作成
- **19-21行目**: muted? - ミュート状態確認

#### Step 2: アクティビティメッセージ作成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | activity_message_handler.rb | `app/models/concerns/activity_message_handler.rb` | アクティビティメッセージ作成ロジック |

**主要処理フロー**:
- **93-95行目**: create_muted_message - ミュートアクティビティ
- **97-99行目**: create_unmuted_message - アンミュートアクティビティ
- **101-106行目**: create_mute_change_activity - 共通のアクティビティ作成処理
  - Current.userが存在する場合のみ作成
  - I18n翻訳でメッセージ内容を生成
  - ActivityMessageJobで非同期作成

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | conversations_controller.rb | `app/controllers/api/v1/accounts/conversations_controller.rb` | mute, unmuteアクションの実装 |

**主要処理フロー**:
- **61-64行目**: mute - ミュートエンドポイント
  - @conversation.mute!を呼び出し
  - head :okを返却
- **66-69行目**: unmute - アンミュートエンドポイント
  - @conversation.unmute!を呼び出し
  - head :okを返却

#### Step 4: 新規会話作成時の処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | conversation.rb | `app/models/conversation.rb` | ブロックされた連絡先の会話ステータス決定 |

**主要処理フロー**:
- **254-261行目**: determine_conversation_status
  - contact.blocked?の場合、status = :resolvedに設定
  - これによりブロックされた連絡先からの新規会話は自動的に解決済みになる

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

```
Api::V1::Accounts::ConversationsController
    │
    ├─ conversation (before_action)
    │      └─ Current.account.conversations.find_by!
    │      └─ authorize @conversation, :show?
    │
    ├─ mute
    │      └─ @conversation.mute! (ConversationMuteHelpers)
    │             ├─ resolved! (Conversationモデル)
    │             ├─ contact.update(blocked: true)
    │             └─ create_muted_message (ActivityMessageHandler)
    │                    └─ Conversations::ActivityMessageJob.perform_later
    │
    └─ unmute
           └─ @conversation.unmute! (ConversationMuteHelpers)
                  ├─ contact.update(blocked: false)
                  └─ create_unmuted_message (ActivityMessageHandler)
                         └─ Conversations::ActivityMessageJob.perform_later
```

### データフロー図

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

conversation_id ───▶ ConversationsController  ───▶ 200 OK
                            │
                            ▼
                    ConversationMuteHelpers
                            │
                    ┌───────┴───────┐
                    ▼               ▼
              Contact           Conversation
              (blocked)         (status)
                    │               │
                    ▼               ▼
              contacts          conversations
              (DB Table)        (DB Table)

                            │
                            ▼
                    ActivityMessageJob
                            │
                            ▼
                        messages
                        (DB Table)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| conversations_controller.rb | `app/controllers/api/v1/accounts/conversations_controller.rb` | コントローラー | mute/unmuteエンドポイント（61-69行目） |
| conversation_mute_helpers.rb | `app/models/concerns/conversation_mute_helpers.rb` | モジュール | mute!/unmute!/muted?の実装 |
| activity_message_handler.rb | `app/models/concerns/activity_message_handler.rb` | モジュール | アクティビティメッセージ作成 |
| conversation.rb | `app/models/conversation.rb` | モデル | 会話モデル、ステータス管理 |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先モデル、blocked属性 |
| conversation_policy.rb | `app/policies/conversation_policy.rb` | ポリシー | アクセス権限制御 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（145-146行目） |
