# 機能設計書 74-カスタムフィールド作成

## 概要

本ドキュメントは、Fat Free CRM の管理者向けカスタムフィールド作成機能の設計を定義する。この機能により、システム管理者はエンティティに新しいカスタムフィールドを追加することができる。

### 本機能の処理概要

カスタムフィールド作成機能は、CRM システムの標準フィールドでは対応できない業務固有の項目を動的に追加する管理者専用機能である。作成時にはフィールドタイプ、ラベル、バリデーション設定などを指定し、対応するデータベースカラムが自動的に追加される。日付範囲や日時範囲を扱うペアフィールドの作成にも対応している。

**業務上の目的・背景**：組織ごとに異なる業務要件に対応するため、標準フィールド以外のカスタム項目を追加する必要がある。例えば、業界固有のコード、社内管理番号、カスタム分類などを追加することで、CRMシステムを業務に最適化できる。この機能により、システム開発者の介入なしに管理者が直接フィールドを追加できる。

**機能の利用シーン**：
- 新しい業務項目が必要になった場合
- 外部システムとの連携のためのマッピング項目を追加する場合
- 組織固有の分類・属性情報を管理する場合
- 日付範囲（開始日〜終了日）などのペアフィールドを追加する場合

**主要な処理内容**：
1. 新規フィールドフォームの表示（newアクション）
2. フィールドタイプに応じたサブフォームの動的表示（subformアクション）
3. フィールドの作成とバリデーション（createアクション）
4. データベースカラムの動的追加
5. Ransack検索用の翻訳登録

**関連システム・外部連携**：本機能は外部システムとの連携はなく、Fat Free CRM 内部のデータベースを直接操作する。カスタムフィールドはActiveRecordのマイグレーションを使わずに動的にカラムを追加する。

**権限による制御**：この機能は管理者権限を持つユーザーのみが実行可能である。一般ユーザーは管理画面にアクセスできず、カスタムフィールドの作成は行えない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 43 | カスタムフィールド管理画面 | 参照画面 | 新規作成ボタン押下元 |

## 機能種別

CRUD操作（Create）/ データ定義操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| field[as] | String | Yes | フィールドタイプ（string, text, email, date, decimal等） | 有効なフィールドタイプであること |
| field[label] | String | Yes | フィールドラベル | 1〜64文字 |
| field[field_group_id] | Integer | Yes | 所属するフィールドグループのID | 存在するFieldGroupIDであること |
| field[hint] | String | No | 入力ヒント | - |
| field[placeholder] | String | No | プレースホルダーテキスト | - |
| field[required] | Boolean | No | 必須項目フラグ | - |
| field[disabled] | Boolean | No | 無効化フラグ | - |
| field[minlength] | Integer | No | 最小文字数 | 0以上の整数 |
| field[maxlength] | Integer | No | 最大文字数 | minlength以上の正の整数 |
| field[collection_string] | String | No | 選択肢（パイプ区切り） | select, radio_buttons, check_boxes の場合に使用 |
| pair[0] | Hash | No | ペアフィールドの開始側設定 | datepair, datetimepair の場合 |
| pair[1] | Hash | No | ペアフィールドの終了側設定 | datepair, datetimepair の場合 |

### 入力データソース

- 画面入力（カスタムフィールド作成フォーム）
- フィールドグループ選択（ドロップダウン）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @field | Field | 作成されたフィールドオブジェクト |

### 出力先

- 画面表示（AJAX応答によるフィールド一覧の更新）
- データベース（fieldsテーブル、エンティティテーブルへのカラム追加）

## 処理フロー

### 処理シーケンス

