# 機能設計書 23-リード編集

## 概要

本ドキュメントは、Fat Free CRMシステムにおけるリード編集機能の設計を定義する。この機能は、既存のリード情報を更新し、キャンペーンの変更やアクセス権限の変更を行う。

### 本機能の処理概要

リード編集機能は、登録済みの見込み顧客情報を最新の状態に保つための機能である。

**業務上の目的・背景**：営業活動の過程でリードの情報は変化する。電話番号やメールアドレスの変更、担当者の変更、ステータスの更新など、リード情報を常に最新に保つことで、正確な顧客管理と効果的な営業活動を支援する。また、キャンペーンの変更時にはリードカウントの整合性を維持する必要がある。

**機能の利用シーン**：リードの連絡先情報が変更された場合、リードの評価（レーティング）を更新する場合、担当者を変更する場合、キャンペーンの紐付けを変更する場合に利用される。

**主要な処理内容**：
1. リード編集フォームの表示（editアクション）
2. 入力データのバリデーション
3. リードデータの更新（キャンペーン変更を含む）
4. キャンペーン別リードカウントの更新（キャンペーン変更時）
5. アクセス権限の更新

**関連システム・外部連携**：キャンペーン機能との連携により、キャンペーン変更時のリードカウント整合性を維持する。

**権限による制御**：CanCanによる認可制御が行われ、ユーザーは自分が作成したリード、自分に割り当てられたリード、または編集権限が付与されたリードのみ編集可能である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 18 | リード編集フォーム | 主画面 | リード情報更新 |
| 18 | リード編集フォーム | 補助機能 | タグ付け（機能No.83） |
| 18 | リード編集フォーム | 補助機能 | アクセス権限管理（機能No.84） |

## 機能種別

CRUD操作（UPDATE） / データ更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | リードID | 数値、存在確認 |
| lead[first_name] | String | 条件付き | 名 | Setting.require_first_namesがtrueの場合必須、最大64文字 |
| lead[last_name] | String | 条件付き | 姓 | Setting.require_last_namesがtrueの場合必須、最大64文字 |
| lead[company] | String | No | 会社名 | 最大64文字 |
| lead[title] | String | No | 役職 | 最大64文字 |
| lead[source] | String | No | リード獲得元 | 設定値リストから選択 |
| lead[status] | String | No | ステータス | 設定値リストから選択 |
| lead[email] | String | No | メールアドレス | 最大64文字 |
| lead[phone] | String | No | 電話番号 | 最大32文字 |
| lead[rating] | Integer | No | 評価（星） | 0-5の整数 |
| lead[campaign_id] | Integer | No | キャンペーンID | 存在するキャンペーンID |
| lead[access] | String | No | アクセス権限 | Public/Private/Shared |
| lead[assigned_to] | Integer | No | 担当者ID | 存在するユーザーID |

### 入力データソース

- URL: PUT `/leads/:id`
- HTTPメソッド: PUT/PATCH
- データソース: Webフォーム入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @lead | Lead | 更新されたリードオブジェクト |
| @campaigns | Array | キャンペーン選択肢（エラー時） |

### 出力先

- HTML形式: リダイレクト（成功時）またはフォーム再表示（エラー時）
- JS形式: AJAX応答（update.js.haml）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ PUT /leads/:id を受信
2. 認証・認可チェック
   └─ CanCanによる権限確認
3. リード取得
   └─ 指定IDのリードを取得
4. アクセス権限設定
   └─ accessパラメータがあれば先に設定
5. 更新処理
   └─ update_with_lead_countersメソッドで更新
6. キャンペーンカウント更新
   └─ キャンペーン変更時は旧・新両方のカウントを更新
7. サイドバーデータ更新
   └─ 呼び出し元に応じてサイドバー情報を更新
8. レスポンス生成
   └─ フォーマットに応じた出力
```

### フローチャート

```mermaid
flowchart TD
    A[PUT /leads/:id] --> B[認証チェック]
    B --> C{認可チェック}
    C -->|許可| D[リードデータ取得]
    C -->|拒否| E[403 Forbidden]
    D --> F[アクセス権限先行設定]
    F --> G[update_with_lead_counters]
    G --> H{キャンペーン変更?}
    H -->|Yes| I[旧キャンペーンカウント減少]
    H -->|No| J[通常更新]
    I --> K[新キャンペーンカウント増加]
    K --> L{保存成功?}
    J --> L
    L -->|Yes| M[サイドバー更新]
    L -->|No| N[エラー情報設定]
    M --> O[レスポンス生成]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-23-01 | キャンペーン変更時カウント調整 | キャンペーン変更時は旧キャンペーンのleads_countを減算し、新キャンペーンのleads_countを加算 | キャンペーン変更時 |
| BR-23-02 | アクセス権限先行設定 | user_ids=メソッドがaccess値に依存するため、accessを先に設定 | 常時 |
| BR-23-03 | 共有アクセス検証 | accessが"Shared"の場合、少なくとも1人のユーザー選択が必要 | access="Shared"時 |
| BR-23-04 | 変換済みリード制限 | statusが"converted"のリードは通常編集に制限あり | converted時 |

### 計算ロジック

