# 画面設計書 24-ルール編集画面（モバイル）

## 概要

本ドキュメントは、Actual Budgetアプリケーションにおけるモバイル向けルール編集画面の設計仕様を定義するものである。

### 本画面の処理概要

モバイルデバイス向けに最適化された取引自動分類ルールの作成・編集画面である。条件とアクションを組み合わせてルールを定義し、新規作成または既存ルールの更新を行う。デスクトップ版がモーダルダイアログを使用するのに対し、モバイル版では独立した画面として実装されている。

**業務上の目的・背景**：モバイル環境でもルールの詳細な設定を可能にすることで、外出先でのルール調整ニーズに応える。銀行同期で新規取引がインポートされた際に、その場でルールを作成・調整できることで、カテゴリ分類の精度を迅速に向上させることができる。条件（支払先、金額、日付など）とアクション（カテゴリ設定、ノート追加など）を視覚的に組み合わせて、複雑なルールも直感的に作成できる。

**画面へのアクセス方法**：ルール管理画面（モバイル）から特定のルールをタップ、または新規追加ボタンをタップすることで `/rules/:id` または `/rules/new` ルートに遷移する。

**主要な操作・処理内容**：
1. 条件の追加・編集・削除（フィールド、演算子、値の設定）
2. アクションの追加・編集・削除（設定フィールド、値の設定）
3. 条件の結合方法の切り替え（AND/OR）
4. ルールのステージ設定（Pre/Default/Post）
5. 該当する取引のプレビュー表示
6. ルールの保存・削除

**画面遷移**：
- 遷移元：ルール管理画面（モバイル） `/rules`
- 遷移先：ルール管理画面（モバイル） `/rules`（保存完了時・キャンセル時）

**権限による表示制御**：特別な権限制御は設けられていない。スケジュールにリンクされたルールは削除ボタンが非表示となる。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | 取引ルール | 主機能 | モバイルでのルール詳細編集 |

## 画面種別

編集

## URL/ルーティング

`/rules/:id` または `/rules/new`

- `:id` - 編集対象のルールID（UUID形式）
- `new` - 新規ルール作成モード
- モバイル・デスクトップ両対応（レスポンシブ）
- デスクトップではモーダルダイアログ、モバイルでは独立画面

## 入出力項目

| 項目名 | 項目ID | データ型 | 入力/出力 | 必須 | 説明 |
|--------|--------|----------|-----------|------|------|
| ルールID | id | string | 入力（URL） | 任意 | 編集対象のルールID。'new'の場合は新規作成 |
| 条件結合方法 | conditionsOp | 'and' / 'or' | 入力 | 必須 | 条件の結合方法 |
| ステージ | stage | 'pre' / null / 'post' | 入力 | 必須 | ルール実行ステージ |
| 条件配列 | conditions | array | 入力 | 必須 | 条件定義の配列 |
| アクション配列 | actions | array | 入力 | 必須 | アクション定義の配列 |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| ヘッダータイトル | string | "Create Rule" / "Edit Rule" / "Rule" |
| 戻るボタン | button | 前画面に戻る |
| ステージ選択 | toggle | Pre/Default/Post の選択 |
| 条件結合方法 | select | "all" / "any" の選択 |
| 条件エディタ | component | 条件の編集UI |
| アクションエディタ | component | アクションの編集UI |
| 該当取引一覧 | table | ルールに該当する取引のプレビュー |
| 削除ボタン | button | ルール削除（編集時のみ、スケジュール非リンク時） |
| キャンセルボタン | button | 編集キャンセル |
| 保存ボタン | button | ルール保存 |
| ローディング表示 | text | データ取得中に表示 |

## イベント仕様

### 1-画面初期表示（編集モード）

URLパラメータの `id` を使用してルールデータをサーバーから取得する。

**処理フロー**:
1. `id` が 'new' でない場合、`rule-get` API を呼び出し
2. 取得したルールを `rule` ステートに設定
3. 条件・アクションをパースして編集可能な形式に変換
4. ルールが見つからない場合、`/rules` へリダイレクト
5. スケジュールとの紐づきを確認

### 2-画面初期表示（新規作成モード）

デフォルトのルール構造を生成する。

**処理フロー**:
1. デフォルト条件（payee, is, ''）を設定
2. デフォルトアクション（category, set, ''）を設定
3. `location.state.rule` がある場合はマージ

