# 画面設計書 14-キャンペーン編集フォーム

## 概要

Fat Free CRM における既存キャンペーン（マーケティングキャンペーン）の情報を編集するためのフォーム画面設計書である。

### 本画面の処理概要

本画面は、既存のキャンペーン情報を更新するための入力フォームであり、Ajax通信によるモーダル形式で表示される。

**業務上の目的・背景**：
キャンペーンの進行に伴い、計画の変更や実績の反映が必要となる場合がある。本画面を通じて、キャンペーン名、期間、ステータス、目標値などの情報を更新し、キャンペーンの進捗管理を行う。また、アクセス権限の変更により、情報共有範囲を柔軟に調整できる。

**画面へのアクセス方法**：
1. キャンペーン詳細画面で「Edit」ボタンをクリック
2. キャンペーン一覧画面で編集アイコンをクリック
3. Ajax通信でモーダルとして表示される

**主要な操作・処理内容**：
1. キャンペーン基本情報の編集（名前、開始日、終了日、ステータス）
2. 背景情報の編集（オプション）
3. タグの編集
4. キャンペーン目標の編集（リード数、コンバージョン率、収益、予算）
5. 目標テキストの編集
6. カスタムフィールドの編集
7. アクセス権限の変更（Public/Private/Shared）
8. 保存またはキャンセル

**画面遷移**：
- 遷移元: キャンペーン詳細画面、キャンペーン一覧画面
- 遷移先（保存成功時）: 遷移元画面（更新）
- 遷移先（キャンセル時）: 遷移元画面（変更なし）

**権限による表示制御**：
- キャンペーンのアクセス権限に基づいて編集可否が決定
- 作成者または共有されたユーザーのみ編集可能
- 管理者は全てのキャンペーンを編集可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 13 | キャンペーン編集 | 主機能 | キャンペーン情報更新 |
| 83 | タグ付け | 補助機能 | キャンペーンへのタグ付け |
| 84 | アクセス権限管理 | 補助機能 | キャンペーンのアクセス権限設定 |

## 画面種別

編集

## URL/ルーティング

| HTTPメソッド | URL | アクション | 説明 |
|-------------|-----|----------|------|
| GET | /campaigns/:id/edit | edit | 編集フォーム表示 |
| PUT/PATCH | /campaigns/:id | update | 更新実行 |

## 入出力項目

### 基本情報セクション

| 項目名 | 入力形式 | 必須 | 最大長 | 説明 |
|--------|---------|------|--------|------|
| キャンペーン名 | テキスト | ○ | 64文字 | キャンペーンの名称 |
| 開始日 | 日付ピッカー | - | - | キャンペーン開始日 |
| 終了日 | 日付ピッカー | - | - | キャンペーン終了日 |
| ステータス | セレクトボックス | - | - | planned/started/on_hold/completed/called_off |
| 背景情報 | テキストエリア | - | 255文字 | キャンペーンの背景説明（設定有効時のみ表示） |
| タグ | テキスト | - | - | カンマ区切りでタグを入力 |

### 目標セクション（Objectives）

| 項目名 | 入力形式 | 必須 | 説明 |
|--------|---------|------|------|
| 目標リード数 | 数値 | - | 獲得目標リード数 |
| 目標コンバージョン率 | 数値 | - | 目標コンバージョン率（%） |
| 目標収益 | 数値 | - | 目標収益額 |
| 予算 | 数値 | - | キャンペーン予算 |
| 目標（テキスト） | テキストエリア | - | 目標の詳細説明 |

### カスタムフィールドセクション

| 項目名 | 入力形式 | 必須 | 説明 |
|--------|---------|------|------|
| カスタムフィールド | 動的 | 設定依存 | 管理者が定義したカスタムフィールド |

### 権限セクション

| 項目名 | 入力形式 | 必須 | 説明 |
|--------|---------|------|------|
| アクセス権限 | ラジオボタン | ○ | Public/Private/Shared |
| 共有ユーザー | マルチセレクト | 条件付 | Shared選択時に表示 |
| 共有グループ | マルチセレクト | 条件付 | Shared選択時に表示 |

## 表示項目

既存値が各入力フィールドにプリセットされる。

## イベント仕様

