# 画面設計書 10-取引先編集フォーム

## 概要

本ドキュメントは、Fat Free CRMの取引先編集フォームの設計仕様を記述したものです。

### 本画面の処理概要

既存取引先（Account）の情報を編集するためのフォームです。取引先詳細画面または取引先一覧画面からモーダル/インラインで表示され、基本情報、連絡先情報、アクセス権限を変更して更新します。

**業務上の目的・背景**：本フォームは、既存の取引先情報（顧客、パートナー企業、競合他社など）を更新するための機能を提供します。住所変更、担当者変更、カテゴリ変更など、取引先に関する情報の最新化を行います。アクセス権限の変更により、チーム内での情報共有範囲を再設定できます。

**画面へのアクセス方法**：取引先詳細画面または取引先一覧画面の「Edit」ボタン/リンクをクリックするか、URL `/accounts/:id/edit` にアクセスします（通常はAJAXで表示）。

**主要な操作・処理内容**：
1. 取引先名を変更する
2. 担当者を変更する
3. カテゴリを変更する
4. 評価（Rating）を変更する
5. 背景情報を変更する
6. タグを変更する
7. 連絡先情報（電話、FAX、メール、ウェブサイト）を変更する
8. 住所（請求先・配送先）を変更する
9. カスタムフィールドを変更する
10. アクセス権限を変更する
11. 「Save Account」ボタンをクリックして保存する

**画面遷移**：
- この画面に遷移してくる画面: 取引先詳細画面（編集ボタン）、取引先一覧画面（編集リンク）
- この画面から遷移できる画面: 取引先詳細画面（保存成功後/キャンセル時）、取引先一覧画面（一覧からの編集時）

**権限による表示制御**：
- 認証済みユーザーのみがアクセス可能
- 取引先へのアクセス権限（所有者/共有ユーザー/公開）を持つユーザーのみが編集可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | 取引先更新 | 主機能 | 取引先情報の変更、更新実行 |
| 83 | タグ付け | 補助機能 | 取引先タグの変更 |
| 84 | アクセス権限管理 | 補助機能 | 取引先のアクセス権限変更 |

## 画面種別

編集（モーダル/インライン）

## URL/ルーティング

| メソッド | URL | アクション |
|----------|-----|----------|
| GET | `/accounts/:id/edit` | accounts#edit (AJAX) |
| PUT/PATCH | `/accounts/:id` | accounts#update (AJAX) |

## 入出力項目

| 項目名 | 項目ID | 入力/出力 | データ型 | 必須 | 最大長 | 説明 |
|--------|--------|----------|---------|------|--------|------|
| 取引先名 | account[name] | 入力 | String | 必須 | 64 | 取引先の名前 |
| 担当者 | account[assigned_to] | 入力 | Integer | - | - | 割り当てるユーザーID |
| カテゴリ | account[category] | 入力 | String | - | 32 | affiliate/competitor/customer/partner/reseller/vendor/other |
| 評価 | account[rating] | 入力 | Integer | - | - | 0-5の評価値 |
| 背景情報 | account[background_info] | 入力 | String | - | 255 | 取引先の背景情報 |
| タグ | account[tag_list] | 入力 | String | - | - | カンマ区切りのタグ |
| フリーダイヤル | account[toll_free_phone] | 入力 | String | - | 32 | フリーダイヤル番号 |
| 電話番号 | account[phone] | 入力 | String | - | 32 | 電話番号 |
| FAX | account[fax] | 入力 | String | - | 32 | FAX番号 |
| ウェブサイト | account[website] | 入力 | String | - | 64 | ウェブサイトURL |
| メールアドレス | account[email] | 入力 | String | - | 254 | メールアドレス |
| 緯度 | account[latitude] | 入力 | Decimal | - | - | 緯度（-90〜90） |
| 経度 | account[longitude] | 入力 | Decimal | - | - | 経度（-180〜180） |
| 請求先住所 | account[billing_address_attributes] | 入力 | Object | - | - | 請求先住所情報 |
| 配送先住所 | account[shipping_address_attributes] | 入力 | Object | - | - | 配送先住所情報 |
| アクセス権限 | account[access] | 入力 | String | - | 8 | Public/Private/Shared |
| 共有ユーザー | account[user_ids] | 入力 | Array | - | - | 共有するユーザーID |
| ユーザーID（hidden） | account[user_id] | 入力 | Integer | - | - | 所有者ユーザーID（変更不可） |

