# 機能設計書 36-カスタムレポート保存

## 概要

本ドキュメントは、作成したレポート設定を保存・呼び出しする機能について記述する。

### 本機能の処理概要

**業務上の目的・背景**：ユーザーは頻繁に使用するレポート設定（期間、フィルタ条件、グラフタイプ等）を毎回手動で設定し直す手間を避けたい。本機能は、カスタマイズしたレポート設定に名前を付けて保存し、後から呼び出せるようにすることで、分析作業の効率化を支援する。また、ダッシュボードウィジェットとして配置することで、重要なレポートへの素早いアクセスを可能にする。

**機能の利用シーン**：
- カスタマイズしたレポート設定の保存
- 保存したレポートの呼び出し・再利用
- レポート名の変更
- 不要になったレポートの削除
- ダッシュボードへのレポートウィジェット追加
- 保存済みレポートの設定更新

**主要な処理内容**：
1. レポート設定の新規保存（名前、ダッシュボード選択）
2. 既存レポートの設定更新
3. レポート名の変更
4. レポートの削除
5. 保存済みレポート一覧からの選択・読み込み
6. 変更状態（modified）の追跡

**関連システム・外部連携**：データベース（custom_reportsテーブル）、ダッシュボード機能

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 30 | レポートダッシュボード画面 | 補助機能 | カスタムレポート設定の保存・読み込み |
| 34 | カスタムレポート画面 | 主機能 | カスタマイズ可能なレポートの作成・編集 |

## 機能種別

CRUD操作 / データ永続化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| customReportItems | CustomReportEntity | Yes | 現在のレポート設定 | - |
| report | CustomReportEntity | Yes | ベースとなるレポートエンティティ | - |
| savedStatus | string | Yes | 保存状態（'saved', 'modified'） | - |
| onReportChange | function | Yes | レポート変更時のコールバック | - |
| dashboardPages | DashboardEntity[] | Yes | ダッシュボードページ一覧 | - |

### 入力データソース

- custom_reportsテーブル（保存済みレポート設定）
- dashboard_pagesテーブル（ダッシュボードページ一覧）
- dashboardテーブル（ダッシュボードウィジェット）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | 保存されたレポートID |
| name | string | レポート名 |
| error | string | エラーメッセージ（失敗時） |

### 出力先

custom_reportsテーブル、dashboardテーブル

## 処理フロー

### 処理シーケンス

