# 機能設計書 24-リード削除

## 概要

本ドキュメントは、Fat Free CRMシステムにおけるリード削除機能の設計を定義する。この機能は、不要になったリード情報をシステムから削除し、関連するキャンペーンのリードカウントを適切に更新する。

### 本機能の処理概要

リード削除機能は、見込み顧客情報をシステムから削除するための機能である。

**業務上の目的・背景**：重複登録されたリード、無効になったリード、または誤って登録されたリードを削除することで、データベースの整合性を保ち、有効なリードのみを営業活動の対象とするために必要な機能である。削除時にはキャンペーンのリードカウントも自動的に更新される。

**機能の利用シーン**：重複リードを発見した場合、連絡不能なリードを整理する場合、テストデータを削除する場合、または誤登録を取り消す場合に利用される。

**主要な処理内容**：
1. 削除対象リードの特定
2. 削除権限の確認
3. 関連データの処理（タスク等の依存関係）
4. リードレコードの削除
5. キャンペーンリードカウントの更新
6. 画面の更新（一覧/詳細からの呼び出しに応じた処理）

**関連システム・外部連携**：キャンペーン機能との連携により、削除時にキャンペーンのリードカウントが自動的にデクリメントされる。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 15 | リード一覧画面 | 補助機能 | リードの削除処理 |
| 16 | リード詳細画面 | 遷移先機能 | 詳細画面からのリード削除 |

## 機能種別

CRUD操作（DELETE） / データ削除

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | リードID | 数値、存在確認 |

### 入力データソース

- URL: DELETE `/leads/:id`
- HTTPメソッド: DELETE
- データソース: URLパラメータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @lead | Lead | 削除されたリードオブジェクト |
| @leads | Array | 削除後のリード一覧（一覧画面から呼ばれた場合） |
| @campaign | Campaign | 関連キャンペーン（キャンペーン画面から呼ばれた場合） |
| @lead_status_total | Hash | ステータス別リード件数 |

### 出力先

- HTML形式: リダイレクト（リード一覧画面）
- JS形式: AJAX応答（destroy.js.haml）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ DELETE /leads/:id を受信
2. 認証・認可チェック
   └─ CanCanによる権限確認
3. リード取得
   └─ 指定IDのリードを取得
4. リード削除
   └─ destroyメソッド実行
5. コールバック処理
   └─ after_destroyでキャンペーンカウント更新
6. 呼び出し元判定
   └─ 一覧画面/詳細画面/関連画面からの判定
7. レスポンス生成
   └─ フォーマットと呼び出し元に応じた出力
```

### フローチャート

```mermaid
flowchart TD
    A[DELETE /leads/:id] --> B[認証チェック]
    B --> C{認可チェック}
    C -->|許可| D[リードデータ取得]
    C -->|拒否| E[403 Forbidden]
    D --> F[@lead.destroy]
    F --> G[after_destroy: キャンペーンカウント減少]
    G --> H{呼び出し元判定}
    H -->|一覧画面| I[リード一覧更新]
    H -->|詳細画面| J[フラッシュメッセージ設定]
    H -->|キャンペーン画面| K[キャンペーン情報更新]
    I --> L{一覧が空?}
    L -->|Yes| M[前ページを取得]
    L -->|No| N[現在ページ表示]
    M --> N
    J --> O[リダイレクト]
    N --> P[AJAX応答]
    K --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-24-01 | キャンペーンカウント減少 | 削除時にキャンペーンのleads_countを1減少 | キャンペーン紐付け時 |
| BR-24-02 | 依存レコード削除 | リードに紐付くタスクも一緒に削除（dependent: :destroy） | 常時 |
| BR-24-03 | 連絡先参照維持 | リードから変換された連絡先のlead_idはnullify | 常時 |
| BR-24-04 | 空一覧時ページ調整 | 削除後に一覧が空になった場合、前ページを表示 | 一覧画面からの削除時 |

### 計算ロジック

キャンペーンカウント更新:
- `Campaign.decrement_counter(:leads_count, campaign_id)` （after_destroyコールバック）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| リード削除 | leads | DELETE | deleted_atの設定（論理削除の場合）または物理削除 |
| タスク削除 | tasks | DELETE | 関連タスクの削除（dependent: :destroy） |
| アドレス削除 | addresses | DELETE | 関連アドレスの削除（dependent: :destroy） |
| 連絡先参照解除 | contacts | UPDATE | lead_idをNULLに設定（dependent: :nullify） |
| キャンペーン更新 | campaigns | UPDATE | leads_countのデクリメント |
| 権限削除 | permissions | DELETE | 関連権限レコードの削除 |

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

