# 機能設計書 62-ラベル管理（連絡先）

## 概要

本ドキュメントは、Chatwootにおける連絡先（Contact）に対するラベル管理機能の設計を記述する。連絡先を分類・整理するためのラベル（タグ）を付与・管理する機能である。

### 本機能の処理概要

連絡先ラベル管理機能は、連絡先にラベル（タグ）を付与して分類・整理するための機能を提供する。acts-as-taggable-on gem を使用したタグ管理システムを基盤としている。

**業務上の目的・背景**：カスタマーサポート業務において、連絡先を適切に分類することは、効率的な顧客管理とターゲティングに不可欠である。例えば、VIP顧客、クレーム顧客、特定製品の利用者など、様々な観点で連絡先を分類することで、キャンペーン送信の対象選定や優先対応の判断が容易になる。

**機能の利用シーン**：
- 顧客をカテゴリ別に分類したい場合（VIP、一般、トライアルユーザー等）
- キャンペーン送信のターゲットグループを定義する場合
- 特定の属性を持つ顧客を識別・検索する場合
- 連絡先一覧をラベルでフィルタリングする場合

**主要な処理内容**：
1. ラベル一覧取得（Index）：連絡先に付与されているラベル一覧を取得
2. ラベル更新（Create）：連絡先に付与するラベルを一括更新（置換）

**関連システム・外部連携**：
- acts-as-taggable-on gem によるタグ管理
- taggings テーブルでの関連付け管理
- キャンペーン機能での連絡先フィルタリング（audience 選定）

**権限による制御**：連絡先へのアクセス権限を持つエージェントのみがラベル操作が可能。アカウント単位でのアクセス制御が適用される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | 連絡先一覧画面 | 主画面 | 連絡先へのラベル付与 |
| 22 | ラベル別連絡先一覧 | 参照画面 | ラベルによるフィルタリング |

## 機能種別

CRUD操作（Read/Update）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| contact_id | Integer | Yes | 対象連絡先ID | URLパラメータ、存在確認 |
| labels | Array[String] | No | 付与するラベル名の配列 | 配列形式 |

### 入力データソース

- 画面入力：連絡先一覧画面・編集画面からの入力
- URLパラメータ：contact_id

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| payload | Array[String] | 連絡先に付与されているラベル名の配列 |

### 出力先

- API レスポンス（JSON形式）
- フロントエンド連絡先画面

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ API エンドポイントで受信（/api/v1/accounts/:account_id/contacts/:contact_id/labels）

2. 認証・認可チェック
   └─ BaseController で Current.account, Current.user を設定
   └─ 連絡先の存在確認と所属確認

3. アクション実行
   └─ index: 連絡先の label_list を取得
   └─ create: update_labels メソッドでラベルを一括更新

4. レスポンス生成
   └─ JBuilder テンプレートでJSON形式に整形
   └─ payload キーでラベル配列を返却
```

### フローチャート

```mermaid
flowchart TD
    A[APIリクエスト受信] --> B{認証済み?}
    B -->|No| C[401 Unauthorized]
    B -->|Yes| D{連絡先存在?}
    D -->|No| E[404 Not Found]
    D -->|Yes| F{アクション判定}
    F -->|index| G[label_list取得]
    F -->|create| H[update_labels実行]
    H --> I[taggingsテーブル更新]
    I --> J[label_list取得]
    G --> K[JSONレスポンス]
    J --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ラベル置換 | create アクションは既存ラベルを新しいラベル配列で完全に置換する | createアクション |
| BR-02 | 空配列対応 | 空の配列を指定すると全ラベルが削除される | createアクション |
| BR-03 | ラベル名正規化 | ラベル名は acts-as-taggable-on の仕様に従い正規化される | 全アクション |

### 計算ロジック

特に計算ロジックはなし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| index | taggings | SELECT | 連絡先のラベル一覧を取得 |
| create | taggings | DELETE + INSERT | 既存のラベル関連を削除し、新しいラベルを関連付け |
| create | tags | INSERT | 新しいタグ名が指定された場合に作成 |

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

