# 機能設計書 31-商談編集

## 概要

本ドキュメントは、Fat Free CRMシステムにおける「商談編集」機能の設計を定義する。既存の商談（Opportunity）情報を更新するための機能である。

### 本機能の処理概要

商談編集機能は、営業プロセス管理において登録済みの商談情報を最新の状態に保つための重要な機能である。

**業務上の目的・背景**：営業活動において商談の進捗状況は刻々と変化する。商談のステージ（prospecting、negotiation、won、lost等）、金額、確度、クローズ予定日などの情報を適時に更新することで、正確なパイプライン管理と売上予測が可能となる。また、取引先や連絡先との紐付けを変更することで、商談の関係性を正確に追跡できる。

**機能の利用シーン**：営業担当者が顧客との商談後に進捗を更新する場合、商談のステージが変化した場合（例：提案段階から交渉段階へ）、商談金額や確度が変更された場合、担当者の割り当てを変更する場合、取引先との紐付けを変更する場合に利用される。

**主要な処理内容**：
1. 商談編集フォームの表示（edit アクション）
2. 商談情報のバリデーションと更新（update アクション）
3. 取引先情報との連携更新
4. アクセス権限（パーミッション）の更新
5. サイドバー情報の再取得とUI更新

**関連システム・外部連携**：商談は取引先（Account）、キャンペーン（Campaign）、連絡先（Contact）と関連付けられる。これらの関連エンティティとの紐付けを編集時に変更可能。

**権限による制御**：商談の編集は、商談を作成したユーザー、商談にアサインされたユーザー、または商談が共有設定されている場合は共有先ユーザーのみが実行可能。管理者はすべての商談を編集可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 27 | 商談編集フォーム | 主画面 | 商談情報更新フォームの表示と保存 |
| 24 | 商談一覧画面 | 参照画面 | 一覧からの編集呼び出し |
| 25 | 商談詳細画面 | 参照画面 | 詳細画面からの編集呼び出し |

## 機能種別

CRUD操作（Update） / データ更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 商談ID | 存在チェック、アクセス権チェック |
| opportunity[name] | String | Yes | 商談名 | 必須、最大64文字 |
| opportunity[stage] | String | No | 商談ステージ | 設定された選択肢のいずれか |
| opportunity[amount] | Decimal | No | 商談金額 | 数値形式 |
| opportunity[discount] | Decimal | No | 割引額 | 数値形式 |
| opportunity[probability] | Integer | No | 成約確度（%） | 数値形式 |
| opportunity[closes_on] | Date | No | クローズ予定日 | 日付形式 |
| opportunity[source] | String | No | 商談ソース | 設定された選択肢 |
| opportunity[assigned_to] | Integer | No | 担当者ID | ユーザー存在チェック |
| opportunity[access] | String | No | アクセス権限 | Public/Private/Shared |
| opportunity[background_info] | String | No | 背景情報 | 最大255文字 |
| account[id] | Integer | No | 既存取引先ID | 取引先存在チェック |
| account[name] | String | No | 新規取引先名 | 最大64文字 |

### 入力データソース

- 画面入力（商談編集フォーム）
- セッション情報（current_user）
- 既存の商談データ（DBからの取得）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @opportunity | Opportunity | 更新された商談オブジェクト |
| @accounts | Array | ユーザーがアクセス可能な取引先一覧（エラー時） |
| @account | Account | 関連付けられた取引先 |
| flash[:notice] | String | 成功/エラーメッセージ |

### 出力先

- 画面表示（AJAX応答によるフォーム更新）
- データベース（opportunitiesテーブル、account_opportunitiesテーブル）

## 処理フロー

### 処理シーケンス

```
1. editアクション（編集フォーム表示）
   └─ 商談データの取得とフォーム用データの準備
2. 商談IDによるレコード取得
   └─ cancancanによるアクセス権チェック
3. 関連取引先の取得
   └─ 既存の取引先または新規取引先オブジェクトの準備
4. ユーザーがアクセス可能な取引先一覧の取得
   └─ フォームのドロップダウン用データ
5. updateアクション（更新処理）
   └─ フォーム送信後の更新処理
6. update_with_account_and_permissions呼び出し
   └─ 取引先連携を含む更新処理
7. 取引先の作成または選択
   └─ 新規作成か既存選択かの判定
8. 商談レコードの更新
   └─ バリデーション実行後にDB保存
9. サイドバーデータの再取得（必要な場合）
   └─ 一覧画面からの呼び出し時
10. 応答の生成
   └─ JS/HTML形式での応答
```

### フローチャート

