# 機能設計書 96-通知一覧・管理

## 概要

本ドキュメントは、Chatwoot における通知一覧・管理機能の設計を記載する。

### 本機能の処理概要

通知一覧・管理機能は、ユーザーに対する各種通知（会話作成、会話割り当て、新着メッセージ、メンション、SLA違反等）の表示、既読管理、削除を行う機能である。

**業務上の目的・背景**：カスタマーサポートエージェントは、担当する会話に関する重要なイベントを迅速に把握する必要がある。通知機能により、新しい会話の割り当て、メンション、SLA期限の超過などの重要なイベントをリアルタイムで認識し、迅速な対応が可能になる。

**機能の利用シーン**：
- 新しい会話が割り当てられた際に通知を受け取る
- 他のエージェントからメンションされた際に通知を受け取る
- SLAの応答期限が超過した際にアラートを受け取る
- 未読通知を確認して対応漏れを防ぐ

**主要な処理内容**：
1. 通知一覧の取得（ページネーション・フィルタ対応）
2. 通知の既読マーク
3. 通知の未読マーク
4. 通知の削除
5. 未読件数の取得

**関連システム・外部連携**：
- WebSocket（リアルタイム通知配信）
- プッシュ通知サービス（FCM）
- メール通知サービス

**権限による制御**：通知はユーザー単位で管理され、自分の通知のみ操作可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | 通知一覧画面 | 主画面 | 通知の一覧表示・既読管理・削除 |

## 機能種別

CRUD操作 / リアルタイム通知

## 入力仕様

### 入力パラメータ（一覧取得）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| page | Integer | No | ページ番号 | デフォルト: 1 |
| includes | Array[String] | No | 含めるタイプ（read/snoozed） | - |
| sort_order | String | No | ソート順 | デフォルト: desc |

### 入力パラメータ（更新・削除）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 通知ID | 必須 |

### 入力パラメータ（一括既読）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| primary_actor_type | String | No | 主体タイプ（Conversation） | PRIMARY_ACTORSに含まれる |
| primary_actor_id | Integer | No | 主体ID | - |

### 入力データソース

画面入力（API経由）

## 出力仕様

### 出力データ（一覧取得）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| data.meta.unread_count | Integer | 未読通知数 |
| data.meta.count | Integer | 総通知数 |
| data.meta.current_page | Integer | 現在のページ |
| data.payload | Array | 通知リスト |
| data.payload[].id | Integer | 通知ID |
| data.payload[].notification_type | String | 通知種別 |
| data.payload[].push_message_title | String | プッシュ通知タイトル |
| data.payload[].push_message_body | String | プッシュ通知本文 |
| data.payload[].primary_actor_type | String | 主体タイプ |
| data.payload[].primary_actor_id | Integer | 主体ID |
| data.payload[].primary_actor | Object | 主体オブジェクト |
| data.payload[].read_at | DateTime | 既読日時 |
| data.payload[].secondary_actor | Object | 副体オブジェクト |
| data.payload[].user | Object | 対象ユーザー |
| data.payload[].created_at | Integer | 作成日時（UNIX時間） |
| data.payload[].last_activity_at | Integer | 最終活動日時（UNIX時間） |
| data.payload[].snoozed_until | DateTime | スヌーズ終了日時 |
| data.payload[].meta | Object | メタ情報 |

### 出力先

API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ NotificationsControllerがリクエストを受信
2. 認証確認
   └─ current_userの確認
3. 各アクション実行
   ├─ index: NotificationFinderで通知一覧取得
   ├─ update: 通知を既読に更新
   ├─ unread: 通知を未読に更新
   ├─ destroy: 通知を削除
   └─ unread_count: 未読件数取得
4. レスポンス返却
   └─ jbuilderまたはJSONでレスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{認証確認}
    B -->|未認証| C[401エラー]
    B -->|認証済| D{アクション判定}
    D -->|index| E[NotificationFinder初期化]
    D -->|update| F[通知取得]
    D -->|unread| G[通知取得]
    D -->|destroy| H[通知取得]
    D -->|unread_count| I[未読件数計算]
    E --> J[フィルタ適用]
    J --> K[ページネーション]
    K --> L[レスポンス返却]
    F --> M[read_at更新]
    M --> L
    G --> N[read_at = nil更新]
    N --> L
    H --> O[通知削除]
    O --> P[200 OK]
    I --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-96-01 | ユーザー制限 | 自分の通知のみ操作可能 | 全操作 |
| BR-96-02 | アカウント制限 | 現在のアカウントの通知のみ表示 | 全操作 |
| BR-96-03 | デフォルトフィルタ | デフォルトでは未読・非スヌーズのみ表示 | index |
| BR-96-04 | ページネーション | 15件/ページ | index |
| BR-96-05 | ソート順 | last_activity_atの降順（新しい順） | index |
| BR-96-06 | 通知タイプ | 8種類の通知タイプをサポート | 全体 |

### 通知タイプ一覧

| タイプ | 値 | 説明 |
|--------|-----|------|
| conversation_creation | 1 | 会話作成 |
| conversation_assignment | 2 | 会話割り当て |
| assigned_conversation_new_message | 3 | 割り当て会話の新着メッセージ |
| conversation_mention | 4 | 会話内メンション |
| participating_conversation_new_message | 5 | 参加中会話の新着メッセージ |
| sla_missed_first_response | 6 | SLA初回応答期限超過 |
| sla_missed_next_response | 7 | SLA次回応答期限超過 |
| sla_missed_resolution | 8 | SLA解決期限超過 |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| index | notifications | SELECT | 通知一覧取得 |
| update | notifications | UPDATE | read_at更新 |
| unread | notifications | UPDATE | read_at = nil更新 |
| destroy | notifications | DELETE | 通知削除 |
| read_all | notifications | UPDATE | 一括read_at更新 |
| unread_count | notifications | SELECT | 未読件数カウント |

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

