# 機能設計書 212-メッセージ通知

## 概要

本ドキュメントは、Chatwootにおける「メッセージ通知」機能の設計仕様を記載する。本機能は、新しいメッセージが作成された際に、関連するエージェント（会話担当者および参加者）に対してアプリ内通知を生成するサービスを提供する。

### 本機能の処理概要

この機能は、incoming（顧客からの受信）またはoutgoing（エージェントからの送信）メッセージが作成された際に、会話の担当者（assignee）および参加者（participants）に対して通知を生成する。これにより、エージェントは自分が関わっている会話の新着メッセージをリアルタイムで把握できる。

**業務上の目的・背景**：カスタマーサポート業務において、顧客からの問い合わせに迅速に対応することは顧客満足度向上の鍵となる。複数のエージェントがチームで対応する環境では、新着メッセージの見落としを防ぐ仕組みが不可欠である。本機能は、担当者だけでなく会話に参加しているすべてのエージェントに通知を配信することで、チーム全体での情報共有とスムーズな引き継ぎを実現する。また、既にメンション通知や割り当て通知で通知済みのユーザーには重複通知を送らない仕組みにより、通知の氾濫を防止している。

**機能の利用シーン**：
- 顧客が会話に新しいメッセージを送信した際、担当エージェントに通知
- エージェントがメッセージを送信した際、他の参加エージェントに通知
- チームで対応中の会話で、他メンバーの応答状況を把握

**主要な処理内容**：
1. メッセージ作成イベントを受信（NotificationListener経由）
2. メッセージがnotifiable?（incoming or outgoing）かを判定
3. 会話の担当者（assignee）への通知生成
4. 会話の参加者（participants）への通知生成
5. 既に通知済みのユーザーは重複通知をスキップ
6. NotificationBuilderを通じて通知レコードを作成

**関連システム・外部連携**：
- NotificationListener（イベントリスナー）
- NotificationBuilder（通知生成ビルダー）
- Notification::PushNotificationJob（プッシュ通知ジョブ）
- Notification::EmailNotificationJob（メール通知ジョブ）

**権限による制御**：本機能自体に直接の権限制御はないが、会話への参加権限を持つユーザーのみが通知対象となる。担当者や参加者として登録されていないユーザーには通知されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | 通知一覧画面 | 結果表示画面 | 生成された通知の一覧表示 |
| 11 | 会話詳細画面 | 参照画面 | メッセージ送受信による通知生成のトリガー |
| 9 | ダッシュボードホーム画面 | 参照画面 | 未読通知カウントの表示 |

## 機能種別

イベント処理 / 通知生成

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| message | Message | Yes | 新規作成されたメッセージオブジェクト | 存在チェック |

### 入力データソース

- Message: messages テーブル
- Conversation: conversations テーブル（message.conversation経由）
- User: users テーブル（conversation.assignee, conversation_participants経由）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| notification | Notification | 生成された通知レコード |

### 出力先

- notifications テーブル（Notificationレコード作成）
- WebSocket（リアルタイム通知配信）
- 非同期ジョブ（プッシュ通知、メール通知）

## 処理フロー

### 処理シーケンス

```
1. メッセージ作成イベント受信
   └─ NotificationListener.message_created から呼び出し

2. 通知対象判定
   └─ message.notifiable? で incoming or outgoing かチェック
   └─ 対象外の場合は処理終了

3. 担当者通知処理（notify_conversation_assignee）
   ├─ 担当者が存在しない場合はスキップ
   ├─ 既に通知済みの場合はスキップ
   ├─ 担当者がメッセージ送信者の場合はスキップ
   └─ NotificationBuilder で 'assigned_conversation_new_message' 通知を作成

4. 参加者通知処理（notify_participating_users）
   ├─ 会話の参加者一覧を取得
   ├─ 送信者がUserの場合は除外
   ├─ 各参加者について：
   │   ├─ 既に通知済みの場合はスキップ
   │   └─ NotificationBuilder で 'participating_conversation_new_message' 通知を作成
```

### フローチャート

