# 画面設計書 24-商談一覧画面

## 概要

本ドキュメントは、Fat Free CRMシステムにおける商談一覧画面の設計を記述する。商談のステージ別フィルタリング、検索、一覧表示、CSV/XLSエクスポートなどの機能を提供する画面である。

### 本画面の処理概要

商談一覧画面は、CRMシステムにおける営業パイプライン管理の中核となる画面である。商談をステージ別に分類し、進捗状況を一目で把握できるようにする。

**業務上の目的・背景**：営業マネージャーおよび営業担当者が、全商談の状況を把握し、優先順位付けや進捗管理を行う必要がある。本画面により、商談パイプラインの可視化、営業予測の基盤提供、営業活動の効率化を実現する。

**画面へのアクセス方法**：
- ナビゲーションバーの「Opportunities」リンクをクリック
- ダッシュボードの商談セクションから「すべて表示」をクリック
- URL直接アクセス（`/opportunities`）

**主要な操作・処理内容**：
1. 商談の一覧表示（カード形式、リスト形式切替可）
2. ステージ別フィルタリング（Prospecting, Analysis, Proposal等）
3. テキスト検索（商談名、ID）
4. Ransackによる高度な検索
5. ソート順の変更（名前、金額、確度、クローズ日等）
6. ページネーション
7. CSV/XLSエクスポート
8. 新規商談作成フォームの表示

**画面遷移**：
- 遷移元：ダッシュボード、取引先詳細画面、キャンペーン詳細画面、連絡先詳細画面
- 遷移先：商談詳細画面、商談新規作成フォーム、商談編集フォーム

**権限による表示制御**：ユーザーは自分が作成した商談、自分に割り当てられた商談、またはPublic/Shared設定の商談のみ表示可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 28 | 商談一覧表示 | 主機能 | 商談一覧表示、ステージフィルタリング、CSV/XLSエクスポート |
| 81 | 高度な検索 | 補助機能 | Ransackによる詳細検索 |
| 32 | 商談削除 | 補助機能 | 商談の削除処理 |

## 画面種別

一覧画面

## URL/ルーティング

- URL: `/opportunities`
- HTTPメソッド: GET
- コントローラ: `OpportunitiesController#index`
- フォーマット: HTML, JS, JSON, CSV, XLS

### 関連URL

| URL | メソッド | アクション | 説明 |
|-----|---------|----------|------|
| /opportunities/filter | POST | filter | ステージフィルター適用 |
| /opportunities/redraw | GET | redraw | 表示再描画 |
| /opportunities/advanced_search | GET | advanced_search | 高度な検索フォーム |

## 入出力項目

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|------|------|
| page | Integer | 任意 | ページ番号 |
| per_page | Integer | 任意 | 1ページあたり表示件数 |
| query | String | 任意 | 検索クエリ |
| stage | String | 任意 | フィルターするステージ |
| sort_by | String | 任意 | ソート項目 |
| q | Hash | 任意 | Ransack検索パラメータ |

### 出力データ（インスタンス変数）

| 変数名 | 型 | 説明 |
|--------|-----|------|
| @opportunities | Array | 商談オブジェクト配列（ページネーション済み） |
| @stage | Array | ステージ選択肢 |
| @opportunity_stage_total | Hash | ステージ別件数 |
| @per_page | Integer | 1ページあたり表示件数 |
| @sort_by | String | 現在のソート項目 |
| @search_results_count | Integer | 検索結果総件数 |

## 表示項目

### タイトルバー

| 項目名 | 説明 |
|--------|------|
| タイトル | 「Opportunities」 |
| 新規作成ボタン | 商談新規作成フォームを開く |
| ビュー切替ボタン | 表示形式切替（コンパクト/詳細） |

### 検索バー

| 項目名 | 入力形式 | 説明 |
|--------|---------|------|
| 検索ボックス | テキスト | 商談名・IDで検索 |
| 高度な検索リンク | リンク | Ransack検索フォームを開く |

### サイドバー（フィルターパネル）

