# 機能設計書 13-キャンペーン編集

## 概要

本ドキュメントは、Fat Free CRMにおけるキャンペーン編集機能の設計を記述する。既存のキャンペーン情報を更新する機能である。

### 本機能の処理概要

**業務上の目的・背景**：キャンペーンの進行に伴い、予算の調整、目標値の変更、ステータスの更新など、キャンペーン情報の修正が必要になる。この機能により、キャンペーンの最新状態を常にシステムに反映し、正確な情報管理を実現する。

**機能の利用シーン**：キャンペーンの予算や目標を変更する場合、キャンペーンのステータスを更新する場合（計画中→実行中→完了など）、担当者の変更や開始日・終了日の調整を行う場合に利用される。また、アクセス権限の変更（Public→Sharedなど）にも使用される。

**主要な処理内容**：
1. 編集フォームの表示（editアクション）
2. 既存キャンペーン情報のフォームへの表示
3. 入力された更新情報のバリデーション
4. キャンペーンレコードの更新
5. サイドバーデータの更新（一覧ページからの呼び出し時）
6. 成功/失敗に応じたレスポンス生成

**関連システム・外部連携**：特になし。内部のCRMデータとして管理される。

**権限による制御**：CanCanによるアクセス権限管理が行われる。キャンペーンの所有者または共有権限を持つユーザーのみ編集可能。アクセス権限を変更する際は、accessパラメータを先に設定する必要がある。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | キャンペーン編集フォーム | 主画面 | キャンペーン情報更新 |
| 12 | キャンペーン詳細画面 | 参照画面 | 編集完了後の遷移先 |
| 11 | キャンペーン一覧画面 | 参照画面 | 一覧からの編集時の遷移元 |

## 機能種別

CRUD操作（Update）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | キャンペーンID | 数値形式、存在チェック |
| campaign[name] | String | Yes | キャンペーン名（最大64文字） | 必須、ユニーク制約 |
| campaign[status] | String | No | ステータス | Setting.campaign_statusの値のみ許可 |
| campaign[budget] | Decimal | No | 予算 | 数値（12桁、小数2桁） |
| campaign[target_leads] | Integer | No | 目標リード数 | 整数 |
| campaign[target_conversion] | Float | No | 目標コンバージョン率 | 浮動小数点 |
| campaign[target_revenue] | Decimal | No | 目標収益 | 数値（12桁、小数2桁） |
| campaign[starts_on] | Date | No | 開始日 | 日付形式 |
| campaign[ends_on] | Date | No | 終了日 | 日付形式、開始日より後 |
| campaign[objectives] | Text | No | 目的・目標 | テキスト |
| campaign[access] | String | No | アクセス権限 | Public/Private/Shared |
| campaign[assigned_to] | Integer | No | 担当者ID | 存在するユーザーID |
| campaign[background_info] | String | No | 背景情報 | 最大255文字 |

### 入力データソース

- URLパラメータ（キャンペーンID）
- フォーム入力（AJAX PUT/PATCH）
- セッション情報（現在のユーザー情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @campaign | Campaign | 更新されたキャンペーンオブジェクト |
| @previous | Campaign/Integer | 直前のキャンペーン（インライン編集時） |

### 出力先

- AJAX（JS）レスポンス
- JSON/XMLレスポンス（API経由）

## 処理フロー

### 処理シーケンス