```mermaid
flowchart TD
    A[編集リクエスト受信] --> B[商談データ取得]
    B --> C{アクセス権あり?}
    C -->|No| D[403エラー]
    C -->|Yes| E[編集フォーム表示]
    E --> F[ユーザー入力]
    F --> G[更新リクエスト送信]
    G --> H{バリデーション}
    H -->|失敗| I[エラー表示]
    I --> F
    H -->|成功| J{取引先の処理}
    J -->|新規作成| K[取引先作成]
    J -->|既存選択| L[取引先紐付け更新]
    J -->|紐付け解除| M[取引先関連削除]
    K --> N[商談レコード更新]
    L --> N
    M --> N
    N --> O{呼び出し元判定}
    O -->|一覧画面| P[サイドバー更新]
    O -->|詳細/他画面| Q[画面更新]
    P --> R[完了]
    Q --> R
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-01 | 商談名必須 | 商談名は必須入力 | 常時 |
| BR-31-02 | ステージ選択肢 | ステージは設定されたオプションから選択 | ステージ入力時 |
| BR-31-03 | 共有時ユーザー必須 | アクセス権限がSharedの場合、共有先ユーザーの選択必須 | access=Shared時 |
| BR-31-04 | 数値項目 | 金額、確度、割引は数値形式 | 該当項目入力時 |
| BR-31-05 | 取引先連携 | 取引先名が空の場合は紐付け解除 | 取引先名クリア時 |

### 計算ロジック

- 加重金額（weighted_amount）: `(amount - discount) * probability / 100`
- この計算値はパイプラインレポートで使用される

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 商談更新 | opportunities | UPDATE | 商談情報の更新 |
| 取引先作成 | accounts | INSERT | 新規取引先の同時作成 |
| 取引先紐付け更新 | account_opportunities | UPDATE/INSERT/DELETE | 商談-取引先関連の更新 |
| パーミッション更新 | permissions | INSERT/DELETE | アクセス権限の更新 |
| 変更履歴記録 | versions | INSERT | PaperTrailによる履歴記録 |

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

#### opportunities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | フォーム入力値 | 必須 |
| UPDATE | stage | フォーム入力値 | 設定値から選択 |
| UPDATE | amount | フォーム入力値 | 数値 |
| UPDATE | discount | フォーム入力値 | 数値 |
| UPDATE | probability | フォーム入力値 | 整数 |
| UPDATE | closes_on | フォーム入力値 | 日付 |
| UPDATE | source | フォーム入力値 | 設定値から選択 |
| UPDATE | assigned_to | フォーム入力値 | ユーザーID |
| UPDATE | access | フォーム入力値 | Public/Private/Shared |
| UPDATE | background_info | フォーム入力値 | テキスト |
| UPDATE | updated_at | 現在日時 | 自動設定 |

#### account_opportunities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | account_id | 選択/作成された取引先ID | 取引先変更時 |
| INSERT/UPDATE | opportunity_id | 対象商談ID | 商談ID |
| DELETE | - | 取引先紐付け解除時 | 取引先クリア時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定IDの商談が存在しない | エラー画面表示 |
| 403 | Forbidden | アクセス権限がない | アクセス拒否画面表示 |
| 422 | Validation Error | バリデーション失敗 | エラーメッセージ表示、フォーム再表示 |
| - | missing_opportunity_name | 商談名未入力 | エラーメッセージ表示 |
| - | share_opportunity | Shared選択時にユーザー未選択 | エラーメッセージ表示 |

### リトライ仕様

バリデーションエラー時はフォームを再表示し、ユーザーが修正して再送信可能。

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

- 商談の更新、取引先の作成/紐付け更新、パーミッションの更新は暗黙的なトランザクション内で実行
- いずれかの処理が失敗した場合はロールバック
- PaperTrailによる履歴記録も同一トランザクション内で実行

## パフォーマンス要件

- 編集フォーム表示: 1秒以内
- 更新処理: 2秒以内
- 取引先一覧の取得にはmy(current_user)スコープを使用し、ユーザーがアクセス可能なレコードのみ取得

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

- CanCanCanによるアクセス権限チェック（load_and_authorize_resource）
- CSRF対策（Railsデフォルト）
- Strong Parametersによる入力パラメータのホワイトリスト制御
- アクセス権限（Public/Private/Shared）による閲覧・編集制御
- XSS対策（出力時のエスケープ）

## 備考

- 商談のステージオプションはSetting.unroll(:opportunity_stage)から取得
- デフォルトステージはSetting[:opportunity_default_stage]で設定
- 取引先の同時作成機能により、既存取引先の選択または新規取引先の作成が可能

---

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

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

### 推奨読解順序

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

商談（Opportunity）モデルの構造とリレーションを理解することが重要。

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

**読解のコツ**:
- **8-28行目**: スキーマコメントでテーブル構造を把握
- **30-40行目**: belongs_to、has_one、has_manyの関連を確認
- **84-87行目**: バリデーションルールを確認
- **109-119行目**: save_with_account_and_permissionsメソッドの処理フロー
- **123-133行目**: update_with_account_and_permissionsメソッドで更新処理の実装を確認

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

OpportunitiesControllerのeditとupdateアクションが処理の起点。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | editアクションとupdateアクションの実装 |

**主要処理フロー**:
1. **54-63行目**: editアクション - 編集フォーム表示の準備
2. **56-58行目**: 取引先情報とアカウント一覧の取得
3. **60行目**: 前のレコードID取得（ページネーション用）
4. **89-110行目**: updateアクション - 更新処理の実行
5. **93行目**: update_with_account_and_permissionsの呼び出し
6. **94-100行目**: 呼び出し元に応じたサイドバー更新

#### Step 3: 親コントローラの共通処理を理解する

EntitiesControllerの共通処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entities_controller.rb | `app/controllers/entities_controller.rb` | 共通のCRUD処理、認可処理 |

**主要処理フロー**:
- **15行目**: load_and_authorize_resourceによる認可
- **97-104行目**: entityメソッドによるインスタンス変数アクセス
- **116-122行目**: set_optionsでページネーション設定

#### Step 4: ビューの構造を理解する

編集フォームのビューファイルを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | _edit.html.haml | `app/views/opportunities/_edit.html.haml` | 編集フォームの構造 |
| 4-2 | edit.js.haml | `app/views/opportunities/edit.js.haml` | AJAX応答 |
| 4-3 | update.js.haml | `app/views/opportunities/update.js.haml` | 更新後のJS処理 |

**主要処理フロー**:
- **2行目**: simple_form_forによるフォーム生成
- **9-13行目**: 各セクションのパーシャルレンダリング
- **16行目**: サブミットボタンと取引先担当者の同期処理

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

```
OpportunitiesController#edit
    │
    ├─ load_and_authorize_resource (CanCanCan)
    │      └─ Ability#can?
    │
    ├─ Opportunity.find (ActiveRecord)
    │
    ├─ Account.my(current_user).order('name')
    │
    └─ respond_with(@opportunity)
           └─ render "edit.js.haml"
                  └─ render "_edit.html.haml"

