# 機能設計書 166-Widgetラベル

## 概要

本ドキュメントは、Chatwoot WebウィジェットにおけるWidgetラベル管理機能の設計を記載する。この機能はエンドユーザー（訪問者）がWebサイト上のチャットウィジェットを通じて会話にラベルを追加・削除するためのAPIを提供する。

### 本機能の処理概要

Widgetラベル機能は、Webサイト訪問者の会話に対してラベルを付与・削除する機能を提供する。これにより、訪問者自身やWidgetのカスタマイズにより会話を分類・タグ付けすることができる。ラベルはアカウントで事前に定義されている必要がある。

**業務上の目的・背景**：会話の分類とタグ付けを訪問者側から行えるようにすることで、訪問者の意図や要望に基づいた適切なルーティングやフィルタリングが可能になる。例えば、「販売」「技術サポート」「請求」などのラベルを訪問者に選択させることで、適切なチームへの自動割り当てが実現できる。

**機能の利用シーン**：
- 訪問者が問い合わせの種類を選択してラベルを付与する場面
- プリチャットフォームでカテゴリを選択した際にラベルを設定する場面
- チャットボットが訪問者の回答に基づいてラベルを付与する場面
- 不要になったラベルを会話から削除する場面

**主要な処理内容**：
1. 会話へのラベル追加（create）
2. 会話からのラベル削除（destroy）

**関連システム・外部連携**：
- アカウントのラベルマスタ（事前定義必須）
- オートメーションルール（ラベルベースのトリガー）

**権限による制御**：
- website_tokenによるウィジェット認証が必要
- アカウントで定義済みのラベルのみ追加可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 116 | メッセージ画面 | 参照画面 | ラベル操作の実行元 |

## 機能種別

CRUD操作（Create/Delete）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| website_token | String | Yes | ウェブウィジェットの識別トークン | 有効なトークンである必要がある |
| label | String | Yes（create時） | 追加するラベル名 | アカウントで定義済み |
| id | String | Yes（destroy時） | 削除するラベル名 | - |

### 入力データソース

- Webウィジェット（JavaScript SDK）からのHTTPリクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| (なし) | - | 204 No Contentを返却 |

### 出力先

- HTTPステータスコードのみ（204 No Content）

## 処理フロー

### 処理シーケンス

```
1. ラベル追加（create）
   └─ 会話の存在確認
   └─ ラベルがアカウントで定義済みか確認
   └─ 両条件を満たす場合：会話のlabel_listに追加
   └─ 会話を保存
   └─ 204 No Contentを返却

2. ラベル削除（destroy）
   └─ 会話の存在確認
   └─ 会話が存在する場合：会話のlabel_listから削除
   └─ 会話を保存
   └─ 204 No Contentを返却
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{アクション判定}

    B -->|create| C{会話存在?}
    C -->|No| D[何もしない]
    C -->|Yes| E{ラベル定義済み?}
    E -->|No| D
    E -->|Yes| F[label_listに追加]
    F --> G[会話保存]
    G --> H[204 No Content]
    D --> H

    B -->|destroy| I{会話存在?}
    I -->|No| J[何もしない]
    I -->|Yes| K[label_listから削除]
    K --> L[会話保存]
    L --> H
    J --> H
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-166-01 | ラベル定義チェック | アカウントで定義済みのラベルのみ追加可能 | create時 |
| BR-166-02 | 会話存在チェック | 会話が存在しない場合は何もしない | create/destroy時 |
| BR-166-03 | 重複防止 | 同一ラベルの重複追加は自動的に無視される | create時 |
| BR-166-04 | 削除時のラベル存在チェック不要 | 存在しないラベルの削除はエラーにならない | destroy時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| create | labels | SELECT | ラベル定義の存在確認 |
| create | conversations | UPDATE | label_listの更新 |
| destroy | conversations | UPDATE | label_listの更新 |

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

#### labels

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | account_id = @current_account.id | アカウント条件 |
| SELECT | - | title = params[:label] | ラベル名条件 |

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | label_list | add/remove後の値 | acts_as_taggableで管理 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | website_tokenが無効 | 正しいトークンを使用 |

### リトライ仕様

特になし（同期処理のため）

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

単一レコードの更新のため暗黙的トランザクション

## パフォーマンス要件

- レスポンス時間: 200ms以内

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

- website_tokenによるウィジェット認証
- 未定義ラベルの追加防止
- 他アカウントのラベル使用防止

## 備考

- label_listはacts_as_taggableで管理されている
- ラベルの追加・削除後もレスポンスは常に204 No Content
- 会話が存在しない場合でも204を返却（エラーにならない）
- 削除時はラベルの存在確認を行わない

---

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

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

### 推奨読解順序

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

ラベルに関連するデータモデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | label.rb | `app/models/label.rb` | ラベルモデルの構造、title |
| 1-2 | conversation.rb | `app/models/conversation.rb` | acts_as_taggableによるlabel_list |

**読解のコツ**: acts_as_taggableの使い方（label_list.add, label_list.remove）を理解すること。

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

処理の起点となるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | labels_controller.rb | `app/controllers/api/v1/widget/labels_controller.rb` | ラベル操作の実装 |

**主要処理フロー**:
1. **2-9行目**: create処理（会話存在確認、ラベル定義確認、追加、保存）
2. **11-18行目**: destroy処理（会話存在確認、削除、保存）
3. **22-25行目**: label_defined_in_account?（ラベル定義確認）
4. **27-29行目**: permitted_params（許可パラメータ）

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

```
Api::V1::Widget::LabelsController
    │
    ├─ BaseController (認証・共通処理)
    │      ├─ set_web_widget
    │      ├─ set_contact
    │      └─ conversation
    │
    ├─ label_defined_in_account?
    │      └─ @current_account.labels.find_by(title:)
    │
    ├─ conversation.label_list.add
    │      └─ acts_as_taggable
    │
    └─ conversation.label_list.remove
           └─ acts_as_taggable
```

### データフロー図

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

website_token ────────▶ BaseController#set_web_widget ────▶ @web_widget
                              │
                        BaseController#conversation ──────▶ @conversation
                              │
label ────────────────▶ label_defined_in_account? ────────▶ 判定結果
                              │
                        label_list.add / remove ──────────▶ 更新後のlabel_list
                              │
                        conversation.save! ───────────────▶ データベース更新
                              │
                        head :no_content ─────────────────▶ 204 No Content
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| labels_controller.rb | `app/controllers/api/v1/widget/labels_controller.rb` | ソース | Widgetラベルのメインコントローラー |
| base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | ソース | Widget APIの基底コントローラー |
| label.rb | `app/models/label.rb` | ソース | ラベルモデル |
| conversation.rb | `app/models/conversation.rb` | ソース | 会話モデル（acts_as_taggable） |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（412行目） |