### 01-フォーム表示

「Edit」リンクをクリックすると、Ajax通信で編集フォームが読み込まれ、既存の値がプリセットされた状態でモーダルとして展開される。

### 02-ステータス変更

ステータスセレクトボックスでステータスを変更する。現在のステータスが初期選択される。

### 03-日付変更

開始日・終了日フィールドをクリックすると、日付ピッカーが表示される。既存の日付が初期値として設定される。

### 04-目標セクション展開

「Objectives」セクション見出しをクリックすると、折りたたまれた目標入力フォームが展開/折りたたみされる。編集モードではデフォルトで展開されている場合がある。

### 05-タグ編集

タグ入力フィールドで既存タグの追加・削除を行う。

### 06-アクセス権限変更

- 現在の権限設定が初期選択される
- 権限を変更する際、Sharedの場合は共有先の選択が必要

### 07-保存ボタン押下

「Save Campaign」ボタンをクリックすると、Ajax通信でPUT/PATCHリクエストが送信される。成功時は詳細画面または一覧画面が更新され、フォームが閉じる。失敗時はエラーメッセージが表示される。

### 08-キャンセル

「Cancel」リンクをクリックすると、フォームが閉じ、変更は破棄される。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存 | campaigns | UPDATE | キャンペーン情報更新 |
| 保存（権限変更） | permissions | DELETE/INSERT | 権限レコード更新 |
| 保存（タグ変更） | taggings | DELETE/INSERT | タグ紐付け更新 |
| 保存 | versions | INSERT | 更新履歴記録 |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | 入力値 | 必須 |
| UPDATE | access | 入力値 | Public/Private/Shared |
| UPDATE | status | 入力値 | |
| UPDATE | budget | 入力値 | |
| UPDATE | target_leads | 入力値 | |
| UPDATE | target_conversion | 入力値 | |
| UPDATE | target_revenue | 入力値 | |
| UPDATE | starts_on | 入力値 | |
| UPDATE | ends_on | 入力値 | |
| UPDATE | objectives | 入力値 | |
| UPDATE | background_info | 入力値 | |
| UPDATE | updated_at | 現在日時 | |

#### permissions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | * | asset_type='Campaign', asset_id=:id | 既存権限削除 |
| INSERT | user_id | 選択されたユーザーID | Shared時のみ |
| INSERT | group_id | 選択されたグループID | Shared時のみ |

#### versions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | item_type | 'Campaign' | |
| INSERT | item_id | キャンペーンID | |
| INSERT | event | 'update' | |
| INSERT | whodunnit | current_user.id | |
| INSERT | object | 変更前の値（YAML） | |
| INSERT | object_changes | 変更差分（YAML） | |

## メッセージ仕様

| メッセージ種別 | メッセージコード | 表示内容 | 表示条件 |
|--------------|----------------|---------|---------|
| エラー | missing_campaign_name | "Campaign name is required." | 名前未入力時 |
| エラー | dates_not_in_sequence | "End date must be after start date." | 終了日が開始日より前の場合 |
| エラー | share_campaign | "Please select users and/or groups to share with." | Shared選択時に共有先未選択 |
| エラー | uniqueness | "Campaign name has already been taken." | 同名キャンペーンが存在する場合 |

## 例外処理

| 例外ケース | 処理内容 | 表示 |
|-----------|---------|------|
| バリデーションエラー | フォーム再表示 | エラーメッセージをフィールド上部に表示 |
| 認証エラー | ログイン画面にリダイレクト | Deviseのデフォルトメッセージ |
| 権限エラー | 403エラー | アクセス権限がありません |
| データ未存在 | 404エラー | 該当するキャンペーンが見つかりません |

## 備考

