# 画面設計書 43-カスタムフィールド管理画面

## 概要

本ドキュメントは、Fat Free CRMの管理画面におけるカスタムフィールド管理画面の設計書です。管理者がCRMエンティティ（連絡先、取引先、リード、商談、キャンペーン、タスク）に対してカスタムフィールドを追加・管理するための画面を定義します。

### 本画面の処理概要

本画面は、CRMの標準フィールドに加えて、組織固有のデータ項目を追加するためのカスタムフィールドを管理する画面です。エンティティ別のタブ構成で、各エンティティに対するフィールドグループとフィールドを階層的に管理できます。フィールドグループはタグと関連付けることができ、特定のタグが付いたレコードにのみ表示される条件付きフィールドを実現します。

**業務上の目的・背景**：業種や業態によって必要なデータ項目は異なります。カスタムフィールド機能により、コード修正なしに組織固有のデータ項目を追加でき、CRMを柔軟にカスタマイズできます。フィールドグループによる論理的な分類と、ドラッグ&ドロップによる並び替え機能で、ユーザーにとって使いやすいフォーム構成を実現します。

**画面へのアクセス方法**：管理画面のナビゲーションから「Custom Fields」タブを選択、または `/admin/fields` に直接アクセスします。

**主要な操作・処理内容**：
1. エンティティ別タブ切り替え（Contact、Lead、Account、Opportunity、Campaign、Task）
2. フィールドグループの作成・編集・削除
3. フィールドの作成・編集・削除
4. フィールドグループの並び替え（ドラッグ&ドロップ）
5. フィールドの並び替え（ドラッグ&ドロップ、グループ間移動可能）
6. フィールドタイプの選択（テキスト、数値、日付、選択肢など）

**画面遷移**：管理画面トップ（No.34）から遷移します。フィールドグループ・フィールドの作成・編集はインラインフォームで行われます。詳細表示は別画面（No.44）に遷移します。

**権限による表示制御**：管理者（admin権限を持つユーザー）のみがアクセス可能です。一般ユーザーがアクセスしようとした場合は、管理画面トップへリダイレクトされます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 73 | カスタムフィールド管理 | 主機能 | カスタムフィールド一覧表示 |
| 74 | カスタムフィールド作成 | 補助機能 | 新規カスタムフィールド作成 |
| 75 | カスタムフィールド編集 | 補助機能 | カスタムフィールドの編集 |
| 76 | カスタムフィールド削除 | 補助機能 | カスタムフィールドの削除 |
| 77 | カスタムフィールド並び替え | 補助機能 | カスタムフィールドの表示順変更 |

## 画面種別

一覧（管理画面、タブUI）

## URL/ルーティング

- **URL**: `/admin/fields`
- **HTTPメソッド**: GET（一覧表示）
- **ルーティング定義**: `config/routes.rb` 内の `namespace :admin do resources :fields do collection do ... post :sort ... end end end`
- **コントローラ**: `Admin::FieldsController#index`

## 入出力項目

フィールド作成・編集時のフォーム項目：

| 項目名 | 項目ID | データ型 | 必須 | 最大長 | 入力形式 | 備考 |
|--------|--------|----------|------|--------|----------|------|
| ラベル | field[label] | string | Yes | 128 | テキスト | 画面表示用ラベル |
| フィールドタイプ | field[as] | string | Yes | 32 | 選択 | string, text, email, select, date等 |
| プレースホルダー | field[placeholder] | string | No | 255 | テキスト | 入力欄のヒント |
| ヒント | field[hint] | string | No | 255 | テキスト | 入力説明 |
| 選択肢 | field[collection_string] | text | 条件付き | - | パイプ区切り | selectタイプ時のみ |
| 必須 | field[required] | boolean | No | - | チェックボックス | 必須入力フラグ |
| 無効 | field[disabled] | boolean | No | - | チェックボックス | 入力不可フラグ |
| 最小長 | field[minlength] | integer | No | - | 数値 | 文字数下限 |
| 最大長 | field[maxlength] | integer | No | - | 数値 | 文字数上限 |

## 表示項目

| 項目名 | 表示形式 | データソース | 備考 |
|--------|----------|--------------|------|
| エンティティタブ | タブリスト | list_of_entities | Contact, Lead, Account, Opportunity, Campaign, Task |
| フィールドグループリスト | リスト | klass.field_groups | エンティティごとのフィールドグループ |
| フィールドグループ名 | テキスト | field_group.label | グループ見出し |
| フィールドリスト | リスト | field_group.fields.without_pairs | グループ内のフィールド一覧 |
| フィールドラベル | テキスト | field.label | フィールド表示名 |
| フィールドタイプ | テキスト | field.as | フィールドの種類 |
| 作成リンク（グループ） | link | new_admin_field_group_path | フィールドグループ作成 |
| 作成リンク（フィールド） | link | - | フィールド作成フォーム表示 |
| 並び替えハンドル | アイコン | - | ドラッグ&ドロップ用 |

## イベント仕様

### 1-エンティティタブ切り替え

