# 機能設計書 30-商談作成

## 概要

本ドキュメントは、Fat Free CRMシステムにおける商談作成機能の設計を定義する。この機能は、新規の商談情報を登録し、取引先との関連付けやコメントの追加を行う。

### 本機能の処理概要

商談作成機能は、営業活動における商談情報をシステムに登録するための機能である。

**業務上の目的・背景**：営業チームが取引先との商談を管理し、売上予測や営業パイプラインの追跡を行うために必要な機能である。商談の金額、確度、クローズ予定日を記録することで、売上予測の精度向上と営業活動の優先順位付けを実現する。取引先と同時に作成することも、既存の取引先に紐付けることも可能である。

**機能の利用シーン**：見積もり依頼を受けた場合、提案を開始する場合、リードから変換された商談を補完する場合、既存顧客から新たな受注機会が発生した場合に利用される。また、取引先詳細画面やキャンペーン詳細画面からも商談を直接作成できる。

**主要な処理内容**：
1. 商談新規作成フォームの表示（newアクション）
2. 取引先の選択または新規作成
3. 入力データのバリデーション
4. 商談データのデータベースへの保存
5. キャンペーンとの関連付け（オプション）
6. 連絡先との関連付け（オプション）
7. 初期コメントの追加（オプション）
8. アクセス権限の設定

**関連システム・外部連携**：取引先機能、キャンペーン機能、連絡先機能との連携により、商談に関連する情報を紐付ける。

**権限による制御**：CanCanによる認可制御が行われ、認証済みユーザーのみが商談を作成可能である。作成時にアクセス権限（Public/Private/Shared）を設定できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | 商談新規作成フォーム | 主画面 | 新規商談情報入力、取引先同時作成、作成実行 |
| 26 | 商談新規作成フォーム | 補助機能 | タグ付け（機能No.83） |
| 26 | 商談新規作成フォーム | 補助機能 | アクセス権限管理（機能No.84） |
| 8 | 取引先詳細画面 | 遷移先機能 | 取引先画面からの商談作成 |
| 12 | キャンペーン詳細画面 | 遷移先機能 | キャンペーン画面からの商談作成 |
| 21 | 連絡先詳細画面 | 遷移先機能 | 連絡先画面からの商談作成 |

## 機能種別

CRUD操作（CREATE） / データ登録

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| opportunity[name] | String | Yes | 商談名 | 必須、最大64文字 |
| opportunity[stage] | String | No | ステージ | 設定値リストから選択 |
| opportunity[probability] | Integer | No | 確度（%） | 0-100の整数 |
| opportunity[amount] | Decimal | No | 金額 | 正の数値 |
| opportunity[discount] | Decimal | No | 割引額 | 正の数値 |
| opportunity[closes_on] | Date | No | クローズ予定日 | 日付形式 |
| opportunity[source] | String | No | ソース | 設定値リストから選択 |
| opportunity[access] | String | No | アクセス権限 | Public/Private/Shared |
| opportunity[assigned_to] | Integer | No | 担当者ID | 存在するユーザーID |
| account[id] | Integer | No | 既存取引先ID | 存在するアカウントID |
| account[name] | String | No | 新規取引先名 | 取引先同時作成時に使用 |
| campaign | Integer | No | キャンペーンID | 存在するキャンペーンID |
| contact | Integer | No | 連絡先ID | 存在する連絡先ID |
| comment_body | String | No | 初期コメント | 任意文字列 |
| opportunity[tag_list] | String | No | タグ（カンマ区切り） | 任意文字列 |
| opportunity[background_info] | String | No | 背景情報 | 最大255文字 |

### 入力データソース

- URL: POST `/opportunities`
- HTTPメソッド: POST
- データソース: Webフォーム入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @opportunity | Opportunity | 作成された商談オブジェクト |
| @opportunities | Array | 一覧画面から呼ばれた場合の商談一覧 |
| @account | Account | 取引先オブジェクト |
| @accounts | Array | 取引先選択肢（エラー時） |
| @contact | Contact | 関連連絡先（連絡先画面から呼ばれた場合） |
| @campaign | Campaign | 関連キャンペーン（キャンペーン画面から呼ばれた場合） |

### 出力先

- HTML形式: リダイレクト（成功時）またはフォーム再表示（エラー時）
- JS形式: AJAX応答（create.js.haml）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POST /opportunities を受信
2. 認証・認可チェック
   └─ CanCanによる権限確認
3. Opportunityオブジェクト生成
   └─ 新規Opportunityインスタンスを作成
4. 取引先処理
   └─ 既存取引先の選択または新規作成
5. バリデーション・保存
   └─ save_with_account_and_permissionsメソッドで保存
6. 連絡先紐付け
   └─ 連絡先IDがあれば商談に紐付け
