# 機能設計書 40-バッチ予算更新

## 概要

本ドキュメントは、複数の予算更新操作をバッチ処理として一括実行する機能について記述する。

### 本機能の処理概要

**業務上の目的・背景**：予算データの更新操作は個別に実行すると、それぞれが独立したトランザクションとして処理され、同期メッセージも個別に送信される。これは大量の予算設定を行う場合にパフォーマンスの問題を引き起こす。バッチ予算更新機能は、複数の予算操作を1つのトランザクション内でまとめて実行し、パフォーマンスを大幅に向上させる。

**機能の利用シーン**：
- インポート時の一括予算設定
- 年度の予算一括コピー
- テンプレート予算の適用
- API経由での大量予算更新
- 複数カテゴリへの一括予算配分

**主要な処理内容**：
1. バッチ開始（api/batch-budget-start）
2. Promise生成によるトランザクション制御
3. ユーザー関数の実行（複数の予算操作）
4. バッチ終了（api/batch-budget-end）
5. メッセージの一括送信

**関連システム・外部連携**：CRDTメッセージングシステム、SQLiteデータベース

**権限による制御**：予算ファイルへの書き込み権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | バッチ更新はAPI機能のため画面を持たない |

## 機能種別

データ更新 / バッチ処理 / パフォーマンス最適化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| func | () => Promise<void> | Yes | バッチ内で実行する非同期関数 | - |

### 入力データソース

API呼び出し元からのコールバック関数

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | void | 戻り値なし |

### 出力先

なし（副作用としてデータベース更新）

## 処理フロー

### 処理シーケンス

```
1. batchBudgetUpdates呼び出し
   └─ コールバック関数を受け取る
2. api/batch-budget-start送信
   └─ バッチモード開始
3. batchPromise生成
   ├─ IMPORT_MODEの場合：db.asyncTransaction開始
   └─ 通常モードの場合：batchMessages開始
4. ユーザー関数実行
   └─ 複数の予算更新操作
5. api/batch-budget-end送信
   └─ batchPromise.resolve()呼び出し
6. トランザクション完了
   └─ メッセージ一括送信
```

### フローチャート