OpportunitiesController#update
    │
    ├─ load_and_authorize_resource (CanCanCan)
    │
    ├─ Opportunity#update_with_account_and_permissions
    │      ├─ Account.create_or_select_for
    │      │      ├─ Account.find (既存選択時)
    │      │      └─ Account.create (新規作成時)
    │      │
    │      └─ Opportunity#save
    │             ├─ validates_presence_of :name
    │             ├─ validates_numericality_of
    │             └─ validate :users_for_shared_access
    │
    ├─ get_data_for_sidebar (一覧画面からの呼び出し時)
    │      └─ Opportunity.my(current_user).group(:stage).count
    │
    └─ respond_with(@opportunity)
           └─ render "update.js.haml"
```

### データフロー図

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

商談編集フォーム ──────▶ OpportunitiesController#edit ──────▶ 編集フォーム表示
     │                         │
     │                         ▼
     │                  @opportunity取得
     │                  @accounts取得
     │                         │
     ▼                         ▼
フォーム送信 ──────────▶ OpportunitiesController#update
                               │
                               ▼
                    update_with_account_and_permissions
                               │
                    ┌──────────┼──────────┐
                    ▼          ▼          ▼
              取引先更新   商談更新   パーミッション更新
                    │          │          │
                    └──────────┼──────────┘
                               ▼
                    ┌──────────┴──────────┐
                    ▼                     ▼
            opportunitiesテーブル   account_opportunitiesテーブル
                               │
                               ▼
                         UI更新（AJAX）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | コントローラ | 商談のCRUD処理 |
| entities_controller.rb | `app/controllers/entities_controller.rb` | コントローラ | エンティティ共通処理 |
| opportunity.rb | `app/models/entities/opportunity.rb` | モデル | 商談のビジネスロジック |
| _edit.html.haml | `app/views/opportunities/_edit.html.haml` | ビュー | 編集フォームテンプレート |
| edit.js.haml | `app/views/opportunities/edit.js.haml` | ビュー | 編集フォーム表示AJAX |
| update.js.haml | `app/views/opportunities/update.js.haml` | ビュー | 更新後AJAX処理 |
| _top_section.html.haml | `app/views/opportunities/_top_section.html.haml` | ビュー | フォーム上部セクション |
| ability.rb | `app/models/users/ability.rb` | モデル | 認可ルール定義 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（125-142行目） |
