# 機能設計書 55-連絡先CRUD

## 概要

本ドキュメントは、Chatwootにおける連絡先（Contact）の作成・表示・更新・削除機能について記述する。連絡先は顧客情報を管理する中心的なエンティティであり、会話、インボックス、メッセージと紐づけられる。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポートにおいて、顧客の基本情報（名前、メールアドレス、電話番号等）を一元管理することは重要である。連絡先管理により、複数チャネルからの問い合わせを同一顧客として認識し、一貫した顧客体験を提供できる。また、顧客情報の蓄積により、過去の対応履歴を参照した効率的なサポートが可能となる。

**機能の利用シーン**：
- 新規顧客の連絡先を手動で作成する
- 既存顧客の情報（名前、メール、電話番号等）を更新する
- 連絡先一覧から顧客情報を確認する
- 不要な連絡先を削除する
- 連絡先に紐づく会話履歴を確認する
- 連絡先にカスタム属性を設定する

**主要な処理内容**：
1. 連絡先の作成（基本情報、カスタム属性、インボックス紐づけ）
2. 連絡先の表示（一覧取得、詳細取得）
3. 連絡先の更新（基本情報、カスタム属性のマージ更新）
4. 連絡先の削除（オンライン状態チェック後に削除）
5. アバター画像の管理

**関連システム・外部連携**：
- IPルックアップサービス（連絡先のIP地理情報取得）
- ActiveStorage（アバター画像の保存）

**権限による制御**：連絡先のCRUD操作にはエージェント権限以上が必要。管理者は全ての連絡先にアクセス可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | 連絡先一覧画面 | 主画面 | 連絡先の一覧表示 |
| 24 | 連絡先編集画面 | 主画面 | 連絡先情報の編集 |
| 21 | セグメント別連絡先一覧 | 参照画面 | セグメント別連絡先の表示 |
| 22 | ラベル別連絡先一覧 | 参照画面 | ラベル別連絡先の表示 |
| 23 | アクティブ連絡先一覧 | 参照画面 | アクティブ連絡先の表示 |

## 機能種別

CRUD操作

## 入力仕様

### 入力パラメータ（作成・更新）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | String | No | 連絡先名 | - |
| email | String | No | メールアドレス | メール形式、アカウント内でユニーク |
| phone_number | String | No | 電話番号 | E.164形式（+[国番号][番号]）、アカウント内でユニーク |
| identifier | String | No | 外部識別子 | アカウント内でユニーク |
| avatar | File | No | アバター画像 | 画像ファイル形式 |
| avatar_url | String | No | アバターURL | URL形式 |
| blocked | Boolean | No | ブロックフラグ | - |
| additional_attributes | Object | No | 追加属性 | JSONB |
| custom_attributes | Object | No | カスタム属性 | JSONB |
| inbox_id | Integer | No | インボックスID | 存在するインボックス |
| source_id | String | No | ソースID | インボックス紐づけ時に使用 |

### 入力データソース

- 管理画面からの手動入力
- APIからの連絡先作成リクエスト
- Widget経由での自動作成

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | 連絡先ID |
| name | String | 連絡先名 |
| email | String | メールアドレス |
| phone_number | String | 電話番号 |
| identifier | String | 外部識別子 |
| thumbnail | String | アバターURL |
| additional_attributes | Object | 追加属性 |
| custom_attributes | Object | カスタム属性 |
| blocked | Boolean | ブロックフラグ |
| contact_inboxes | Array | 紐づくインボックス一覧 |

### 出力先

- contactsテーブル
- contact_inboxesテーブル（インボックス紐づけ時）
- API レスポンス

## 処理フロー

### 処理シーケンス（作成）

```
1. リクエスト受信
   └─ 認可チェック（check_authorization）
2. パラメータ検証
   └─ メール、電話番号、identifierのユニークチェック
3. 連絡先作成
   └─ トランザクション内でContact.create!
4. インボックス紐づけ（オプション）
   └─ ContactInboxBuilder#performでcontact_inbox作成
5. アバター処理（オプション）
   └─ avatar_urlがあれば非同期でダウンロード
6. イベント発行
   └─ CONTACT_CREATEDイベントをディスパッチ
7. IPルックアップ（オプション）
   └─ 非同期でIP地理情報を取得
```

