# 機能設計書 78-アカウントデータインポート

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるアカウントデータのインポート機能の設計を記述する。

### 本機能の処理概要

他のFizzyインスタンス（自己ホスト版またはSaaS版）からエクスポートされたZIPファイルをインポートし、新しいアカウントとしてデータを復元する機能である。インポート処理では新規アカウントが自動的に作成され、エクスポートデータに含まれるすべてのボード、カード、ユーザー設定などが復元される。

**業務上の目的・背景**：自己ホスト版からSaaS版への移行、またはその逆方向の移行を可能にする。また、別のFizzyアカウントからデータを引き継ぐ場合にも使用される。これにより、プラットフォーム間のデータポータビリティを確保し、ユーザーがサービス形態を自由に選択できるようにしている。

**機能の利用シーン**：
- 自己ホスト版からfizzy.do（SaaS版）へ移行する場合
- fizzy.doから自己ホスト版へ移行する場合
- 災害復旧時にバックアップからデータを復元する場合

**主要な処理内容**：
1. インポート画面でエクスポートZIPファイルをアップロード
2. 新規アカウントを自動作成
3. バックグラウンドジョブでデータをインポート（checkステップとprocessステップ）
4. 完了時またはエラー時にメール通知

**関連システム・外部連携**：
- Active Storage：ZIPファイルの一時保存
- メール送信システム：完了/失敗通知メール

**権限による制御**：認証済みユーザーであれば誰でもインポート可能（新規アカウント作成のため）。インポート状況の確認はインポートを開始したIdentityのみ。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 55 | インポート画面 | 主画面 | ZIPファイルをアップロードしてインポート開始 |
| 56 | インポート状況画面 | 結果表示画面 | インポート処理の状況を表示 |

## 機能種別

データ入力（バッチ処理）

## 入力仕様

### 入力パラメータ

#### create（インポート開始）
| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| file | File | Yes | エクスポートZIPファイル | .zip形式 |

#### show（状況表示）
| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | UUID | Yes | インポートID | 本人のインポートであること |

### 入力データソース

- ファイルアップロード：エクスポートZIPファイル
- セッション：Current.identity（実行者）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @import | Account::Import | インポート状況情報 |
| 完了/失敗通知メール | Email | インポート結果通知 |

### 出力先

- 画面表示：show.html.erb（状況表示）
- メール送信：完了または失敗通知

## 処理フロー

### 処理シーケンス

```
1. ユーザーがインポート画面にアクセス
   └─ new.html.erbを表示

2. ZIPファイルをアップロードしてフォーム送信
   └─ createアクションが実行

3. 新規アカウント作成
   └─ Signup.new(identity:, full_name: "Import", skip_account_seeding: true)
   └─ signup.complete

4. インポートレコード作成
   └─ account.imports.create!(identity:, file:)

5. バックグラウンドジョブをキュー
   └─ import.process_later → Account::DataImportJob.perform_later

6. ジョブ実行（2ステップ）
   └─ step :check - データ整合性チェック
   └─ step :process - 実際のインポート処理

7. 完了/失敗処理
   └─ 成功時: mark_completed, ImportMailer.completed
   └─ 失敗時: mark_as_failed, ImportMailer.failed

8. ユーザーが状況画面で確認
   └─ Turbo Streamsでリアルタイム更新
```

### フローチャート