## 表示項目

| 項目名 | 表示条件 | 説明 |
|--------|---------|------|
| フォームタイトル | 常時 | 編集フォームのタイトル |
| 閉じるリンク | 常時 | フォームを閉じるリンク |
| 取引先名入力フィールド | 常時 | 必須入力フィールド（現在値がセット） |
| 担当者選択 | 常時 | ユーザー選択ドロップダウン（現在値がセット） |
| カテゴリ選択 | 常時 | カテゴリドロップダウン（現在値がセット） |
| 評価選択 | 常時 | 星評価セレクター（現在値がセット） |
| 背景情報入力 | Setting.background_info含む場合 | テキストエリア（現在値がセット） |
| タグ入力 | 常時 | タグ入力フィールド（現在値がセット） |
| 連絡先情報セクション | 常時 | 電話、FAX、メール、ウェブサイト（現在値がセット） |
| 住所セクション | 常時 | 請求先・配送先住所（現在値がセット） |
| カスタムフィールドセクション | カスタムフィールドが定義されている場合 | フィールドグループ単位で表示（現在値がセット） |
| アクセス権限セクション | 常時 | 権限設定ラジオボタン（現在値がセット） |
| 保存ボタン | 常時 | 「Save Account」ボタン |
| キャンセルリンク | 常時 | フォームを閉じるリンク |

## イベント仕様

### 1-Save Accountボタン押下

**トリガー**: 「Save Account」ボタンのクリック

**処理フロー**:
1. フォームデータがAJAXでPUT/PATCHリクエストとして送信される
2. `AccountsController#update` が実行される
3. アクセス権限を先に設定（73行目: `@account.access = params[:account][:access]`）
4. バリデーションを実行
   - 取引先名: 必須、一意性（Setting.require_unique_account_names有効時）
   - カテゴリ: 設定値のいずれか
   - 評価: 0-5の範囲
   - 緯度/経度: 有効な範囲
5. バリデーション成功時:
   - Accountレコードを更新
   - 関連する住所レコードを更新（nested attributes）
   - アクセス権限に応じてpermissionsレコードを更新
   - バージョン履歴を記録
   - サイドバーデータを更新
   - フォームを閉じて詳細画面または一覧を再描画
6. バリデーション失敗時:
   - エラーメッセージを表示
   - フォームを再表示

### 2-キャンセルリンク押下

**トリガー**: 「cancel」リンクのクリック

**処理フロー**:
1. AJAXリクエストで `/accounts/:id/edit` が呼び出される（cancel=true）
2. フォームを閉じる

### 3-閉じるリンク押下

**トリガー**: フォーム右上の閉じるリンクのクリック

**処理フロー**:
1. AJAXリクエストで `/accounts/:id/edit` が呼び出される
2. フォームを閉じる

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下（成功時） | accounts | UPDATE | 取引先レコードの更新 |
| 保存ボタン押下（成功時） | addresses | INSERT/UPDATE/DELETE | 住所レコードの作成/更新/削除 |
| 保存ボタン押下（成功時） | permissions | INSERT/UPDATE/DELETE | 権限レコードの更新（Shared変更時） |
| 保存ボタン押下（成功時） | taggings | INSERT/DELETE | タグ関連レコードの更新 |
| 保存ボタン押下（成功時） | versions | INSERT | バージョン履歴レコードの作成 |

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

#### accounts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | assigned_to | フォーム入力値 | 担当者 |
| UPDATE | name | フォーム入力値 | 必須 |
| UPDATE | access | フォーム入力値 | アクセス権限 |
| UPDATE | category | フォーム入力値 | カテゴリ |
| UPDATE | rating | フォーム入力値 | 評価 |
| UPDATE | website | フォーム入力値 | ウェブサイト |
| UPDATE | toll_free_phone | フォーム入力値 | フリーダイヤル |
| UPDATE | phone | フォーム入力値 | 電話番号 |
| UPDATE | fax | フォーム入力値 | FAX |
| UPDATE | email | フォーム入力値 | メールアドレス |
| UPDATE | background_info | フォーム入力値 | 背景情報 |
| UPDATE | latitude | フォーム入力値 | 緯度 |
| UPDATE | longitude | フォーム入力値 | 経度 |
| UPDATE | updated_at | 現在日時 | 更新日時 |

