# 機能設計書 21-定期取引スケジュール

## 概要

本ドキュメントは、Actual Budget における定期取引スケジュール機能の設計仕様を記述する。この機能は、繰り返し発生する取引をスケジュールとして登録し、自動的に管理することを目的とする。

### 本機能の処理概要

定期取引スケジュール機能は、毎月の家賃支払い、定期的なサブスクリプション料金、給与の入金など、繰り返し発生する取引を事前にスケジュールとして登録し、支払い漏れの防止と予算管理の効率化を実現する機能である。

**業務上の目的・背景**：家計管理において、毎月の固定費や定期的な支出・収入は予測可能であり、これらを事前に登録しておくことで、将来の資金繰りを把握しやすくなる。また、支払い期日の管理が容易になり、支払い忘れによる延滞を防止できる。

**機能の利用シーン**：
- 毎月の家賃・住宅ローンの支払い登録
- 光熱費・通信費などの固定費の管理
- 給与や定期収入の登録
- サブスクリプションサービス（Netflix、Spotify等）の支払い管理
- 保険料、税金などの定期支払いの管理

**主要な処理内容**：
1. スケジュールの作成：繰り返しパターン（毎日、毎週、毎月、毎年）と金額、支払先、口座を指定してスケジュールを登録
2. スケジュールの更新：既存スケジュールの条件変更、次回日付のリセット
3. スケジュールの削除：不要になったスケジュールの削除
4. 次回日付の自動計算：繰り返しルールに基づいて次回実行日を自動算出
5. スケジュールの検出：過去の取引パターンから自動的にスケジュール候補を検出
6. ステータス管理：予定、期日、支払済み、未払い等のステータス管理

**関連システム・外部連携**：
- ルールエンジン：スケジュールは内部的にルールとして管理され、取引とのマッチングに使用される
- 同期機能：スケジュール情報は他デバイスと同期される

**権限による制御**：特別な権限制御はなく、予算ファイルにアクセスできるユーザーはスケジュールの作成・編集・削除が可能である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | スケジュール一覧画面（デスクトップ） | 主画面 | 定期取引スケジュールの一覧表示・管理 |
| 17 | スケジュール一覧画面（モバイル） | 主画面 | モバイルでのスケジュール一覧表示 |
| 18 | スケジュール編集画面（モバイル） | 主画面 | モバイルでのスケジュール編集 |

## 機能種別

CRUD操作 / 計算処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | No | スケジュールID（更新時は必須） | UUID形式 |
| name | string | No | スケジュール名 | 重複不可 |
| conditions | Condition[] | Yes | 日付、金額、支払先、口座などの条件 | date条件は必須 |
| posts_transaction | boolean | No | 取引を自動生成するかどうか | - |
| completed | boolean | No | 完了フラグ | - |
| resetNextDate | boolean | No | 次回日付をリセットするか | - |

### 条件（Condition）の構造

| フィールド | 型 | 説明 |
|-----------|-----|------|
| op | string | 演算子（is, isapprox, isbetween） |
| field | string | フィールド名（date, amount, payee, account） |
| value | any | 値（日付条件の場合はRecurConfig） |

### RecurConfig（繰り返し設定）の構造

| フィールド | 型 | 説明 |
|-----------|-----|------|
| start | string | 開始日（YYYY-MM-DD） |
| frequency | string | 頻度（daily, weekly, monthly, yearly） |
| interval | number | 間隔（例：2なら隔週） |
| patterns | Pattern[] | 詳細パターン（月の第1月曜日など） |
| skipWeekend | boolean | 週末をスキップするか |
| weekendSolveMode | string | 週末の場合の移動方向（before/after） |
| endMode | string | 終了モード（never, after_n_occurrences, on_date） |
| endOccurrences | number | 終了回数 |
| endDate | string | 終了日 |

### 入力データソース

- 画面入力（スケジュール一覧画面からの直接入力）
- API呼び出し（外部システムからの登録）
- 自動検出（過去の取引パターンからの推奨）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | スケジュールID |
| name | string | スケジュール名 |
| rule | string | 関連ルールID |
| next_date | string | 次回実行日 |
| completed | boolean | 完了フラグ |
| posts_transaction | boolean | 自動取引生成フラグ |
| _payee | string | 支払先ID（計算フィールド） |
| _account | string | 口座ID（計算フィールド） |
| _amount | number | 金額（計算フィールド） |
| _date | RecurConfig | 日付条件（計算フィールド） |

### 出力先

- データベース（schedulesテーブル、schedules_next_dateテーブル、rulesテーブル）
- クライアント画面（スケジュール一覧、予算画面）

## 処理フロー

### 処理シーケンス