7. コメント追加
   └─ comment_bodyがあれば初期コメントを追加
8. サイドバーデータ更新
   └─ 呼び出し元に応じてサイドバー情報を更新
9. レスポンス生成
   └─ フォーマットに応じた出力
```

### フローチャート

```mermaid
flowchart TD
    A[POST /opportunities] --> B[認証チェック]
    B --> C{認可チェック}
    C -->|許可| D[Opportunityオブジェクト生成]
    C -->|拒否| E[403 Forbidden]
    D --> F[取引先選択/作成]
    F --> G[save_with_account_and_permissions]
    G --> H{保存成功?}
    H -->|Yes| I[連絡先紐付け]
    H -->|No| J[エラー情報設定]
    I --> K[コメント追加]
    K --> L{呼び出し元判定}
    L -->|一覧画面| M[商談一覧更新]
    L -->|取引先画面| N[取引先情報更新]
    L -->|キャンペーン画面| O[キャンペーン情報更新]
    M --> P[レスポンス生成]
    N --> P
    O --> P
    J --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-30-01 | 商談名必須 | nameは必須項目 | 常時 |
| BR-30-02 | デフォルトステージ | stageのデフォルト値は"prospecting" | ステージ未指定時 |
| BR-30-03 | 取引先作成/選択 | account[id]があれば既存取引先を使用、なければaccount[name]で新規作成 | 常時 |
| BR-30-04 | キャンペーン紐付け | campaignパラメータがあればキャンペーンに紐付け | campaign指定時 |
| BR-30-05 | 連絡先紐付け | contactパラメータがあれば商談に連絡先を紐付け | contact指定時 |
| BR-30-06 | キャンペーンカウント更新 | 商談作成時に関連キャンペーンのopportunities_countを増加 | キャンペーン紐付け時 |
| BR-30-07 | 共有アクセス検証 | accessが"Shared"の場合、少なくとも1人のユーザー選択が必要 | access="Shared"時 |
| BR-30-08 | 確度・金額検証 | probability、amount、discountは数値であること | 入力時 |

### 計算ロジック

重み付け金額の計算（表示用）:
- `weighted_amount = (amount.to_f - discount.to_f) * probability.to_i / 100.0`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 商談作成 | opportunities | INSERT | 新規商談情報の挿入 |
| 取引先作成/取得 | accounts | SELECT/INSERT | 既存取引先の取得または新規作成 |
| 取引先-商談関連 | account_opportunities | INSERT | 取引先と商談の関連付け |
| 連絡先-商談関連 | contact_opportunities | INSERT | 連絡先と商談の関連付け |
| 権限設定 | permissions | INSERT | 共有設定時の権限レコード作成 |
| コメント追加 | comments | INSERT | 初期コメントの挿入 |
| キャンペーン更新 | campaigns | UPDATE | opportunities_countのインクリメント |
| タグ追加 | taggings | INSERT | タグの関連付け |

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

#### opportunities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id | current_user.id | 作成者 |
| INSERT | campaign_id | params[:campaign] | キャンペーン紐付け |
| INSERT | assigned_to | params[:opportunity][:assigned_to] | 担当者 |
| INSERT | name | params[:opportunity][:name] | 商談名 |
| INSERT | stage | params[:opportunity][:stage] | ステージ |
| INSERT | probability | params[:opportunity][:probability] | 確度 |
| INSERT | amount | params[:opportunity][:amount] | 金額 |
| INSERT | closes_on | params[:opportunity][:closes_on] | クローズ予定日 |
| INSERT | access | params[:opportunity][:access] | アクセス権限 |
| INSERT | created_at | 現在日時 | 自動設定 |

#### account_opportunities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | 取引先ID | |
| INSERT | opportunity_id | 商談ID | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | Validation Error | 商談名未入力 | エラーメッセージ表示 |
| 422 | Validation Error | 確度・金額が非数値 | エラーメッセージ表示 |
| 422 | Validation Error | 共有設定時にユーザー未選択 | エラーメッセージ表示 |
| 403 | Forbidden | 認可エラー | エラーページ表示 |

### リトライ仕様

リトライは実装されていない（ユーザー操作による再送信）

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

- 商談作成、取引先作成、関連付け、権限設定、コメント追加は同一トランザクション内で実行
- キャンペーンのopportunities_count更新はafter_createコールバックで実行

## パフォーマンス要件

- フォーム表示のレスポンスタイム: 1秒以内
- 保存処理のレスポンスタイム: 2秒以内

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

- CanCanによる認可制御でアクセス権限をチェック
- Strong Parametersによる入力パラメータ制限
- XSS対策としてビューでのエスケープ処理

## 備考

- 取引先詳細画面、連絡先詳細画面、キャンペーン詳細画面からの作成時はparams[:related]で関連情報を受け取る
- 取引先選択UIはJavaScriptで「既存選択」と「新規作成」を切り替え