| 項目名 | 説明 |
|--------|------|
| All | 全件数を表示、全件表示に切替 |
| Prospecting | Prospectingステージの件数と選択 |
| Analysis | Analysisステージの件数と選択 |
| Proposal | Proposalステージの件数と選択 |
| Presentation | Presentationステージの件数と選択 |
| Negotiation | Negotiationステージの件数と選択 |
| Final Review | Final Reviewステージの件数と選択 |
| Won | 受注済みの件数と選択 |
| Lost | 失注の件数と選択 |
| Other | その他ステージの件数と選択 |

### 商談一覧

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| 商談名 | opportunity.name | 詳細画面へのリンク |
| 金額 | opportunity.amount | 金額（通貨形式） |
| 確度 | opportunity.probability | 確度（%） |
| 加重金額 | opportunity.weighted_amount | 金額×確度 |
| ステージ | opportunity.stage | 現在のステージ |
| クローズ予定日 | opportunity.closes_on | 成約予定日 |
| 取引先 | opportunity.account.name | 関連取引先（リンク） |
| 担当者 | opportunity.assignee.full_name | 担当ユーザー |
| タグ | opportunity.tags | 付与されたタグ |

### フッター

| 項目名 | 説明 |
|--------|------|
| ページネーション | ページ切替リンク |
| 表示件数選択 | 10/20/30件等の選択 |
| エクスポートリンク | CSV/XLSダウンロードリンク |

## イベント仕様

### 1-商談名クリック

- **トリガー**: 商談名のリンクをクリック
- **処理**: 商談詳細画面へ遷移
- **遷移先**: `/opportunities/:id`

### 2-新規作成ボタンクリック

- **トリガー**: 「Create Opportunity」ボタンをクリック
- **処理**: Ajax経由で新規作成フォームを取得し表示
- **遷移先**: 同一画面内にフォーム表示

### 3-ステージフィルター選択

- **トリガー**: サイドバーのステージをクリック
- **処理**:
  1. Ajax経由でフィルター適用
  2. セッションにフィルター状態を保存
  3. 一覧を再描画
- **遷移先**: 同一画面（一覧更新）

### 4-検索実行

- **トリガー**: 検索ボックスで入力後Enter、または検索ボタンクリック
- **処理**: クエリを含めて一覧を再取得
- **遷移先**: 同一画面（検索結果表示）

### 5-ソート変更

- **トリガー**: ソート項目を変更
- **処理**:
  1. ユーザー設定に保存
  2. 一覧を再描画
- **遷移先**: 同一画面（ソート反映）

### 6-CSVエクスポート

- **トリガー**: CSV/XLSリンクをクリック
- **処理**: 現在のフィルター・検索条件で全件エクスポート
- **レスポンス**: ファイルダウンロード

### 7-削除実行

- **トリガー**: 削除リンク/ボタンをクリック、確認ダイアログでOK
- **処理**:
  1. Ajax経由で削除実行
  2. 一覧から該当商談を削除
  3. ステージ別件数を更新
- **遷移先**: 同一画面（一覧更新）

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 一覧表示 | opportunities | SELECT | 商談一覧取得 |
| フィルター適用 | - | - | セッションに状態保存 |
| 削除実行 | opportunities | UPDATE | deleted_at設定（論理削除） |
| 削除実行 | campaigns | UPDATE | opportunities_count減算 |

### テーブル別更新項目詳細

#### opportunities（削除時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | deleted_at | 現在日時 | 論理削除 |

## メッセージ仕様

| 種別 | メッセージキー | 表示条件 |
|------|---------------|---------|
| 情報 | :msg_asset_deleted | 商談削除成功時 |
| 情報 | - | 検索結果0件時「No opportunities found」 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 権限なし商談へのアクセス | 一覧から除外（表示されない） |

## 備考

- ステージ設定はSetting.unroll(:opportunity_stage)で取得
- ソートはweighted_sort（金額×確度）を含むカスタムソートに対応
- 検索は商談名とID（数字の場合）の両方に対応
- エクスポート時はページネーションなしで全件出力
- ビュー表示形式はユーザー設定に保存

---

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

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

### 推奨読解順序

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

