# 機能設計書 10-キャンペーン一覧表示

## 概要

本ドキュメントは、Fat Free CRMシステムにおけるキャンペーン一覧表示機能の設計を定義する。この機能は、キャンペーンを一覧表示し、ステータスでのフィルタリングとCSV/XLS/RSS/ATOM形式でのエクスポートを提供する。

### 本機能の処理概要

キャンペーン一覧表示機能は、マーケティングキャンペーンの管理・閲覧を提供する中核機能である。キャンペーンのステータス管理、リード・商談との関連付け、効果測定のためのデータ表示を行う。

**業務上の目的・背景**：マーケティング活動においてキャンペーン（広告、展示会、セミナーなど）の管理は重要である。本機能により、進行中のキャンペーンの状況把握、予算管理、目標達成度の確認が可能となる。また、RSS/ATOMフィードにより外部ツールとの連携も可能である。

**機能の利用シーン**：
- マーケティング担当者が進行中のキャンペーンを確認する場面
- マネージャーがキャンペーンの予算と実績を比較する場面
- キャンペーンデータをExcelで分析するためにエクスポートする場面
- RSSリーダーでキャンペーン更新を購読する場面

**主要な処理内容**：
1. ユーザーがアクセス可能なキャンペーンの取得
2. ステータスフィルタリングの適用
3. テキスト検索・高度な検索（Ransack）の適用
4. ページネーションの適用
5. CSV/XLS/RSS/ATOM形式でのエクスポート

**関連システム・外部連携**：RSS/ATOMフィードにより外部のRSSリーダーやニュース集約サービスとの連携が可能。CSV/XLSエクスポート機能によりExcel等の外部ツールとのデータ連携が可能。

**権限による制御**：`uses_user_permissions`によりアクセス権限が制御される。ユーザーは自分が所有するキャンペーン、割り当てられたキャンペーン、または共有されたキャンペーンのみを閲覧可能。

## 関連画面

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

## 機能種別

データ表示・検索・エクスポート（READ操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| page | Integer | No | ページ番号 | 正の整数 |
| per_page | Integer | No | 1ページあたりの件数 | 1-200の範囲 |
| query | String | No | 検索キーワード | - |
| status | String | No | ステータスフィルタ | 設定で定義されたステータス値 |
| q | Hash | No | Ransack検索条件 | - |
| sort_by | String | No | ソート条件 | Campaign.sort_by_mapで定義された値 |

### 入力データソース

- URLパラメータ
- セッション情報（current_user、フィルタ設定）
- ユーザー設定（Preference）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @campaigns | ActiveRecord::Relation<Campaign> | キャンペーン一覧 |
| @campaign_status_total | HashWithIndifferentAccess | ステータス別件数 |
| @per_page | Integer | 1ページあたりの件数 |
| @sort_by | String | 現在のソート条件 |
| @search_results_count | Integer | 検索結果総件数 |

### 出力先

- HTML画面表示（デフォルト）
- CSV形式（format: csv）
- XLS形式（format: xls）
- RSS形式（format: rss）
- ATOM形式（format: atom）

## 処理フロー

### 処理シーケンス

```
1. before_action実行
   ├─ set_current_tab: タブ状態設定
   ├─ set_view: 表示形式設定
   ├─ set_options: ページング・ソート設定
   ├─ load_ransack_search: 高度な検索条件ロード
   ├─ load_and_authorize_resource: リソース認可
   └─ get_data_for_sidebar: サイドバーデータ取得
2. get_campaigns実行（get_list_of_recordsのエイリアス）
   ├─ ransack_searchによる検索条件適用
   ├─ sessionからフィルタ取得・適用
   ├─ text_search実行（query指定時）
   ├─ タグ検索実行（#タグ指定時）
   ├─ ソート適用
   └─ ページネーション適用
3. respond_with実行
   ├─ HTML: index.html.hamlレンダリング
   ├─ CSV: CSVデータ生成
   ├─ XLS: index.xls.builderレンダリング
   ├─ RSS: index.rss.builderレンダリング
   └─ ATOM: index.atom.builderレンダリング
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[認証・認可チェック]
    B --> C[サイドバーデータ取得]
    C --> D[検索条件構築]
    D --> E{フィルタあり?}
    E -->|Yes| F[ステータスフィルタ適用]
    E -->|No| G[検索実行]
    F --> G
    G --> H{query指定?}
    H -->|Yes| I[テキスト検索適用]
    H -->|No| J[ソート適用]
    I --> J
    J --> K[ページネーション適用]
    K --> L{フォーマット判定}
    L -->|HTML| M[HTML画面レンダリング]
    L -->|CSV| N[CSVエクスポート]
    L -->|XLS| O[XLSエクスポート]
    L -->|RSS| P[RSSフィード生成]
    L -->|ATOM| Q[ATOMフィード生成]
    M --> R[終了]
    N --> R
    O --> R
    P --> R
    Q --> R
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | デフォルトページサイズ | 1ページあたり20件 | per_page未指定時 |
| BR-002 | 最大ページサイズ | 1ページあたり最大200件 | per_page指定時 |
| BR-003 | デフォルトソート | created_at DESC（作成日降順） | sort_by未指定時 |
| BR-004 | アクセス権限 | 所有・割当・共有されたキャンペーンのみ表示 | 常時 |
| BR-005 | エクスポート時ページング無効 | CSV/XLS/RSS/ATOMエクスポート時はページネーション無効 | format指定時 |

### 計算ロジック

**ステータス別件数計算**：
```ruby
@campaign_status_total = HashWithIndifferentAccess[all: Campaign.my(current_user).count, other: 0]
Setting.campaign_status.each { |key| @campaign_status_total[key] = 0 }
status_counts = Campaign.my(current_user).where(status: Setting.campaign_status).group(:status).count
# ... 集計処理
@campaign_status_total[:other] += @campaign_status_total[:all]
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キャンペーン取得 | campaigns | SELECT | ユーザーがアクセス可能なキャンペーンを取得 |
| ステータス集計 | campaigns | SELECT | ステータス別の件数を集計 |
| 関連データ | tags | SELECT | includesによる関連データ取得 |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | my(current_user)スコープ | アクセス権限フィルタリング |
| SELECT | status, COUNT(*) | GROUP BY status | サイドバー集計用 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | 未ログイン状態でアクセス | ログイン画面へリダイレクト |
| 403 | 認可エラー | アクセス権限がない | エラーメッセージ表示 |

