# 機能設計書 35-トランスクリプト

## 概要

本ドキュメントは、Chatwootにおける会話トランスクリプト機能の設計仕様を記載する。会話の全メッセージ履歴をメール形式で指定されたメールアドレスに送信する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポート業務において、会話の履歴を外部に共有する必要がある場合がある。例えば、顧客への対応履歴の送付、社内での情報共有、コンプライアンス対応としての記録保持、外部システムへの履歴転送などの用途で、トランスクリプト機能が活用される。

**機能の利用シーン**：
- 顧客に対応履歴をメールで送付する場合
- 上司やチームメンバーに会話内容を共有する場合
- 法的対応や監査のために会話記録を保存する場合
- CRMや外部システムに会話履歴を連携する場合
- 会話終了後の振り返りや品質管理のためにダウンロードする場合

**主要な処理内容**：
1. メールアドレスの受け取り：送信先メールアドレスをパラメータとして受け取る
2. メッセージのフィルタリング：会話内のincoming/outgoingメッセージを抽出
3. メール送信：ConversationReplyMailerを使用して非同期でメール送信

**関連システム・外部連携**：
- ConversationReplyMailer：メール送信処理
- Action Mailer / deliver_later：非同期メール配信
- SMTPサーバー：実際のメール送信

**権限による制御**：
- 会話へのアクセス権限（ConversationPolicy#show?）を持つユーザーのみがトランスクリプトを送信可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | トランスクリプト送信ボタンの操作 |

## 機能種別

メール送信 / データ連携

## 入力仕様

### 入力パラメータ

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

### 入力データソース

- URLパラメータ: account_id, id (conversation display_id)
- リクエストボディ: email

## 出力仕様

### 出力データ

#### 正常時

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

#### エラー時

| 項目名 | 型 | 説明 |
|--------|-----|------|
| error | String | エラーメッセージ（"email param missing"） |

### 出力先

- HTTPレスポンス（head :ok または JSON error）
- メール配信（非同期）

### メール内容

| 項目名 | 説明 |
|--------|------|
| Subject | "[#{display_id}] Conversation Transcript" |
| From | アカウントのサポートメールアドレス |
| To | リクエストで指定されたメールアドレス |
| Body | 会話内の全メッセージ（incoming/outgoing）のHTML形式リスト |

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ account_id, conversation_idの検証

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

3. メールアドレス検証
   └─ params[:email].blank? のチェック
   └─ 空の場合は422エラーを返却

4. トランスクリプトメッセージ抽出
   └─ conversation.messages.chat.select(&:conversation_transcriptable?)
   └─ incoming?またはoutgoing?のメッセージのみ

5. メール送信
   └─ ConversationReplyMailer.conversation_transcript()
   └─ deliver_laterで非同期送信

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

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[会話取得]
    B --> C{認可チェック}
    C -->|失敗| D[403 Forbidden]
    C -->|成功| E{メールアドレス存在?}
    E -->|No| F[422 email param missing]
    E -->|Yes| G[transcriptableメッセージ抽出]
    G --> H[ConversationReplyMailer生成]
    H --> I[deliver_later]
    I --> J[200 OK]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-35-1 | メールアドレス必須 | email パラメータが空の場合はエラーを返却 | 常時 |
| BR-35-2 | メッセージフィルタリング | incoming または outgoing のメッセージのみをトランスクリプトに含める | 常時 |
| BR-35-3 | 非同期送信 | メール送信はdeliver_laterで非同期実行 | 常時 |
| BR-35-4 | SMTP設定必須 | SMTPが設定されていない場合はメール送信処理をスキップ | 常時 |
| BR-35-5 | タイムゾーン対応 | インボックスにタイムゾーンが設定されている場合はそれを使用 | メール本文生成時 |

### 計算ロジック

- メール件名: `"[##{conversation.display_id}] #{I18n.t('conversations.reply.transcript_subject')}"`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| transcript | conversations | SELECT | 会話の取得 |
| transcript | messages | SELECT | メッセージ一覧取得 |
| transcript | attachments | SELECT | 添付ファイル取得（メール本文用） |

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

本機能は読み取り専用。DBへの書き込みは発生しない。

## エラー処理

### エラーケース一覧

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

### リトライ仕様

- メール送信はSidekiqで非同期実行されるため、送信失敗時はSidekiqのリトライ機構に従う

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

- 読み取り専用のため、トランザクションは使用しない
- メール送信は非同期のため、メイン処理とは別のジョブとして実行

## パフォーマンス要件