```
1. メニュー選択
   └─ save/update/rename/delete/reload/reset/choose から選択
2. 名前入力（新規保存/リネーム時）
   └─ レポート名の入力とバリデーション
3. ダッシュボード選択（新規保存時）
   └─ ウィジェットを追加するダッシュボードを選択
4. API呼び出し
   └─ report/create, report/update, report/delete
5. ダッシュボードウィジェット追加（新規保存時）
   └─ dashboard-add-widget API呼び出し
6. 親コンポーネントへの通知
   └─ onReportChangeコールバック
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[メニュー表示]
    B --> C{操作選択}
    C -->|save-report| D[名前入力画面]
    D --> E[ダッシュボード選択]
    E --> F[report/create API]
    F --> G[dashboard-add-widget API]
    C -->|update-report| H[report/update API]
    C -->|rename-report| I[名前入力画面]
    I --> J[report/update API]
    C -->|delete-report| K[削除確認画面]
    K --> L[report/delete API]
    C -->|reload-report| M[onReportChange: reload]
    C -->|reset-report| N[onReportChange: reset]
    C -->|choose-report| O[レポート選択画面]
    O --> P[onReportChange: choose]
    G --> Q[onReportChange: add-update]
    H --> Q
    J --> R[onReportChange: rename]
    L --> S[onReportChange: reset]
    Q --> T[終了]
    R --> T
    S --> T
    M --> T
    N --> T
    P --> T
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 名前必須 | レポート名は必須 | 新規保存/リネーム時 |
| BR-02 | 名前重複不可 | 同名のレポートは作成不可 | 新規保存/リネーム時 |
| BR-03 | ダッシュボード必須 | 新規保存時はダッシュボード選択必須 | 新規保存時 |
| BR-04 | 変更状態追跡 | レポート設定変更時はmodified状態に | 設定変更時 |
| BR-05 | conditionsOp必須 | conditionsOpは'and'または'or'のみ | 保存時 |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 新規保存 | custom_reports | INSERT | レポート設定を保存 |
| 新規保存 | dashboard | INSERT | ウィジェットを追加 |
| 更新 | custom_reports | UPDATE | レポート設定を更新 |
| リネーム | custom_reports | UPDATE | 名前のみ更新 |
| 削除 | custom_reports | DELETE | レポートを削除（tombstone設定） |

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

#### custom_reports

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUID | 新規生成 |
| INSERT | name | ユーザー入力 | 必須 |
| INSERT | start_date | レポート設定値 | - |
| INSERT | end_date | レポート設定値 | - |
| INSERT | conditions | JSON形式 | フィルタ条件 |
| INSERT | conditions_op | 'and' \| 'or' | 必須 |
| INSERT | graph_type | レポート設定値 | - |
| UPDATE | * | 同上 | - |
| DELETE | tombstone | 1 | 論理削除 |

#### dashboard

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | type | 'custom-report' | ウィジェットタイプ |
| INSERT | width | 4 | デフォルト幅 |
| INSERT | height | 2 | デフォルト高さ |
| INSERT | meta | { id: reportId } | レポートIDを格納 |
| INSERT | dashboard_page_id | 選択されたページID | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 名前重複エラー | 同名レポートが存在 | エラーメッセージ表示、名前再入力 |
| - | 名前空エラー | 名前が空 | エラーメッセージ表示 |
| - | ダッシュボード未選択 | ダッシュボードが選択されていない | エラーメッセージ表示 |

### リトライ仕様

特になし。エラー時は名前入力画面を再表示。

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

report/create, report/update, report/deleteは各々undoable（元に戻し可能）として実装されている。

## パフォーマンス要件

特になし。

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

- 保存されたレポート設定は予算ファイル内に保存される
- 同期時は暗号化されて送信される

## 備考

- SaveReportコンポーネントは複数のPopoverで構成されている
- 操作種別によって表示されるPopoverが切り替わる
- useReportsフックで保存済みレポート一覧を取得

---

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

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

### 推奨読解順序

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

レポート関連の型定義を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models.ts | `packages/loot-core/types/models/index.ts` | CustomReportEntity, DashboardEntity型定義 |

#### Step 2: UIコンポーネントを理解する

SaveReportコンポーネントの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SaveReport.tsx | `packages/desktop-client/src/components/reports/SaveReport.tsx` | レポート保存UI全体の実装 |

**主要処理フロー**:
- **28-57行目**: `SaveReportProps` - コンポーネントのプロパティ定義
- **59-70行目**: `SaveReportWrapper` - ダッシュボードデータ読み込み
- **72-93行目**: コンポーネント初期化とステート定義
- **94-99行目**: `onApply` - 保存済みレポートの選択処理
- **101-162行目**: `onAddUpdate` - 新規保存/更新/リネーム処理
  - **106-140行目**: 新規保存（report/create + dashboard-add-widget）
  - **145-162行目**: 更新/リネーム（report/update）
- **164-169行目**: `onDelete` - 削除処理
- **171-209行目**: `onMenuSelect` - メニュー選択ハンドラー
- **244-255行目**: メインメニューPopover（SaveReportMenu）
- **257-301行目**: 名前入力Popover（SaveReportName）
- **303-310行目**: レポート選択Popover（SaveReportChoose）
- **312-323行目**: 削除確認Popover（SaveReportDelete）

#### Step 3: サーバー側実装を理解する

レポートCRUD APIの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | app.ts | `packages/loot-core/src/server/reports/app.ts` | レポートAPI実装 |

**主要処理フロー**:
- **14-81行目**: `reportModel` - レポートモデル（バリデーション、型変換）
- **83-111行目**: `reportNameExists` - 名前重複チェック
- **113-132行目**: `createReport` - 新規作成
- **134-149行目**: `updateReport` - 更新
- **151-153行目**: `deleteReport` - 削除
- **162-166行目**: app.method定義（undoable, mutator適用）

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

```
SaveReport (SaveReport.tsx:72)
    │
    ├─ SaveReportMenu (PopoverメニューUI)
    │
    ├─ onMenuSelect (SaveReport.tsx:171)
    │      │
    │      ├─ 'save-report' → setNameMenuOpen
    │      │
    │      ├─ 'update-report' → onAddUpdate
    │      │
    │      ├─ 'rename-report' → setNameMenuOpen
    │      │
    │      ├─ 'delete-report' → setDeleteMenuOpen
    │      │
    │      ├─ 'reload-report' → onReportChange
    │      │
    │      ├─ 'reset-report' → onReportChange
    │      │
    │      └─ 'choose-report' → setChooseMenuOpen
    │
    ├─ onAddUpdate (SaveReport.tsx:101)
    │      │
    │      ├─ sendCatch('report/create', ...) ───▶ app.ts:createReport
    │      │      └─ db.insertWithSchema
    │      │
    │      ├─ send('dashboard-add-widget', ...) ───▶ ウィジェット追加
    │      │
    │      └─ sendCatch('report/update', ...) ───▶ app.ts:updateReport
    │             └─ db.updateWithSchema
    │
    ├─ onDelete (SaveReport.tsx:164)
    │      └─ send('report/delete', ...) ───▶ app.ts:deleteReport
    │             └─ db.delete_
    │
    └─ onApply (SaveReport.tsx:94)
           └─ onReportChange({ type: 'choose', savedReport })
```

### データフロー図

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

customReportItems ────┐
                      │
report ───────────────┼──▶ SaveReport ──▶ メニュー選択
                      │          │              │
savedStatus ──────────┘          │              │
                                 ▼              ▼
                           操作に応じた     APIコール
                           Popover表示         │
                                 │              ▼
                                 │         custom_reports
                                 │         dashboard
                                 │              │
                                 ▼              ▼
                           onReportChange ───▶ 親コンポーネント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SaveReport.tsx | `packages/desktop-client/src/components/reports/SaveReport.tsx` | ソース | レポート保存UIメイン |
| SaveReportMenu.tsx | `packages/desktop-client/src/components/reports/SaveReportMenu.tsx` | ソース | 操作メニュー |
| SaveReportName.tsx | `packages/desktop-client/src/components/reports/SaveReportName.tsx` | ソース | 名前入力UI |
| SaveReportChoose.tsx | `packages/desktop-client/src/components/reports/SaveReportChoose.tsx` | ソース | レポート選択UI |
| SaveReportDelete.tsx | `packages/desktop-client/src/components/reports/SaveReportDelete.tsx` | ソース | 削除確認UI |
| app.ts | `packages/loot-core/src/server/reports/app.ts` | ソース | レポートAPI実装 |
| useReports.ts | `packages/desktop-client/src/hooks/useReports.ts` | ソース | レポート一覧取得フック |
| useDashboard.ts | `packages/desktop-client/src/hooks/useDashboard.ts` | ソース | ダッシュボード取得フック |