```
1. newアクション呼び出し
   └─ 新規Fieldオブジェクト生成
   └─ フォーム表示
2. subformアクション呼び出し（AJAX）
   └─ フィールドタイプに応じたサブフォーム表示
3. createアクション呼び出し
   └─ パラメータ取得（field_params）
   └─ フィールドタイプに応じたクラス特定（Field.lookup_class）
   └─ ペアフィールドの場合：CustomFieldPair.create_pair
   └─ 通常フィールドの場合：klass.create(field_params)
   └─ バリデーション実行
   └─ データベースカラム追加（add_column コールバック）
   └─ Ransack翻訳登録（add_ransack_translation コールバック）
4. レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[新規作成ボタン押下] --> B[newアクション]
    B --> C[フォーム表示]
    C --> D[フィールドタイプ選択]
    D --> E[subformアクション]
    E --> F[タイプ別サブフォーム表示]
    F --> G[フォーム入力完了]
    G --> H[作成ボタン押下]
    H --> I[createアクション]
    I --> J{ペアフィールド?}
    J -->|Yes| K[CustomFieldPair.create_pair]
    J -->|No| L{フィールドタイプ指定あり?}
    L -->|Yes| M[klass.create]
    L -->|No| N[Field.new + valid?]
    K --> O{バリデーション成功?}
    M --> O
    N --> O
    O -->|No| P[エラー表示]
    O -->|Yes| Q[DBカラム追加]
    Q --> R[Ransack翻訳登録]
    R --> S[一覧更新]
    P --> C
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-74-01 | 管理者権限必須 | カスタムフィールド作成は管理者ユーザーのみ実行可能 | 常時 |
| BR-74-02 | ラベル必須 | フィールドラベルは必須入力 | フィールド作成時 |
| BR-74-03 | ラベル長制限 | フィールドラベルは64文字以内 | フィールド作成時 |
| BR-74-04 | タイプ必須 | フィールドタイプは必須選択 | フィールド作成時 |
| BR-74-05 | カラム名自動生成 | データベースカラム名は cf_ プレフィックス + ラベルから自動生成 | フィールド作成時 |
| BR-74-06 | カラム名重複回避 | 既存カラム名と重複する場合は数字サフィックスを付与 | フィールド作成時 |
| BR-74-07 | ペアフィールド連動 | ペアフィールドは2つのフィールドが連動して作成される | datepair, datetimepair の場合 |

### 計算ロジック

#### カラム名生成ロジック
```
1. base_name = 'cf_' + label.downcase.gsub(/[^a-z0-9]+/, '_')
2. 既存カラムと重複チェック
3. 重複時は suffix を付与（_2, _3, ...）
4. 最終的なカラム名を決定
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| フィールド作成 | fields | INSERT | 新規フィールドレコード作成 |
| カラム追加 | accounts/contacts/leads/opportunities/campaigns | ALTER TABLE ADD COLUMN | 動的カラム追加 |

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

#### fields テーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | type | 'CustomField' または派生クラス名 | STI識別子 |
| INSERT | field_group_id | フォーム入力値 | 所属グループ |
| INSERT | name | 自動生成（cf_xxx） | カラム名 |
| INSERT | label | フォーム入力値 | 表示ラベル |
| INSERT | as | フォーム入力値 | フィールドタイプ |
| INSERT | hint | フォーム入力値 | ヒントテキスト |
| INSERT | placeholder | フォーム入力値 | プレースホルダー |
| INSERT | required | フォーム入力値 | 必須フラグ |
| INSERT | disabled | フォーム入力値 | 無効フラグ |
| INSERT | minlength | フォーム入力値 | 最小文字数 |
| INSERT | maxlength | フォーム入力値 | 最大文字数 |
| INSERT | collection | パイプ区切りから配列変換 | 選択肢 |
| INSERT | position | 自動設定（acts_as_list） | 表示順 |
| INSERT | pair_id | ペアフィールドのID | ペアの場合のみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | 管理者権限がない | ログインページへリダイレクト |
| 422 | ValidationError | ラベル未入力、タイプ未選択、バリデーション失敗 | フォームにエラーメッセージ表示 |
| 500 | DatabaseError | カラム追加失敗 | エラーログ出力、管理者通知 |

### リトライ仕様

リトライは不要（ユーザーがエラー修正後に再度作成ボタンを押下）

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

fieldsテーブルへのINSERTとエンティティテーブルへのALTER TABLEは別トランザクションで実行される。fieldsレコードの作成が成功した後にカラム追加が行われる（before_createコールバック）。

## パフォーマンス要件

- レスポンス時間：3秒以内（カラム追加を含む）
- ALTER TABLE はデータ量に応じて時間がかかる可能性あり

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

- 管理者認証必須（before_action :require_admin_user）
- Strong Parameters によるマスアサインメント防止
- CSRF トークン検証（Rails標準）
- SQLインジェクション対策（ActiveRecordによるパラメータバインド）

## 備考

- CustomField は動的にデータベースカラムを追加する仕組みを採用
- カラム追加後は klass.reset_column_information でキャッシュをクリア
- serialize_custom_fields! でシリアライズ設定を更新
- 孤立したカラムを削除するためのrakeタスクが別途提供されている