- simple_form_for を使用したフォーム生成
- remote: true により Ajax 送信
- one_submit_only によりダブルサブミット防止
- edit: true パラメータが各パーシャルに渡され、編集モード固有の表示制御が行われる
- アクセス権限は最初に設定してからuser_idsを設定する必要がある（依存関係）
- @previous 変数で前のキャンペーンへのナビゲーションリンクが提供される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/entities/campaign.rb` | バリデーション定義（64-68行目）を確認 |

**読解のコツ**: 編集時のバリデーションは新規作成時と同様。`validates_uniqueness_of :name, scope: [:user_id, :deleted_at]` で同一ユーザー内の重複チェックを行う。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | campaigns_controller.rb | `app/controllers/entities/campaigns_controller.rb` | editアクション（85-90行目） |
| 2-2 | campaigns_controller.rb | `app/controllers/entities/campaigns_controller.rb` | updateアクション（108-114行目） |

**主要処理フロー**:
1. **87行目**: @previous で前のキャンペーンを取得（一覧からの連続編集用）
2. **111行目**: access を最初に設定（user_ids との依存関係のため）
3. **112行目**: 更新成功時、一覧から呼ばれた場合はサイドバーも更新

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _edit.html.haml | `app/views/campaigns/_edit.html.haml` | 編集フォームの構成 |

**主要処理フロー**:
- **1行目**: .remote クラスでコンテナを定義
- **2行目**: simple_form_for でフォーム開始
- **3行目**: 閉じるリンク
- **8-12行目**: 各セクションのパーシャルレンダリング（edit: true）

#### Step 4: 新規作成との差異を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | _edit.html.haml | `app/views/campaigns/_edit.html.haml` | 編集フォーム固有の処理 |
| 4-2 | _new.html.haml | `app/views/campaigns/_new.html.haml` | 比較対象 |

**主要な差異**:
- 編集フォームはコメント追加セクションがない
- ボタンラベルが「Save Campaign」（新規は「Create Campaign」）
- パーシャルに `edit: true` パラメータが渡される

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

```
[HTTP Request: GET /campaigns/:id/edit]
    │
    ├─ routes.rb (64行目)
    │      └─ CampaignsController#edit
    │
    ├─ CampaignsController#edit (85-90行目)
    │      ├─ @campaign (CanCanCanによるロード)
    │      ├─ @previous (前のキャンペーン取得)
    │      └─ respond_with(@campaign)
    │
    └─ campaigns/_edit.html.haml
           ├─ simple_form_for(@campaign, remote: true)
           ├─ campaigns/_top_section.html.haml (edit: true)
           ├─ fields/_edit_custom_field_group.html.haml (edit: true)
           ├─ campaigns/_objectives.html.haml (edit: true)
           ├─ fields/_groups.html.haml (edit: true)
           ├─ entities/_permissions.html.haml (edit: true)
           └─ hook(:entity_form)

[HTTP Request: PUT /campaigns/:id]
    │
    └─ CampaignsController#update (108-114行目)
           ├─ @campaign.access = resource_params[:access]
           ├─ @campaign.update(resource_params)
           └─ get_data_for_sidebar (一覧から呼ばれた場合)
```

### データフロー図

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

フォーム入力 ───────▶ params[:campaign]
                           │
                           ▼
                    CampaignsController#update
                           │
                    ┌──────┴──────┐
                    ▼             ▼
              access設定       バリデーション
              (最初に実行)          │
                    │         失敗 │
                    │             ▼
                    │        エラーメッセージ
                    ▼
              campaigns テーブル UPDATE
                    │
              ┌─────┼─────┐
              ▼     ▼     ▼
          permissions taggings versions
           UPDATE    UPDATE   INSERT
                    │
                    ▼
              Ajax レスポンス ───▶ 詳細/一覧画面更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaign.rb | `app/models/entities/campaign.rb` | モデル | キャンペーンのバリデーション定義 |
| campaigns_controller.rb | `app/controllers/entities/campaigns_controller.rb` | コントローラー | edit/updateアクション |
| _edit.html.haml | `app/views/campaigns/_edit.html.haml` | ビュー | 編集フォームメインテンプレート |
| _top_section.html.haml | `app/views/campaigns/_top_section.html.haml` | ビュー | 基本情報入力セクション |
| _objectives.html.haml | `app/views/campaigns/_objectives.html.haml` | ビュー | 目標入力セクション |
| _permissions.html.haml | `app/views/entities/_permissions.html.haml` | ビュー | 権限設定セクション |
| _tags.html.haml | `app/views/shared/_tags.html.haml` | ビュー | タグ入力セクション |
| routes.rb | `config/routes.rb` | 設定 | URLルーティング定義 |
