# 画面設計書 22-ルール管理画面（デスクトップ）

## 概要

本ドキュメントは、Actual Budgetアプリケーションにおけるデスクトップ向けルール管理画面の設計仕様を定義するものである。

### 本画面の処理概要

取引の自動分類ルールを一覧表示し、作成・編集・削除を行うための管理画面である。ユーザーは条件とアクションを組み合わせたルールを定義することで、インポートされた取引に対して自動的にカテゴリ割り当てや支払先設定などの処理を適用できる。

**業務上の目的・背景**：家計管理において、銀行明細からインポートした取引を手動で分類するのは煩雑な作業である。本画面は、ルールベースの自動分類機能を提供することで、繰り返し発生する取引（給与、光熱費、定期購読等）の分類作業を自動化し、ユーザーの作業負担を大幅に軽減する。ルールは条件（支払先名、金額、日付など）とアクション（カテゴリ設定、ノート追加など）の組み合わせで定義され、柔軟な自動化を実現する。

**画面へのアクセス方法**：サイドバーの「ルール」メニュー、またはコマンドバーから `/rules` ルートに遷移することでアクセスする。デスクトップ版では `ManageRulesPage` コンポーネントが表示され、モバイル版では `MobileRulesPage` が表示される。

**主要な操作・処理内容**：
1. ルール一覧の表示（無限スクロール対応）
2. ルールのフィルタリング・検索
3. 新規ルールの作成（モーダルダイアログ）
4. 既存ルールの編集（モーダルダイアログ）
5. ルールの一括削除（複数選択対応）
6. ルールはステージ（Pre/Default/Post）順、条件の特定性順で自動ソート

**画面遷移**：
- 遷移元：サイドバー、コマンドバー、支払先管理画面（関連ルール表示時）
- 遷移先：ルール編集モーダル（新規作成・編集時）

**権限による表示制御**：特別な権限制御は設けられていない。予算ファイルにアクセスできるユーザーであればルール管理が可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | 取引ルール | 主機能 | 取引自動分類ルールの一覧表示・管理 |

## 画面種別

一覧

## URL/ルーティング

`/rules`

- デスクトップ・モバイル両対応（レスポンシブ）
- デスクトップ版では `ManageRulesPage` → `ManageRules` コンポーネント
- モバイル版では `MobileRulesPage` コンポーネント

## 入出力項目

| 項目名 | 項目ID | データ型 | 入力/出力 | 必須 | 説明 |
|--------|--------|----------|-----------|------|------|
| 検索フィルタ | filter | string | 入力 | 任意 | ルール検索用のテキスト入力 |
| 選択ルール | selectedItems | Set<string> | 入力 | 任意 | 削除対象として選択されたルールのID集合 |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| ヘッダータイトル | string | "Rules" と表示 |
| ガイダンステキスト | string | "Rules are always run in the order that you see them." |
| ドキュメントリンク | link | ルールに関するドキュメントへのリンク |
| 検索フィールド | input | ルール検索用入力フィールド |
| ルールヘッダー | component | ルール一覧のカラムヘッダー |
| ルール一覧 | list | 条件・アクション情報を含むルール行 |
| 削除ボタン | button | 選択したルールの一括削除 |
| 新規作成ボタン | button | 新しいルールを作成 |

## イベント仕様

### 1-画面初期表示

画面表示時にルール一覧をサーバーから取得する。

**処理フロー**:
1. `loadRules()` 関数を実行
2. `payeeId` が指定されている場合は `payees-get-rules` API、そうでない場合は `rules-get` API を呼び出し
3. 取得したルールを `allRules` ステートに設定
4. 完了したスケジュールに紐づくルールをフィルタアウト
5. フィルタ文字列に基づいて表示ルールを絞り込み
6. ページネーション（100件 + 50件ずつ追加読み込み）を適用

### 2-ルール検索

検索フィールドへの入力でルールをフィルタリングする。

**処理フロー**:
1. `onSearchChange` イベントで `filter` ステートを更新
2. `ruleToString()` 関数でルールを文字列化
3. 正規化した検索文字列との部分一致でフィルタリング
4. ページカウンタをリセット

### 3-ルール新規作成

「Create new rule」ボタン押下時にルール編集モーダルを開く。