```mermaid
flowchart TD
    A[batchBudgetUpdates開始] --> B[batch-budget-start]
    B --> C{batchPromise存在?}
    C -->|Yes| D[APIError: 二重起動]
    C -->|No| E{IMPORT_MODE?}
    E -->|Yes| F[db.asyncTransaction]
    E -->|No| G[batchMessages]
    F --> H[Promise生成]
    G --> H
    H --> I[ユーザー関数実行]
    I --> J[batch-budget-end]
    J --> K{batchPromise存在?}
    K -->|No| L[APIError: 未開始]
    K -->|Yes| M[resolve呼び出し]
    M --> N[トランザクション完了]
    N --> O[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 二重起動禁止 | バッチ処理中に新しいバッチを開始できない | 常時 |
| BR-02 | 必須ペアリング | startとendは必ずペアで呼び出す必要がある | 常時 |
| BR-03 | finally保証 | endはtry-finallyで確実に呼び出される | API使用時 |
| BR-04 | モード別処理 | IMPORT_MODEとnormalでトランザクション方式が異なる | 常時 |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| バッチ内操作 | zero_budgets / reflect_budgets | INSERT/UPDATE | 予算設定 |
| バッチ内操作 | messages_crdt | INSERT | 同期メッセージ（一括） |

### トランザクション方式

- **IMPORT_MODE時**: `db.asyncTransaction`でデータベーストランザクションを使用
- **通常時**: `batchMessages`でCRDTメッセージをバッチ処理

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | APIError | バッチ処理中に新しいバッチを開始 | 既存バッチ終了後に再試行 |
| - | APIError | バッチ未開始でendを呼び出し | startを先に呼び出す |

### リトライ仕様

特になし。finallyブロックでendが確実に呼ばれるため、異常終了時も適切にクリーンアップされる。

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

バッチ内の全操作は1つのトランザクションとして処理される。
- 成功時：全変更がコミット
- 失敗時：全変更がロールバック（IMPORT_MODE時）

## パフォーマンス要件

- 個別実行と比較して大幅なパフォーマンス向上
- 同期メッセージの一括送信によるオーバーヘッド削減
- 大量の予算設定（年間×カテゴリ数）を効率的に処理

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

- API経由の呼び出しには適切な認証が必要
- バッチ処理中は他のクライアントからの同期が待機状態

## 備考

- インポート処理（api/start-import後）では特別なトランザクションモードで動作
- 通常時はCRDTメッセージバッチングを使用して同期効率を向上

---

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

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

### 推奨読解順序

#### Step 1: API関数を理解する

バッチ予算更新のエントリーポイントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | methods.ts | `packages/api/methods.ts` | batchBudgetUpdates関数 |

**主要処理フロー**:
- **67-74行目**: `batchBudgetUpdates` - メイン関数
  - **68行目**: `send('api/batch-budget-start')` - バッチ開始
  - **69-71行目**: try-finally構造でユーザー関数を実行
  - **72行目**: `send('api/batch-budget-end')` - バッチ終了（finally内）

**読解のコツ**:
- try-finallyパターンにより、エラー発生時もendが確実に呼ばれる
- 引数として渡された非同期関数は一度だけ実行される

#### Step 2: ハンドラー実装を理解する

サーバー側のバッチ処理ハンドラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | api.ts | `packages/loot-core/src/server/api.ts` | batch-budget-start/end ハンドラー |

**主要処理フロー**:
- **126行目**: `batchPromise` - グローバル状態変数
- **128-149行目**: `api/batch-budget-start` ハンドラー
  - **129-131行目**: 二重起動チェック
  - **136-141行目**: IMPORT_MODE時はdb.asyncTransaction使用
  - **142-148行目**: 通常時はbatchMessages使用
  - **138-140行目**: Promise生成とbatchPromiseへの格納
- **151-158行目**: `api/batch-budget-end` ハンドラー
  - **152-154行目**: 未開始チェック
  - **156行目**: batchPromise.resolve()呼び出し
  - **157行目**: batchPromise = null でリセット

**読解のコツ**:
- batchPromiseは「ゲート」として機能し、resolve()されるまでトランザクションを開いたままにする
- IMPORT_MODEはインポート処理専用の軽量モード

#### Step 3: 型定義を理解する

ハンドラーの型定義を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | api-handlers.ts | `packages/loot-core/src/types/api-handlers.ts` | 型定義 |

**主要処理フロー**:
- **26行目**: `'api/batch-budget-start': () => Promise<void>` - 開始ハンドラー型
- **28行目**: `'api/batch-budget-end': () => Promise<void>` - 終了ハンドラー型

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

```
外部システム / クライアント
    │
    └─ batchBudgetUpdates (methods.ts:67)
           │
           ├─ send('api/batch-budget-start')
           │      │
           │      └─ handlers['api/batch-budget-start'] (api.ts:128)
           │             │
           │             ├─ 二重起動チェック
           │             │
           │             └─ IMPORT_MODE?
           │                    │
           │                    ├─ Yes: db.asyncTransaction()
           │                    │         └─ new Promise() → batchPromise
           │                    │
           │                    └─ No: batchMessages()
           │                            └─ new Promise() → batchPromise
           │
           ├─ func() ← ユーザーのコールバック実行
           │      │
           │      ├─ setBudgetAmount()
           │      ├─ setBudgetCarryover()
           │      └─ ... その他の予算操作
           │
           └─ send('api/batch-budget-end') (finally)
                  │
                  └─ handlers['api/batch-budget-end'] (api.ts:151)
                         │
                         ├─ 未開始チェック
                         ├─ batchPromise.resolve()
                         └─ batchPromise = null
```

### データフロー図

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

func ───────────▶ batchBudgetUpdates
                        │
                        ├─▶ batch-budget-start
                        │        │
                        │        ▼
                        │   batchPromise生成
                        │        │
                        │        ▼
                        │   トランザクション開始
                        │
                        ├─▶ func() 実行
                        │        │
                        │        ├─▶ 予算操作1
                        │        ├─▶ 予算操作2
                        │        └─▶ 予算操作N
                        │              │
                        │              ▼
                        │        操作はバッファリング
                        │
                        └─▶ batch-budget-end
                                 │
                                 ▼
                         batchPromise.resolve()
                                 │
                                 ▼
                         トランザクション完了 ───▶ DB更新
                                 │
                                 ▼
                         メッセージ一括送信 ───▶ 同期
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| methods.ts | `packages/api/methods.ts` | ソース | API関数定義 |
| api.ts | `packages/loot-core/src/server/api.ts` | ソース | ハンドラー実装 |
| api-handlers.ts | `packages/loot-core/src/types/api-handlers.ts` | ソース | 型定義 |
| sync.ts | `packages/loot-core/src/server/sync.ts` | ソース | batchMessages関数 |
| db/index.ts | `packages/loot-core/src/server/db/index.ts` | ソース | asyncTransaction関数 |
