# 機能設計書 26-リードのプロモート

## 概要

本ドキュメントは、Fat Free CRMシステムにおけるリードのプロモート機能の設計を定義する。この機能は、リードを連絡先、取引先、商談に実際に変換し、リードのステータスを「converted（変換済み）」に変更する。

### 本機能の処理概要

リードのプロモート機能は、機能No.25「リードの変換（コンバート）」画面から送信されたデータに基づき、リードを連絡先・取引先・商談に変換する機能である。

**業務上の目的・背景**：営業プロセスにおいて、見込み顧客が購買プロセスに進んだ段階で、リードを正式な顧客（連絡先・取引先）および商談に変換する必要がある。この変換により、リードの情報を引き継ぎつつ、顧客管理と商談管理を開始できる。変換済みのリードは営業パイプラインの分析対象外となる。

**機能の利用シーン**：変換画面（機能No.25）で入力された情報を基に、リードから連絡先・取引先・商談を一括作成する場合に利用される。既存取引先を選択した場合は取引先作成をスキップし、商談名を入力しなかった場合は商談作成をスキップする。

**主要な処理内容**：
1. 取引先の作成または選択（create_or_select_for）
2. 商談の作成（create_for）- オプション
3. 連絡先の作成（create_for）
4. リードステータスの更新（"converted"）
5. サイドバー情報の更新

**関連システム・外部連携**：取引先機能、連絡先機能、商談機能との連携により、リードからの一括変換を実現する。

**権限による制御**：CanCanによる認可制御が行われ、リードへの更新権限を持つユーザーのみがプロモートを実行可能である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | リード変換画面 | 遷移先機能 | リードを連絡先・取引先・商談に変換実行 |

## 機能種別

CRUD操作（CREATE/UPDATE） / データ変換処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | リードID | 数値、存在確認 |
| account[id] | Integer | No | 既存取引先ID | 存在するアカウントID |
| account[name] | String | 条件付き | 新規取引先名 | account[id]がない場合は必須 |
| account[user_id] | Integer | No | 取引先作成者ID | 存在するユーザーID |
| account[assigned_to] | Integer | No | 取引先担当者ID | 存在するユーザーID |
| opportunity[name] | String | No | 商談名 | 入力時のみ商談作成 |
| opportunity[stage] | String | No | 商談ステージ | 設定値リストから選択 |
| opportunity[probability] | Integer | No | 確度 | 0-100の整数 |
| opportunity[amount] | Decimal | No | 金額 | 正の数値 |
| opportunity[closes_on] | Date | No | クローズ予定日 | 日付形式 |
| access | String | No | アクセス権限 | Public/Private/Shared/Lead |

### 入力データソース

- URL: PUT/PATCH `/leads/:id/promote`
- HTTPメソッド: PUT/PATCH
- データソース: 変換フォームからのPOST

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @lead | Lead | 変換されたリードオブジェクト |
| @account | Account | 作成/選択された取引先 |
| @opportunity | Opportunity | 作成された商談（オプション） |
| @contact | Contact | 作成された連絡先 |
| @accounts | Array | 取引先一覧（エラー時） |
| @stage | Array | 商談ステージ一覧 |

### 出力先

- JS形式: AJAX応答（promote.js.haml）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ PUT /leads/:id/promote を受信
2. 認証・認可チェック
   └─ CanCanによる権限確認
3. リード取得
   └─ 指定IDのリードを取得
4. promoteメソッド呼び出し
   └─ Lead#promoteで取引先・商談・連絡先を作成
5. エラーチェック
   └─ 各エンティティのエラー確認
6. ステータス更新
   └─ 成功時: @lead.convert（status="converted"）
7. サイドバー更新
   └─ 呼び出し元に応じてサイドバー情報を更新
8. レスポンス生成
   └─ 成功/エラーに応じた出力