キャンペーンカウント更新:
- 旧キャンペーン: `Campaign.decrement_counter(:leads_count, old_campaign_id)`
- 新キャンペーン: `Campaign.increment_counter(:leads_count, new_campaign_id)`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| リード更新 | leads | UPDATE | リード情報の更新 |
| 権限更新 | permissions | DELETE/INSERT | 権限設定の更新 |
| 旧キャンペーン更新 | campaigns | UPDATE | leads_countのデクリメント |
| 新キャンペーン更新 | campaigns | UPDATE | leads_countのインクリメント |
| アドレス更新 | addresses | UPDATE | 住所情報の更新（任意） |
| タグ更新 | taggings | DELETE/INSERT | タグの関連付け更新 |

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

#### leads

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | first_name | params[:lead][:first_name] | 名 |
| UPDATE | last_name | params[:lead][:last_name] | 姓 |
| UPDATE | campaign_id | params[:lead][:campaign_id] | キャンペーン紐付け |
| UPDATE | assigned_to | params[:lead][:assigned_to] | 担当者 |
| UPDATE | access | params[:lead][:access] | アクセス権限 |
| UPDATE | updated_at | 現在日時 | 自動設定 |

#### campaigns（キャンペーン変更時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | leads_count | leads_count - 1 | 旧キャンペーン |
| UPDATE | leads_count | leads_count + 1 | 新キャンペーン |

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

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

- リード更新と権限更新は同一トランザクション内で実行
- キャンペーンカウント更新はupdate_with_lead_countersメソッド内で明示的に制御

## パフォーマンス要件

- 編集フォーム表示のレスポンスタイム: 1秒以内
- 更新処理のレスポンスタイム: 2秒以内

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

- CanCanによる認可制御でアクセス権限をチェック
- 他ユーザーのリードは閲覧権限があっても編集不可
- XSS対策としてビューでのエスケープ処理

## 備考

- 編集フォームは詳細画面からインライン表示される（AJAXモーダル）
- @previousでフォーム表示前の状態を保持し、キャンセル時に復元

---

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

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

### 推奨読解順序

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

リードのデータ構造とキャンペーンカウント更新ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lead.rb | `app/models/entities/lead.rb` | update_with_lead_countersメソッド、カウンター操作 |

**読解のコツ**:
- **108-119行目**: update_with_lead_countersメソッドでキャンペーン変更時のカウント調整ロジック
- **170-177行目**: increment_leads_count/decrement_leads_countの実装

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

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

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

**主要処理フロー**:
1. **54-60行目** (editアクション): キャンペーン取得、@previous設定
2. **83-93行目** (updateアクション): access先行設定、update_with_lead_counters呼び出し、サイドバー更新
3. **86行目**: accessを先に設定（user_ids=がaccess値に依存）
4. **87行目**: update_with_lead_countersでキャンペーン変更対応

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

編集フォームの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _edit.html.haml | `app/views/leads/_edit.html.haml` | 編集フォームの構造 |

**主要処理フロー**:
- **1行目**: .remoteクラスでモーダル表示
- **2行目**: simple_form_forでフォーム生成（remote: true）
- **8-14行目**: 各セクションのパーシャルをrender（edit: trueオプション付き）

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

```
LeadsController#edit
    │
    ├─ get_campaigns
    │      └─ Campaign.my(current_user).order('name')
    │
    ├─ @previous = Lead.my(current_user).find_by_id(...)
    │
    └─ respond_with(@lead)
           └─ edit.js.haml → _edit.html.haml

LeadsController#update
    │
    ├─ @lead.access = resource_params[:access]
    │
    ├─ @lead.update_with_lead_counters(resource_params)
    │      │
    │      ├─ {キャンペーン変更なし}
    │      │      └─ save
    │      │
    │      └─ {キャンペーン変更あり}
    │             ├─ decrement_leads_count (旧)
    │             ├─ save
    │             └─ increment_leads_count (新)
    │
    └─ update_sidebar
           └─ get_data_for_sidebar
```

### データフロー図

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

PUT /leads/:id ───▶ LeadsController#update ───▶ HTML/JS
      │                     │
      │                     ├─▶ Lead.find
      │                     │
      │                     ├─▶ update_with_lead_counters
      │                     │       ├─▶ [leads]テーブル UPDATE
      │                     │       ├─▶ [campaigns]テーブル UPDATE (旧)
      │                     │       └─▶ [campaigns]テーブル UPDATE (新)
      │                     │
      │                     └─▶ [permissions]テーブル UPDATE
      │
      └─▶ update.js.haml
              └─▶ _lead.html.haml (表示更新)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| leads_controller.rb | `app/controllers/entities/leads_controller.rb` | コントローラー | リード操作のエントリーポイント |
| lead.rb | `app/models/entities/lead.rb` | モデル | リードのデータモデル定義 |
| _edit.html.haml | `app/views/leads/_edit.html.haml` | ビュー | 編集フォーム |
| edit.js.haml | `app/views/leads/edit.js.haml` | ビュー | AJAX応答でフォーム表示 |
| update.js.haml | `app/views/leads/update.js.haml` | ビュー | 更新完了後のAJAX応答 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
