# 機能設計書 71-営業時間中送信

## 概要

本ドキュメントは、Chatwootのキャンペーン機能における「営業時間中送信」オプションの仕様を定義する機能設計書である。

### 本機能の処理概要

キャンペーンを営業時間内にのみ送信するように制御する機能。Webサイトウィジェットで表示されるキャンペーンメッセージを、顧客が営業時間内にサイトを訪問した場合のみ表示することで、リアルタイムでの対応が可能な時間帯に限定したキャンペーン配信を実現する。

**業務上の目的・背景**：顧客がキャンペーンメッセージに反応した際、営業時間外であればエージェントが即座に対応できないため、顧客体験の低下につながる。本機能により、エージェントが確実に対応可能な時間帯にのみキャンペーンを表示することで、顧客満足度の向上とスムーズなコミュニケーションを実現する。

**機能の利用シーン**：
- ライブチャット対応を前提としたプロモーションキャンペーンの配信
- 即座のサポート対応が期待される問い合わせ促進キャンペーン
- 時間限定のオファーやセールス案内

**主要な処理内容**：
1. キャンペーン作成・更新時に`trigger_only_during_business_hours`フラグを設定
2. ウィジェット側でキャンペーン一覧を取得時、営業時間フラグを含めて応答
3. フロントエンドでキャンペーンフィルタリング時、現在が営業時間内かどうかを判定
4. 営業時間外の場合、該当キャンペーンを表示対象から除外

**関連システム・外部連携**：
- インボックス設定の営業時間（Working Hours）設定と連携
- Webウィジェットでの表示制御に使用

**権限による制御**：キャンペーンの作成・更新権限を持つ管理者ロールが設定可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 99 | ライブチャットキャンペーン画面 | 主画面 | キャンペーン作成・編集時に営業時間中送信オプションを設定 |

## 機能種別

データ設定 / フィルタリング処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| trigger_only_during_business_hours | Boolean | No | 営業時間中のみ送信するかどうか | true/false、デフォルトはfalse |

### 入力データソース

- 管理画面からのキャンペーン作成・編集フォーム入力
- API経由でのキャンペーン設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| trigger_only_during_business_hours | Boolean | 営業時間中のみ送信するかのフラグ |

### 出力先

- キャンペーンAPIレスポンス
- ウィジェットAPIレスポンス
- データベース（campaignsテーブル）

## 処理フロー

### 処理シーケンス

```
1. キャンペーン設定の保存
   └─ 管理者がキャンペーン作成/更新時に「営業時間中のみ」オプションを設定
2. キャンペーンデータの永続化
   └─ campaignsテーブルのtrigger_only_during_business_hoursカラムに保存
3. ウィジェットからのキャンペーン取得リクエスト
   └─ GET /api/v1/widget/campaigns でキャンペーン一覧を取得
4. クライアント側でのフィルタリング
   └─ filterCampaigns関数でisInBusinessHoursフラグと照合
5. 表示対象キャンペーンの決定
   └─ 条件を満たすキャンペーンのみウィジェットに表示
```

### フローチャート