### 3-条件追加

条件エディタで「+」ボタンを押すと新しい条件を追加する。

**処理フロー**:
1. 既存条件から未使用のフィールドを提案
2. デフォルト演算子と空の値で新条件を挿入
3. `conditions` ステートを更新

### 4-条件変更

条件のフィールド・演算子・値を変更する。

**処理フロー**:
1. フィールド変更時、対応する型と演算子を自動設定
2. 演算子変更時、値の形式を調整（oneOf ↔ 単一値）
3. 値変更時、型に応じたバリデーション

### 5-アクション追加

アクションエディタで「+」ボタンを押すと新しいアクションを追加する。

**処理フロー**:
1. 未使用のアクションフィールドを提案
2. デフォルト操作（set）と空の値で新アクションを挿入
3. `actionSplits` ステートを更新

### 6-保存処理

保存ボタン押下時にルールをサーバーに保存する。

**処理フロー**:
1. 条件・アクションをアンパースして保存形式に変換
2. `rule-add`（新規）または `rule-update`（編集）API を呼び出し
3. バリデーションエラーがある場合、エラー表示
4. 成功時、Undo通知を表示し `/rules` へ遷移

### 7-削除処理

削除ボタン押下時に確認ダイアログを表示してルールを削除する。

**処理フロー**:
1. `confirm-delete` モーダルを表示
2. 確認後、`rule-delete` API を呼び出し
3. 成功時、Undo通知を表示し `/rules` へ遷移
4. 失敗時、エラー通知を表示

### 8-該当取引プレビュー

条件変更時に該当する取引を自動検索して表示する。

**処理フロー**:
1. 条件が変更されるたびに `make-filters-from-conditions` API を呼び出し
2. 生成されたフィルタで取引を検索
3. `SimpleTransactionsTable` で結果を表示

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

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

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

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

#### rules

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

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG-001 | 成功 | "Rule saved successfully" | 保存成功時（Undo通知） |
| MSG-002 | 成功 | "Rule deleted successfully" | 削除成功時（Undo通知） |
| MSG-003 | エラー | "Failed to delete rule. Please try again." | 削除失敗時 |
| MSG-004 | ローディング | "Loading rule..." | ルール取得中 |
| MSG-005 | 確認 | "Are you sure you want to delete this rule?" | 削除確認ダイアログ |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| ルールが見つからない | `/rules` へリダイレクト |
| バリデーションエラー | 該当フィールドにエラー表示 |
| 保存API失敗 | エラーをコンソール出力 |
| 削除API失敗 | エラー通知を表示 |
| 新規モードで削除試行 | 削除ボタンが非表示のため発生しない |

## 備考

- 本画面はモバイル・デスクトップ両方で使用される
- デスクトップではモーダルダイアログとして表示
- スケジュールにリンクされたルールは削除不可
- 分割取引（Split Transactions）のアクション設定も対応
- テンプレート機能と数式機能はフィーチャーフラグで制御

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models/index.d.ts | `packages/loot-core/src/types/models/index.d.ts` | RuleEntity, NewRuleEntity, RuleConditionEntity, RuleActionEntity型 |
| 1-2 | rules.ts | `packages/loot-core/src/shared/rules.ts` | FIELD_TYPES, getValidOps, parse, unparse関数 |

**読解のコツ**: 条件は `{ field, op, value, type }` 形式、アクションは `{ op, field, value, options }` 形式。`parse/unparse` で保存形式と編集形式を相互変換する。

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

モバイル用ページコンポーネントの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | MobileRuleEditPage.tsx | `packages/desktop-client/src/components/mobile/rules/MobileRuleEditPage.tsx` | ページラッパー。ルール取得、ヘッダー、RuleEditorの配置 |

**主要処理フロー**:
1. **23-29行目**: コンポーネント定義とフック初期化
2. **31-32行目**: ローカルステート（rule, isLoading）
3. **34-44行目**: スケジュール紐づき確認クエリ
4. **50-71行目**: useEffectでルール取得（rule-get API）
5. **74-94行目**: デフォルトルール定義（新規作成時）
6. **96-145行目**: イベントハンドラ（handleSave, handleCancel, handleDelete）
7. **182-203行目**: JSXレンダリング（Page, RuleEditor）

#### Step 3: ルールエディタを理解する