### リトライ仕様

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

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

本機能はSELECT操作のみであり、トランザクション制御は不要。

## パフォーマンス要件

- 画面表示時間: 3秒以内を目標
- includesによるN+1クエリの回避
- ページネーションによるデータ量制限

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

- 認証: Deviseによるユーザー認証が必須
- 認可: CanCanによるアクセス権限制御
- SQLインジェクション: Ransackによる安全なクエリ構築
- RSS/ATOM: 認証済みユーザーのみアクセス可能

## 備考

- 表示形式（長形式/短形式）はユーザー設定で切り替え可能
- フィルタ設定はセッションに保存され、ページ遷移後も維持される
- RSS/ATOMフィードはキャンペーンのリード情報を含む

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/entities/campaign.rb` | Campaignモデルの属性・関連・スコープ |
| 1-2 | lead.rb | `app/models/entities/lead.rb` | リードとの関連 |
| 1-3 | opportunity.rb | `app/models/entities/opportunity.rb` | 商談との関連 |

**読解のコツ**:
- スキーマ情報（8-32行目）でカラム構成を確認
- `has_many :leads`、`has_many :opportunities`の関連に注目
- `scope :state`でステータスフィルタリングを確認

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | routes.rb | `config/routes.rb` | /campaignsルート（64-82行目） |
| 2-2 | campaigns_controller.rb | `app/controllers/entities/campaigns_controller.rb` | indexアクション |

**主要処理フロー**:
1. **13-20行目**: indexアクション - キャンペーン取得とレスポンス
2. **14行目**: get_campaigns呼び出し（get_list_of_recordsのエイリアス）
3. **17-18行目**: XLS/CSV形式のレンダリング

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

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

**主要処理フロー**:
- **191-206行目**: get_data_for_sidebar - ステータス別件数集計
- **192-195行目**: 初期化（all件数とステータスごとの0初期化）
- **200行目**: GROUP BYによる集計
- **205行目**: otherの計算

#### Step 4: ビュー層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.html.haml | `app/views/campaigns/index.html.haml` | 一覧画面レイアウト |
| 4-2 | _campaign.html.haml | `app/views/campaigns/_campaign.html.haml` | キャンペーン行表示 |
| 4-3 | _sidebar_index.html.haml | `app/views/campaigns/_sidebar_index.html.haml` | サイドバー |
| 4-4 | index.xls.builder | `app/views/campaigns/index.xls.builder` | XLSエクスポート |

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

```
CampaignsController#index
    │
    ├─ before_action
    │   ├─ set_current_tab (ApplicationController)
    │   ├─ set_view (EntitiesController)
    │   ├─ set_options (EntitiesController)
    │   ├─ load_ransack_search (EntitiesController)
    │   ├─ load_and_authorize_resource (CanCan)
    │   └─ get_data_for_sidebar
    │
    ├─ get_campaigns (alias: get_list_of_records)
    │   ├─ ransack_search.result
    │   ├─ scope.state(filter)
    │   ├─ scope.text_search(query)
    │   ├─ scope.tagged_with(tags)
    │   ├─ order_by_attributes
    │   ├─ scope.paginate
    │   └─ scope.includes(*list_includes)
    │
    └─ respond_with(@campaigns)
        ├─ format.html
        ├─ format.csv
        ├─ format.xls
        ├─ format.rss
        └─ format.atom
```

### データフロー図

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

URLパラメータ ───▶ CampaignsController#index ───▶ @campaigns
(page,query,         │                            @campaign_status_total
 status)             │                                 │
                     ▼                                 ▼
              get_list_of_records               index.html.haml
                     │                          index.xls.builder
                     ├─ ransack_search          index.rss.builder
                     ├─ state filter            index.atom.builder
                     ├─ text_search             CSV出力
                     ├─ order                       │
                     └─ paginate                   │
                     │                             │
                     ▼                             ▼
              データベース                   ブラウザ/ファイル/フィード
              (campaigns, tags)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaigns_controller.rb | `app/controllers/entities/campaigns_controller.rb` | ソース | メインコントローラー |
| entities_controller.rb | `app/controllers/entities_controller.rb` | ソース | 基底コントローラー |
| campaign.rb | `app/models/entities/campaign.rb` | ソース | キャンペーンモデル |
| index.html.haml | `app/views/campaigns/index.html.haml` | テンプレート | 一覧画面 |
| _campaign.html.haml | `app/views/campaigns/_campaign.html.haml` | テンプレート | キャンペーン行 |
| _sidebar_index.html.haml | `app/views/campaigns/_sidebar_index.html.haml` | テンプレート | サイドバー |
| _index_long.html.haml | `app/views/campaigns/_index_long.html.haml` | テンプレート | 長形式表示 |
| _index_brief.html.haml | `app/views/campaigns/_index_brief.html.haml` | テンプレート | 短形式表示 |
| index.xls.builder | `app/views/campaigns/index.xls.builder` | テンプレート | XLSエクスポート |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