#### addresses

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | street1 | フォーム入力値 | 住所1 |
| UPDATE | street2 | フォーム入力値 | 住所2 |
| UPDATE | city | フォーム入力値 | 市区町村 |
| UPDATE | state | フォーム入力値 | 都道府県 |
| UPDATE | zipcode | フォーム入力値 | 郵便番号 |
| UPDATE | country | フォーム入力値 | 国 |

#### versions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | item_type | 'Account' | 対象モデル |
| INSERT | item_id | 取引先ID | 対象レコードID |
| INSERT | event | 'update' | イベント種別 |
| INSERT | whodunnit | current_user.id | 操作ユーザー |
| INSERT | object | 更新前の状態（YAML） | 変更前データ |
| INSERT | object_changes | 変更内容（YAML） | 変更差分 |
| INSERT | created_at | 現在日時 | 作成日時 |

## メッセージ仕様

| メッセージID | メッセージ種別 | メッセージ内容 | 表示条件 |
|-------------|---------------|---------------|---------|
| save_account | ボタン | Save Account | 保存ボタン |
| account | オブジェクト名 | Account | エラーメッセージ用 |
| missing_account_name | エラー | Please specify account name. | 取引先名未入力時 |
| share_account | エラー | Please specify users with whom to share the account. | Shared選択時にユーザー未選択 |
| intro | 案内 | You can add %{value} information later. | 各セクションの案内 |
| keep_private | ラベル | Keep it private, do not share with others | Private選択時のラベル |
| make_public | ラベル | Share it with everyone | Public選択時のラベル |
| share_with | ラベル | Share it with the following people | Shared選択時のラベル |

## 例外処理

| 例外条件 | 処理内容 | 遷移先 |
|---------|---------|--------|
| 未認証アクセス | ログイン画面にリダイレクト | ログイン画面 |
| 権限なしアクセス | アクセス拒否エラー | 取引先一覧画面 |
| 取引先が存在しない | 404エラー | 取引先一覧画面 |
| 取引先名が未入力 | バリデーションエラー表示 | フォーム再表示 |
| 取引先名が重複（一意性設定有効時） | バリデーションエラー表示 | フォーム再表示 |
| Shared選択時にユーザー未選択 | バリデーションエラー表示 | フォーム再表示 |

## 備考

- Simple Formを使用してフォームを生成
- AJAXで非同期送信（`remote: true`）
- 二重送信防止機能あり（`one_submit_only`）
- 編集時は既存データがフォームにプリセットされる
- `edit: true` パラメータにより編集モードでパーシャルが描画される
- カスタムフィールドはフィールドグループ単位で表示（`edit_custom_field_group`）
- `Setting.compound_address` の設定により住所入力形式が変わる（16-19行目）
- アクセス権限はuser_ids設定前に先に設定する必要がある（コントローラー73行目参照）
- `detect_previous_id` により前のレコードへの遷移サポート（48行目）

---

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

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

### 推奨読解順序

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

まず、取引先更新に関わるデータ構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | account.rb | `app/models/entities/account.rb` | Accountモデルのバリデーション（76-82行目）、nested attributes（46-47行目）を確認 |
| 1-2 | schema.rb | `db/schema.rb` | accountsテーブル、addressesテーブル、versionsテーブルの構造を確認 |

**読解のコツ**: Accountモデルの76-79行目でバリデーション定義が確認できます。編集時も新規作成時と同じバリデーションが適用されます。

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

処理の起点となるコントローラーを特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | accounts_controller.rb | `app/controllers/entities/accounts_controller.rb` | editアクション（47-51行目）、updateアクション（70-76行目）を確認 |
| 2-2 | routes.rb | `config/routes.rb` | 取引先編集関連のルーティングを確認 |

**主要処理フロー**:
1. **47-51行目**: `edit` - 編集フォームの準備
2. **48行目**: 前のレコードIDの検出（キーボードナビゲーション用）
3. **70-76行目**: `update` - 取引先の更新処理
4. **73行目**: アクセス権限を先に設定（重要：user_ids設定前に必要）
5. **74行目**: レコード更新とサイドバー再取得

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