```mermaid
flowchart TD
    A[開始: インポート画面] --> B[ZIPファイル選択]
    B --> C[フォーム送信]
    C --> D[新規アカウント作成]
    D --> E{アカウント作成成功?}
    E -->|No| F[エラー表示]
    E -->|Yes| G[インポートレコード作成]
    G --> H[DataImportJob<br/>キューイング]
    H --> I[インポート状況画面に<br/>リダイレクト]
    I --> J[バックグラウンド処理]
    J --> K[step: check<br/>データ整合性確認]
    K --> L{チェック成功?}
    L -->|No: conflict| M[status: failed<br/>failed_due_to: conflict]
    L -->|No: invalid| N[status: failed<br/>failed_due_to: invalid_export]
    L -->|Yes| O[step: process<br/>データインポート]
    O --> P{インポート成功?}
    P -->|No| Q[status: failed]
    P -->|Yes| R[status: completed]
    R --> S[all_accessボードへ<br/>アクセス権付与]
    S --> T[ストレージ整合処理]
    T --> U[完了メール送信]
    U --> V[終了]
    M --> W[失敗メール送信]
    N --> W
    Q --> W
    W --> V
    F --> V
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-78-01 | 新規アカウント作成 | インポート時に新しいアカウントが自動的に作成される | createアクション時 |
| BR-78-02 | シード処理スキップ | インポート用アカウントは初期データ（シード）を作成しない | アカウント作成時 |
| BR-78-03 | コンフリクトチェック | 同一データが既に存在する場合はエラー | checkステップ |
| BR-78-04 | 整合性チェック | ZIPファイルがFizzyエクスポートでない場合はエラー | checkステップ |
| BR-78-05 | オーナー限定アクセス | インポート状況は開始したIdentityのみ確認可能 | showアクション時 |
| BR-78-06 | アクセス権付与 | all_accessボードにはインポーターに自動的にアクセス権付与 | 完了時 |
| BR-78-07 | 失敗時クリーンアップ | インポート失敗時は作成されたアカウントも削除される | クリーンアップ時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| アカウント作成 | accounts | INSERT | 新規アカウント作成 |
| インポート作成 | account_imports | INSERT | インポートレコード作成 |
| ステータス更新 | account_imports | UPDATE | status更新 |
| データ復元 | 各種テーブル | INSERT/UPDATE | エクスポートデータの復元 |

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

#### account_imports

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | 新規作成アカウントのID | - |
| INSERT | identity_id | Current.identity.id | - |
| INSERT | status | "pending" | デフォルト値 |
| UPDATE | status | "processing" | check/process開始時 |
| UPDATE | status | "completed" | 完了時 |
| UPDATE | completed_at | Time.current | 完了時 |
| UPDATE | status | "failed" | 失敗時 |
| UPDATE | failure_reason | "conflict"/"invalid_export" | 失敗理由 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | 権限エラー | 他者のインポート状況閲覧試行 | Forbiddenエラー |
| - | conflict | 同一データが既存 | status: failed, failure_reason: conflict |
| - | invalid_export | 不正なZIPファイル | status: failed, failure_reason: invalid_export |
| - | その他エラー | 予期せぬエラー | status: failed |

### リトライ仕様

Account::DataImportJobはActiveJob::Continuableを使用し、チェックポイント付きで処理を再開可能。IntegrityErrorとInvalidFileErrorはdiscard_onで再試行しない。

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

インポート処理はチェックポイント付きのステップ処理。各レコードセットの処理は個別にトランザクション内で実行される。

## パフォーマンス要件

- インポート開始レスポンス：2秒以内（ファイルアップロード除く）
- インポート処理時間：データ量に依存（バックグラウンド実行）
- Turbo Streamsによるリアルタイムステータス更新

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

- ensure_accessed_by_ownerによるIdentity確認
- ZIPFile::InvalidFileErrorによる不正ファイル検出
- Account::DataTransfer::RecordSet::IntegrityErrorによるデータ整合性検証
- インポート失敗時のアカウント自動削除（機密データの残存防止）

## 備考

- レイアウトはlayout "public"を使用（認証画面風のデザイン）
- broadcasts_refreshesによりTurbo Streamsでリアルタイム更新
- エクスポート機能（No.77）と対になる機能
- 期限切れ（completed: 24時間、failed: 7日）のインポートはAccount::Import.cleanupで定期削除

---

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

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

### 推奨読解順序

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

インポートのデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Account::Import | `app/models/account/import.rb` | インポートモデルの全体像 |

**読解のコツ**:
- 1行目: broadcasts_refreshesでTurbo Streams対応
- 9行目: enum :statusでpending/processing/completed/failedを定義
- 10行目: enum :failure_reasonでconflict/invalid_exportを定義
- 18-19行目: process_laterでジョブキューイング
- 41-64行目: processメソッドで実際のインポート処理
- 66-69行目: cleanupで失敗時のアカウント削除

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ImportsController | `app/controllers/account/imports_controller.rb` | new/create/showアクションの実装 |

**主要処理フロー**:
1. **2行目**: layout "public" - 認証画面風レイアウト
2. **4行目**: disallow_account_scope - アカウントスコープ外で実行
3. **5行目**: allow_unauthorized_access only: :show
4. **7行目**: ensure_accessed_by_owner - 本人確認
5. **12-19行目**: createアクション - Signupでアカウント作成→start_import
6. **34-42行目**: start_importでインポート作成とジョブキューイング

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

インポート処理の詳細を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | DataImportJob | `app/jobs/account/data_import_job.rb` | ジョブの実装 |

**主要処理フロー**:
- 2行目: include ActiveJob::Continuable - チェックポイント対応
- 5行目: discard_onでIntegrityError/InvalidFileErrorを処理
- 7-18行目: step :checkとstep :processの2段階処理
- callbackでカーソル位置を保存し、中断/再開に対応

#### Step 4: ビューを理解する

画面表示の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | new.html.erb | `app/views/account/imports/new.html.erb` | インポート開始画面 |
| 4-2 | show.html.erb | `app/views/account/imports/show.html.erb` | 状況表示画面 |

**読解のコツ**:
- new.html.erb: form_withでmultipart: trueを指定
- show.html.erb:
  - 3行目: turbo_stream_from @importでリアルタイム更新
  - 8-30行目: @import.statusで表示を切り替え

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

```
Account::ImportsController#create
    │
    ├─ Signup.new(identity:, full_name: "Import", skip_account_seeding: true)
    │      └─ signup.complete
    │             └─ Account.create_with_owner
    │
    └─ start_import(signup.account)
           │
           ├─ Current.set(account: account)
           │
           ├─ account.imports.create!(identity:, file:)
           │
           └─ import.process_later
                  │
                  └─ Account::DataImportJob.perform_later
                         │
                         ├─ step :check
                         │      └─ import.check(start:, callback:)
                         │             └─ ZipFile.read_from
                         │                    └─ each_record_set.check
                         │
                         └─ step :process
                                └─ import.process(start:, callback:)
                                       │
                                       ├─ ZipFile.read_from
                                       │      └─ each_record_set.import
                                       │
                                       ├─ add_importer_to_all_access_boards
                                       │
                                       ├─ reconcile_cards_count
                                       │
                                       ├─ reconcile_account_storage
                                       │
                                       └─ mark_completed / mark_as_failed
                                              └─ ImportMailer