```mermaid
flowchart TD
    A[メッセージ作成イベント] --> B{notifiable?}
    B -->|No| Z[処理終了]
    B -->|Yes| C[担当者通知処理]

    C --> D{担当者存在?}
    D -->|No| G[参加者通知処理]
    D -->|Yes| E{既に通知済み?}
    E -->|Yes| G
    E -->|No| F{送信者=担当者?}
    F -->|Yes| G
    F -->|No| N1[担当者通知作成]
    N1 --> G

    G --> H[参加者一覧取得]
    H --> I{送信者がUser?}
    I -->|Yes| J[送信者を除外]
    I -->|No| K[全参加者対象]
    J --> K
    K --> L{各参加者}
    L --> M{既に通知済み?}
    M -->|Yes| L
    M -->|No| N2[参加者通知作成]
    N2 --> L
    L -->|完了| Z
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-212-01 | 通知対象メッセージ | incomingまたはoutgoingタイプのメッセージのみ通知対象 | メッセージ作成時 |
| BR-212-02 | 送信者除外 | メッセージ送信者には通知しない | 担当者・参加者判定時 |
| BR-212-03 | 重複通知防止 | 同一メッセージで既に通知済みのユーザーには再通知しない | メンション・割り当て通知との重複 |
| BR-212-04 | 担当者優先 | 担当者への通知は参加者通知より先に処理 | 通知生成順序 |

### 計算ロジック

重複通知チェック:
```ruby
conversation.notifications.exists?(user: user, secondary_actor: message)
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 通知対象確認 | notifications | SELECT | 重複通知チェック |
| 通知作成 | notifications | INSERT | 新規通知レコード作成 |
| 参加者取得 | conversation_participants | SELECT | 会話参加者一覧取得 |

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

#### notifications

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | user, secondary_actor | conversation.notifications.exists?(user: user, secondary_actor: message) | 重複チェック |
| INSERT | notification_type | 'assigned_conversation_new_message' または 'participating_conversation_new_message' | NotificationBuilder経由 |
| INSERT | user_id | 通知対象ユーザーのID | |
| INSERT | account_id | 会話のaccount_id | |
| INSERT | primary_actor_id/type | conversation.id / 'Conversation' | |
| INSERT | secondary_actor_id/type | message.id / 'Message' | |

#### conversation_participants

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | user | conversation.conversation_participants.map(&:user) | 参加者一覧取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ActiveRecord::RecordNotFound | メッセージが削除済み | 処理中断（ジョブ内で発生時はリトライ） |
| - | ActiveRecord::RecordInvalid | 通知レコード作成失敗 | 例外発生、ログ記録 |

### リトライ仕様

本サービス自体にリトライ処理はない。NotificationListenerから同期的に呼び出されるため、エラー発生時はイベント処理の中断となる。

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

本機能単体でのトランザクション管理はない。各通知作成は個別にNotificationBuilder内で行われる。一部の通知作成が失敗しても、他の通知作成には影響しない。

## パフォーマンス要件

- 会話参加者数に比例した処理時間
- 大量の参加者がいる会話では通知生成に時間がかかる可能性あり
- 重複チェックはconversation.notificationsに対するクエリ

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

- 会話の担当者・参加者のみに通知が送信される
- メッセージの送信者には自分自身の通知は送らない
- ブロックされた連絡先からのメッセージに対しても通知は生成される（メンション以外）

## 備考

- 本サービスはMessages::MentionServiceと同時にNotificationListenerから呼び出される
- メンション通知はMentionServiceが担当し、本サービスでは重複チェックで除外

---

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

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

### 推奨読解順序

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

まず、通知システムの基本となるNotificationモデルと関連モデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | 通知モデル。NOTIFICATION_TYPES、リレーション、コールバック |
| 1-2 | message.rb | `app/models/message.rb` | メッセージモデル。message_type、notifiable?メソッド（MessageFilterHelpers経由） |
| 1-3 | message_filter_helpers.rb | `app/models/concerns/message_filter_helpers.rb` | notifiable?の定義。incoming? \|\| outgoing? |
| 1-4 | conversation_participant.rb | `app/models/conversation_participant.rb` | 会話参加者モデル。user_id, conversation_id の関連 |

**読解のコツ**:
- Notificationの`notification_type`はenumで定義（NOTIFICATION_TYPES）
- `primary_actor`は会話、`secondary_actor`はメッセージ
- `notifiable?`はMessageFilterHelpersで定義され、incoming/outgoingのみtrue

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

NotificationListenerからの呼び出しとサービスの構造を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_listener.rb | `app/listeners/notification_listener.rb` | イベントリスナー。message_createdイベントハンドラ |
| 2-2 | new_message_notification_service.rb | `app/services/messages/new_message_notification_service.rb` | メイン処理サービス |

**主要処理フロー**:

