# バッチ設計書 15-Conversations::ReopenSnoozedConversationsJob

## 概要

本ドキュメントは、Chatwootシステムにおけるスヌーズ期限切れ会話の再オープン処理を実行するバッチ処理の設計を記述するものです。

### 本バッチの処理概要

本バッチは、スヌーズ期限（snoozed_until）が過ぎた会話を自動的にオープン状態に戻す処理を実行します。

**業務上の目的・背景**：カスタマーサポート業務において、エージェントは対応を一時的に保留したい会話を「スヌーズ」機能で一定期間非表示にすることができます。例えば、「後で確認が必要」「顧客からの追加情報待ち」などの場合にスヌーズを設定します。本バッチにより、設定したスヌーズ期限が過ぎた会話が自動的にオープン状態に戻り、エージェントの対応待ち一覧に再表示されます。これにより、対応漏れを防止し、顧客対応の品質を維持します。

**バッチの実行タイミング**：TriggerScheduledItemsJobから5分毎にトリガーされます。

**主要な処理内容**：
1. status=snoozedの会話を検索
2. snoozed_untilが過去3日〜現在時刻の範囲にある会話をフィルタ
3. 該当する会話に対してopen!メソッドを呼び出し
4. 会話のステータスをopenに変更

**前後の処理との関連**：本バッチはTriggerScheduledItemsJobから呼び出される子ジョブとして機能します。同様のスヌーズ解除処理として、Notification::ReopenSnoozedNotificationsJobがあります。

**影響範囲**：スヌーズ状態の会話のstatusカラムが更新されます。open!メソッドにより、関連するイベントやコールバックが発動する可能性があります。

## バッチ種別

ステータス更新 / 会話管理

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 5分毎 |
| 実行時刻 | 毎時0分、5分、10分、15分、... |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | TriggerScheduledItemsJob経由 |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| TriggerScheduledItemsJobが稼働していること | 親ジョブからのトリガーが必要 |
| データベース接続が可能であること | conversationsテーブルへのアクセスに必要 |

### 実行可否判定

本バッチは無条件で実行されます。対象データが存在しない場合も正常終了します。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | パラメータは不要 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| conversations | DB | スヌーズ状態の会話レコード |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| conversations | DB | statusがopenに更新される |

### 出力ファイル仕様

ファイル出力はありません。

## 処理フロー

### 処理シーケンス

```
1. バッチ開始
   └─ lowキューから起動
2. スヌーズ状態の会話を検索
   └─ Conversation.where(status: :snoozed)
3. スヌーズ期限フィルタ
   └─ snoozed_until: 3.days.ago..Time.current
4. バッチ単位で処理
   └─ find_each(batch_size: 100)
5. 各会話をオープン状態に変更
   └─ conversation.open!
6. バッチ終了
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[status=snoozedの会話を検索]
    B --> C[snoozed_until範囲でフィルタ]
    C --> D[find_each batch_size:100]
    D --> E{会話あり?}
    E -->|No| G[バッチ終了]
    E -->|Yes| F[open!メソッド呼び出し]
    F --> D
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| スヌーズ会話検索 | conversations | SELECT | status=snoozed, snoozed_until範囲で検索 |
| ステータス更新 | conversations | UPDATE | statusをopenに変更 |

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

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, status, snoozed_until | status=snoozed, snoozed_until BETWEEN 3.days.ago AND Time.current | find_eachで取得 |
| UPDATE | status | open | open!メソッドによる更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | StandardError | DB操作失敗時 | Sidekiqの標準リトライ機構により再実行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqのデフォルト設定 |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | StandardError全般 |

### 障害時対応

バッチ失敗時は次回の定期実行（5分後）で自動的にリカバリされます。緊急時はRailsコンソールから手動実行可能です：
```ruby
Conversations::ReopenSnoozedConversationsJob.perform_now
```

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | 各会話のopen!操作は個別トランザクション |
| コミットタイミング | 各open!完了時 |
| ロールバック条件 | 個別会話の更新失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 通常は少数〜数十件 |
| 目標処理時間 | 数秒〜1分 |
| メモリ使用量上限 | find_each(batch_size: 100)により一定に制御 |

## 排他制御

- 同時実行制御なし
- 各会話のopen!は個別トランザクションで実行
- 5分間隔の実行であるため、重複実行のリスクは低い

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | バッチ開始時 | Sidekiq標準ログ |
| 進捗ログ | - | 明示的なログ出力なし |
| 終了ログ | バッチ終了時 | Sidekiq標準ログ |
| エラーログ | エラー発生時 | Sidekiq標準エラーログ |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 5分以上 | Sidekiq監視システム |
| エラー件数 | 1件以上 | Sidekiq監視システム |

## 備考

- snoozed_until範囲：3.days.ago..Time.current（過去3日〜現在）
  - 3日前の下限を設けることで、古いスヌーズ会話の再処理を避ける
- open!メソッドはConversationモデルのステータス変更メソッド
- find_eachを使用しているため、大量のスヌーズ会話があってもメモリ効率的に処理
- TriggerScheduledItemsJobで5分毎にスケジュールされている
