# 帳票設計書 4-商談一覧

## 概要

本ドキュメントは、Fat Free CRMシステムにおける商談（Opportunities）データのエクスポート帳票に関する設計仕様書です。商談の基本情報、金額・確度情報、関連キャンペーン・取引先、カスタムフィールドを含む一覧データをXLS/CSV形式で出力する機能について記述します。

### 本帳票の処理概要

商談一覧エクスポート機能は、システムに登録された商談（案件）データを一括でダウンロードし、営業パイプラインの分析や売上予測を可能にする帳票出力機能です。

**業務上の目的・背景**：商談管理は営業活動の核心であり、売上予測と経営判断に直結する重要な業務データです。本帳票により、営業パイプラインの可視化、ステージ別の案件分析、確度・金額に基づく売上予測、キャンペーンROIの算出、営業担当者別のパフォーマンス分析、経営報告書のためのデータ抽出など、多様な営業管理・経営ニーズに対応します。特に加重金額（weighted_amount）を含むため、確度を考慮した売上予測に有効です。

**帳票の利用シーン**：週次・月次の営業パイプラインレビュー時、四半期の売上予測作成時、経営会議向け報告資料作成時、営業担当者の業績評価時に利用されます。

**主要な出力内容**：
1. 商談基本情報（ID、名前、ソース、ステージ）
2. 金額情報（金額、割引、加重金額、確度）
3. 関連情報（取引先、キャンペーン、担当者）
4. スケジュール情報（成約予定日、作成日、更新日）
5. カスタムフィールド（システム設定による拡張項目）

**帳票の出力タイミング**：商談一覧画面において、ユーザーがエクスポートボタンをクリックした際にリアルタイムで生成・ダウンロードされます。

**帳票の利用者**：営業担当者、営業マネージャー、経営幹部、経営企画担当者

## 帳票種別

一覧表（エクスポート帳票）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | 商談一覧画面 | /opportunities | XLS/CSVエクスポートリンク |
| - | 商談一覧画面 | /opportunities.xls | XLS形式でダウンロード |
| - | 商談一覧画面 | /opportunities.csv | CSV形式でダウンロード |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | XLS（Excel XML Spreadsheet）/ CSV |
| 用紙サイズ | 該当なし（データエクスポート） |
| 向き | 該当なし |
| ファイル名 | opportunities.xls / opportunities.csv |
| 出力方法 | ダウンロード |
| 文字コード | UTF-8 |

### Excel固有設定

| 項目 | 内容 |
|-----|------|
| シート名 | Opportunities（I18n.t(:tab_opportunities)による国際化対応） |
| 保護設定 | 無 |

## 帳票レイアウト

### レイアウト概要

XLS形式のスプレッドシートとして、1行目にヘッダー、2行目以降にデータ行が続く標準的な表形式レイアウトです。