```
1. ユーザー認証確認
   └─ authenticate_user!でログイン状態を確認
2. キャンペーン取得と権限確認
   └─ load_and_authorize_resourceでID指定のキャンペーンを取得し権限確認
3. 編集フォーム表示（editアクション）
   └─ 既存データをフォームに表示、前回キャンペーン情報取得
4. パラメータ受付（updateアクション）
   └─ accessパラメータを先に設定
5. キャンペーン更新
   └─ バリデーション成功時にデータベースを更新
6. サイドバーデータ更新（一覧ページからの呼び出し時）
   └─ ステータス別カウントを再取得
7. レスポンス生成
   └─ 成功/失敗に応じたJSレスポンス
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[ユーザー認証]
    B --> C{認証OK?}
    C -->|No| D[ログインページへリダイレクト]
    C -->|Yes| E[キャンペーン取得]
    E --> F{存在確認}
    F -->|No| G[404エラー]
    F -->|Yes| H{アクセス権限確認}
    H -->|No| I[403エラー]
    H -->|Yes| J{アクション判定}
    J -->|edit| K[前回キャンペーン取得]
    K --> L[編集フォーム表示]
    J -->|update| M[accessパラメータ設定]
    M --> N[バリデーション実行]
    N --> O{バリデーション成功?}
    O -->|No| P[エラーメッセージ表示]
    O -->|Yes| Q[キャンペーン更新]
    Q --> R{一覧ページから?}
    R -->|Yes| S[サイドバーデータ更新]
    R -->|No| T[成功レスポンス]
    S --> T
    P --> U[失敗レスポンス]
    T --> V[終了]
    U --> V
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-13-01 | キャンペーン名必須 | キャンペーン名は必須入力 | 常時 |
| BR-13-02 | キャンペーン名ユニーク | 同一ユーザー内でキャンペーン名はユニーク | 常時 |
| BR-13-03 | 日付整合性 | 終了日は開始日より後である必要がある | 両日が入力された場合 |
| BR-13-04 | 共有時のユーザー指定 | access=Sharedの場合、共有先ユーザー必須 | Shared選択時 |
| BR-13-05 | ステータス値制約 | ステータスはSetting.campaign_statusの値のみ | ステータス入力時 |
| BR-13-06 | access先行設定 | user_ids設定前にaccessを設定する必要がある | 権限変更時 |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キャンペーン取得 | campaigns | SELECT | ID指定でキャンペーン情報取得 |
| キャンペーン更新 | campaigns | UPDATE | キャンペーンレコード更新 |
| 権限更新 | permissions | DELETE/INSERT | Shared設定変更時の権限レコード更新 |
| 変更履歴記録 | versions | INSERT | 更新イベント記録（PaperTrail） |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全カラム | id = パラメータ指定値 | |
| UPDATE | name | フォーム入力値 | 必須 |
| UPDATE | status | フォーム入力値 | |
| UPDATE | budget | フォーム入力値 | |
| UPDATE | target_leads | フォーム入力値 | |
| UPDATE | target_conversion | フォーム入力値 | |
| UPDATE | target_revenue | フォーム入力値 | |
| UPDATE | starts_on | フォーム入力値 | |
| UPDATE | ends_on | フォーム入力値 | |
| UPDATE | objectives | フォーム入力値 | |
| UPDATE | access | フォーム入力値 | |
| UPDATE | assigned_to | フォーム入力値 | |
| UPDATE | background_info | フォーム入力値 | |
| UPDATE | updated_at | 現在日時 | 自動設定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | 未ログイン状態でアクセス | ログインページへリダイレクト |
| 403 | 権限エラー | 編集権限がないキャンペーン | アクセス拒否メッセージ表示 |
| 404 | 存在しないリソース | 指定IDのキャンペーンが存在しない | 警告メッセージと一覧へリダイレクト |
| 422 | バリデーションエラー | 必須項目未入力、ユニーク制約違反等 | エラーメッセージ表示 |

### リトライ仕様

本機能にリトライ処理は実装されていない。バリデーションエラー時はフォームを再表示し、ユーザーに修正を促す。

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

キャンペーン更新と権限更新は同一トランザクションで実行される。

## パフォーマンス要件

- 編集フォーム表示は1秒以内を目標
- 更新処理は2秒以内を目標

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

- CanCanによるアクセス権限チェック実施
- CSRF対策（protect_from_forgery）
- Strong Parametersによる許可パラメータ制限
- SQLインジェクション対策（ActiveRecord使用）
- XSS対策（ERB::Util使用）

## 備考

- 変更履歴はPaperTrailにより自動記録される（subscribed_usersは除く）
- タグ編集機能は別機能（No.83）として管理

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/entities/campaign.rb` | Campaignモデルの属性、バリデーション |

**読解のコツ**:
- **64-68行目**: バリデーション定義
- **56行目**: `has_paper_trail`で変更履歴記録、ignoreオプションでsubscribed_usersを除外

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

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

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

**主要処理フロー**:
1. **86-90行目**: editアクション - 前回キャンペーン取得
2. **108-114行目**: updateアクション - access先行設定、更新処理
3. **111行目**: `@campaign.access = resource_params[:access]`でaccess先行設定

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _edit.html.haml | `app/views/campaigns/_edit.html.haml` | 編集フォームの構造 |
| 3-2 | edit.js.haml | `app/views/campaigns/edit.js.haml` | 編集フォーム表示用JSテンプレート |

**主要処理フロー**:
- **2行目**: simple_form_forでフォーム生成
- **8-12行目**: 各セクションのパーシャルレンダリング（edit: trueオプション付き）
- **15-18行目**: 保存ボタンとキャンセルリンク

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

```
CampaignsController#edit
    │
    ├─ EntitiesController (継承)
    │      └─ load_and_authorize_resource
    │
    └─ Campaign.my(current_user).find_by_id
           └─ 前回キャンペーン取得

CampaignsController#update
    │
    ├─ access先行設定
    │
    ├─ Campaign#update
    │      ├─ バリデーション実行
    │      └─ UPDATE campaigns
    │
    ├─ versions INSERT (PaperTrail)
    │
    └─ get_data_for_sidebar（一覧ページからの場合）
```

### データフロー図

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

キャンペーンID ───▶ CampaignsController#edit ───▶ 編集フォーム
                          │
                          └─▶ Campaign取得

フォーム入力 ───────▶ CampaignsController#update ───▶ JSレスポンス
                          │
                          ├─▶ access先行設定
                          │
                          ├─▶ バリデーション
                          │
                          └─▶ Campaign更新
                                └─▶ campaigns UPDATE
                                └─▶ versions INSERT
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaigns_controller.rb | `app/controllers/entities/campaigns_controller.rb` | コントローラー | キャンペーン関連アクションの処理 |
| entities_controller.rb | `app/controllers/entities_controller.rb` | コントローラー | エンティティ共通処理 |
| campaign.rb | `app/models/entities/campaign.rb` | モデル | キャンペーンエンティティ定義 |
| _edit.html.haml | `app/views/campaigns/_edit.html.haml` | ビュー | 編集フォームテンプレート |
| edit.js.haml | `app/views/campaigns/edit.js.haml` | ビュー | 編集フォーム表示用JSテンプレート |
| update.js.haml | `app/views/campaigns/update.js.haml` | ビュー | 更新完了用JSテンプレート |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
