# 通知設計書 1-コメント通知

## 概要

本ドキュメントは、Fat Free CRMにおけるコメント通知機能の設計を記載する。エンティティ（Opportunity、Account、Contact、Lead、Campaign、Task）に対してコメントが追加された際、購読者にメール通知を送信する機能について定義する。

### 本通知の処理概要

本通知は、CRMシステム内の各種エンティティに対するコメント追加時に、購読者へリアルタイムでメール通知を送信する機能である。

**業務上の目的・背景**：営業活動やカスタマーリレーションシップ管理において、チームメンバー間のコミュニケーションは極めて重要である。案件（Opportunity）や顧客（Account、Contact）、見込み客（Lead）等に関する情報共有を迅速に行うため、コメント追加時に関係者へ自動的に通知することで、情報の見落としを防ぎ、チーム全体の対応スピードを向上させる。また、@メンションによる特定ユーザーへの呼びかけ機能により、適切な担当者に確実に情報を届けることができる。

**通知の送信タイミング**：エンティティに対して新規コメントが作成された直後（after_createコールバック）に非同期で送信される。コメント本文中に@username形式でユーザーがメンションされた場合は、そのユーザーも購読者として追加された上で通知対象となる。

**通知の受信者**：対象エンティティのsubscribed_usersフィールドに登録されているユーザーのうち、コメント作成者自身を除く全員が受信対象となる。ただし、emailable?（メール送信可能状態）かつsubscribe_to_comment_replies?（コメント返信購読設定がオン）の条件を満たすユーザーのみに送信される。

**通知内容の概要**：メール件名にはエンティティの種類、ID、名前、タグ（存在する場合）が含まれる。本文にはコメント作成者名、対象エンティティ情報、コメント内容、および該当エンティティへのリンクが含まれる。

**期待されるアクション**：受信者はメール本文に記載されたリンクからエンティティ詳細画面に直接アクセスし、コメント内容を確認した上で必要に応じて返信や対応を行うことが期待される。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 通常 |
| リトライ | Active Jobのデフォルト設定に依存 |

### 送信先決定ロジック

1. 対象エンティティのsubscribed_usersフィールドからユーザーIDリストを取得
2. コメント作成者のユーザーIDを除外
3. 残ったユーザーIDに基づきUserモデルを検索
4. 各ユーザーについて以下の条件を満たす場合のみ送信対象とする
   - `emailable?`がtrueを返す（confirmed? AND NOT awaits_approval? AND NOT suspended? AND email.present?）
   - `subscribe_to_comment_replies?`がtrueを返す（ユーザー設定で購読オン）

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | Setting.dig(:email_comment_replies, :address) または Setting.dig(:smtp, :from) または "noreply@fatfreecrm.com" |
| 送信元名称 | コメント作成者のフルネーム（設定アドレスにname部がない場合に付与） |
| 件名 | RE: [{entity_type}:{entity_id}] {entity_name} ({tags})（タグが存在する場合） |
| 形式 | テキスト（text/plain） |

### 本文テンプレート