```
┌─────────────────────────────────────┐
│         ヘッダー行（項目名）          │
├─────────────────────────────────────┤
│         データ行1                    │
│         データ行2                    │
│         ...                         │
│         データ行N                    │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | ID | 商談ID | opportunities.id | Number |
| 2 | User | 作成ユーザー名 | users.name（via user_id） | String |
| 3 | Campaign | キャンペーン名 | campaigns.name（via campaign_id） | String |
| 4 | Assigned To | 担当者名 | users.name（via assigned_to） | String |
| 5 | Account | 取引先名 | accounts.name（via account） | String |
| 6 | Name | 商談名 | opportunities.name | String |
| 7 | Access | アクセス権 | opportunities.access | String |
| 8 | Source | ソース | opportunities.source | String |
| 9 | Stage | ステージ | opportunities.stage | String |
| 10 | Probability | 確度（%） | opportunities.probability | Number |
| 11 | Amount | 金額 | opportunities.amount | Number |
| 12 | Discount | 割引 | opportunities.discount | Number |
| 13 | Weighted Amount | 加重金額 | opportunity.weighted_amount（computed） | Number |
| 14 | Closes On | 成約予定日 | opportunities.closes_on | String |
| 15 | Date Created | 作成日 | opportunities.created_at | String |
| 16 | Date Updated | 更新日 | opportunities.updated_at | String |
| 17+ | カスタムフィールド | 動的に追加 | fields.name | String/Number |

### 明細部

各商談レコードについて、ヘッダー部と同じ順序で値を出力します。

### フッター部

なし（データ一覧のみ）

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| ユーザー権限 | 現在のユーザーがアクセス可能な商談のみ | Yes |
| 検索条件 | 画面で適用されているフィルタ・検索条件を継承 | No |
| ステージフィルタ | session[:opportunities_filter]による状態フィルタ | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | ユーザー設定 | 設定による |
| 2 | created_at | 降順（デフォルト） |

### 改ページ条件

該当なし（全データを単一シートに出力）

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| opportunities | 商談基本情報 | メインテーブル |
| users | ユーザー情報 | opportunities.user_id = users.id, opportunities.assigned_to = users.id |
| campaigns | キャンペーン情報 | opportunities.campaign_id = campaigns.id |
| accounts | 取引先情報 | account_opportunities JOIN |
| fields | カスタムフィールド定義 | fields.klass_name = 'Opportunity' |

### テーブル別参照項目詳細

#### opportunities

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| id | ID | - | 主キー |
| name | Name | - | 商談名、必須、最大64文字 |
| access | Access | - | Public/Private/Shared |
| source | Source | - | 獲得元 |
| stage | Stage | - | 商談ステージ |
| probability | Probability | - | 確度（%）、整数 |
| amount | Amount | - | 金額、decimal(12,2) |
| discount | Discount | - | 割引額、decimal(12,2) |
| closes_on | Closes On | - | 成約予定日 |
| created_at | Date Created | - | - |
| updated_at | Date Updated | - | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| Weighted Amount | (amount - discount) * probability / 100.0 | なし（float） | 確度を考慮した期待売上 |
| データ型判定 | value.respond_to?(:abs) ? 'Number' : 'String' | - | セルのデータ型を動的に判定 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[XLS/CSVエクスポート要求] --> B[OpportunitiesController#index]
    B --> C[EntitiesController#get_list_of_records]
    C --> D[Ransack検索実行]
    D --> E[ページネーション無効化]
    E --> F{出力形式}
    F -->|XLS| G[index.xls.builder実行]
    F -->|CSV| H[FatFreeCRM::ExportCSV.from_array実行]
    G --> I[XMLWorkbook生成]
    H --> J[CSV生成]
    I --> K[ファイルダウンロード]
    J --> K
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| データなし | @opportunities.empty? | 空のワークシートが出力される | ヘッダー・データ行とも出力されない |
| 認証エラー | 未ログイン | ログイン画面へリダイレクト | ログイン後に再実行 |
| 権限エラー | アクセス権限なし | 権限エラーメッセージ | 管理者に権限付与を依頼 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千件 |
| 目標出力時間 | 5秒以内（1000件程度） |
| 同時出力数上限 | 明示的な制限なし |

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

- ユーザー権限に基づくアクセス制御（uses_user_permissions）により、ユーザーがアクセス権を持つ商談のみエクスポート可能
- 認証が必要（authenticate_user!）
- Access列の値（Public/Private/Shared）に基づくデータ可視性制御
- 金額情報を含むため、出力ファイルの取り扱いには注意が必要
- パスワード・トークン等の機密情報はCSVエクスポート時に除外

## 備考

- 国際化対応：ヘッダーラベルはI18nを使用して多言語対応
- カスタムフィールド対応：Opportunity.fieldsメソッドにより動的にカスタムフィールドを追加出力
- ステージは設定により変更可能（Setting.unroll(:opportunity_stage)）
- デフォルトステージは'prospecting'
- キャンペーンとの紐付けにより、opportunities_countが自動的に増減管理される
- weighted_amountは計算項目として動的に算出

---

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

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

### 推奨読解順序

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

まず、商談エンティティの構造とリレーションを理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | opportunity.rb | `app/models/entities/opportunity.rb` | Opportunityモデルの定義、belongs_to :campaign、has_one :account関連、weighted_amountメソッドを確認 |

**読解のコツ**: account関連はhas_one throughで定義されている点、stageのバリデーション、金額計算ロジックに注目

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

処理の起点となるコントローラーを特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entities_controller.rb | `app/controllers/entities_controller.rb` | 共通のindexアクション処理を理解 |
| 2-2 | application_controller.rb | `app/controllers/application_controller.rb` | respond_to宣言、認証フィルタを確認 |

**主要処理フロー**:
1. **138-178行目**: get_list_of_recordsでデータ取得

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

XLS出力の実際の処理を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.xls.builder | `app/views/opportunities/index.xls.builder` | ヘッダー定義（8-23行目）、金額項目の出力、weighted_amountの算出（53行目） |
| 3-2 | header.xls.builder | `app/views/layouts/header.xls.builder` | XMLワークブック構造の定義 |

**主要処理フロー**:
- **7-36行目**: ヘッダー行の生成
- **25-28行目**: カスタムフィールドラベルの動的追加
- **39-70行目**: データ行の生成
- **53行目**: weighted_amountメソッド呼び出し

#### Step 4: 金額計算を理解する

加重金額の計算ロジックを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | opportunity.rb | `app/models/entities/opportunity.rb` | weighted_amountメソッド（103-105行目）の計算式を確認 |

**主要処理フロー**:
- **103-105行目**: (amount.to_f - discount.to_f) * probability.to_i / 100.0

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

```
HTTP Request (GET /opportunities.xls)
    │
    ├─ ApplicationController (認証・権限チェック)
    │      └─ authenticate_user!
    │
    ├─ EntitiesController#index (継承元)
    │      └─ get_list_of_records
    │             ├─ ransack_search.result (検索実行)
    │             ├─ state(filter) (ステージフィルタ)
    │             └─ ページネーション制御
    │
    └─ opportunities/index.xls.builder (ビュー)
           ├─ header.xls.builder (レイアウト)
           │
           ├─ @opportunities.each (データイテレーション)
           │      ├─ opportunity.user.try(:name)
           │      ├─ opportunity.campaign.try(:name)
           │      ├─ opportunity.assignee.try(:name)
           │      ├─ opportunity.account.try(:name)
           │      ├─ opportunity.weighted_amount (計算項目)
           │      └─ Opportunity.fields.each (カスタムフィールド)
           │
           └─ XMLセル出力
```

### データフロー図

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

Request             EntitiesController        Response
/opportunities.xls ───▶ #index            ───▶ opportunities.xls
                         │
                         ▼
                    get_list_of_records
                         │
                         ├─ state(filter) ───▶ ステージフィルタ
                         │
                         ▼
                    @opportunities (Collection)
                         │
                         ▼
                    index.xls.builder
                         │
                         ├─ weighted_amount計算
                         │
                         ▼
                    XML生成 (Builder)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| opportunity.rb | `app/models/entities/opportunity.rb` | ソース | Opportunityモデル定義 |
| entities_controller.rb | `app/controllers/entities_controller.rb` | ソース | 共通コントローラー処理 |
| application_controller.rb | `app/controllers/application_controller.rb` | ソース | 基底コントローラー |
| index.xls.builder | `app/views/opportunities/index.xls.builder` | テンプレート | XLS出力ビュー |
| header.xls.builder | `app/views/layouts/header.xls.builder` | テンプレート | XLSレイアウト |
| export_csv.rb | `lib/fat_free_crm/export_csv.rb` | ソース | CSV出力ユーティリティ |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（125-142行目） |