---

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

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

### 推奨読解順序

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

商談のデータ構造と関連モデルを理解することが最初のステップである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | opportunity.rb | `app/models/entities/opportunity.rb` | 商談モデルの属性定義、バリデーション、コールバック |
| 1-2 | account_opportunity.rb | `app/models/entities/account_opportunity.rb` | 取引先-商談の中間テーブル |

**読解のコツ**:
- **84行目**: validates_presence_of :name - 商談名必須
- **85行目**: validates_numericality_of - 確度・金額・割引の数値検証
- **89-90行目**: after_create/after_destroyでキャンペーンカウンター更新
- **98-100行目**: default_stageメソッドでデフォルトステージ取得
- **109-119行目**: save_with_account_and_permissionsメソッドで取引先付きで保存

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

コントローラーのnew/createアクションがエントリーポイントである。

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

**主要処理フロー**:
1. **35-52行目** (newアクション): デフォルト値設定、取引先準備、関連エンティティ処理
2. **67-87行目** (createアクション): save_with_account_and_permissions呼び出し、連絡先紐付け、コメント追加、サイドバー更新
3. **36行目**: stageにOpportunity.default_stageを設定
4. **37行目**: 新規取引先用の空Accountオブジェクト
5. **70行目**: save_with_account_and_permissions呼び出し
6. **72-79行目**: 呼び出し元に応じたサイドバー更新

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

フォーム表示のロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _new.html.haml | `app/views/opportunities/_new.html.haml` | 新規作成フォームの構造 |

**主要処理フロー**:
- **1行目**: simple_form_forでフォーム生成
- **8行目**: _top_sectionで商談情報入力
- **12行目**: entities/permissionsでアクセス権限設定
- **16行目**: 取引先担当者IDを商談担当者IDと同期

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

```
OpportunitiesController#new
    │
    ├─ @opportunity.attributes = { ... } (デフォルト値設定)
    │      └─ stage: Opportunity.default_stage
    │
    ├─ @account = Account.new(...)
    │
    ├─ @accounts = Account.my(current_user).order('name')
    │
    └─ respond_with(@opportunity)
           └─ new.js.haml → _new.html.haml

OpportunitiesController#create
    │
    ├─ @opportunity.save_with_account_and_permissions(params)
    │      ├─ Account.create_or_select_for(...)
    │      ├─ AccountOpportunity.new(...)
    │      ├─ Campaign.find(params[:campaign])
    │      └─ save
    │            └─ increment_opportunities_count (after_create)
    │
    ├─ contacts << Contact.find(params[:contact])
    │
    ├─ @opportunity.add_comment_by_user(@comment_body, current_user)
    │
    └─ get_data_for_sidebar
```

### データフロー図

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

POST /opportunities ───▶ OpportunitiesController#create ───▶ HTML/JS
      │                        │
      │                        ├─▶ Opportunity.new
      │                        │
      │                        ├─▶ Account.create_or_select_for
      │                        │       └─▶ [accounts]テーブル SELECT/INSERT
      │                        │
      │                        ├─▶ save_with_account_and_permissions
      │                        │       ├─▶ [opportunities]テーブル INSERT
      │                        │       ├─▶ [account_opportunities]テーブル INSERT
      │                        │       └─▶ [campaigns]テーブル UPDATE
      │                        │
      │                        ├─▶ Contact.find (連絡先紐付け)
      │                        │       └─▶ [contact_opportunities]テーブル INSERT
      │                        │
      │                        └─▶ add_comment_by_user
      │                                └─▶ [comments]テーブル INSERT
      │
      └─▶ create.js.haml
              └─▶ _opportunity.html.haml (一覧に追加)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| opportunities_controller.rb | `app/controllers/entities/opportunities_controller.rb` | コントローラー | 商談操作のエントリーポイント |
| opportunity.rb | `app/models/entities/opportunity.rb` | モデル | 商談のデータモデル定義 |
| account.rb | `app/models/entities/account.rb` | モデル | 取引先のデータモデル定義 |
| account_opportunity.rb | `app/models/entities/account_opportunity.rb` | モデル | 取引先-商談の中間モデル |
| _new.html.haml | `app/views/opportunities/_new.html.haml` | ビュー | 新規作成フォーム |
| new.js.haml | `app/views/opportunities/new.js.haml` | ビュー | AJAX応答でフォーム表示 |
| create.js.haml | `app/views/opportunities/create.js.haml` | ビュー | 作成完了後のAJAX応答 |
| _top_section.html.haml | `app/views/opportunities/_top_section.html.haml` | ビュー | フォーム上部セクション |
| _permissions.html.haml | `app/views/entities/_permissions.html.haml` | ビュー | アクセス権限設定 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