- **処理フロー**:
  1. タブリンクをクリック
  2. JavaScriptで対応するセクション（`#{asset}_section`）を表示
  3. 他のセクションを非表示
  4. 選択状態のCSSクラスを更新

### 2-フィールドグループ作成

- **処理フロー**:
  1. 「Create Field Group」リンクをクリック
  2. Ajaxリクエストで `GET /admin/field_groups/new` を呼び出し
  3. インラインフォームが展開される
  4. フォーム送信で `POST /admin/field_groups` を呼び出し
  5. 新しいフィールドグループが一覧に追加される

### 3-フィールド作成

- **処理フロー**:
  1. フィールドグループ内の「Create Field」リンクをクリック
  2. インラインフォームが展開される
  3. フィールドタイプを選択すると、サブフォーム（`/admin/fields/subform`）が動的にロードされる
  4. フォーム送信で `POST /admin/fields` を呼び出し
  5. 新しいフィールドがグループ内に追加される

### 4-フィールドグループ並び替え

- **処理フロー**:
  1. ドラッグ&ドロップで順序を変更
  2. 変更完了時に `POST /admin/field_groups/sort` を自動呼び出し
  3. サーバー側で position カラムが更新される

### 5-フィールド並び替え

- **処理フロー**:
  1. ドラッグ&ドロップで順序を変更（グループ間移動も可能）
  2. 変更完了時に `POST /admin/fields/sort` を自動呼び出し
  3. サーバー側で position と field_group_id が更新される

### 6-フィールドグループ削除

- **処理フロー**:
  1. 「Delete」リンク（確認リンク）をクリック
  2. 確認ダイアログが表示される
  3. 確認後、`DELETE /admin/field_groups/:id` を呼び出し
  4. フィールドグループと配下のフィールドが削除される

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| フィールド作成 | fields | INSERT | 新しいフィールドレコードを作成 |
| フィールド編集 | fields | UPDATE | フィールド情報を更新 |
| フィールド削除 | fields | DELETE | フィールドレコードを削除 |
| フィールド並び替え | fields | UPDATE | position, field_group_idを更新 |
| フィールドグループ作成 | field_groups | INSERT | 新しいグループレコードを作成 |
| フィールドグループ編集 | field_groups | UPDATE | グループ情報を更新 |
| フィールドグループ削除 | field_groups | DELETE | グループと配下のフィールドを削除 |
| フィールドグループ並び替え | field_groups | UPDATE | positionを更新 |

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

#### fields

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | type | フィールドクラス名 | CustomField等 |
| INSERT | field_group_id | 所属グループID | 必須 |
| INSERT | name | 自動生成 | cf_xxx形式 |
| INSERT | label | フォーム入力値 | 必須 |
| INSERT | as | フォーム入力値 | string, text, date等 |
| INSERT | position | 最後尾+1 | acts_as_list |
| UPDATE | position | ドラッグ位置 | 並び替え時 |
| UPDATE | field_group_id | 移動先グループID | グループ間移動時 |

#### field_groups

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | 自動生成または入力値 | - |
| INSERT | label | フォーム入力値 | 必須 |
| INSERT | klass_name | エンティティ名 | Contact, Account等 |
| INSERT | tag_id | 関連タグID | 条件付きフィールド用 |
| INSERT | position | 最後尾+1 | acts_as_list |

## メッセージ仕様

| メッセージ種別 | 条件 | メッセージ内容 | 表示位置 |
|---------------|------|---------------|----------|
| 情報 | 画面ロード時 | "Select an entity type to manage its custom fields" | タイトル下（info） |
| 情報 | 補足説明 | "Field groups can be conditionally displayed based on tags" | info2 |
| 空表示 | フィールドがない | "No fields. Create field link available." | フィールドリスト領域 |
| エラー | ラベルが空 | "Please enter a field label." | フォーム上 |
| エラー | フィールドタイプ未選択 | "Please specify a field type." | フォーム上 |

## 例外処理

| 例外条件 | 処理内容 | 表示・動作 |
|----------|----------|-----------|
| フィールドが見つからない | RecordNotFound例外 | 404エラーページを表示 |
| 権限不足（非管理者） | before_actionでリダイレクト | 管理画面トップへリダイレクト |
| バリデーションエラー | エラーメッセージ表示 | フォーム上部にエラー内容を表示 |
| 不正なフィールドタイプ | バリデーションエラー | "Invalid field type."を表示 |
| minlengthがmaxlengthより大きい | バリデーションエラー | "Min size cannot be greater than max size." |

## 備考

- フィールドの実体は `fields` テーブルに保存され、`type` カラムによるSTI（Single Table Inheritance）で種別を区別します。
- カスタムフィールドは動的にデータベースカラムを追加するのではなく、ActiveRecordのserialize機能やJSON型カラムを活用して値を保存します。
- フィールドグループに `tag_id` を設定すると、そのタグが付いたレコードの編集時にのみ該当フィールドグループが表示されます。
- ペアフィールド（日付範囲など）は `pair_id` で関連付けられ、`without_pairs` スコープで片方のみ表示されます。
- フィールドタイプは `Field::BASE_FIELD_TYPES` に定義され、プラグインで拡張可能です。

