# 機能設計書 20-リード一覧表示

## 概要

本ドキュメントは、Fat Free CRMにおけるリード一覧表示機能の設計を記述する。システムに登録されたリードを一覧表示し、ステータスでフィルタリング、CSV/XLS形式でエクスポートする機能である。

### 本機能の処理概要

**業務上の目的・背景**：リード（見込み顧客）は営業パイプラインの入り口であり、効率的に管理することで商談機会の創出につながる。リード一覧により、営業担当者は未対応のリードを把握し、フォローアップの優先順位を決定できる。ステータス別のフィルタリングにより、対応が必要なリードに集中できる。

**機能の利用シーン**：営業担当者が新規リードを確認する場合、特定ステータス（new、contacted、converted等）のリードを抽出する場合、リード情報を外部システムと連携するためにエクスポートする場合に利用される。また、高度な検索（Ransack）を使って特定条件のリードを抽出する場合にも使用される。

**主要な処理内容**：
1. 現在ユーザーがアクセス可能なリードの取得
2. ステータス、テキスト検索、タグ検索、高度な検索（Ransack）によるフィルタリング
3. ソート、ページネーション処理
4. サイドバー用ステータス別カウント集計
5. 各種フォーマット（HTML、JS、XLS、CSV）でのレスポンス生成

**関連システム・外部連携**：CSV/XLS形式でのエクスポートにより、Excelや外部システムとのデータ連携が可能。

**権限による制御**：CanCanによるアクセス権限管理が行われ、ユーザーがアクセス可能なリードのみ表示される。リードのアクセス設定（Public/Private/Shared/Campaign）により閲覧可否が決定される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 15 | リード一覧画面 | 主画面 | リード一覧表示、ステータスフィルタリング、CSV/XLSエクスポート |

## 機能種別

データ参照（Read操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| page | Integer | No | ページ番号 | 正の整数 |
| per_page | Integer | No | 1ページあたりの表示件数 | 1-200 |
| query | String | No | テキスト検索クエリ | - |
| status | Array | No | フィルタリングするステータス | Setting.lead_statusの値 |
| q | Hash | No | Ransack検索条件 | Ransack形式 |
| format | String | No | レスポンス形式（html/js/xls/csv） | 許可された形式のみ |

### 入力データソース

- URLパラメータ
- セッション情報（現在のユーザー情報、フィルタ設定、ソート設定、ページ情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @leads | Array | リードオブジェクトの配列（ページネーション済み） |
| @lead_status_total | Hash | ステータス別カウント |
| @search_results_count | Integer | 検索結果の総件数 |

### 出力先

- HTML画面表示
- AJAX（JS）レスポンス
- XLS/CSVファイルダウンロード

## 処理フロー

### 処理シーケンス

```
1. ユーザー認証確認
   └─ authenticate_user!でログイン状態を確認
2. サイドバーデータ取得
   └─ get_data_for_sidebarでステータス別カウント集計
3. 検索条件の構築
   └─ Ransack検索オブジェクトのロード
4. リード一覧取得
   └─ get_leadsメソッドで条件に合致するリードを取得
5. ステータスフィルタリング
   └─ セッションのフィルタ設定を適用
6. ソート・ページネーション
   └─ ユーザー設定に基づくソート、ページネーション適用
7. レスポンス生成
   └─ フォーマットに応じた出力（HTML/JS/XLS/CSV）
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[ユーザー認証]
    B --> C{認証OK?}
    C -->|No| D[ログインページへリダイレクト]
    C -->|Yes| E[サイドバーデータ取得]
    E --> F[Ransack検索条件ロード]
    F --> G[get_leads実行]
    G --> H{ステータスフィルタ?}
    H -->|Yes| I[state スコープ適用]
    H -->|No| J{テキスト検索?}
    I --> J
    J -->|Yes| K[text_search適用]
    J -->|No| L{タグ検索?}
    K --> L
    L -->|Yes| M[tagged_with適用]
    L -->|No| N[ソート適用]
    M --> N
    N --> O{XLS/CSV?}
    O -->|Yes| P[全件取得]
    O -->|No| Q[ページネーション適用]
    P --> R[ファイル生成]
    Q --> S[ビュー描画]
    R --> T[終了]
    S --> T
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-20-01 | アクセス権限制御 | リードのaccess設定に基づき閲覧可否を判定 | 常時 |
| BR-20-02 | デフォルトソート | created_at DESC（作成日時の降順） | ソート未指定時 |
| BR-20-03 | デフォルト表示件数 | 20件/ページ | 表示件数未指定時 |
| BR-20-04 | エクスポート時ページネーション無効 | XLS/CSV形式では全件出力 | XLS/CSVリクエスト時 |
| BR-20-05 | ステータスフィルタ | セッションに保存されたフィルタ設定を適用 | 高度な検索以外 |
| BR-20-06 | 「その他」ステータス | Setting.lead_statusに含まれないステータス（NULLを含む）を「その他」として集計 | サイドバー表示時 |

### 計算ロジック

サイドバーステータス集計ロジック：
```ruby
@lead_status_total = {
  all: Lead.my(current_user).count,
  other: 0
}

Setting.lead_status.each { |key| @lead_status_total[key] = 0 }

status_counts = Lead.my(current_user).where(status: Setting.lead_status).group(:status).count
status_counts.each do |key, total|
  @lead_status_total[key.to_sym] = total
  @lead_status_total[:other] -= total