**処理フロー**:
1. デフォルトのルールオブジェクトを生成
2. `pushModal` で `edit-rule` モーダルを表示
3. 保存成功時に `loadRules()` を再実行
4. モーダルはRuleEditorコンポーネントを含む

### 4-ルール編集

ルール行クリック時に編集モーダルを開く。

**処理フロー**:
1. 選択されたルールオブジェクトを取得
2. `pushModal` で `edit-rule` モーダルを表示
3. 保存成功時に `loadRules()` を再実行

### 5-ルール削除（単一）

ルール行の削除アクション実行時に確認なしで削除する。

**処理フロー**:
1. `rule-delete` API を呼び出し
2. 成功時に `loadRules()` を再実行

### 6-ルール一括削除

選択されたルールを一括削除する。

**処理フロー**:
1. `rule-delete-all` API を呼び出し
2. スケジュールに紐づくルールは削除不可
3. 削除失敗があった場合はアラートを表示
4. 成功時に `loadRules()` を再実行
5. 選択状態をクリア

### 7-無限スクロール

スクロール末尾到達時に追加データを読み込む。

**処理フロー**:
1. `InfiniteScrollWrapper` がスクロール検知
2. `loadMore()` 関数でページカウンタをインクリメント
3. 表示件数が 100 + (page * 50) に更新

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 新規作成 | rules | INSERT | ルールの新規登録 |
| 編集保存 | rules | UPDATE | ルールの更新 |
| 削除 | rules | DELETE | ルールの削除 |
| 一括削除 | rules | DELETE | 複数ルールの削除 |

### テーブル別更新項目詳細

#### rules

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUID自動生成 | 一意識別子 |
| INSERT | stage | 'pre' / null / 'post' | 実行ステージ |
| INSERT | conditions_op | 'and' / 'or' | 条件の結合方法 |
| INSERT | conditions | JSON配列 | 条件定義 |
| INSERT | actions | JSON配列 | アクション定義 |
| DELETE | - | WHERE id = :id | 指定IDのルールを削除 |
| DELETE | - | WHERE id IN (:ids) | 複数IDのルールを削除 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG-001 | ガイダンス | "Rules are always run in the order that you see them." | 常時表示 |
| MSG-002 | 警告 | "Some rules were not deleted because they are linked to schedules." | 一括削除時にスケジュール紐づきルールがあった場合 |
| MSG-003 | 空状態 | "No rules" | ルールが存在しない場合 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| ルール取得失敗 | コンソールエラー出力、空配列を設定 |
| スケジュール紐づきルールの削除試行 | 削除をスキップし警告アラートを表示 |
| API通信エラー | ローディング状態を解除 |

## 備考

- ルールは自動的に条件の特定性でソートされる（より具体的な条件のルールが優先）
- ルールのステージ（Pre/Default/Post）は実行順序を制御する
- 支払先管理画面からアクセスした場合、特定の支払先に関連するルールのみ表示（`payeeId` prop）
- ルールはUndo機能と連携し、削除や変更を取り消し可能

---

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

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

### 推奨読解順序

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

ルールデータの型定義を理解し、条件とアクションの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models/index.d.ts | `packages/loot-core/src/types/models/index.d.ts` | RuleEntity, RuleConditionEntity, RuleActionEntity型の定義を確認 |
| 1-2 | rules.ts | `packages/loot-core/src/shared/rules.ts` | FIELD_TYPES, getValidOps等のルール定義ユーティリティ |

**読解のコツ**: ルールは `conditions`（条件配列）と `actions`（アクション配列）で構成される。各条件は `field`, `op`, `value` を持ち、アクションは `op`, `field`, `value` を持つ。

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

ページコンポーネントの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ManageRulesPage.tsx | `packages/desktop-client/src/components/ManageRulesPage.tsx` | シンプルなページラッパー。ManageRulesコンポーネントを呼び出す |
| 2-2 | ManageRules.tsx | `packages/desktop-client/src/components/ManageRules.tsx` | メインロジック。ルール取得・表示・操作を担当 |

**主要処理フロー**:
1. **128-132行目**: コンポーネントprops定義（isModal, payeeId, setLoading）
2. **135-138行目**: ローカルステート定義（allRules, page, filter）
3. **140-154行目**: フィルタデータ取得（schedules, categories, payees, accounts）
4. **156-171行目**: フィルタリングされたルール計算（useMemo）
5. **181-195行目**: loadRules関数定義
6. **223-246行目**: 削除・編集ハンドラ
7. **307-384行目**: JSXレンダリング

