# 画面設計書 27-商談編集フォーム

## 概要

本ドキュメントは、Fat Free CRMシステムにおける商談編集フォームの設計を記述する。既存商談の情報更新、取引先の変更、アクセス権限の変更などの機能を提供するモーダル/Ajax形式のフォームである。

### 本画面の処理概要

商談編集フォームは、既存の商談情報を更新するためのフォームである。ステージの進行、金額の調整、クローズ日の変更など、商談のライフサイクルに応じた更新を行う。

**業務上の目的・背景**：営業活動において商談は常に変化する。ステージの進行、金額交渉の結果、クローズ日の変更などを随時反映し、パイプラインの正確性を維持する必要がある。本フォームにより、リアルタイムな商談情報の更新を実現する。

**画面へのアクセス方法**：
- 商談詳細画面の「編集」リンクをクリック
- 商談一覧画面の各商談の編集アイコンをクリック

**主要な操作・処理内容**：
1. 商談基本情報（名前、ステージ）の変更
2. 金額関連情報（金額、確度、割引、クローズ日）の変更
3. 取引先の変更
4. 担当者の変更
5. タグの変更
6. アクセス権限の変更
7. 変更の保存

**画面遷移**：
- 遷移元：商談詳細画面、商談一覧画面
- 遷移先（成功時）：遷移元画面（更新反映）

**権限による表示制御**：商談の編集権限を持つユーザーのみアクセス可能。

## 関連機能

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

## 画面種別

編集フォーム（モーダル/Ajax）

## URL/ルーティング

### 表示
- URL: `/opportunities/:id/edit`
- HTTPメソッド: GET
- コントローラ: `OpportunitiesController#edit`

### 更新実行
- URL: `/opportunities/:id`
- HTTPメソッド: PATCH/PUT
- コントローラ: `OpportunitiesController#update`

## 入出力項目

### 入力パラメータ（GET /opportunities/:id/edit）

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|------|------|
| id | Integer | 必須 | 商談ID |
| previous | Integer | 任意 | 前の商談ID |

### 入力パラメータ（PATCH /opportunities/:id）

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|------|------|
| id | Integer | 必須 | 商談ID |
| opportunity[name] | String | 必須 | 商談名 |
| opportunity[stage] | String | 任意 | ステージ |
| opportunity[closes_on] | Date | 任意 | クローズ予定日 |
| opportunity[probability] | Integer | 任意 | 確度 |
| opportunity[amount] | Decimal | 任意 | 金額 |
| opportunity[discount] | Decimal | 任意 | 割引額 |
| opportunity[background_info] | String | 任意 | 背景情報 |
| opportunity[assigned_to] | Integer | 任意 | 担当者ユーザーID |
| opportunity[access] | String | 必須 | アクセス権限 |
| opportunity[user_ids] | Array | 条件付き | 共有先ユーザーID |
| opportunity[tag_list] | String | 任意 | タグリスト |
| account[id] | Integer | 任意 | 既存取引先ID |
| account[name] | String | 任意 | 新規取引先名 |

### 出力データ（インスタンス変数）

| 変数名 | 型 | 説明 |
|--------|-----|------|
| @opportunity | Opportunity | 編集対象商談オブジェクト |
| @account | Account | 関連取引先（既存または新規） |
| @accounts | Array | 選択可能取引先一覧 |
| @stage | Array | ステージ選択肢 |
| @previous | Opportunity/Integer | 前の商談 |

## 表示項目

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

| 項目名 | フィールド名 | 入力形式 | 初期値 |
|--------|------------|---------|-------|
| 商談名 | name | テキスト | @opportunity.name |
| ステージ | stage | 選択 | @opportunity.stage |
| クローズ日 | closes_on | 日付 | @opportunity.closes_on |
| 確度 | probability | 数値 | @opportunity.probability |
| 金額 | amount | 数値 | @opportunity.amount |
| 割引 | discount | 数値 | @opportunity.discount |
| 取引先 | account | 選択/入力 | @opportunity.account |
| 担当者 | assigned_to | 選択 | @opportunity.assigned_to |
| 背景情報 | background_info | テキストエリア | @opportunity.background_info |
| タグ | tag_list | タグ入力 | @opportunity.tag_list |

### アクセス権限セクション

| 項目名 | フィールド名 | 入力形式 | 初期値 |
|--------|------------|---------|-------|
| アクセス権限 | access | ラジオボタン | @opportunity.access |
| 共有先 | user_ids | マルチセレクト | @opportunity.user_ids |

## イベント仕様

### 1-保存ボタンクリック