#### leads

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | id = :id | 指定されたリードを削除 | |

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | leads_count | leads_count - 1 | after_destroyコールバック |

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | lead_id | NULL | dependent: :nullify |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定IDのリードが存在しない | エラーページ表示 |
| 403 | Forbidden | 削除権限がない | エラーページ表示 |

### リトライ仕様

リトライは実装されていない（削除は冪等操作）

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

- リード削除と依存レコード削除は同一トランザクション内で実行
- キャンペーンカウント更新はafter_destroyコールバックで実行

## パフォーマンス要件

- 削除処理のレスポンスタイム: 2秒以内

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

- CanCanによる認可制御でアクセス権限をチェック
- 削除操作は取り消し不可のため、確認ダイアログを表示
- 監査ログとしてバージョン履歴に記録される

## 備考

- 削除確認ダイアログはJavaScriptで実装
- AJAX削除の場合は画面をリロードせずにDOM更新

---

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

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

### 推奨読解順序

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

リードの関連付けとコールバックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lead.rb | `app/models/entities/lead.rb` | 関連付け（dependent設定）、after_destroyコールバック |

**読解のコツ**:
- **44行目**: `has_one :contact, dependent: :nullify` - 連絡先のlead_idはnullify
- **45行目**: `has_many :tasks, as: :asset, dependent: :destroy` - タスクは削除
- **82行目**: `after_destroy :decrement_leads_count` - 削除時にカウンター減少
- **175-177行目**: decrement_leads_countの実装

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

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

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

**主要処理フロー**:
1. **97-104行目** (destroyアクション): @lead.destroy実行、respond_to_destroy呼び出し
2. **217-239行目** (respond_to_destroy): HTML/AJAX別の後処理、ページネーション調整
3. **219-227行目**: AJAX応答時の一覧更新ロジック
4. **234-238行目**: HTML応答時のリダイレクト処理

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

削除後の画面更新ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | destroy.js.haml | `app/views/leads/destroy.js.haml` | AJAX削除後のDOM更新 |

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

```
LeadsController#destroy
    │
    ├─ @lead.destroy
    │      │
    │      ├─ dependent: :destroy
    │      │      └─ tasks.destroy_all
    │      │
    │      ├─ dependent: :nullify
    │      │      └─ contact.update(lead_id: nil)
    │      │
    │      └─ after_destroy
    │             └─ decrement_leads_count
    │                    └─ Campaign.decrement_counter(:leads_count, ...)
    │
    └─ respond_to_destroy
           │
           ├─ {AJAX: 一覧画面から}
           │      ├─ get_data_for_sidebar
           │      ├─ get_leads
           │      └─ render :index (一覧が空の場合)
           │
           ├─ {AJAX: 関連画面から}
           │      ├─ current_page = 1
           │      └─ @campaign = @lead.campaign
           │
           └─ {HTML}
                  ├─ flash[:notice] = t(:msg_asset_deleted, ...)
                  └─ redirect_to leads_path
```

### データフロー図

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

DELETE /leads/:id ───▶ LeadsController#destroy ───▶ HTML/JS
      │                     │
      │                     ├─▶ Lead.find
      │                     │
      │                     ├─▶ @lead.destroy
      │                     │       ├─▶ [leads]テーブル DELETE
      │                     │       ├─▶ [tasks]テーブル DELETE
      │                     │       ├─▶ [addresses]テーブル DELETE
      │                     │       ├─▶ [contacts]テーブル UPDATE (lead_id=NULL)
      │                     │       └─▶ [campaigns]テーブル UPDATE (leads_count)
      │                     │
      │                     └─▶ respond_to_destroy
      │
      └─▶ destroy.js.haml
              └─▶ DOM更新 or リダイレクト
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| leads_controller.rb | `app/controllers/entities/leads_controller.rb` | コントローラー | リード操作のエントリーポイント |
| lead.rb | `app/models/entities/lead.rb` | モデル | リードのデータモデル定義 |
| destroy.js.haml | `app/views/leads/destroy.js.haml` | ビュー | AJAX削除後のDOM更新 |
| _lead.html.haml | `app/views/leads/_lead.html.haml` | ビュー | リード行のパーシャル |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