```mermaid
flowchart TD
    A[キャンペーン表示要求] --> B{trigger_only_during_business_hours?}
    B -->|false| C[キャンペーンを表示]
    B -->|true| D{現在は営業時間内?}
    D -->|Yes| C
    D -->|No| E[キャンペーンを非表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-71-01 | 営業時間判定 | インボックスに設定された営業時間を基準に判定 | trigger_only_during_business_hoursがtrueの場合 |
| BR-71-02 | デフォルト動作 | オプション未設定時は常に表示可能 | 新規キャンペーン作成時 |
| BR-71-03 | 対象チャネル | Website（ウィジェット）タイプのみ適用 | ongoing型キャンペーンのみ |

### 計算ロジック

営業時間判定はクライアント側（JavaScript）で実行される。isInBusinessHours変数は、インボックスのworking_hours設定と現在時刻を比較して算出される。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| キャンペーン作成 | campaigns | INSERT | 新規キャンペーンレコードの挿入 |
| キャンペーン更新 | campaigns | UPDATE | 既存キャンペーンのフラグ更新 |
| キャンペーン取得 | campaigns | SELECT | キャンペーン一覧の取得 |

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

#### campaigns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | trigger_only_during_business_hours | true/false | デフォルトはfalse |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バリデーションエラー | 不正な値（Boolean以外）が指定された場合 | 400 Bad Request を返却 |

### リトライ仕様

本機能は設定保存のため、リトライ仕様は特にない。

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

キャンペーンの作成・更新は単一レコード操作のため、ActiveRecordの標準トランザクション制御に依存。

## パフォーマンス要件

- キャンペーン取得API：100ms以内の応答
- ウィジェット側フィルタリング：ブラウザ上で瞬時（10ms以内）

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

- キャンペーン設定変更は認証済み管理者のみ可能
- ウィジェット側での営業時間判定はクライアントサイドで行われるため、厳密なセキュリティ要件はない

## 備考

- ウィジェットのキャンペーン表示は、URL条件（trigger_rules.url）とページ滞在時間（trigger_rules.time_on_page）との組み合わせで判定される
- 営業時間設定はインボックス単位で行われるため、キャンペーンが属するインボックスの営業時間が参照される

---

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

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

### 推奨読解順序

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

キャンペーンモデルの構造とデータベーススキーマを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | campaign.rb | `app/models/campaign.rb` | campaignモデルの定義、trigger_only_during_business_hoursカラムの存在確認 |

**読解のコツ**:
- **15行目**: `trigger_only_during_business_hours`カラムがboolean型でデフォルトfalseで定義されている
- **48-50行目**: enum定義でcampaign_typeとcampaign_statusを確認

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

APIコントローラーでのパラメータ受け付けを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | キャンペーンのCRUD操作 |

**主要処理フロー**:
1. **11-13行目**: createアクションでcampaign_paramsを使用してキャンペーン作成
2. **15-17行目**: updateアクションでキャンペーン更新
3. **30-32行目**: campaign_paramsでtrigger_only_during_business_hoursが許可されている

#### Step 3: ウィジェットでのフィルタリングを理解する

クライアントサイドでの営業時間判定ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | campaignHelper.js | `app/javascript/widget/helpers/campaignHelper.js` | キャンペーンのフィルタリングロジック |

**主要処理フロー**:
- **20-30行目**: formatCampaigns関数でAPIレスポンスをクライアント用に整形、triggerOnlyDuringBusinessHoursフラグを保持
- **33-47行目**: filterCampaigns関数でURL条件と営業時間条件を適用

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

```
CampaignsController#create/update
    │
    └─ Campaign.create!/update!
           └─ campaignsテーブルへ保存

Widget API: /api/v1/widget/campaigns
    │
    └─ Widget::CampaignsController#index
           └─ キャンペーン一覧をJSON形式で返却
                  └─ クライアント(campaignHelper.js)
                         ├─ formatCampaigns: データ整形
                         └─ filterCampaigns: 営業時間フィルタリング
```

### データフロー図

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

管理者設定                  キャンペーン保存               DBへ永続化
(trigger_only_...) ───▶ CampaignsController ───▶ campaignsテーブル

ウィジェット表示            営業時間判定                   表示/非表示
リクエスト ─────────▶ campaignHelper.js ──────▶ キャンペーンUI
                           │
                           └─ isInBusinessHours
                               (インボックス営業時間参照)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| campaign.rb | `app/models/campaign.rb` | モデル | キャンペーンのデータ構造定義 |
| campaigns_controller.rb | `app/controllers/api/v1/accounts/campaigns_controller.rb` | コントローラー | 管理API |
| campaigns_controller.rb | `app/controllers/api/v1/widget/campaigns_controller.rb` | コントローラー | ウィジェットAPI |
| _campaign.json.jbuilder | `app/views/api/v1/models/_campaign.json.jbuilder` | ビュー | APIレスポンステンプレート |
| campaignHelper.js | `app/javascript/widget/helpers/campaignHelper.js` | フロントエンド | 営業時間フィルタリング |
| LiveChatCampaignForm.vue | `app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignForm.vue` | フロントエンド | キャンペーン設定フォーム |