### フローチャート

```mermaid
flowchart TD
    A[連絡先作成リクエスト] --> B[認可チェック]
    B --> C{権限あり?}
    C -->|No| Z1[403 Forbidden]
    C -->|Yes| D[パラメータ検証]
    D --> E{バリデーション通過?}
    E -->|No| Z2[422 Unprocessable Entity]
    E -->|Yes| F[トランザクション開始]
    F --> G[Contact.create!]
    G --> H{inbox_idあり?}
    H -->|Yes| I[ContactInboxBuilder#perform]
    H -->|No| J{avatar_urlあり?}
    I --> J
    J -->|Yes| K[Avatar::AvatarFromUrlJob投入]
    J -->|No| L[トランザクションコミット]
    K --> L
    L --> M[CONTACT_CREATEDイベント発行]
    M --> N[IPルックアップジョブ投入]
    N --> O[レスポンス返却]
    O --> P[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-55-01 | メールユニーク制約 | 同一アカウント内でメールアドレスは一意 | メールアドレス設定時 |
| BR-55-02 | 電話番号ユニーク制約 | 同一アカウント内で電話番号は一意 | 電話番号設定時 |
| BR-55-03 | identifierユニーク制約 | 同一アカウント内でidentifierは一意 | identifier設定時 |
| BR-55-04 | 電話番号形式 | E.164形式（+[国番号][番号]）のみ許可 | 電話番号設定時 |
| BR-55-05 | オンライン削除禁止 | オンライン状態の連絡先は削除不可 | 連絡先削除時 |
| BR-55-06 | 解決済み連絡先 | CRM v2有効時はcontact_type='lead'で解決済み判定 | 連絡先一覧取得時 |
| BR-55-07 | カスタム属性マージ | 更新時は既存値とマージして保存 | カスタム属性更新時 |

### contact_typeの定義

| 値 | 名称 | 説明 |
|----|------|------|
| 0 | visitor | 訪問者（初期状態） |
| 1 | lead | リード |
| 2 | customer | 顧客 |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 連絡先作成 | contacts | INSERT | 新規連絡先を挿入 |
| 連絡先取得 | contacts | SELECT | 連絡先情報を取得 |
| 連絡先更新 | contacts | UPDATE | 連絡先情報を更新 |
| 連絡先削除 | contacts | DELETE | 連絡先を削除 |
| インボックス紐づけ | contact_inboxes | INSERT | 連絡先とインボックスの紐づけ |

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

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | Current.accountのID | 必須 |
| INSERT | name | パラメータから | デフォルト空文字 |
| INSERT | email | パラメータから（小文字変換） | ユニーク制約 |
| INSERT | phone_number | パラメータから | E.164形式、ユニーク制約 |
| INSERT | identifier | パラメータから | ユニーク制約 |
| INSERT | additional_attributes | パラメータから | デフォルト{} |
| INSERT | custom_attributes | パラメータから | デフォルト{} |
| INSERT | blocked | パラメータから | デフォルトfalse |
| INSERT | contact_type | デフォルト | 0（visitor） |

### インデックス

| インデックス名 | カラム | 用途 |
|---------------|--------|------|
| uniq_email_per_account_contact | email, account_id | メールのユニーク制約 |
| uniq_identifier_per_account_contact | identifier, account_id | identifierのユニーク制約 |
| index_contacts_on_phone_number_and_account_id | phone_number, account_id | 電話番号検索 |
| index_contacts_on_account_id_and_last_activity_at | account_id, last_activity_at | アクティビティ順ソート |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | バリデーションエラー | メール形式不正 | エラーメッセージを返却 |
| 422 | ユニーク制約違反 | 重複するメール/電話番号/identifier | エラーメッセージを返却 |
| 422 | オンライン削除エラー | オンライン状態の連絡先を削除 | エラーメッセージを返却 |
| 403 | 権限エラー | 権限のないユーザーによる操作 | 403 Forbiddenを返却 |
| 404 | 存在しないリソース | 指定IDの連絡先が存在しない | 404 Not Foundを返却 |

### リトライ仕様

なし（同期処理）

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

連絡先作成時は、Contact.create!とContactInboxBuilder#performをトランザクションでラップし、原子性を保証。

## パフォーマンス要件

- 一覧取得は15件/ページのページネーション
- 連絡先作成は通常200ms以内で完了
- インデックスによりユニーク制約チェックは高速

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

- 連絡先情報は個人情報を含むため、適切なアクセス制御が必要
- APIレスポンスにはパスワードなどの機密情報を含めない
- アバターURLはサニタイズされる

## 備考

- 空のメールアドレスはNULLとして保存される（DBユニーク制約対応）
- additional_attributesはシステムが自動設定する属性（IP情報等）
- custom_attributesはユーザーが定義するカスタム属性

---

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

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

### 推奨読解順序

#### Step 1: 連絡先モデルを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | contact.rb | `app/models/contact.rb` | 連絡先のスキーマ、バリデーション、関連定義 |

**主要処理フロー**:
- **1-42行目**: スキーマ情報（カラム定義、インデックス）
- **44-48行目**: モジュールのinclude（Avatarable, Labelable等）
- **50-56行目**: バリデーション（email, identifier, phone_number）
- **58-69行目**: アソシエーション（has_many :conversations等）
- **71行目**: contact_type enum定義
- **138-144行目**: stale_without_conversations scope

**読解のコツ**: Railsの`include_mod_with`はEnterprise機能の拡張ポイント。基本機能はOSSコードで完結。

#### Step 2: コントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | contacts_controller.rb | `app/controllers/api/v1/accounts/contacts_controller.rb` | CRUD操作のAPIエンドポイント |

**主要処理フロー**:
- **2-10行目**: Siftによるソート定義
- **19-22行目**: indexアクションで一覧取得
- **60行目**: showアクション（fetch_contactで取得）
- **85-92行目**: createアクションでトランザクション内で作成
- **94-98行目**: updateアクションで更新
- **100-110行目**: destroyアクションでオンラインチェック後削除
- **162-171行目**: build_contact_inboxでインボックス紐づけ

#### Step 3: 連絡先インボックスビルダーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | contact_inbox_builder.rb | `app/builders/contact_inbox_builder.rb` | 連絡先とインボックスの紐づけ処理 |

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

```
Api::V1::Accounts::ContactsController
    │
    ├─ #create
    │      ├─ Contact.new(permitted_params)
    │      ├─ @contact.save!
    │      ├─ build_contact_inbox (オプション)
    │      │      └─ ContactInboxBuilder#perform
    │      └─ process_avatar_from_url (オプション)
    │             └─ Avatar::AvatarFromUrlJob
    │
    ├─ #update
    │      ├─ @contact.assign_attributes
    │      ├─ @contact.save!
    │      └─ process_avatar_from_url (オプション)
    │
    └─ #destroy
           ├─ OnlineStatusTracker.get_presence チェック
           └─ @contact.destroy!
```

### データフロー図

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

APIリクエスト    ContactsController         contacts テーブル
(name, email,  ───▶ バリデーション      ───▶  contact_inboxes テーブル
 phone等)            Contact CRUD               │
                                                ▼
                                          WebSocket通知
                                          (CONTACT_CREATED等)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| contact.rb | `app/models/contact.rb` | モデル | 連絡先のモデル定義 |
| contacts_controller.rb | `app/controllers/api/v1/accounts/contacts_controller.rb` | コントローラー | CRUD APIエンドポイント |
| contact_inbox_builder.rb | `app/builders/contact_inbox_builder.rb` | ビルダー | インボックス紐づけ |
| filter_service.rb | `app/services/contacts/filter_service.rb` | サービス | フィルタリング処理 |
| show.json.jbuilder | `app/views/api/v1/accounts/contacts/show.json.jbuilder` | ビュー | JSONレスポンス生成 |