#### Step 3: ルール一覧コンポーネントを理解する

ルールの表示と選択機能を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RulesList.tsx | `packages/desktop-client/src/components/rules/RulesList.tsx` | ルール一覧のレンダリング |
| 3-2 | RulesHeader.tsx | `packages/desktop-client/src/components/rules/RulesHeader.tsx` | ヘッダー行のレンダリング |

#### Step 4: ルール編集機能を理解する

ルール編集モーダルの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | RuleEditor.tsx | `packages/desktop-client/src/components/rules/RuleEditor.tsx` | ルール編集UIの全体構造。条件・アクションエディタを含む |

**主要処理フロー**:
- **998-1004行目**: RuleEditorコンポーネント定義とprops
- **1006-1024行目**: ローカルステート初期化（conditions, actionSplits, stage等）
- **1247-1291行目**: onSave関数でのAPI呼び出しとバリデーションエラー処理

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

```
ManageRulesPage
    │
    └─ ManageRules
           │
           ├─ useSchedules() / useCategories() / usePayees() / useAccounts()
           │      └─ フィルタ用マスタデータ取得
           │
           ├─ loadRules()
           │      ├─ send('rules-get')
           │      └─ send('payees-get-rules', { id })  // payeeId指定時
           │
           ├─ RulesHeader
           │
           ├─ InfiniteScrollWrapper
           │      └─ RulesList
           │             └─ 各ルール行のレンダリング
           │
           ├─ onEditRule()
           │      └─ pushModal('edit-rule')
           │             └─ RuleEditor
           │
           ├─ onCreateRule()
           │      └─ pushModal('edit-rule')
           │             └─ RuleEditor
           │
           └─ onDeleteSelected()
                  └─ send('rule-delete-all')
```

### データフロー図

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

サーバーDB ───────────▶ send('rules-get') ───────────────▶ allRules state
                             │
useSchedules() ──────▶ スケジュール紐づき ───────────────▶ フィルタアウト
                       ルールの除外
                             │
filter入力 ──────────▶ ruleToString() ───────────────────▶ filteredRules
                       + 文字列マッチング
                             │
page state ──────────▶ slice(0, 100 + page*50) ─────────▶ 表示ルール
                             │
                             ▼
                       RulesList
                             │
ルール行クリック ────▶ onEditRule() ─────────────────────▶ edit-ruleモーダル
                             │
                             ▼
                       RuleEditor
                             │
Save押下 ────────────▶ send('rule-add/update') ──────────▶ サーバーDB更新
                             │
                             └─▶ loadRules() ──────────────▶ 一覧更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ManageRulesPage.tsx | `packages/desktop-client/src/components/ManageRulesPage.tsx` | ソース | ページコンポーネントラッパー |
| ManageRules.tsx | `packages/desktop-client/src/components/ManageRules.tsx` | ソース | ルール管理メインロジック |
| RulesList.tsx | `packages/desktop-client/src/components/rules/RulesList.tsx` | ソース | ルール一覧コンポーネント |
| RulesHeader.tsx | `packages/desktop-client/src/components/rules/RulesHeader.tsx` | ソース | ヘッダーコンポーネント |
| RuleEditor.tsx | `packages/desktop-client/src/components/rules/RuleEditor.tsx` | ソース | ルール編集コンポーネント |
| rules.ts | `packages/loot-core/src/shared/rules.ts` | ソース | ルール関連ユーティリティ |
| InfiniteScrollWrapper.tsx | `packages/desktop-client/src/components/common/InfiniteScrollWrapper.tsx` | ソース | 無限スクロールコンポーネント |
| Search.tsx | `packages/desktop-client/src/components/common/Search.tsx` | ソース | 検索入力コンポーネント |
| useSelected.ts | `packages/desktop-client/src/hooks/useSelected.ts` | ソース | 複数選択フック |
| FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | ソース | ルーティング定義（291行目） |
| models/index.d.ts | `packages/loot-core/src/types/models/index.d.ts` | 型定義 | RuleEntity型定義 |