条件・アクション編集の詳細を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RuleEditor.tsx | `packages/desktop-client/src/components/rules/RuleEditor.tsx` | 編集UI全体。ConditionsList, ActionEditor, SimpleTransactionsTable |

**主要処理フロー**:
- **998-1004行目**: RuleEditorコンポーネント定義とprops
- **1006-1024行目**: ステート初期化（conditions, actionSplits, stage, conditionsOp）
- **1032-1070行目**: 該当取引検索処理（useEffect）
- **1074-1111行目**: アクション追加処理
- **1113-1178行目**: アクション変更処理
- **1247-1291行目**: 保存処理（rule-add/rule-update API）

#### Step 4: 条件エディタを理解する

条件の追加・編集UIを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | RuleEditor.tsx | `packages/desktop-client/src/components/rules/RuleEditor.tsx` | ConditionsList, ConditionEditor（787-967行目） |

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

```
MobileRuleEditPage
    │
    ├─ useParams()
    │      └─ id 取得（'new' または UUID）
    │
    ├─ useSchedules()
    │      └─ スケジュール紐づき確認
    │
    ├─ send('rule-get', { id })
    │      └─ 既存ルール取得（編集モード時）
    │
    ├─ handleSave()
    │      └─ RuleEditor の onSave prop へ
    │
    ├─ handleDelete()
    │      ├─ pushModal('confirm-delete')
    │      └─ send('rule-delete', id)
    │
    └─ RuleEditor
           │
           ├─ ステージ選択（StageButton）
           │
           ├─ 条件結合方法選択（FieldSelect）
           │
           ├─ ConditionsList
           │      └─ ConditionEditor（複数）
           │             ├─ FieldSelect
           │             ├─ OpSelect
           │             └─ GenericInput
           │
           ├─ ActionEditor（複数）
           │      ├─ OpSelect
           │      ├─ FieldSelect
           │      └─ GenericInput
           │
           ├─ SimpleTransactionsTable
           │      └─ 該当取引プレビュー
           │
           └─ onSave()
                  ├─ unparse(conditions)
                  ├─ unparse(actions)
                  └─ send('rule-add/update')
```

### データフロー図

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

URL param (id) ───────▶ useParams() ─────────────────────▶ ルールID取得
                             │
                             ▼
                     send('rule-get') ───────────────────▶ rule state
                             │
                             ▼
                     parse(conditions/actions) ──────────▶ 編集可能形式
                             │
ユーザー編集 ─────────▶ ConditionEditor/ActionEditor ───▶ conditions/actions state
                             │
                             ▼
                     send('make-filters-from-conditions')
                             │
                             ▼
                     aqlQuery(transactions) ─────────────▶ 該当取引表示
                             │
Save押下 ─────────────▶ unparse() ───────────────────────▶ 保存形式
                             │
                             ▼
                     send('rule-add/update') ────────────▶ サーバーDB更新
                             │
                             └─▶ navigate('/rules') ─────▶ 一覧へ遷移
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MobileRuleEditPage.tsx | `packages/desktop-client/src/components/mobile/rules/MobileRuleEditPage.tsx` | ソース | モバイル用ページラッパー |
| RuleEditor.tsx | `packages/desktop-client/src/components/rules/RuleEditor.tsx` | ソース | ルール編集UI（共通） |
| rules.ts | `packages/loot-core/src/shared/rules.ts` | ソース | parse/unparse、フィールド定義 |
| GenericInput.tsx | `packages/desktop-client/src/components/util/GenericInput.tsx` | ソース | 汎用入力コンポーネント |
| SimpleTransactionsTable.tsx | `packages/desktop-client/src/components/transactions/SimpleTransactionsTable.tsx` | ソース | 取引プレビュー表示 |
| useSchedules.ts | `packages/desktop-client/src/hooks/useSchedules.ts` | ソース | スケジュール取得フック |
| MobileBackButton.tsx | `packages/desktop-client/src/components/mobile/MobileBackButton.tsx` | ソース | 戻るボタンコンポーネント |
| Page.tsx | `packages/desktop-client/src/components/Page.tsx` | ソース | ページレイアウトコンポーネント |
| FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | ソース | ルーティング定義（294-296行目） |
| models/index.d.ts | `packages/loot-core/src/types/models/index.d.ts` | 型定義 | ルール関連型定義 |
