# 機能設計書 32-商談削除

## 概要

本ドキュメントは、Fat Free CRMシステムにおける「商談削除」機能の設計を定義する。既存の商談（Opportunity）レコードを削除するための機能である。

### 本機能の処理概要

商談削除機能は、不要になった商談や誤って作成された商談をシステムから削除するための機能である。

**業務上の目的・背景**：営業活動において、重複して作成された商談、キャンセルされた商談、または誤入力による不要な商談を削除することで、パイプラインの正確性を維持する。削除することでステージ別集計やレポートから除外され、正確な営業分析が可能となる。キャンペーンに紐付く商談の場合は、キャンペーンの商談数カウンターも自動的にデクリメントされる。

**機能の利用シーン**：重複商談の整理、キャンセルになった商談の削除、テストデータの削除、誤って作成した商談の削除、失注後の整理（lost ステータスとして残すか削除するかはビジネス判断）。

**主要な処理内容**：
1. 削除対象商談の特定と権限チェック
2. 関連エンティティ（取引先、キャンペーン）の情報取得
3. 商談レコードの削除実行
4. キャンペーン商談数カウンターのデクリメント
5. 関連する account_opportunities レコードの削除（dependent: :destroy）
6. UI更新（一覧/詳細画面のリフレッシュ）

**関連システム・外部連携**：商談削除時に、関連するキャンペーンの opportunities_count カウンターがデクリメントされる。取引先との関連（account_opportunities）も自動的に削除される。

**権限による制御**：商談の削除は、商談を作成したユーザー、または管理者のみが実行可能。CanCanCanによる認可チェックが行われる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | 商談一覧画面 | 主画面 | 一覧からの削除操作 |
| 25 | 商談詳細画面 | 主画面 | 詳細画面からの削除操作 |
| 8 | 取引先詳細画面 | 参照画面 | 取引先関連商談の削除 |
| 12 | キャンペーン詳細画面 | 参照画面 | キャンペーン関連商談の削除 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 削除対象の商談ID | 存在チェック、アクセス権チェック |

### 入力データソース

- URLパラメータ（商談ID）
- セッション情報（current_user）
- リファラー情報（呼び出し元画面の判定）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @opportunity | Opportunity | 削除された商談オブジェクト |
| @account | Account | 関連取引先（取引先画面からの呼び出し時） |
| @campaign | Campaign | 関連キャンペーン（キャンペーン画面からの呼び出し時） |
| flash[:notice] | String | 削除完了メッセージ（HTML応答時） |

### 出力先

- 画面表示（AJAX応答またはリダイレクト）
- データベース（レコード削除）

## 処理フロー

### 処理シーケンス

```
1. 削除リクエスト受信
   └─ DELETE /opportunities/:id
2. 商談レコードの取得
   └─ CanCanCanによる認可チェック
3. 呼び出し元画面の判定
   └─ リファラーURLの解析
4. 関連エンティティの取得（必要な場合）
   └─ 取引先またはキャンペーンの取得
5. 商談レコードの削除
   └─ dependent: :destroy による関連レコードも削除
6. キャンペーンカウンターのデクリメント
   └─ after_destroy コールバック
7. 応答の生成
   └─ AJAX/HTML形式での応答
```

### フローチャート