---

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

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

### 推奨読解順序

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

カスタムフィールドのデータモデルとカラム追加ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | field.rb | `app/models/fields/field.rb` | BASE_FIELD_TYPES、バリデーション、lookup_class |
| 1-2 | custom_field.rb | `app/models/fields/custom_field.rb` | add_column、generate_column_name、カラム追加ロジック |
| 1-3 | custom_field_pair.rb | `app/models/fields/custom_field_pair.rb` | create_pair、ペアフィールド作成ロジック |

**読解のコツ**: CustomFieldの核心は動的カラム追加にある。`add_column`メソッド（125-130行目）がbefore_createコールバックで呼ばれ、対象テーブルに新しいカラムを追加する。

**主要処理フロー**:
- **field.rb 45-60行目**: `BASE_FIELD_TYPES` - サポートされるフィールドタイプとDB型のマッピング
- **field.rb 123-125行目**: `lookup_class` - フィールドタイプからクラス名を取得
- **custom_field.rb 56-57行目**: `before_create :add_column` - カラム追加コールバック
- **custom_field.rb 107-115行目**: `generate_column_name` - カラム名生成ロジック
- **custom_field.rb 125-130行目**: `add_column` - データベースカラム追加処理
- **custom_field_pair.rb 13-21行目**: `create_pair` - ペアフィールド作成

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

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

**主要処理フロー**:
- **28-31行目**: `new` アクション - 新規Fieldオブジェクト生成
- **43-56行目**: `create` アクション - フィールド作成のメイン処理
- **97-111行目**: `subform` アクション - フィールドタイプ別サブフォーム表示
- **115-117行目**: `field_params` - Strong Parameters

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

```
Admin::FieldsController
    │
    ├─ new アクション
    │      └─ Field.new
    │
    ├─ subform アクション
    │      └─ Field.lookup_class(as)
    │             └─ klass.new(field_group_id, as)
    │
    └─ create アクション
           ├─ field_params（Strong Parameters）
           │
           ├─ Field.lookup_class(as)
           │
           ├─【ペアフィールドの場合】
           │      └─ CustomFieldPair.create_pair
           │             ├─ klass.create（開始側）
           │             │      └─ before_create :add_column
           │             │             └─ connection.add_column
           │             │
           │             └─ klass.create（終了側）
           │                    └─ before_create :add_column
           │
           └─【通常フィールドの場合】
                  └─ klass.create(field_params)
                         └─ before_create :add_column
                                ├─ generate_column_name
                                ├─ connection.add_column
                                ├─ klass.reset_column_information
                                └─ klass.serialize_custom_fields!
```

### データフロー図

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

field[as](Form) ─────▶ subform アクション ───────▶ サブフォーム(HTML)
                              │
                              ▼
                        Field.lookup_class

field[*](Form) ──────▶ create アクション ────────▶ フィールド一覧更新
                              │
                              ▼
                        CustomField.create
                              │
                              ├─ fields テーブル INSERT
                              │
                              └─ add_column コールバック
                                     │
                                     ├─ generate_column_name
                                     │
                                     ├─ エンティティテーブル ALTER TABLE
                                     │
                                     └─ add_ransack_translation
                                            │
                                            ▼
                                        I18n.backend
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| field.rb | `app/models/fields/field.rb` | モデル | Fieldモデル基底クラス、フィールドタイプ定義 |
| custom_field.rb | `app/models/fields/custom_field.rb` | モデル | CustomFieldモデル、動的カラム追加 |
| custom_field_pair.rb | `app/models/fields/custom_field_pair.rb` | モデル | ペアフィールドモデル |
| custom_field_date_pair.rb | `app/models/fields/custom_field_date_pair.rb` | モデル | 日付ペアフィールド |
| custom_field_datetime_pair.rb | `app/models/fields/custom_field_datetime_pair.rb` | モデル | 日時ペアフィールド |
| field_group.rb | `app/models/fields/field_group.rb` | モデル | FieldGroupモデル |
| fields_controller.rb | `app/controllers/admin/fields_controller.rb` | コントローラー | フィールドCRUD処理 |
| application_controller.rb | `app/controllers/admin/application_controller.rb` | コントローラー | 管理者基底コントローラー |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（190-198行目） |
