# 機能設計書 54-プライベートノート

## 概要

本ドキュメントは、Chatwootにおけるプライベートノート機能について記述する。本機能は、エージェントが会話内で他のエージェントにのみ表示される内部メモを作成する機能である。顧客には表示されず、チーム内での情報共有やコンテキストの引き継ぎに使用される。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポートにおいて、エージェント間で顧客に見せたくない情報（対応の経緯、注意事項、内部調整等）を共有する必要がある。プライベートノートにより、同一会話を引き継ぐ別のエージェントに背景情報を伝えることができ、シームレスな顧客対応が可能となる。

**機能の利用シーン**：
- 会話を別のエージェントに引き継ぐ際の申し送り事項の記録
- 顧客の過去のトラブルや特記事項のメモ
- 対応方針の内部協議・相談
- 技術的な調査結果の共有
- @メンションで特定のエージェントに通知を送る

**主要な処理内容**：
1. プライベートフラグ付きメッセージの作成
2. メッセージの表示フィルタリング（private=true のメッセージは顧客に非表示）
3. WebSocket配信時のフィルタリング
4. 初回返信判定からの除外
5. 待機時間計算からの除外

**関連システム・外部連携**：なし（内部機能のみ）。

**権限による制御**：プライベートノートの作成・閲覧はエージェント権限以上が必要。顧客（Contact）には表示されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | プライベートノートの作成・表示 |

## 機能種別

CRUD操作 / メッセージングフィルタリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| content | String | Yes | ノート内容 | 最大150,000文字 |
| private | Boolean | Yes | プライベートフラグ | true固定 |
| message_type | String | No | メッセージタイプ | outgoing |

### 入力データソース

- エージェントの会話詳細画面からの入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| message.id | Integer | メッセージID |
| message.content | String | ノート内容 |
| message.private | Boolean | true（プライベート） |
| message.message_type | Enum | outgoing |
| message.sender | User | 送信者（エージェント） |

### 出力先

- messagesテーブル
- エージェント向けWebSocket配信（顧客には配信されない）

## 処理フロー

### 処理シーケンス

```
1. プライベートノート作成リクエスト
   └─ content, private=true を受け取る
2. メッセージ保存
   └─ message_type=outgoing, private=true で保存
3. コールバック処理
   └─ 初回返信判定をスキップ
   └─ 待機時間更新をスキップ
4. WebSocket配信
   └─ エージェントにのみ配信
5. 表示時フィルタリング
   └─ chat scope で private=false のみ表示
```

### フローチャート

```mermaid
flowchart TD
    A[プライベートノート作成] --> B[MessageBuilder#perform]
    B --> C[private=true でメッセージ作成]
    C --> D[DB保存]
    D --> E{private=true?}
    E -->|Yes| F[初回返信判定をスキップ]
    E -->|No| G[通常の返信処理]
    F --> H[待機時間更新をスキップ]
    H --> I[エージェントにのみWebSocket配信]
    G --> J[顧客にもWebSocket配信]
    I --> K[終了]
    J --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-54-01 | 顧客非表示 | private=true のメッセージは顧客に表示しない | 常時 |
| BR-54-02 | 初回返信除外 | プライベートノートは初回返信時間の計算に含めない | 初回返信判定時 |
| BR-54-03 | 待機時間除外 | プライベートノートは待機時間のリセット対象外 | 待機時間計算時 |
| BR-54-04 | chat scope除外 | chat scopeでは private=false のメッセージのみ取得 | メッセージ取得時 |
| BR-54-05 | メール通知除外 | private=true のメッセージはメール通知対象外 | 通知処理時 |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| プライベートノート作成 | messages | INSERT | private=true でメッセージを挿入 |

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

#### messages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | 会話のaccount_id | 必須 |
| INSERT | inbox_id | 会話のinbox_id | 必須 |
| INSERT | conversation_id | 会話のID | 必須 |
| INSERT | message_type | 1（outgoing） | 送信メッセージ |
| INSERT | content | ノート内容 | 最大150,000文字 |
| INSERT | private | true | プライベートフラグ |
| INSERT | sender_type | 'User' | エージェント |
| INSERT | sender_id | ユーザーID | 作成者 |

### privateカラムの定義

- 型: boolean
- デフォルト: false
- NOT NULL制約あり

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バリデーションエラー | コンテンツが空または150,000文字超過 | エラーメッセージを返却 |
| - | 権限エラー | 権限のないユーザーによる作成 | 403 Forbiddenを返却 |

### リトライ仕様

なし（同期処理）

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

メッセージ作成は単一のINSERT操作であり、特別なトランザクション管理は不要。

## パフォーマンス要件

- メッセージ作成は通常100ms以内で完了
- chat scopeのインデックスにより、プライベートメッセージのフィルタリングは高速

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

- プライベートノートは顧客に絶対に表示されてはならない
- WebSocket配信時のフィルタリングが必須
- Widget/Public APIからの取得時に除外が必須
- 顧客がアクセス可能なエンドポイントからは取得できないようにする

## 備考

- privateカラムのデフォルトはfalse
- 顧客向けのchat scopeでは `where(private: false)` が適用される

---

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

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

### 推奨読解順序

#### Step 1: メッセージモデルのprivate関連定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | message.rb | `app/models/message.rb` | privateカラムの定義、chat scope、関連ロジック |

**主要処理フロー**:
- **12行目**: privateカラムの定義（:boolean, default: FALSE, not null）
- **116行目**: chat scopeで `where(private: false)` を定義
- **197行目**: email_notifiable_message?でprivate?をチェック
- **211行目**: valid_first_reply?でprivate?をチェック
- **215行目**: where.not(private: true)で初回返信判定から除外
- **327行目**: update_waiting_sinceで!privateをチェック

**読解のコツ**: Railsのbooleanカラムは自動的に`private?`メソッドを生成する。コード内では`private?`でプライベートフラグをチェックしている。

#### Step 2: メッセージビルダーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | message_builder.rb | `app/builders/messages/message_builder.rb` | privateパラメータの受け取りと設定 |

**主要処理フロー**:
- **10行目**: @private = params[:private] || false でパラメータを取得
- **138行目**: message_paramsでprivate: @privateを設定

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | messages_controller.rb | `app/controllers/api/v1/accounts/conversations/messages_controller.rb` | プライベートノート作成のエントリーポイント |

**主要処理フロー**:
- **8-13行目**: createアクションでMessageBuilderを呼び出し

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

```
Api::V1::Accounts::Conversations::MessagesController#create
    │
    └─ Messages::MessageBuilder#perform
           │
           ├─ params[:private] = true を受け取り
           │
           └─ message.save! (private: true)
                  │
                  ├─ コールバック処理
                  │      ├─ valid_first_reply? → false (private のため)
                  │      └─ update_waiting_since → スキップ (private のため)
                  │
                  └─ MESSAGE_CREATED イベント発行
```

### データフロー図

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

エージェント入力    MessageBuilder           messages (private=true)
content + private ───▶ DB保存           ───▶  WebSocket（エージェントのみ）
= true
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| message.rb | `app/models/message.rb` | モデル | privateカラム定義、chat scope、関連ロジック |
| message_builder.rb | `app/builders/messages/message_builder.rb` | ビルダー | プライベートノート作成処理 |
| messages_controller.rb | `app/controllers/api/v1/accounts/conversations/messages_controller.rb` | コントローラー | API エントリーポイント |