```mermaid
flowchart TD
    A[削除リクエスト受信] --> B[商談データ取得]
    B --> C{アクセス権あり?}
    C -->|No| D[403エラー]
    C -->|Yes| E{呼び出し元判定}
    E -->|取引先画面| F[取引先情報取得]
    E -->|キャンペーン画面| G[キャンペーン情報取得]
    E -->|その他| H[削除処理へ]
    F --> H
    G --> H
    H --> I[商談レコード削除]
    I --> J[関連レコード削除]
    J --> K{キャンペーン紐付あり?}
    K -->|Yes| L[カウンターデクリメント]
    K -->|No| M[応答生成]
    L --> M
    M --> N{応答形式}
    N -->|AJAX| O[JS応答]
    N -->|HTML| P[リダイレクト]
    O --> Q[UI更新]
    P --> R[一覧画面へ]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-32-01 | 削除権限 | 商談作成者または管理者のみ削除可能 | 常時 |
| BR-32-02 | 関連削除 | 商談削除時に関連するaccount_opportunitiesも削除 | 常時 |
| BR-32-03 | カウンター更新 | キャンペーン紐付時はopportunities_countをデクリメント | キャンペーン紐付あり時 |
| BR-32-04 | 物理削除 | 商談は論理削除ではなく物理削除される | 常時 |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 商談削除 | opportunities | DELETE | 商談レコードの物理削除 |
| 関連削除 | account_opportunities | DELETE | 取引先-商談関連の削除 |
| 関連削除 | contact_opportunities | DELETE | 連絡先-商談関連の削除 |
| 関連削除 | tasks | DELETE | 関連タスクの削除 |
| カウンター更新 | campaigns | UPDATE | opportunities_countのデクリメント |
| 履歴削除 | versions | DELETE | PaperTrail履歴の削除 |

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

#### opportunities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | id = 指定ID | 物理削除 |

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | opportunities_count | opportunities_count - 1 | カウンターキャッシュ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定IDの商談が存在しない | エラー画面表示 |
| 403 | Forbidden | 削除権限がない | アクセス拒否画面表示 |

### リトライ仕様

削除処理は即座に完了するため、リトライ仕様は特になし。

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

- 商談削除と関連レコードの削除は同一トランザクション内で実行
- キャンペーンカウンターの更新も同一トランザクション内（after_destroyコールバック）
- いずれかの処理が失敗した場合はロールバック

## パフォーマンス要件

- 削除処理: 1秒以内
- dependent: :destroyにより関連レコードも一括削除

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

- CanCanCanによるアクセス権限チェック（load_and_authorize_resource）
- CSRF対策（Railsデフォルト）
- 削除は物理削除のため、データの復元は不可
- 削除前の確認ダイアログは実装されていないため、UIレベルでの確認が必要

## 備考

- 削除された商談はPaperTrailの履歴からも削除される
- 誤削除防止のため、UI側で確認ダイアログを表示することを推奨
- lost ステータスとして残すことで履歴を保持することも可能

---

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

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

### 推奨読解順序

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

商談モデルの関連と削除時の挙動を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | opportunity.rb | `app/models/entities/opportunity.rb` | dependent: :destroy の定義を確認 |

**読解のコツ**:
- **34-35行目**: `has_one :account_opportunity, dependent: :destroy` - 取引先関連の自動削除
- **36-37行目**: `has_many :contact_opportunities, dependent: :destroy` - 連絡先関連の自動削除
- **38行目**: `has_many :tasks, as: :asset, dependent: :destroy` - 関連タスクの自動削除
- **89-91行目**: `after_create :increment_opportunities_count`, `after_destroy :decrement_opportunities_count` - カウンターキャッシュの管理
- **182-185行目**: `decrement_opportunities_count` メソッドの実装

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

OpportunitiesControllerのdestroyアクションが削除処理の起点。

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

**主要処理フロー**:
1. **112-126行目**: destroyアクション全体
2. **114-115行目**: `called_from_landing_page?(:accounts)` - 取引先画面からの呼び出しチェック
3. **116-118行目**: `called_from_landing_page?(:campaigns)` - キャンペーン画面からの呼び出しチェック
4. **120行目**: `@opportunity.destroy` - 削除処理の実行
5. **122-125行目**: 応答形式に応じた処理分岐

#### Step 3: 応答処理を理解する

削除後の応答処理（AJAX/HTML）を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | respond_to_destroyメソッド |
| 3-2 | destroy.js.haml | `app/views/opportunities/destroy.js.haml` | AJAX応答のビュー |

**主要処理フロー**:
- **175-193行目**: `respond_to_destroy` メソッド
- **176-187行目**: AJAX応答時の処理（サイドバー更新、一覧再取得）
- **188-192行目**: HTML応答時の処理（フラッシュメッセージ、リダイレクト）

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

```
OpportunitiesController#destroy
    │
    ├─ load_and_authorize_resource (CanCanCan)
    │      └─ Ability#can?(:destroy, @opportunity)
    │
    ├─ called_from_landing_page?(:accounts)
    │      └─ @opportunity.account 取得
    │
    ├─ called_from_landing_page?(:campaigns)
    │      └─ @opportunity.campaign 取得
    │
    ├─ @opportunity.destroy
    │      ├─ before_destroy callbacks
    │      ├─ DELETE FROM opportunities WHERE id = ?
    │      ├─ dependent: :destroy
    │      │      ├─ AccountOpportunity.destroy
    │      │      ├─ ContactOpportunity.destroy_all
    │      │      └─ Task.destroy_all
    │      └─ after_destroy callbacks
    │             └─ decrement_opportunities_count
    │                    └─ Campaign.decrement_counter
    │
    └─ respond_to_destroy
           ├─ AJAX: get_data_for_sidebar, get_opportunities
           └─ HTML: flash[:notice], redirect_to
```

### データフロー図

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

削除リクエスト ────────▶ OpportunitiesController#destroy
DELETE /opportunities/:id          │
                                   ▼
                          認可チェック (CanCanCan)
                                   │
                                   ▼
                          呼び出し元判定
                                   │
                    ┌──────────────┼──────────────┐
                    ▼              ▼              ▼
              取引先取得     キャンペーン取得    直接削除
                    │              │              │
                    └──────────────┼──────────────┘
                                   ▼
                          @opportunity.destroy
                                   │
                    ┌──────────────┼──────────────┐
                    ▼              ▼              ▼
              opportunities   account_         tasks
              DELETE          opportunities    DELETE
                              DELETE
                                   │
                                   ▼
                          カウンター更新
                          (campaigns.opportunities_count)
                                   │
                                   ▼
                    ┌──────────────┴──────────────┐
                    ▼                             ▼
              AJAX応答                       HTML応答
              destroy.js.haml                redirect_to
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | コントローラ | 削除アクションの実装 |
| opportunity.rb | `app/models/entities/opportunity.rb` | モデル | 削除時のコールバック定義 |
| destroy.js.haml | `app/views/opportunities/destroy.js.haml` | ビュー | AJAX削除後のJS処理 |
| ability.rb | `app/models/users/ability.rb` | モデル | 削除権限の定義 |
| routes.rb | `config/routes.rb` | 設定 | DELETE /opportunities/:id ルーティング |
| account_opportunity.rb | `app/models/relationships/account_opportunity.rb` | モデル | 取引先-商談関連 |
| contact_opportunity.rb | `app/models/relationships/contact_opportunity.rb` | モデル | 連絡先-商談関連 |