```
{コメント作成者名} commented on {エンティティ種別}: {エンティティ名}

{コメント内容}


--
Reply to this email directly to add a new comment, or view the {エンティティ種別} online: {エンティティURL}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 本通知には添付ファイルはない |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @entity | コメント対象エンティティ | comment.commentable | Yes |
| @entity_type | エンティティ種別名 | @entity.class.to_s | Yes |
| @entity_name | エンティティ名 | @entity.full_name または @entity.name | Yes |
| @comment | コメントオブジェクト | 引数で渡されたcomment | Yes |
| @user | コメント作成者 | comment.user | Yes |
| user | 通知受信者 | 引数で渡されたuser | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| モデルコールバック | Comment after_create | subscribed_usersに登録されたユーザーが存在 | コメント作成完了時に自動発火 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| コメント作成者自身 | 自分が作成したコメントについては通知を受け取らない |
| emailable?がfalse | 未確認、承認待ち、停止中、メールアドレス未設定のユーザー |
| subscribe_to_comment_replies?がfalse | ユーザー設定でコメント通知を無効にしている場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[コメント作成] --> B[after_create: subscribe_mentioned_users]
    B --> C[after_create: subscribe_user_to_entity]
    C --> D[after_create: notify_subscribers]
    D --> E[subscribed_usersから通知対象取得]
    E --> F{コメント作成者を除外}
    F --> G[各ユーザーについてループ]
    G --> H{emailable?}
    H -->|No| I[スキップ]
    H -->|Yes| J{subscribe_to_comment_replies?}
    J -->|No| I
    J -->|Yes| K[SubscriptionMailer.comment_notification.deliver_later]
    K --> L[メール送信キューに追加]
    L --> M[Active Jobによる非同期送信]
    I --> N{次のユーザー}
    N -->|あり| G
    N -->|なし| O[終了]
    M --> O
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| comments | コメント情報の取得 | commentable_type、commentable_idでポリモーフィック関連 |
| users | 購読者情報の取得 | email、emailable条件の確認 |
| accounts/contacts/leads/opportunities/campaigns/tasks | エンティティ情報、subscribed_users | ポリモーフィック参照 |
| settings | SMTP設定、送信元アドレス | Settingモデル経由 |

### テーブル別参照項目詳細

#### comments

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | コメント識別 | 作成されたコメント |
| user_id | コメント作成者 | - |
| commentable_id | 対象エンティティID | - |
| commentable_type | 対象エンティティ種別 | - |
| comment | コメント本文 | - |

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザー識別 | subscribed_usersに含まれるID |
| email | 送信先メールアドレス | - |
| first_name, last_name | フルネーム表示 | - |
| confirmed_at | メール送信可否判定 | emailable?の判定 |
| suspended_at | メール送信可否判定 | emailable?の判定 |
| subscribe_to_comment_replies | 通知設定 | 購読オプトイン |

#### エンティティテーブル（accounts、contacts、leads等）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | エンティティ識別 | commentable_id |
| name / first_name, last_name | エンティティ名 | 件名・本文表示用 |
| subscribed_users | 購読者リスト | シリアライズされた配列 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| エンティティテーブル | UPDATE | コメント作成者をsubscribed_usersに追加 |

#### subscribed_users更新

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| UPDATE | subscribed_users | 既存配列 + コメント作成者ID | subscribe_user_to_entityメソッドで実行 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPサーバー接続エラー | Active Jobのリトライ機構に委譲 |
| テンプレートエラー | 変数未設定 | 標準例外処理（ログ出力） |
| 宛先不正 | emailカラムが不正な形式 | emailable?でフィルタリング |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Active Jobのデフォルト設定（通常3回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なネットワークエラー、SMTPサーバーエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 設定なし（システムのSMTP制限に依存） |
| 1日あたり上限 | 設定なし |

### 配信時間帯

送信時間帯の制限はなし。コメント作成時に即座にキューに追加され、非同期で送信される。

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

- コメント内容はsanitize処理を経て本文に含まれる（HTMLタグの除去）
- 送信先メールアドレスはデータベースに保存された確認済みアドレスのみ
- 停止中・未承認ユーザーへの送信はemailable?チェックで防止
- privateコメントについても同様のロジックで送信されるため、subscribed_usersの管理が重要

## 備考

- @username形式でメンションされたユーザーは、コメント作成前（before_create）にsubscribed_usersに追加される
- コメント作成者自身も自動的にsubscribed_usersに追加されるが、自分宛ての通知は除外される
- 複数のエンティティタイプ（Account、Contact、Lead、Opportunity、Campaign、Task）で同一のロジックが適用される

---

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

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

### 推奨読解順序

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

コメントとエンティティの関係性、購読者リストの構造を理解することが最初のステップである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | comment.rb | `app/models/polymorphic/comment.rb` | Commentモデルの構造、ポリモーフィック関連、コールバック定義 |
| 1-2 | account.rb | `app/models/entities/account.rb` | subscribed_usersのシリアライズ方法、エンティティ基本構造 |
| 1-3 | user.rb | `app/models/users/user.rb` | emailable?メソッド、subscribe_to_comment_replies属性 |

**読解のコツ**: Railsのbelongs_to :commentable, polymorphic: trueにより、Commentは複数のエンティティタイプ（Account、Contact等）と関連付けられる。subscribed_usersはserialize :subscribed_users, type: Arrayで配列としてYAML形式でDBに保存される。

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

コメント作成時のコールバックチェーンが通知の起点となる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | comment.rb | `app/models/polymorphic/comment.rb` | before_create、after_createコールバックの実行順序 |

**主要処理フロー**:
1. **34行目**: `before_create :subscribe_mentioned_users` - @メンションされたユーザーを購読者に追加
2. **35行目**: `after_create :subscribe_user_to_entity, :notify_subscribers` - コメント作成者を購読者に追加後、通知送信

#### Step 3: 購読者登録ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | comment.rb | `app/models/polymorphic/comment.rb` | subscribe_mentioned_users、subscribe_user_to_entityメソッド |

**主要処理フロー**:
- **63-70行目**: `subscribe_mentioned_users` - コメント本文から@usernameパターンを正規表現で抽出し、該当ユーザーを購読者に追加
- **48-51行目**: `subscribe_user_to_entity` - commentable.subscribed_usersにユーザーIDを追加してsave

#### Step 4: 通知送信ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | comment.rb | `app/models/polymorphic/comment.rb` | notify_subscribersメソッド |
| 4-2 | subscription_mailer.rb | `app/mailers/subscription_mailer.rb` | comment_notificationメソッド |
| 4-3 | comment_notification.text.erb | `app/views/subscription_mailer/comment_notification.text.erb` | メールテンプレート |

**主要処理フロー**:
- **54-58行目（comment.rb）**: 購読者リストからコメント作成者を除外し、emailable?かつsubscribe_to_comment_replies?のユーザーに対してdeliver_later
- **9-25行目（subscription_mailer.rb）**: メール件名の組み立て（エンティティ情報、タグ）、送信元アドレスの決定
- **1-7行目（template）**: I18n翻訳を使用した本文生成

#### Step 5: 送信可否判定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | user.rb | `app/models/users/user.rb` | emailable?メソッドの条件 |

**主要処理フロー**:
- **138-140行目**: `confirmed? && !awaits_approval? && !suspended? && email.present?`

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

```
CommentsController#create（または他の作成元）
    │
    └─ Comment.create
            │
            ├─ [before_create] subscribe_mentioned_users
            │      └─ User.find_by_username
            │      └─ subscribe_user_to_entity (メンションユーザー用)
            │
            └─ [after_create] subscribe_user_to_entity
            │      └─ commentable.subscribed_users << user.id
            │      └─ commentable.save
            │
            └─ [after_create] notify_subscribers
                   └─ User.where(id: subscribed_users)
                   └─ user.emailable?
                   └─ user.subscribe_to_comment_replies?
                   └─ SubscriptionMailer.comment_notification(subscriber, self)
                          └─ mail(subject:, to:, from:, date:)
                                 └─ comment_notification.text.erb