```
1. スケジュール作成
   └─ 日付条件の検証
   └─ 重複名称のチェック
   └─ 次回日付の計算
   └─ ルールの作成（link-scheduleアクション付き）
   └─ schedules_next_dateレコードの作成
   └─ schedulesレコードの作成

2. スケジュール更新
   └─ ルールの取得
   └─ 条件の更新
   └─ 次回日付の再計算（必要に応じて）
   └─ schedulesレコードの更新

3. スケジュール削除
   └─ 関連ルールの削除
   └─ schedulesレコードの削除

4. 次回日付の自動進行
   └─ 支払済みスケジュールの検出
   └─ 繰り返しスケジュールの次回日付更新
   └─ 単発スケジュールの完了処理
```

### フローチャート

```mermaid
flowchart TD
    A[スケジュール作成リクエスト] --> B{日付条件あり?}
    B -->|No| C[エラー: 日付条件必須]
    B -->|Yes| D{名前重複?}
    D -->|Yes| E[エラー: 名前重複]
    D -->|No| F[次回日付を計算]
    F --> G[ルールを作成]
    G --> H[schedules_next_dateを作成]
    H --> I[schedulesを作成]
    I --> J[完了: スケジュールID返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-21-1 | 日付条件必須 | スケジュール作成には日付条件が必須 | 作成時 |
| BR-21-2 | 名前の一意性 | 同一名のスケジュールは作成不可 | 作成・更新時 |
| BR-21-3 | ルール不変 | スケジュールに紐づくルールIDは変更不可 | 更新時 |
| BR-21-4 | 週末スキップ | skipWeekendがtrueの場合、週末を前後にずらす | 次回日付計算時 |
| BR-21-5 | 終了条件 | endModeに応じて繰り返しを終了 | 次回日付計算時 |

### 計算ロジック

**次回日付の計算**:
- RScheduleライブラリを使用して、繰り返しルールに基づく次回日付を算出
- 現在日以降で最も近い日付を取得
- skipWeekendがtrueの場合、getDateWithSkippedWeekendで調整

**ステータスの判定**:
- completed: 完了済み
- paid: 該当日付の取引が存在
- due: 次回日付が今日
- upcoming: 次回日付が今日から設定日数以内
- missed: 次回日付が過去
- scheduled: 上記以外

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| スケジュール作成 | rules | INSERT | リンクルールの作成 |
| スケジュール作成 | schedules_next_date | INSERT | 次回日付情報の作成 |
| スケジュール作成 | schedules | INSERT | スケジュール本体の作成 |
| スケジュール更新 | rules | UPDATE | 条件の更新 |
| スケジュール更新 | schedules_next_date | UPDATE | 次回日付の更新 |
| スケジュール更新 | schedules | UPDATE | スケジュール情報の更新 |
| スケジュール削除 | rules | DELETE | リンクルールの削除 |
| スケジュール削除 | schedules | DELETE | スケジュールの削除 |

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

#### schedules

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUID | 新規作成時に生成 |
| INSERT | name | ユーザー指定 | NULL可 |
| INSERT | rule | 作成したルールID | 必須 |
| INSERT | completed | false | デフォルト値 |
| INSERT | posts_transaction | ユーザー指定 | デフォルトfalse |
| UPDATE | completed | true/false | 完了状態の更新 |
| DELETE | tombstone | 1 | 論理削除 |

#### schedules_next_date

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | schedule_id | スケジュールID | 外部キー |
| INSERT | local_next_date | 計算した次回日付 | DateRepr形式 |
| INSERT | base_next_date | 計算した次回日付 | DateRepr形式 |
| UPDATE | local_next_date | 新しい次回日付 | 進行時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| DATE_REQUIRED | バリデーションエラー | 日付条件が未指定 | 日付条件を指定する |
| DUPLICATE_NAME | バリデーションエラー | 同名スケジュールが存在 | 別の名前を使用する |
| RULE_CHANGE_NOT_ALLOWED | 操作エラー | ルールIDの変更を試行 | ルールIDは変更不可 |
| RULE_NOT_FOUND | 内部エラー | 関連ルールが見つからない | 自動修復（新ルール生成） |

### リトライ仕様

特になし（データベースのトランザクション内で処理）

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

- スケジュール作成・更新・削除はbatchMessages関数でラップされ、複数のデータベース操作が同期メッセージとして一括処理される
- 同期メッセージはCRDTベースで管理され、他デバイスとの同期時に適用される

## パフォーマンス要件

- スケジュール一覧の取得: 100件以下で1秒以内
- 次回日付の計算: 1スケジュールあたり100ms以内

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

- スケジュール情報は予算ファイル内に保存され、E2E暗号化が有効な場合は暗号化される
- APIアクセスには認証が必要

## 備考

- スケジュールは内部的にルールと連携しており、ルールの`link-schedule`アクションによって取引との紐付けが行われる
- `schedules_json_paths`テーブルにより、ルール条件のJSONパスがキャッシュされ、クエリの効率化が図られている

---

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

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

### 推奨読解順序

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

まず、スケジュールに関連するデータ構造と型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schedules.ts | `packages/loot-core/src/shared/schedules.ts` | スケジュールの状態判定、繰り返し設定の変換ロジック |
| 1-2 | models (types) | `packages/loot-core/types/models/` | ScheduleEntity, RecurConfigの型定義 |

**読解のコツ**:
- `getStatus`関数（18-42行目）でスケジュールの状態（due, paid, missed等）がどう判定されるか確認
- `recurConfigToRSchedule`関数（261-315行目）で繰り返し設定がRScheduleライブラリ形式に変換される

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

APIからの呼び出し経路を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | methods.ts | `packages/api/methods.ts` | 外部API（createSchedule, updateSchedule, deleteSchedule, getSchedules） |

**主要処理フロー**:
1. **312-314行目**: `createSchedule` - スケジュール作成APIの定義
2. **316-326行目**: `updateSchedule` - スケジュール更新APIの定義
3. **328-330行目**: `deleteSchedule` - スケジュール削除APIの定義
4. **332-334行目**: `getSchedules` - スケジュール一覧取得APIの定義

#### Step 3: コアロジックを理解する

スケジュール管理の中核となる処理を読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | app.ts | `packages/loot-core/src/server/schedules/app.ts` | スケジュールCRUD操作の実装本体 |

**主要処理フロー**:
- **186-236行目**: `createSchedule` - スケジュール作成処理
  - 日付条件の検証、次回日付計算、ルール作成、DBレコード作成
- **240-314行目**: `updateSchedule` - スケジュール更新処理
  - ルール取得、条件更新、次回日付リセット
- **316-325行目**: `deleteSchedule` - スケジュール削除処理
- **109-167行目**: `setNextDate` - 次回日付の設定・更新ロジック
- **445-525行目**: `advanceSchedulesService` - スケジュールの自動進行サービス

#### Step 4: スケジュール検出ロジックを理解する

過去の取引パターンからスケジュールを自動検出する機能。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | find-schedules.ts | `packages/loot-core/src/server/schedules/find-schedules.ts` | パターン検出アルゴリズム |

**主要処理フロー**:
- **319-391行目**: `findSchedules` - メイン検出関数
  - 各口座の取引を分析し、週次・隔週・月次などのパターンを検出
- **142-149行目**: `weekly` - 週次パターンの検出
- **151-160行目**: `every2weeks` - 隔週パターンの検出
- **162-179行目**: `monthly` - 月次パターンの検出

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

```
API Layer (methods.ts)
    │
    ├─ createSchedule ─────────────────┐
    │                                  │
    ├─ updateSchedule ─────────────────┼─▶ schedules/app.ts
    │                                  │       │
    ├─ deleteSchedule ─────────────────┤       ├─ createSchedule()
    │                                  │       │      ├─ insertRule()
    └─ getSchedules ───────────────────┘       │      ├─ db.insertWithUUID()
                                               │      └─ db.insertWithSchema()
                                               │
                                               ├─ updateSchedule()
                                               │      ├─ getRuleForSchedule()
                                               │      ├─ updateRule()
                                               │      └─ setNextDate()
                                               │
                                               ├─ deleteSchedule()
                                               │      └─ db.delete_()
                                               │
                                               └─ advanceSchedulesService()
                                                      ├─ getStatus()
                                                      ├─ setNextDate()
                                                      └─ postTransactionForSchedule()