---

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

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

### 推奨読解順序

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

フィールドとフィールドグループの関連を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | field.rb | `app/models/fields/field.rb` | Fieldモデル、STI、バリデーション、フィールドタイプ定義 |
| 1-2 | schema.rb | `db/schema.rb` | fieldsテーブル（246-271行目）、field_groupsテーブル（235-244行目）の構造 |

**読解のコツ**: `BASE_FIELD_TYPES` 定数（45-60行目）でサポートされるフィールドタイプを確認してください。`acts_as_list` による並び替え機能にも注目。

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

コントローラのアクションを確認します。

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

**主要処理フロー**:
1. **15-16行目**: `index` アクション - 空（ビューで list_of_entities を使用）
2. **43-56行目**: `create` アクション - フィールドタイプに応じたクラスでインスタンス化
3. **84-93行目**: `sort` アクション - ドラッグ&ドロップによる並び替え処理
4. **97-111行目**: `subform` アクション - フィールドタイプ選択時の動的フォーム

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

画面の構造を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.html.haml | `app/views/admin/fields/index.html.haml` | メインテンプレート、タブ構成 |
| 3-2 | _field_group.html.haml | `app/views/admin/field_groups/_field_group.html.haml` | フィールドグループパーシャル |

**主要処理フロー**:
- **11-16行目（index）**: エンティティ別タブリストの生成
- **18-34行目（index）**: エンティティごとのフィールドグループセクション
- **31行目（index）**: `data-sortable` 属性でドラッグ&ドロップを有効化
- **21行目（_field_group）**: フィールドリストのソート設定

#### Step 4: ルーティングを理解する

URLとコントローラの対応を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | routes.rb | `config/routes.rb` | admin名前空間内のfieldsリソース定義（190-198行目） |

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

```
ブラウザ（/admin/fields アクセス）
    │
    └─ GET /admin/fields
           │
           └─ Admin::FieldsController#index
                  │
                  └─ index.html.haml レンダリング
                         │
                         ├─ list_of_entities
                         │      └─ [Contact, Lead, Account, ...]
                         │
                         └─ klass.field_groups
                                │
                                └─ _field_group.html.haml
                                       │
                                       ├─ field_group.fields.without_pairs
                                       │      └─ _field.html.haml
                                       │
                                       └─ data-sortable
                                              └─ sort_admin_fields_path

フィールド作成フロー：
    │
    ├─ POST /admin/fields
    │      │
    │      └─ FieldsController#create
    │             │
    │             ├─ Field.lookup_class(as)
    │             │      └─ BASE_FIELD_TYPES[as][:klass]
    │             │
    │             └─ klass.create(field_params)
    │
    └─ GET /admin/fields/subform
           │
           └─ FieldsController#subform
                  │
                  └─ _subform.html.haml
```

### データフロー図

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

URL アクセス
(/admin/fields) ───▶ FieldsController#index ───▶ 一覧画面表示
                            │
                            └─ ビューヘルパー: list_of_entities
                                   │
                                   └─ Contact, Lead, Account, ...

エンティティ選択 ───▶ JavaScript タブ切り替え ───▶ セクション表示切替
                            │
                            └─ klass.field_groups
                                   │
                                   └─ field_groups テーブル
                                          │
                                          └─ fields テーブル

フィールド作成 ───▶ FieldsController#create
フォーム入力              │
                         ├─ Field.lookup_class(as)
                         │      └─ type 決定
                         │
                         └─▶ fields テーブル (INSERT)

並び替え ───▶ FieldsController#sort
(ドラッグ&ドロップ)    │
                      └─▶ fields テーブル (UPDATE position)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| field.rb | `app/models/fields/field.rb` | モデル | フィールドエンティティ、STI基底クラス |
| custom_field.rb | `app/models/fields/custom_field.rb` | モデル | カスタムフィールド実装 |
| field_group.rb | `app/models/fields/field_group.rb` | モデル | フィールドグループエンティティ |
| fields_controller.rb | `app/controllers/admin/fields_controller.rb` | コントローラ | フィールドCRUD処理 |
| field_groups_controller.rb | `app/controllers/admin/field_groups_controller.rb` | コントローラ | グループCRUD処理 |
| index.html.haml | `app/views/admin/fields/index.html.haml` | ビュー | 一覧画面メインテンプレート |
| _field_group.html.haml | `app/views/admin/field_groups/_field_group.html.haml` | ビュー | グループパーシャル |
| _field.html.haml | `app/views/admin/fields/_field.html.haml` | ビュー | フィールドパーシャル |
| _form.html.haml | `app/views/admin/fields/_form.html.haml` | ビュー | フィールド作成フォーム |
| routes.rb | `config/routes.rb` | 設定 | URLルーティング定義 |
| schema.rb | `db/schema.rb` | 設定 | データベーススキーマ |
