# 機能設計書 188-バルク接触アクション

## 概要

本ドキュメントは、Chatwootにおける「バルク接触アクション」機能の設計仕様を定義する。本機能は、複数の連絡先（Contact）に対して一括で操作（ラベル付与、削除など）を行うための機能である。

### 本機能の処理概要

本機能は、複数の連絡先IDを指定して、一括でラベルの追加や連絡先の削除を行う。バックグラウンドジョブとして非同期で実行されるため、大量の連絡先を処理する場合でもUIがブロックされない。

**業務上の目的・背景**：顧客データベースの管理において、特定条件に合致する連絡先にラベルを一括付与したり、不要になった連絡先を一括削除したりするニーズがある。CRMとしての機能を効率的に運用するため、バルク操作が必要となる。

**機能の利用シーン**：マーケティングキャンペーン対象者に一括でラベルを付与する。セグメント分けのためのラベル付けを行う。データクレンジングで不要な連絡先を一括削除する。

**主要な処理内容**：
1. 連絡先IDリストの受信
2. アクション種別の判定（ラベル追加 or 削除）
3. 各連絡先への操作実行

**関連システム・外部連携**：Sidekiqによる非同期ジョブ処理

**権限による制御**：連絡先削除の場合はContact.destroy?権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | 連絡先一覧画面 | 主画面 | 複数連絡先選択と一括操作 |

## 機能種別

バッチ処理 / 非同期処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| type | String | Yes | 対象タイプ | "Contact" または "contact" |
| ids | Array[Integer] | Yes | 連絡先IDリスト | 配列 |
| action_name | String | No | アクション種別 | "delete" など |
| labels | Object | No | ラベル操作 | |
| labels.add | Array[String] | No | 追加するラベル | 文字列配列 |

### 入力データソース

- APIリクエスト: POST /api/v1/accounts/{account_id}/bulk_actions

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| HTTP Status | Integer | 200 OK（ジョブエンキュー成功時） |

### 出力先

- HTTPレスポンス（head :ok）
- 連絡先データの更新/削除

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ BulkActionsController#create

2. タイプ判定
   └─ normalized_typeで "Contact" を判定

3. 権限チェック（削除時）
   └─ authorize(Contact, :destroy?)

4. ジョブエンキュー
   └─ Contacts::BulkActionJob.perform_laterを呼び出し

5. レスポンス返却
   └─ head :ok