商談エンティティのデータ構造とスコープを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | opportunity.rb | `app/models/entities/opportunity.rb` | スコープ定義（43-71行目）、特にstate, won, lost, pipeline |
| 1-2 | opportunity.rb | `app/models/entities/opportunity.rb` | weighted_amount計算（103-105行目） |

**読解のコツ**: `scope :state`（43-45行目）でステージフィルタリングの実装を確認。`weighted_sort`スコープでカスタムソートを理解する。

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

コントローラーのindexアクションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | indexアクション（15-22行目）、get_data_for_sidebar（196-217行目） |
| 2-2 | entities_controller.rb | `app/controllers/entities_controller.rb` | get_list_of_records（138-178行目） |

**主要処理フロー**:
1. **16行目**: `get_opportunities(page:, per_page:)` で商談一覧取得
2. **18-19行目**: CSV/XLSフォーマット対応
3. **196-217行目**: `get_data_for_sidebar`でステージ別件数を集計

#### Step 3: ビューテンプレートを理解する

一覧画面の構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.html.haml | `app/views/opportunities/index.html.haml` | ページ構造、パーシャル呼び出し |
| 3-2 | _opportunity.html.haml | `app/views/opportunities/_opportunity.html.haml` | 商談カード表示 |

**主要処理フロー**:
- **3行目**: `render 'entities/title_bar'` - タイトルバー
- **7行目**: `render 'search'` - 検索バー
- **11-15行目**: 商談一覧のレンダリング（コレクション）
- **17行目**: ページネーション

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

```
OpportunitiesController#index
    │
    ├─ before_action :load_settings
    │      └─ @stage = Setting.unroll(:opportunity_stage)
    │
    ├─ before_action :get_data_for_sidebar
    │      └─ @opportunity_stage_total = {...}
    │
    ├─ before_action :set_params
    │      └─ session[:opportunities_filter] 更新
    │
    ├─ get_opportunities (alias: get_list_of_records)
    │      │
    │      ├─ ransack_search.result
    │      │
    │      ├─ scope.state(filter) - ステージフィルター
    │      │
    │      ├─ scope.text_search(query) - テキスト検索
    │      │
    │      ├─ order_by_attributes (weighted_sort.order)
    │      │
    │      └─ scope.paginate(page:, per_page:)
    │
    └─ respond_with(@opportunities)
           ├─ format.html → index.html.haml
           │      ├─ entities/_title_bar.html.haml
           │      ├─ _search.html.haml
           │      ├─ _opportunity.html.haml (collection)
           │      └─ shared/_paginate.html.haml
           ├─ format.csv → CSV出力
           └─ format.xls → XLS出力
```

### データフロー図

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

Request
/opportunities ────────▶ OpportunitiesController#index
    │                           │
    ├── page ──────────────────┤
    ├── per_page ──────────────┤
    ├── query ─────────────────┤
    ├── stage ─────────────────┤
    └── sort_by ───────────────┤
                                │
                                ├──▶ Opportunity.my(current_user)
                                │         │
                                │         └──▶ [opportunities] SELECT
                                │
                                ├──▶ state(filter) scope
                                │
                                ├──▶ text_search(query)
                                │
                                ├──▶ weighted_sort.order
                                │
                                ├──▶ paginate
                                │
                                ├──▶ get_data_for_sidebar
                                │         │
                                │         └──▶ ステージ別COUNT
                                │
                                └────────────────────────▶ HTML/CSV/XLS
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| opportunity.rb | `app/models/entities/opportunity.rb` | モデル | 商談エンティティ、スコープ定義 |
| opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | コントローラ | 一覧表示処理 |
| entities_controller.rb | `app/controllers/entities_controller.rb` | コントローラ | 共通処理（get_list_of_records） |
| index.html.haml | `app/views/opportunities/index.html.haml` | テンプレート | 一覧画面メインビュー |
| _opportunity.html.haml | `app/views/opportunities/_opportunity.html.haml` | テンプレート | 商談カード |
| _search.html.haml | `app/views/opportunities/_search.html.haml` | テンプレート | 検索バー |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（125-142行目） |