```

### データフロー図

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

ZIPファイル ───────▶ Account::ImportsController#create ──┬─▶ リダイレクト
(ファイルアップロード)     │                               │   (show画面)
                           │                               │
                           ├──▶ Signup（アカウント作成）   └─▶ account INSERT
                           │
                           └──▶ imports INSERT

バックグラウンド ───▶ Account::DataImportJob ────────────▶ 各種テーブル
                           │                                 INSERT/UPDATE
                           ├──▶ step: check
                           │
                           └──▶ step: process
                                  │
                                  └─▶ ImportMailer ─────────▶ 完了/失敗メール

Turbo Streams ─────▶ show.html.erb ─────────────────────▶ リアルタイム更新
(broadcasts_refreshes)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ImportsController | `app/controllers/account/imports_controller.rb` | コントローラー | インポート操作のエントリーポイント |
| Account::Import | `app/models/account/import.rb` | モデル | インポートデータ管理・処理ロジック |
| Account::DataImportJob | `app/jobs/account/data_import_job.rb` | ジョブ | バックグラウンドインポート処理 |
| new.html.erb | `app/views/account/imports/new.html.erb` | ビュー | インポート開始画面 |
| show.html.erb | `app/views/account/imports/show.html.erb` | ビュー | 状況表示画面 |
| ImportMailer | `app/mailers/import_mailer.rb` | メーラー | 完了/失敗通知メール |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（10行目） |