6. バックグラウンド処理
   ├─ アクション判定
   │      ├─ delete_requested? → delete_contacts
   │      └─ labels_to_add → assign_labels
   └─ 完了
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{type判定}
    B -->|Contact| C[delete_contact_action?]
    C -->|Yes| D[authorize Contact :destroy?]
    C -->|No| E[スキップ]

    D --> F[enqueue_contact_job]
    E --> F

    F --> G[Contacts::BulkActionJob.perform_later]
    G --> H[head :ok]

    subgraph Background Job
        I[BulkActionService#perform] --> J{action判定}
        J -->|delete| K[BulkDeleteService#perform]
        J -->|labels| L[BulkAssignLabelsService#perform]
        K --> M[各連絡先destroy!]
        L --> N[各連絡先add_labels]
    end

    H --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-188-01 | 対象タイプ制限 | type は "Contact" | 常時 |
| BR-188-02 | 削除権限必須 | action_name が "delete" の場合は削除権限が必要 | 削除時 |
| BR-188-03 | IDで検索 | 連絡先はIDで直接検索 | 常時 |
| BR-188-04 | 削除優先 | delete_requested?がtrueの場合はラベル追加より削除を優先 | 判定時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 連絡先取得 | contacts | SELECT | IDで対象連絡先を取得 |
| ラベル追加 | labels, taggings | INSERT/UPDATE | ラベルの追加 |
| 連絡先削除 | contacts | DELETE | 連絡先の削除 |

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

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | account_id = @account.id, id IN (ids) | |
| DELETE | - | id IN (ids) | 削除時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | 削除権限がない | エラーレスポンス返却 |
| 422 | Unprocessable Entity | 不正なtype指定 | エラーレスポンス返却 |
| 500 | Internal Server Error | ジョブ実行エラー | Sidekiqリトライ |

### リトライ仕様

Sidekiqのデフォルトリトライ設定に従う。

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

各連絡先の削除は個別のトランザクションで実行。1件の失敗が他に影響しない。

## パフォーマンス要件

- ジョブはmediumキューで実行
- 大量の連絡先でもUIはブロックされない
- 即座にhead :okを返却

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

- アカウント境界の厳守（account_idでスコープ）
- 削除操作の権限チェック

## 備考

- 削除操作はfind_each(&:destroy!)で1件ずつ実行
- ラベル追加は正常に追加されたIDのリストを返却（サービス内部）

---

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

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

### 推奨読解順序

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

まず、APIエントリーポイントを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | bulk_actions_controller.rb | `app/controllers/api/v1/accounts/bulk_actions_controller.rb` | Contact向け処理 |

**読解のコツ**:
- **7-10行目**: Contactタイプの処理 - 権限チェックとジョブエンキュー
- **30-36行目**: enqueue_contact_job - ジョブ呼び出し
- **38-44行目**: check_authorization_for_contact_action - 削除時の権限チェック

#### Step 2: バックグラウンドジョブを理解する

非同期処理の実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | bulk_action_job.rb | `app/jobs/contacts/bulk_action_job.rb` | ジョブ実装 |

**主要処理フロー**:
- **4-13行目**: performメソッド - BulkActionServiceを呼び出し

#### Step 3: サービスクラスを理解する

ビジネスロジックの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | bulk_action_service.rb | `app/services/contacts/bulk_action_service.rb` | メインサービス |
| 3-2 | bulk_delete_service.rb | `app/services/contacts/bulk_delete_service.rb` | 削除サービス |
| 3-3 | bulk_assign_labels_service.rb | `app/services/contacts/bulk_assign_labels_service.rb` | ラベル追加サービス |

**主要処理フロー**:
- **bulk_action_service.rb 8-13行目**: perform - アクション判定と振り分け
- **bulk_delete_service.rb 7-11行目**: perform - 削除実行
- **bulk_assign_labels_service.rb 8-17行目**: perform - ラベル追加実行

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

```
POST /api/v1/accounts/:account_id/bulk_actions (type=Contact)
    │
    └─ BulkActionsController#create
           │
           ├─ check_authorization_for_contact_action
           │      └─ authorize(Contact, :destroy?) (削除時のみ)
           │
           └─ enqueue_contact_job
                  │
                  └─ Contacts::BulkActionJob.perform_later
                         │
                         └─ BulkActionService#perform
                                │
                                ├─ delete_requested?
                                │      └─ BulkDeleteService#perform
                                │             └─ contacts.find_each(&:destroy!)
                                │
                                └─ labels_to_add.any?
                                       └─ BulkAssignLabelsService#perform
                                              └─ contacts.find_each { |c| c.add_labels }
```

### データフロー図

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

APIリクエスト ─────────▶ BulkActionsController ─────────▶ head :ok
(ids[], action_name,           │
labels)                        ▼
                       Contacts::BulkActionJob (非同期)
                               │
                               ▼
                       BulkActionService#perform
                               │
                    ┌──────────┴──────────┐
                    ▼                     ▼
              BulkDeleteService     BulkAssignLabelsService
                    │                     │
                    ▼                     ▼
              contacts DELETE       taggings INSERT
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| bulk_actions_controller.rb | `app/controllers/api/v1/accounts/bulk_actions_controller.rb` | コントローラー | APIエントリーポイント |
| bulk_action_job.rb | `app/jobs/contacts/bulk_action_job.rb` | ジョブ | 非同期ジョブ |
| bulk_action_service.rb | `app/services/contacts/bulk_action_service.rb` | サービス | アクション振り分け |
| bulk_delete_service.rb | `app/services/contacts/bulk_delete_service.rb` | サービス | 削除処理 |
| bulk_assign_labels_service.rb | `app/services/contacts/bulk_assign_labels_service.rb` | サービス | ラベル追加処理 |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先モデル |