画面表示のロジックを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | _edit.html.haml | `app/views/accounts/_edit.html.haml` | 編集フォームのメイン構造を確認 |
| 3-2 | _top_section.html.haml | `app/views/accounts/_top_section.html.haml` | 基本情報入力セクション（edit: trueパラメータ） |
| 3-3 | _contact_info.html.haml | `app/views/accounts/_contact_info.html.haml` | 連絡先情報入力セクション |

**主要処理フロー**:
- **1行目**: `.remote` - リモートフォームのコンテナ
- **2行目**: `simple_form_for(@account, ..., remote: true)` - AJAXフォーム生成
- **3行目**: `link_to_close` - フォームを閉じるリンク
- **4行目**: `f.hidden_field :user_id` - 所有者IDの保持
- **6行目**: `f.error_messages` - エラーメッセージ表示領域
- **8行目**: top_sectionパーシャル（基本情報）、edit: trueで編集モード
- **9行目**: カスタムフィールドグループ（編集用）
- **10行目**: 連絡先情報
- **11行目**: フィールドグループ
- **12行目**: アクセス権限設定（entity: @accountで現在の権限を表示）
- **15-21行目**: 保存ボタンとキャンセルリンク

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

```
ブラウザ (GET /accounts/:id/edit) [AJAX]
    │
    ├─ routes.rb (resources :accounts)
    │
    └─ AccountsController#edit
           │
           ├─ load_and_authorize_resource
           │      └─ @account = Account.find(params[:id])
           │
           ├─ detect_previous_id (キーボードナビゲーション用)
           │
           └─ respond_with(@account)
                   │
                   └─ edit.js.haml → _edit.html.haml をレンダリング

ブラウザ (PUT/PATCH /accounts/:id) [AJAX]
    │
    ├─ routes.rb (resources :accounts)
    │
    └─ AccountsController#update
           │
           ├─ load_and_authorize_resource
           │      └─ @account = Account.find(params[:id])
           │
           ├─ @account.access = params[:account][:access]
           │      └─ アクセス権限を先に設定
           │
           ├─ @account.update(resource_params)
           │      ├─ バリデーション実行
           │      ├─ before_save コールバック
           │      │      └─ nullify_blank_category
           │      └─ UPDATEs (accounts, addresses, permissions, taggings)
           │             └─ versions への INSERT (PaperTrail)
           │
           ├─ get_data_for_sidebar (成功時のみ)
           │
           └─ respond_with(@account)
                   │
                   ├─ [成功] update.js.haml → フォーム閉じる、詳細/一覧再描画
                   └─ [失敗] edit.js.haml → エラー表示
```

### データフロー図

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

フォーム入力値    ───▶  AccountsController#update
（既存値含む）              │
                            ▼
                      アクセス権限設定
                            │
                            ▼
                      バリデーション
                            │
                   ┌────────┴────────┐
                   ▼                 ▼
             [成功]              [失敗]
                │                   │
                ▼                   ▼
        DBへUPDATE            エラーメッセージ
        ├─ accounts               │
        ├─ addresses              ▼
        ├─ permissions       フォーム再表示
        ├─ taggings
        └─ versions (INSERT)
                │
                ▼
        フォームを閉じる
        ├─ 詳細画面再描画
        └─ サイドバー更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| accounts_controller.rb | `app/controllers/entities/accounts_controller.rb` | コントローラー | 取引先編集処理の制御 |
| _edit.html.haml | `app/views/accounts/_edit.html.haml` | テンプレート | 編集フォームの表示 |
| _top_section.html.haml | `app/views/accounts/_top_section.html.haml` | テンプレート | 基本情報セクション |
| _contact_info.html.haml | `app/views/accounts/_contact_info.html.haml` | テンプレート | 連絡先情報セクション |
| _permissions.html.haml | `app/views/entities/_permissions.html.haml` | テンプレート | アクセス権限セクション |
| edit_custom_field_group | `app/views/fields/_edit_custom_field_group.html.haml` | テンプレート | カスタムフィールド編集 |
| account.rb | `app/models/entities/account.rb` | モデル | 取引先モデル |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
| fat_free_crm.en-US.yml | `config/locales/fat_free_crm.en-US.yml` | 設定 | 国際化メッセージ定義 |
| update.js.haml | `app/views/accounts/update.js.haml` | テンプレート | 更新成功時のJavaScript |
| edit.js.haml | `app/views/accounts/edit.js.haml` | テンプレート | 編集フォーム表示のJavaScript |