```

### データフロー図

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

ユーザー入力         ┌─────────────────┐
  ├─ 日付条件  ────▶│                 │
  ├─ 金額      ────▶│  createSchedule │────▶ schedules テーブル
  ├─ 支払先    ────▶│                 │────▶ schedules_next_date テーブル
  └─ 口座      ────▶│                 │────▶ rules テーブル
                    └─────────────────┘

同期イベント         ┌─────────────────┐
  └─ sync完了  ────▶│ advanceService  │────▶ 次回日付更新
                    │                 │────▶ 取引自動生成（設定時）
                    └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app.ts | `packages/loot-core/src/server/schedules/app.ts` | ソース | スケジュールCRUD操作の実装 |
| find-schedules.ts | `packages/loot-core/src/server/schedules/find-schedules.ts` | ソース | スケジュール自動検出 |
| schedules.ts | `packages/loot-core/src/shared/schedules.ts` | ソース | 共通ユーティリティ関数 |
| methods.ts | `packages/api/methods.ts` | ソース | 外部API定義 |
| transaction-rules.ts | `packages/loot-core/src/server/transactions/transaction-rules.ts` | ソース | ルール管理（スケジュールリンク） |
| db.ts | `packages/loot-core/src/server/db/` | ソース | データベース操作 |
| rschedule.ts | `packages/loot-core/src/server/util/rschedule.ts` | ソース | 繰り返し日付計算ライブラリ |