- **トリガー**: 「商談を保存」ボタンをクリック
- **処理**:
  1. account_assigned_toにopportunity_assigned_toをコピー
  2. フォームがAjaxでPATCH送信される
  3. サーバー側でバリデーション実行
  4. 取引先の変更処理
  5. 商談レコードの更新
- **成功時**: 遷移元画面更新、サイドバーデータ更新
- **失敗時**: エラーメッセージ表示、フォーム再表示

### 2-キャンセルリンククリック

- **トリガー**: 「キャンセル」リンクをクリック
- **処理**: Ajax経由でフォームを閉じる
- **遷移先**: フォーム表示前の状態に戻る

### 3-取引先選択/新規作成切替

- **トリガー**: 取引先ドロップダウンまたはテキスト入力
- **処理**: 既存選択、新規作成、または紐付け解除

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 商談更新 | opportunities | UPDATE | 商談レコード更新 |
| 取引先変更 | account_opportunities | INSERT/DELETE | 関連レコード変更 |
| 取引先新規作成 | accounts | INSERT | 新規取引先作成（指定時） |
| タグ変更 | taggings | INSERT/DELETE | タグ関連更新 |
| バージョン記録 | versions | INSERT | 更新履歴記録 |

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

#### opportunities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | フォーム入力値 | |
| UPDATE | stage | フォーム入力値 | |
| UPDATE | closes_on | フォーム入力値 | |
| UPDATE | probability | フォーム入力値 | |
| UPDATE | amount | フォーム入力値 | |
| UPDATE | discount | フォーム入力値 | |
| UPDATE | assigned_to | フォーム入力値 | |
| UPDATE | access | フォーム入力値 | |
| UPDATE | updated_at | 現在日時 | |

## メッセージ仕様

| 種別 | メッセージキー | 表示条件 |
|------|---------------|---------|
| エラー | :missing_opportunity_name | 商談名が未入力 |
| エラー | :share_opportunity | Shared選択時に共有先未指定 |
| エラー | バリデーションエラー | 各フィールドの制約違反 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 商談が存在しない | 404エラー |
| 編集権限なし | CanCanによるアクセス拒否 |
| バリデーションエラー | フォーム再表示、エラーメッセージ表示 |

## 備考

- フォームはremote: trueでAjax送信される
- キャンペーンIDは編集時にhidden_fieldで保持（変更不可）
- 取引先の担当者は商談の担当者と同期される（JavaScript）
- カスタムフィールドはFieldGroupの設定に応じて動的表示
- PaperTrailによる変更履歴追跡が有効

---

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

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

### 推奨読解順序

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

商談更新時に関連するデータ構造と更新ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | opportunity.rb | `app/models/entities/opportunity.rb` | update_with_account_and_permissions（122-133行目） |

**読解のコツ**: 取引先の紐付け解除（account = nil）の条件を確認。

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

コントローラーのeditとupdateアクションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | editアクション（55-63行目）、updateアクション（91-110行目） |

**主要処理フロー（edit）**:
1. **57行目**: @accountに既存取引先またはAccount.newをセット
2. **58行目**: @accountsで選択可能取引先一覧を取得
3. **60行目**: @previousで前の商談を取得

**主要処理フロー（update）**:
1. **93行目**: update_with_account_and_permissionsによる更新
2. **94-100行目**: 呼び出し元に応じたサイドバーデータ更新

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

編集フォームの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _edit.html.haml | `app/views/opportunities/_edit.html.haml` | フォーム構造、hidden_fieldでcampaign_id保持 |

**主要処理フロー**:
- **5行目**: campaign_idをhidden_fieldで保持
- **16行目**: 保存ボタンのonclickでaccount_assigned_toをコピー

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

```
OpportunitiesController#edit
    │
    ├─ before_action :load_settings
    │
    ├─ load_and_authorize_resource (CanCan)
    │
    ├─ @account = @opportunity.account || Account.new
    │
    ├─ @accounts = Account.my.order('name')
    │
    ├─ @previous = Opportunity.my.find_by_id(...)
    │
    └─ respond_with(@opportunity)

OpportunitiesController#update
    │
    ├─ @opportunity.update_with_account_and_permissions(params)
    │      │
    │      ├─ Account.create_or_select_for (または nil)
    │      │
    │      ├─ self.access = params[:opportunity][:access]
    │      │
    │      └─ save
    │
    ├─ get_data_for_sidebar (呼び出し元に応じて)
    │
    └─ respond_with(@opportunity)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| opportunity.rb | `app/models/entities/opportunity.rb` | モデル | 更新ロジック |
| opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | コントローラ | edit/updateアクション |
| _edit.html.haml | `app/views/opportunities/_edit.html.haml` | テンプレート | 編集フォーム |
| _top_section.html.haml | `app/views/opportunities/_top_section.html.haml` | テンプレート | 入力項目 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