#### taggings（acts-as-taggable-on）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | taggable_type = 'Contact', taggable_id = :contact_id | ラベル取得 |
| DELETE | - | taggable_type = 'Contact', taggable_id = :contact_id | 既存ラベル削除 |
| INSERT | taggable_type | 'Contact' | |
| INSERT | taggable_id | 連絡先ID | |
| INSERT | tag_id | 対応するtags.id | |
| INSERT | context | 'labels' | acts-as-taggable-on のコンテキスト |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 認証トークンが無効または期限切れ | 再認証を促す |
| 404 | Not Found | 連絡先が存在しない | 正しいIDを指定 |

### リトライ仕様

特にリトライ仕様はなし。

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

- update_labels は update! を使用するため、暗黙的にトランザクション管理される
- acts-as-taggable-on の内部処理でトランザクション管理

## パフォーマンス要件

- taggings テーブルには適切なインデックスが設定されている
- label_list は acts-as-taggable-on のキャッシュ機構を利用

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

- アカウント単位でのアクセス制御（Current.account.contacts でスコープ）
- 連絡先の所属確認を BaseController で実施
- Strong Parameters による入力パラメータの制限（labels のみ許可）

## 備考

- Labelable concern により、Contact モデルに acts_as_taggable_on :labels が追加されている
- 同じ LabelConcern を会話（Conversation）のラベル管理でも使用している

---

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

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

### 推奨読解順序

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

まず、ラベル管理の基盤となる Labelable concern を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | labelable.rb | `app/models/concerns/labelable.rb` | acts-as-taggable-on の設定と更新メソッド |

**読解のコツ**: acts-as-taggable-on gem の仕様を理解しておくと、label_list や update_labels の動作が理解しやすい。

**主要処理フロー**:
- **5行目**: `acts_as_taggable_on :labels` - :labels コンテキストでタグ付け機能を追加
- **8-10行目**: `update_labels` - ラベルを一括更新（label_list に配列を代入して update!）
- **12-18行目**: `add_labels` - 既存ラベルに追加（本機能では未使用）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | labels_controller.rb | `app/controllers/api/v1/accounts/contacts/labels_controller.rb` | ラベルAPIエンドポイント |
| 2-2 | label_concern.rb | `app/controllers/concerns/label_concern.rb` | 共通ロジック |

**主要処理フロー（labels_controller.rb）**:
- **1行目**: BaseController を継承
- **2行目**: `include LabelConcern` - 共通ロジックをインクルード
- **6-8行目**: `model` - 操作対象として @contact を返す
- **10-12行目**: `permitted_params` - labels 配列のみ許可

**主要処理フロー（label_concern.rb）**:
- **2-5行目**: `create` - model.update_labels でラベル更新、結果を @labels に格納
- **7-9行目**: `index` - model.label_list でラベル一覧取得

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | contact.rb | `app/models/contact.rb` | Labelable のインクルード |

**主要処理フロー**:
- **47行目**: `include Labelable` - Labelable concern をインクルード

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

```
Api::V1::Accounts::Contacts::LabelsController
    │
    ├─ LabelConcern (インクルード)
    │      ├─ create アクション
    │      │      └─ model.update_labels(labels)
    │      │              └─ Labelable#update_labels
    │      │                      └─ update!(label_list: labels)
    │      │                              └─ acts-as-taggable-on
    │      │
    │      └─ index アクション
    │             └─ model.label_list
    │                     └─ acts-as-taggable-on
    │
    └─ model メソッド
           └─ @contact を返却
```

### データフロー図

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

contact_id (URL) ───▶ BaseController#ensure_contact ───▶ @contact
                              │
labels (Array) ──────▶ LabelConcern#create ─────────────▶ taggings テーブル
 (POST body)                  │                           (DELETE + INSERT)
                              │
                      model.update_labels(labels)
                              │
                      Labelable#update_labels
                              │
                      update!(label_list: labels)
                              │
                              ▼
                      label_list 取得 ────────────────▶ JSON レスポンス
                                                         (payload: [...])
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| labels_controller.rb | `app/controllers/api/v1/accounts/contacts/labels_controller.rb` | コントローラー | ラベルAPI（連絡先用） |
| label_concern.rb | `app/controllers/concerns/label_concern.rb` | コントローラーconcern | ラベル操作の共通ロジック |
| labelable.rb | `app/models/concerns/labelable.rb` | モデルconcern | acts-as-taggable-on 設定 |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先モデル（Labelableインクルード） |
| base_controller.rb | `app/controllers/api/v1/accounts/contacts/base_controller.rb` | コントローラー | 連絡先関連APIの共通処理 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