```

### データフロー図

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

Comment.create ───────▶ subscribe_mentioned_users ───────▶ subscribed_users更新
       │                        │
       │                        ▼
       │               subscribe_user_to_entity ─────────▶ subscribed_users更新
       │                        │
       │                        ▼
       │               notify_subscribers
       │                        │
       ├─ comment       User.where(subscribed_users)
       ├─ commentable           │
       └─ user                  ▼
                        emailable? / subscribe_to_comment_replies?
                                │
                                ▼
                        SubscriptionMailer.comment_notification
                                │
                                ▼
                        ActiveJob Queue ──────────────────▶ SMTP Server ──▶ Email
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| comment.rb | `app/models/polymorphic/comment.rb` | ソース | Commentモデル、コールバック、通知トリガー |
| subscription_mailer.rb | `app/mailers/subscription_mailer.rb` | ソース | メーラークラス、メール生成ロジック |
| comment_notification.text.erb | `app/views/subscription_mailer/comment_notification.text.erb` | テンプレート | メール本文テンプレート |
| user.rb | `app/models/users/user.rb` | ソース | ユーザーモデル、emailable?メソッド |
| account.rb | `app/models/entities/account.rb` | ソース | Accountエンティティ、subscribed_users定義 |
| contact.rb | `app/models/entities/contact.rb` | ソース | Contactエンティティ、subscribed_users定義 |
| lead.rb | `app/models/entities/lead.rb` | ソース | Leadエンティティ、subscribed_users定義 |
| opportunity.rb | `app/models/entities/opportunity.rb` | ソース | Opportunityエンティティ、subscribed_users定義 |
| campaign.rb | `app/models/entities/campaign.rb` | ソース | Campaignエンティティ、subscribed_users定義 |
| task.rb | `app/models/polymorphic/task.rb` | ソース | Taskエンティティ、subscribed_users定義 |
| fat_free_crm.en-US.yml | `config/locales/fat_free_crm.en-US.yml` | 設定 | 翻訳定義（comment_notification） |
| action_mailer.rb | `config/initializers/action_mailer.rb` | 設定 | ActionMailer初期化、SMTP設定 |
| subscription_mailer_spec.rb | `spec/mailers/subscription_mailer_spec.rb` | テスト | メーラーのテスト |
| comment_spec.rb | `spec/models/polymorphic/comment_spec.rb` | テスト | Commentモデルのテスト |