```

### フローチャート

```mermaid
flowchart TD
    A[PUT /leads/:id/promote] --> B[認証チェック]
    B --> C{認可チェック}
    C -->|許可| D[リードデータ取得]
    C -->|拒否| E[403 Forbidden]
    D --> F[@lead.promote実行]
    F --> G[Account.create_or_select_for]
    G --> H[Opportunity.create_for]
    H --> I[Contact.create_for]
    I --> J{エラーチェック}
    J -->|エラーなし| K[@lead.convert]
    J -->|エラーあり| L[エラー情報設定]
    K --> M[update_sidebar]
    M --> N[成功レスポンス]
    L --> O[エラーレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-26-01 | 取引先作成/選択 | account[id]があれば既存取引先を使用、なければaccount[name]で新規作成 | 常時 |
| BR-26-02 | 商談作成条件 | opportunity[name]が入力された場合のみ商談を作成 | opportunity[name]が存在時 |
| BR-26-03 | 連絡先情報引き継ぎ | リードのfirst_name、last_name、email、phone等を連絡先にコピー | 常時 |
| BR-26-04 | カスタムフィールド引き継ぎ | リードのカスタムフィールドを連絡先にコピー（フィールドが存在する場合） | 常時 |
| BR-26-05 | ステータス変更 | プロモート成功時にリードのstatusを"converted"に変更 | 成功時 |
| BR-26-06 | 権限継承 | accessが"Lead"の場合、リードの権限を連絡先・取引先・商談に継承 | access="Lead"時 |
| BR-26-07 | リード-連絡先関連付け | 作成された連絡先のlead_idに元のリードIDを設定 | 常時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 取引先作成/取得 | accounts | SELECT/INSERT | 既存取引先の取得または新規作成 |
| 商談作成 | opportunities | INSERT | 新規商談の作成（オプション） |
| 連絡先作成 | contacts | INSERT | 新規連絡先の作成 |
| 取引先-連絡先関連 | account_contacts | INSERT | 取引先と連絡先の関連付け |
| 取引先-商談関連 | account_opportunities | INSERT | 取引先と商談の関連付け |
| 連絡先-商談関連 | contact_opportunities | INSERT | 連絡先と商談の関連付け |
| リード更新 | leads | UPDATE | statusを"converted"に更新 |
| アドレスコピー | addresses | INSERT | リードの住所を連絡先にコピー |

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

#### leads

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | status | 'converted' | convertメソッドで更新 |

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | lead_id | リードID | 元のリードとの関連付け |
| INSERT | user_id | account[user_id] または取引先のuser_id | 作成者 |
| INSERT | assigned_to | account[assigned_to] | 担当者 |
| INSERT | first_name | リードのfirst_name | 名前のコピー |
| INSERT | last_name | リードのlast_name | 姓のコピー |
| INSERT | email | リードのemail | メールのコピー |
| INSERT | phone | リードのphone | 電話番号のコピー |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定IDのリードが存在しない | エラーページ表示 |
| 403 | Forbidden | 更新権限がない | エラーページ表示 |
| 422 | Validation Error | 取引先/商談/連絡先のバリデーションエラー | エラーメッセージ表示 |

### リトライ仕様

リトライは実装されていない（ユーザー操作による再送信）

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

- 取引先、商談、連絡先の作成は個別のトランザクションで実行
- リードのステータス更新はすべての作成が成功した後に実行
- いずれかのエンティティでエラーが発生した場合、ステータス更新は行われない

## パフォーマンス要件

- プロモート処理のレスポンスタイム: 3秒以内

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

- CanCanによる認可制御でアクセス権限をチェック
- リードの権限継承オプション（access="Lead"）によるセキュリティ一貫性

## 備考

- 変換済みリードは一覧画面のフィルタで確認可能
- 商談名を入力しない場合は商談は作成されない

---

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

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

### 推奨読解順序

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

変換処理に関わるモデルの関連を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lead.rb | `app/models/entities/lead.rb` | promoteメソッド、convertメソッド |
| 1-2 | account.rb | `app/models/entities/account.rb` | create_or_select_forメソッド |
| 1-3 | opportunity.rb | `app/models/entities/opportunity.rb` | create_forメソッド |
| 1-4 | contact.rb | `app/models/entities/contact.rb` | create_forメソッド |

**読解のコツ**:
- **lead.rb 124-133行目**: promoteメソッド - 取引先・商談・連絡先を作成して返す
- **lead.rb 136-138行目**: convertメソッド - statusを"converted"に更新
- **account.rb 119-137行目**: create_or_select_for - 既存取引先の検索または新規作成
- **opportunity.rb 153-167行目**: create_for - リードから商談を作成
- **contact.rb 171-205行目**: create_for - リードから連絡先を作成

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

コントローラーのpromoteアクションがエントリーポイントである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | leads_controller.rb | `app/controllers/entities/leads_controller.rb` | promoteアクションの実装 |

**主要処理フロー**:
1. **120-134行目** (promoteアクション):
   - **121行目**: @lead.promote(params)呼び出し
   - **122行目**: @accounts取得（エラー時の再表示用）
   - **123行目**: @stage取得（エラー時の再表示用）
   - **126行目**: エラーチェック（account, opportunity, contactすべて）
   - **127行目**: 成功時に@lead.convert実行

#### Step 3: ビューを理解する

プロモート完了後の画面更新を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | promote.js.haml | `app/views/leads/promote.js.haml` | 成功/エラー時の画面更新 |

**主要処理フロー**:
- **5-20行目**: status=="converted"時の画面更新
- **11-14行目**: キャンペーン画面からの呼び出し時は商談を追加表示

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

```
LeadsController#promote
    │
    ├─ @lead.promote(params)
    │      │
    │      ├─ Account.create_or_select_for(self, account_params)
    │      │      ├─ Account.find(params[:id]) (既存選択時)
    │      │      └─ Account.new(...).save (新規作成時)
    │      │
    │      ├─ Opportunity.create_for(self, account, opportunity_params)
    │      │      └─ Opportunity.new(...).save (name入力時のみ)
    │      │
    │      └─ Contact.create_for(self, account, opportunity, params)
    │             ├─ Contact.new(リード属性をコピー)
    │             ├─ AccountContact.new (取引先との関連)
    │             └─ contact.opportunities << opportunity (商談との関連)
    │
    ├─ エラーチェック
    │      └─ @account.errors.empty? && @opportunity.errors.empty? && @contact.errors.empty?
    │
    ├─ @lead.convert (成功時)
    │      └─ update_attribute(:status, "converted")
    │
    └─ update_sidebar
```

### データフロー図

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

PUT /leads/:id/promote ───▶ LeadsController#promote ───▶ JS
      │                           │
      │                           ├─▶ Lead#promote
      │                           │       │
      │                           │       ├─▶ Account.create_or_select_for
      │                           │       │       └─▶ [accounts]テーブル SELECT/INSERT
      │                           │       │
      │                           │       ├─▶ Opportunity.create_for
      │                           │       │       ├─▶ [opportunities]テーブル INSERT
      │                           │       │       └─▶ [account_opportunities]テーブル INSERT
      │                           │       │
      │                           │       └─▶ Contact.create_for
      │                           │               ├─▶ [contacts]テーブル INSERT
      │                           │               ├─▶ [account_contacts]テーブル INSERT
      │                           │               └─▶ [contact_opportunities]テーブル INSERT
      │                           │
      │                           └─▶ Lead#convert
      │                                   └─▶ [leads]テーブル UPDATE (status)
      │
      └─▶ promote.js.haml
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| leads_controller.rb | `app/controllers/entities/leads_controller.rb` | コントローラー | リード操作のエントリーポイント |
| lead.rb | `app/models/entities/lead.rb` | モデル | promoteメソッドの実装 |
| account.rb | `app/models/entities/account.rb` | モデル | create_or_select_forメソッド |
| opportunity.rb | `app/models/entities/opportunity.rb` | モデル | create_forメソッド |
| contact.rb | `app/models/entities/contact.rb` | モデル | create_forメソッド |
| promote.js.haml | `app/views/leads/promote.js.haml` | ビュー | プロモート完了後のDOM更新 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