#### notifications

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT (index) | * | user_id = current_user.id AND account_id = current_account.id | フィルタ・ページネーション適用 |
| SELECT (unread_count) | COUNT(*) | user_id = current_user.id AND account_id = current_account.id AND read_at IS NULL | スヌーズ除外 |
| UPDATE (update) | read_at | DateTime.now.utc | - |
| UPDATE (unread) | read_at | nil | - |
| DELETE | - | id = params[:id] | 物理削除 |
| UPDATE (read_all) | read_at | DateTime.now.utc | 条件に一致するすべて |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 未認証 | ログインが必要 |
| 404 | Not Found | 通知が存在しない | 正しいIDを指定 |

### リトライ仕様

特になし。

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

- update/unread/destroy: 単一レコード操作でトランザクション管理
- read_all: update_allで一括更新（単一SQL）

## パフォーマンス要件

- 一覧取得は15件/ページでページネーション
- 複合インデックス（user_id, account_id, snoozed_until, read_at）による高速検索
- last_activity_atにインデックス設定

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

- current_user.notifications で自分の通知のみアクセス
- account_id による所有権チェック

## 備考

通知はモデル作成時に自動的にプッシュ通知・メール通知のジョブが登録される。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | 通知のスキーマ定義、通知タイプ、コールバック |

**読解のコツ**:
- **1-28行目**: スキーマ情報（テーブル構造、インデックス）
- **38-47行目**: NOTIFICATION_TYPESのenum定義
- **51-54行目**: コールバック（プッシュ通知・メール通知ジョブ登録）
- **56行目**: PRIMARY_ACTORS定義（Conversationのみ）
- **58-76行目**: push_event_data メソッド（WebSocket配信用データ）
- **89-112行目**: push_message_title メソッド（通知タイトル生成）
- **155-164行目**: process_notification_delivery（通知配信処理）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notifications_controller.rb | `app/controllers/api/v1/accounts/notifications_controller.rb` | APIエンドポイントの実装 |

**主要処理フロー**:
1. **2行目**: RESULTS_PER_PAGE = 15
2. **9-13行目**: index アクション - NotificationFinderで取得
3. **15-25行目**: read_all アクション - 一括既読
4. **27-30行目**: update アクション - 既読更新
5. **32-35行目**: unread アクション - 未読更新
6. **37-40行目**: destroy アクション - 削除
7. **51-54行目**: unread_count アクション

#### Step 3: Finderロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_finder.rb | `app/finders/notification_finder.rb` | 通知検索ロジック |

**主要処理フロー**:
- **13-14行目**: notifications メソッド - ページネーション・ソート適用
- **17-25行目**: unread_count メソッド - 未読件数計算
- **39-40行目**: find_all_notifications - ベースクエリ
- **43-44行目**: filter_snoozed_notifications - スヌーズフィルタ
- **47-48行目**: filter_read_notifications - 既読フィルタ

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

```
Api::V1::Accounts::NotificationsController
    │
    ├─ before_action :fetch_notification (update/destroy/snooze/unread)
    │      └─ current_user.notifications.find(params[:id])
    │
    ├─ index
    │      └─ NotificationFinder.new(Current.user, Current.account, params)
    │             │
    │             ├─ find_all_notifications
    │             │      └─ user.notifications.where(account_id:)
    │             │
    │             ├─ filter_snoozed_notifications
    │             │      └─ where(snoozed_until: nil) unless includes snoozed
    │             │
    │             ├─ filter_read_notifications
    │             │      └─ where(read_at: nil) unless includes read
    │             │
    │             └─ page().per(15).order(last_activity_at:)
    │
    ├─ read_all
    │      └─ notifications.where(read_at: nil).update_all(read_at: now)
    │
    ├─ update
    │      └─ @notification.update(read_at: now)
    │
    ├─ unread
    │      └─ @notification.update(read_at: nil)
    │
    └─ destroy
           └─ @notification.destroy
```

### データフロー図

```
[イベント発生]               [通知作成]                    [通知配信]

会話作成 ─────────────┐
                      │
会話割り当て ─────────┼──▶ Notification.create
                      │           │
メッセージ受信 ───────┤           ├──▶ after_create_commit
                      │           │           │
メンション ───────────┤           │           ├──▶ PushNotificationJob
                      │           │           │
SLA違反 ──────────────┘           │           ├──▶ EmailNotificationJob
                                  │           │
                                  │           └──▶ dispatch_create_event
                                  │                      │
                                  │                      ▼
                                  │               WebSocket配信
                                  │
                                  ▼
                            API経由で取得
                                  │
                                  ▼
                            NotificationFinder
                                  │
                                  ▼
                            JSON Response
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notifications_controller.rb | `app/controllers/api/v1/accounts/notifications_controller.rb` | コントローラー | APIエンドポイント |
| notification.rb | `app/models/notification.rb` | モデル | データ定義・コールバック |
| notification_finder.rb | `app/finders/notification_finder.rb` | Finder | 検索・フィルタロジック |
| index.json.jbuilder | `app/views/api/v1/accounts/notifications/index.json.jbuilder` | ビュー | JSON出力 |
| push_notification_job.rb | `app/jobs/notification/push_notification_job.rb` | ジョブ | プッシュ通知 |
| email_notification_job.rb | `app/jobs/notification/email_notification_job.rb` | ジョブ | メール通知 |
| delete_notification_job.rb | `app/jobs/notification/delete_notification_job.rb` | ジョブ | 一括削除 |
