# バッチ設計書 1-ffcrm:dropbox:run

## 概要

本ドキュメントは、Fat Free CRMにおけるメールDropboxクローラー実行バッチ（ffcrm:dropbox:run）の設計について記載する。

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

このバッチは、IMAPメールサーバーに接続し、指定されたフォルダ内の未読メールを取得して、CRMシステム内の適切なエンティティ（Account、Contact、Lead）に自動的に関連付ける処理を行う。

**業務上の目的・背景**：顧客とのメールコミュニケーションを手動でCRMに登録する手間を省き、メールを自動的に適切な顧客レコードに紐付けることで、営業活動の効率化と顧客情報の一元管理を実現する。メールの見落としや登録漏れを防ぎ、顧客対応履歴を完全に保持することが可能となる。

**バッチの実行タイミング**：定期実行（cron等で設定）または手動実行。メール受信の頻度に応じて、数分〜数時間間隔での定期実行が推奨される。

**主要な処理内容**：
1. IMAPサーバーへの接続とログイン
2. 指定フォルダ内の未読メール（NOT SEEN）の検索
3. 各メールの送信者がシステム内の有効なユーザーかを検証
4. メール本文の解析による関連付け先エンティティの特定
5. Emailレコードの作成とエンティティへの関連付け
6. 処理済みメールのアーカイブまたは破棄

**前後の処理との関連**：本バッチ実行前に、ffcrm:dropbox:setupによるIMAPフォルダのセットアップが完了している必要がある。また、Settingテーブルにemail_dropbox設定が登録されていることが前提となる。

**影響範囲**：emails、accounts、contacts、leadsテーブルへのデータ追加・更新が行われる。IMAPサーバー上のメールフラグ（既読/削除）も変更される。

## バッチ種別

データ連携 / メール処理

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 随時（cron設定による定期実行推奨） |
| 実行時刻 | 任意 |
| 実行曜日 | 該当なし |
| 実行日 | 該当なし |
| トリガー | cron/手動 |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| IMAP設定完了 | Setting.email_dropboxに有効な接続情報が設定されていること |
| フォルダ存在 | scan_folder、move_to_folder、move_invalid_to_folderが存在すること |
| Rails環境 | Railsアプリケーション環境がロードされていること |

### 実行可否判定

IMAPサーバーへの接続が成功した場合のみ処理を継続。接続失敗時は処理を中断しnilを返却する。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | Rakeタスクとして引数なしで実行 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| IMAPメールサーバー | IMAP | 指定フォルダ内の未読メールを取得 |
| settings (Setting.email_dropbox) | DB | IMAP接続情報、フォルダ設定を取得 |
| users | DB | 送信者の認証に使用 |
| accounts | DB | メール関連付け先の検索 |
| contacts | DB | メール関連付け先の検索 |
| leads | DB | メール関連付け先の検索 |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| emails | DB | 処理したメール情報をEmailモデルとして保存 |
| accounts | DB | 新規Account作成時、updated_at更新 |
| contacts | DB | 新規Contact作成時、updated_at更新 |
| leads | DB | 新規Lead作成時、status更新、updated_at更新 |
| IMAPサーバー | IMAP | メールのフラグ変更（Seen、Deleted） |

### 出力ファイル仕様

ファイル出力なし（データベースおよびIMAPサーバーへの出力のみ）

## 処理フロー

### 処理シーケンス

```
1. Dropboxインスタンス生成
   └─ Setting.email_dropboxから設定を読み込み
2. IMAPサーバー接続
   └─ @settings[:server]、[:port]、[:ssl]で接続
   └─ @settings[:user]、[:password]でログイン
3. scan_folderを選択
   └─ @imap.select(@settings[:scan_folder])
4. 未読メール検索
   └─ @imap.uid_search(['NOT', 'SEEN'])
5. 各メールに対して処理
   └─ メール取得（RFC822形式）
   └─ 有効性チェック（text/html以外）
   └─ 送信者チェック（Userテーブルに存在確認）
   └─ processメソッドによるエンティティ特定と関連付け
   └─ アーカイブ処理
6. 接続終了
   └─ logout、disconnect
```

### フローチャート