- メール送信は非同期で行われるため、APIレスポンス時間に影響しない
- 大量のメッセージがある会話でもレスポンスは即座に返却

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

- ConversationPolicy#show?による会話アクセス権限チェック
- メールアドレスの検証は空チェックのみ（フォーマット検証はなし）
- メール内容には会話の全メッセージが含まれるため、送信先には注意が必要

## 備考

- 添付ファイルはメール本文にリンクとして含まれる
- マークダウン形式のメッセージはHTML形式にレンダリングされる
- アクティビティメッセージ（システムメッセージ）はトランスクリプトに含まれない

---

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

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

### 推奨読解順序

#### Step 1: メッセージフィルタリングを理解する

まず、どのメッセージがトランスクリプトに含まれるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | message_filter_helpers.rb | `app/models/concerns/message_filter_helpers.rb` | conversation_transcriptable?メソッド |

**読解のコツ**: incoming?またはoutgoing?のメッセージのみがトランスクリプト対象。アクティビティメッセージは除外される。

**主要処理フロー**:
- **20-22行目**: conversation_transcriptable? - トランスクリプト対象判定
  - incoming?またはoutgoing?の場合のみtrue

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

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

**主要処理フロー**:
- **71-76行目**: transcript - トランスクリプトエンドポイント
  - params[:email].blank?でメールアドレスチェック
  - 空の場合は422エラー返却
  - ConversationReplyMailer.conversation_transcript().deliver_later

#### Step 3: メーラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | conversation_reply_mailer.rb | `app/mailers/conversation_reply_mailer.rb` | conversation_transcriptメソッド |

**主要処理フロー**:
- **45-60行目**: conversation_transcript - トランスクリプトメール生成
  - smtp_config_set_or_development?でSMTP設定チェック
  - init_conversation_attributesで会話情報初期化
  - messages.chat.select(&:conversation_transcriptable?)でメッセージ抽出
  - mail()でメール送信

#### Step 4: メールテンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | conversation_transcript.html.erb | `app/views/mailers/conversation_reply_mailer/conversation_transcript.html.erb` | メール本文テンプレート |

**主要処理フロー**:
- **1-50行目**: メッセージループ
  - 送信者名、メール情報（Email Inbox用）の表示
  - ChatwootMarkdownRenderer でマークダウンをHTMLに変換
  - 添付ファイルのリンク表示
  - タイムゾーン対応の日時表示

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

```
Api::V1::Accounts::ConversationsController#transcript
    │
    ├─ conversation (before_action)
    │      └─ Current.account.conversations.find_by!
    │      └─ authorize @conversation, :show?
    │
    ├─ params[:email].blank? チェック
    │      └─ 422エラー（空の場合）
    │
    └─ ConversationReplyMailer
           └─ with(account: @conversation.account)
           └─ conversation_transcript(@conversation, params[:email])
                  │
                  ├─ smtp_config_set_or_development?
                  ├─ init_conversation_attributes
                  ├─ messages.chat.select(&:conversation_transcriptable?)
                  │      └─ MessageFilterHelpers#conversation_transcriptable?
                  │
                  └─ mail(to:, from:, subject:)
                         └─ deliver_later
                                └─ Sidekiq Job
```

### データフロー図

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

conversation_id ───▶ ConversationsController  ───▶ 200 OK
email                      │
                           ▼
                ConversationReplyMailer
                           │
                    ┌──────┴──────┐
                    ▼             ▼
              Conversation    Messages
              (account,       (incoming,
               inbox)          outgoing)
                    │             │
                    └──────┬──────┘
                           ▼
                   Mail Template
                           │
                           ▼
                   deliver_later
                           │
                           ▼
                    SMTP Server
                           │
                           ▼
                    [メール送信]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| conversations_controller.rb | `app/controllers/api/v1/accounts/conversations_controller.rb` | コントローラー | transcriptエンドポイント（71-76行目） |
| conversation_reply_mailer.rb | `app/mailers/conversation_reply_mailer.rb` | メーラー | conversation_transcriptメソッド（45-60行目） |
| message_filter_helpers.rb | `app/models/concerns/message_filter_helpers.rb` | モジュール | conversation_transcriptable?（20-22行目） |
| conversation_transcript.html.erb | `app/views/mailers/conversation_reply_mailer/conversation_transcript.html.erb` | テンプレート | メール本文HTML |
| conversation_policy.rb | `app/policies/conversation_policy.rb` | ポリシー | アクセス権限制御 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（147行目） |