**notification_listener.rb**:
1. **50-55行目**: `message_created`イベントハンドラ
2. **53行目**: `Messages::MentionService.new(message: message).perform` - メンション処理
3. **54行目**: `Messages::NewMessageNotificationService.new(message: message).perform` - 本サービス呼び出し

**new_message_notification_service.rb**:
1. **2行目**: `pattr_initialize [:message!]` - messageを必須パラメータとして初期化
2. **4-9行目**: `perform` - メインエントリーポイント
3. **5行目**: `return unless message.notifiable?` - 通知対象判定
4. **7行目**: `notify_conversation_assignee` - 担当者通知
5. **8行目**: `notify_participating_users` - 参加者通知
6. **15-27行目**: `notify_conversation_assignee` - 担当者通知処理
7. **29-44行目**: `notify_participating_users` - 参加者通知処理
8. **48-50行目**: `already_notified?` - 重複通知チェック

#### Step 3: 通知生成処理を理解する

NotificationBuilderの処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_builder.rb | `app/builders/notification_builder.rb` | 通知生成ビルダー |

**主要処理フロー**:
1. **2行目**: パラメータ初期化（notification_type, user, account, primary_actor, secondary_actor）
2. **4-6行目**: `perform` - build_notificationを呼び出し
3. **25-38行目**: `build_notification` - 通知レコード作成
4. **27行目**: conversation_creation通知のサブスクリプションチェック
5. **29行目**: ブロックされた連絡先のチェック（メンション以外はスキップしない）
6. **31-37行目**: `user.notifications.create!` - 通知レコード作成

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

```
[メッセージ作成]
Message.create
    │
    ▼
Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED)
    │
    ▼
NotificationListener.message_created(event)
    │
    ├─ Messages::MentionService.new(message:).perform
    │      └─ [@メンションがある場合の通知処理]
    │
    └─ Messages::NewMessageNotificationService.new(message:).perform
           │
           ├─ message.notifiable?
           │      └─ MessageFilterHelpers#notifiable? [incoming? || outgoing?]
           │
           ├─ notify_conversation_assignee
           │      ├─ conversation.assignee.blank? チェック
           │      ├─ already_notified?(assignee) チェック
           │      ├─ assignee == sender チェック
           │      └─ NotificationBuilder.new(
           │              notification_type: 'assigned_conversation_new_message',
           │              user: assignee,
           │              account: account,
           │              primary_actor: conversation,
           │              secondary_actor: message
           │          ).perform
           │
           └─ notify_participating_users
                  ├─ conversation.conversation_participants.map(&:user)
                  ├─ [送信者がUserの場合は除外]
                  └─ 各参加者に対して:
                         ├─ already_notified?(participant) チェック
                         └─ NotificationBuilder.new(
                                 notification_type: 'participating_conversation_new_message',
                                 user: participant,
                                 ...
                             ).perform
```

### データフロー図

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

Message                    Messages::NewMessageNotificationService
  │                                    │
  ├─ message_type ─────────────▶  notifiable? 判定
  │   (incoming/outgoing)              │
  │                                    ▼
  └─ conversation ─────────────▶  ┌────────────────────────┐
        │                         │ notify_conversation_   │
        ├─ assignee ─────────────▶│     assignee           │──────▶  Notification
        │                         │                        │         (assigned_conversation_
        └─ conversation_          │ notify_participating_  │          new_message)
           participants ─────────▶│     users              │──────▶  Notification
                                  └────────────────────────┘         (participating_conversation_
                                           │                          new_message)
                                           ▼
                                  already_notified? チェック
                                  (conversation.notifications.exists?)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| new_message_notification_service.rb | `app/services/messages/new_message_notification_service.rb` | サービス | メイン処理。担当者・参加者への通知生成 |
| notification_listener.rb | `app/listeners/notification_listener.rb` | リスナー | message_createdイベントのハンドリング |
| notification_builder.rb | `app/builders/notification_builder.rb` | ビルダー | 通知レコードの生成 |
| notification.rb | `app/models/notification.rb` | モデル | 通知データモデル |
| message.rb | `app/models/message.rb` | モデル | メッセージデータモデル |
| message_filter_helpers.rb | `app/models/concerns/message_filter_helpers.rb` | Concern | notifiable?メソッドの定義 |
| conversation_participant.rb | `app/models/conversation_participant.rb` | モデル | 会話参加者データモデル |
| conversation.rb | `app/models/conversation.rb` | モデル | 会話データモデル（assignee, participantsリレーション） |