```mermaid
flowchart TD
    A[バッチ開始] --> B[Dropboxインスタンス生成]
    B --> C[IMAPサーバー接続]
    C --> D{接続成功?}
    D -->|失敗| E[処理中断]
    D -->|成功| F[未読メール検索]
    F --> G{メールあり?}
    G -->|なし| H[処理終了]
    G -->|あり| I[メール取得]
    I --> J{有効なメール?}
    J -->|無効| K[破棄処理]
    J -->|有効| L{送信者確認}
    L -->|不明| K
    L -->|確認済| M[エンティティ検索・作成]
    M --> N[Email作成・関連付け]
    N --> O[アーカイブ処理]
    O --> P{次のメール?}
    K --> P
    P -->|あり| I
    P -->|なし| H
    H --> Q[ログ出力]
    Q --> R[切断]
    R --> S[バッチ終了]
    E --> S
```

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

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

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 送信者検索 | users | SELECT | emailまたはalt_emailで検索 |
| エンティティ検索 | accounts, contacts, leads | SELECT | emailまたはalt_emailで検索 |
| メール保存 | emails | INSERT | 処理したメール情報を保存 |
| エンティティ作成 | accounts, contacts, leads | INSERT | 該当エンティティが存在しない場合に作成 |
| Lead状態更新 | leads | UPDATE | status='new'の場合'contacted'に更新 |
| タッチ更新 | accounts, contacts, leads | UPDATE | updated_atを更新 |

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

#### emails

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | imap_message_id | email.message_id | IMAPメッセージID |
| INSERT | user_id | @sender.id | 送信者のユーザーID |
| INSERT | mediator_type, mediator_id | エンティティの型とID | ポリモーフィック関連 |
| INSERT | sent_from | email.from.first | 送信元アドレス |
| INSERT | sent_to | email.to.join(", ") | 送信先アドレス |
| INSERT | cc | email.cc.join(", ") | CCアドレス |
| INSERT | subject | email.subject | 件名 |
| INSERT | body | plain_text_body(email) | 本文 |
| INSERT | received_at | email.date | 受信日時 |
| INSERT | sent_at | email.date | 送信日時 |

#### contacts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | email, alt_email | recipient.downcase | メールアドレスで検索 |
| INSERT | user_id | @sender.id | 新規作成時 |
| INSERT | first_name | recipient_local.capitalize | ローカル部から生成 |
| INSERT | last_name | "(unknown)" | デフォルト値 |
| INSERT | email | recipient | メールアドレス |
| INSERT | access | default_access | アクセス権限 |
| INSERT | account_id | 関連Account ID | ドメインから検索または作成 |

#### leads

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | status | 'contacted' | status='new'の場合のみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | IMAP接続エラー | サーバー接続失敗 | 警告ログを出力し処理中断 |
| - | メール処理エラー | 個別メール処理失敗 | 該当メールを破棄し次を処理 |
| - | 認証エラー | IMAP認証失敗 | 警告ログを出力し処理中断 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（バッチレベルでのリトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

### 障害時対応

1. IMAPサーバー接続エラーの場合、ログを確認し接続設定を見直す
2. メール処理エラーの場合、move_invalid_to_folderに移動されたメールを確認
3. 必要に応じてffcrm:dropbox:run:dryでドライラン実行し問題を特定

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | メール単位（個別メールごとに独立） |
| コミットタイミング | Email.create、エンティティ作成時に自動コミット |
| ロールバック条件 | 個別メール処理失敗時（他メールには影響なし） |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 数件〜数百件/実行 |
| 目標処理時間 | メール件数依存（1件あたり数秒程度） |
| メモリ使用量上限 | 特に制限なし |

## 排他制御

同一バッチの同時実行は推奨されない。複数プロセスで同時実行した場合、同じメールを複数回処理する可能性がある。cronでの定期実行時は、前回実行完了を待ってから次回実行するよう設定すること。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | IMAP接続時 | "connecting & logging in to {server}..." |
| 進捗ログ | 各メール処理時 | "fetched new message...", From/Subject/message_id |
| 終了ログ | バッチ終了時 | "messages processed={total} archived={archived} discarded={discarded}" |
| エラーログ | エラー発生時 | エラー詳細とバックトレース |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 処理時間 | 設定なし | - |
| エラー件数 | 設定なし | - |

## 備考

- メール本文の1行目にキーワード（account/campaign/contact/lead/opportunity）と名前を記載することで、明示的に関連付け先を指定可能
- 転送メールの場合、本文内のメールアドレスからエンティティを検索
- Setting.default_accessが"Shared"の場合、新規エンティティは"Private"として作成される