end
@lead_status_total[:other] += @lead_status_total[:all]
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| リード一覧取得 | leads | SELECT | アクセス権限を考慮した一覧取得 |
| ステータス集計 | leads | SELECT | ステータス別のグループカウント |
| タグ取得 | tags, taggings | SELECT | リードに紐付くタグ情報取得（includes） |

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

#### leads

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全カラム | my(current_user)スコープ、deleted_at IS NULL | ページネーション・ソート適用 |
| SELECT(集計) | status, COUNT | my(current_user)、Setting.lead_status内 | GROUP BY status |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | 未ログイン状態でアクセス | ログインページへリダイレクト |

### リトライ仕様

本機能にリトライ処理は実装されていない。

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

参照系処理のみであり、明示的なトランザクション管理は不要。

## パフォーマンス要件

- 一覧表示は2秒以内を目標
- XLS/CSVエクスポートは10秒以内を目標（件数依存）

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

- CanCanによるアクセス権限チェック実施
- CSRF対策（protect_from_forgery）
- SQLインジェクション対策（ActiveRecord/Ransack使用）
- XSS対策（ERB::Util使用）

## 備考

- 高度な検索（Ransack）機能は別機能（No.81）として管理
- オートコンプリート機能は別機能（No.82）として管理
- リードのステータスはSetting.lead_statusで定義される

---

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

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

### 推奨読解順序

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

まず、リードエンティティの構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lead.rb | `app/models/entities/lead.rb` | Leadモデルの属性、関連、スコープ定義 |

**読解のコツ**:
- **40-48行目**: 関連定義（belongs_to :user, :campaign, has_one :contact等）
- **54-56行目**: `state`スコープでステータスフィルタリングを確認
- **62行目**: `text_search`スコープで名前・会社・メール検索を確認
- **71行目**: `sortable`で利用可能なソートキーを確認

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

処理の起点となるコントローラーのindexアクションを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | leads_controller.rb | `app/controllers/entities/leads_controller.rb` | indexアクションの処理内容 |

**主要処理フロー**:
1. **9行目**: `before_action :get_data_for_sidebar`でサイドバーデータ取得
2. **14-21行目**: indexアクション - get_leads呼び出し、フォーマット別レスポンス
3. **199行目**: `alias get_leads get_list_of_records`でget_list_of_recordsを使用

#### Step 3: サイドバーデータ取得を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | leads_controller.rb | `app/controllers/entities/leads_controller.rb` | get_data_for_sidebarメソッドの詳細 |

**主要処理フロー**:
- **242-263行目**: `get_data_for_sidebar`メソッド - ステータス別カウント集計
- **246-262行目**: allカウント、各ステータスカウント、otherカウントの計算ロジック

#### Step 4: 基底コントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | entities_controller.rb | `app/controllers/entities_controller.rb` | get_list_of_recordsメソッドの詳細 |

**主要処理フロー**:
- **138-178行目**: `get_list_of_records`メソッド - 検索、フィルタ、ソート、ページネーションの統合処理
- **150-151行目**: セッションからフィルタ取得、stateスコープ適用

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.html.haml | `app/views/leads/index.html.haml` | 一覧画面のレイアウト構成 |
| 5-2 | _lead.html.haml | `app/views/leads/_lead.html.haml` | 個別リードの表示 |
| 5-3 | _sidebar_index.html.haml | `app/views/leads/_sidebar_index.html.haml` | サイドバーのステータスフィルタ |

**主要処理フロー**:
- **10-15行目**: リード一覧のレンダリング、空の場合のハンドリング
- **17-18行目**: ページネーションとエクスポートボタン

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

```
LeadsController#index
    │
    ├─ get_data_for_sidebar
    │      └─ Lead.my(current_user).group(:status).count
    │
    ├─ EntitiesController (継承)
    │      ├─ load_and_authorize_resource
    │      └─ set_options (per_page, sort_by設定)
    │
    └─ get_leads (= get_list_of_records)
           │
           ├─ ransack_search.result
           │
           ├─ state(filter) [フィルタ設定時]
           │      └─ Lead.state
           │
           ├─ text_search (オプション)
           │
           ├─ tagged_with (オプション)
           │
           ├─ order
           │
           └─ paginate
```

### データフロー図

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

検索条件 ─────────▶ LeadsController#index ─────▶ HTML画面/XLS/CSV
                          │
ステータスフィルタ         ├─▶ get_data_for_sidebar
                          │     └─▶ ステータス別カウント
                          │
ページ/ソート設定          ├─▶ get_list_of_records
                          │     ├─▶ state フィルタ
                          │     ├─▶ Ransack検索
                          │     ├─▶ text_search
                          │     ├─▶ tagged_with
                          │     └─▶ paginate
                          │
                          └─▶ respond_with
                                ├─▶ HTML: ビュー描画
                                ├─▶ JS: AJAX更新
                                └─▶ XLS/CSV: ファイル生成
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| leads_controller.rb | `app/controllers/entities/leads_controller.rb` | コントローラー | リード関連アクションの処理 |
| entities_controller.rb | `app/controllers/entities_controller.rb` | コントローラー | エンティティ共通処理 |
| lead.rb | `app/models/entities/lead.rb` | モデル | リードエンティティ定義 |
| index.html.haml | `app/views/leads/index.html.haml` | ビュー | 一覧画面テンプレート |
| _lead.html.haml | `app/views/leads/_lead.html.haml` | ビュー | リードパーシャル |
| _sidebar_index.html.haml | `app/views/leads/_sidebar_index.html.haml` | ビュー | サイドバーパーシャル |
| index.xls.builder | `app/views/leads/index.xls.builder` | ビュー | XLSエクスポートテンプレート |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
