# 通知設計書 53-GDPRリクエスト通知

## 概要

本ドキュメントは、Bagisto モバイルアプリにおけるGDPRリクエスト通知の設計を記載する。GDPR（EU一般データ保護規則）に基づくデータリクエスト（更新・削除）の作成、取り消し（Revoke）操作の成否をユーザーに通知する機能の仕様を定義する。

### 本通知の処理概要

GDPRリクエスト通知は、ユーザーがGDPRデータリクエスト画面でデータの更新リクエストまたは削除リクエストを作成した際、またはリクエストを取り消した際に、操作の成功または失敗をSnackBarで通知する機能である。

**業務上の目的・背景**：GDPR（EU一般データ保護規則）により、EU域内のユーザーは自身の個人データに関して「アクセス権」「訂正権」「削除権（忘れられる権利）」等の権利を持つ。Bagistoアプリではこれらの権利行使をサポートするため、GDPRリクエスト機能を提供している。本通知は、ユーザーがリクエストを正常に送信できたか、またはエラーが発生したかを即座にフィードバックし、ユーザーが適切な対応を取れるようにする。プライバシー保護に関わる重要な操作であるため、操作結果の明確な通知が不可欠である。

**通知の送信タイミング**：以下の操作完了時に通知が表示される。
1. GDPRリクエスト作成（CreateGdprRequest）が成功または失敗した時
2. GDPRリクエスト取り消し（RevokeGdprRequest）が成功または失敗した時
3. GDPR検索リクエスト（GdprSearchRequest）が失敗した時
4. GDPRデータPDFエクスポート（GdprPdfRequest）が失敗した時

**通知の受信者**：現在アプリを操作しているログイン済みユーザー本人。GDPR機能はログインユーザーのみが利用可能。

**通知内容の概要**：SnackBarによる通知で、操作の成功時は「Request created」、取り消し成功時はサーバーからのメッセージ、失敗時は具体的なエラーメッセージが表示される。

**期待されるアクション**：成功時はリクエスト一覧が自動更新されるため、ユーザーは新しいリクエストが一覧に追加されたことを確認できる。失敗時はエラー内容を確認し、必要に応じて再試行または問題を解決することが期待される。

## 通知種別

アプリ内通知（SnackBar）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（BlocConsumer listener内で即座に表示） |
| 優先度 | 中（操作結果のフィードバック） |
| リトライ | 無（ユーザーによる再操作が必要） |

### 送信先決定ロジック

操作を実行したログインユーザー本人に対してのみ表示される。端末上のローカル通知であり、送信先の決定ロジックは不要。

## 通知テンプレート

### アプリ内通知（SnackBar）の場合

| 項目 | 内容 |
|-----|------|
| 表示位置 | 画面下部 |
| 背景色 | デフォルト（Flutterテーマに依存） |
| 表示時間 | SnackBarデフォルト（約4秒） |

### 本文テンプレート

#### リクエスト作成成功時
```
Request created
```

#### リクエスト作成失敗時
```
{エラーメッセージ} または "Failed to create request"
```

#### リクエスト取り消し成功時
```
{サーバーからのメッセージ}
```

#### リクエスト取り消し失敗時
```
{エラーメッセージ} または "Failed to revoke request"
```

#### 検索失敗時
```
{エラーメッセージ} または "Failed to fetch search results"
```

#### PDFエクスポート失敗時
```
{エラーメッセージ} または "Failed to export PDF"
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| state.message | サーバーからの成功メッセージ | API レスポンス (BaseModel.message) | No |
| state.error | エラーメッセージ | 例外メッセージまたはAPIエラー | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | CreateRequestModal保存ボタン押下 | CreateGdprRequestState受信時 | GDPRリクエスト作成APIのレスポンス受信後 |
| 画面操作 | リクエストカードRevokeボタン押下 | RevokeGdprRequestState受信時 | GDPRリクエスト取り消しAPIのレスポンス受信後 |
| 画面操作 | 検索フィールド入力後Enter | GdprSearchRequestState.fail時 | GDPR検索APIのエラーレスポンス受信後 |
| 画面操作 | PDFエクスポートボタン押下 | GdprPdfRequestState.fail時 | PDFエクスポートAPIのエラーレスポンス受信後 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 検索成功時 | GdprSearchRequestState.success の場合は通知しない（一覧が更新される） |
| PDFエクスポート成功時 | GdprPdfRequestState.success の場合は通知しない（ファイル保存処理が実行される） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザー操作] --> B[GdprBloc.add Event]
    B --> C[GdprLoadingState]
    C --> D[API呼び出し]
    D --> E{API結果}
    E -->|成功| F[成功State emit]
    E -->|失敗| G[失敗State emit]
    F --> H[BlocConsumer listener]
    G --> H
    H --> I{State種別判定}
    I -->|CreateGdprRequestState.success| J[SnackBar: Request created]
    I -->|CreateGdprRequestState.fail| K[SnackBar: エラーメッセージ]
    I -->|RevokeGdprRequestState.success| L[SnackBar: サーバーメッセージ]
    I -->|RevokeGdprRequestState.fail| M[SnackBar: エラーメッセージ]
    J --> N[FetchGdprRequestsList再取得]
    L --> N
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| gdpr_data_requests（サーバー側） | GDPRリクエスト一覧取得 | API経由でアクセス |

### テーブル別参照項目詳細

#### gdpr_data_requests（API経由）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | リクエストID表示・操作対象特定 | customer_id = ログインユーザー |
| status | リクエスト状態表示 | customer_id = ログインユーザー |
| type | リクエスト種別（UPDATE/DELETE） | customer_id = ログインユーザー |
| message | リクエストメッセージ | customer_id = ログインユーザー |
| created_at | 作成日時 | customer_id = ログインユーザー |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| gdpr_data_requests（サーバー側） | INSERT | 新規GDPRリクエスト作成 |
| gdpr_data_requests（サーバー側） | UPDATE | リクエスト取り消し（status更新） |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| API通信エラー | ネットワーク接続なし | SnackBarでエラーメッセージ表示 |
| サーバーエラー | バックエンドでの処理失敗 | SnackBarでエラーメッセージ表示 |
| バリデーションエラー | 必須項目未入力 | フォームバリデーションで事前にブロック |
| 認証エラー | セッション切れ等 | SnackBarでエラーメッセージ表示 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（自動リトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（API側でレート制限） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

24時間いつでも表示可能

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

- ログインユーザーのみがGDPR機能にアクセス可能（ドロワーメニューでログイン状態チェック）
- リクエストにはcustomerEmailが関連付けられ、本人のリクエストのみ操作可能
- エラーメッセージに個人情報は含めない
- GDPRリクエストはプライバシーに関わる重要な操作のため、操作ログがサーバー側で記録される想定

## 備考

- GDPRリクエストには2種類のタイプがある：UPDATE（データ更新）、DELETE（データ削除）
- リクエスト作成時はメッセージ（理由）の入力が必須
- PDFエクスポート成功時はBase64エンコードされたPDFファイルをローカルに保存
- HTMLビューはWebブラウザで外部URLを開く形式

---

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

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

### 推奨読解順序

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

GDPRリクエストのデータモデルとStateクラスの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | gdpr_state.dart | `lib/screens/gdpr/bloc/gdpr_state.dart` | GdprState継承クラス、CreateGdprRequestState、RevokeGdprRequestStateの構造（51-90行目） |
| 1-2 | gdpr_event.dart | `lib/screens/gdpr/bloc/gdpr_event.dart` | CreateGdprRequest、RevokeGdprRequestイベントの定義（12-31行目） |

**読解のコツ**: FlutterのBlocパターンを使用しており、Event -> Bloc -> Stateの流れでデータが処理される。各Stateクラスはsuccess/failのファクトリメソッドを持つ。

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

GDPR画面のBlocConsumerでのState処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | gdpr_screen.dart | `lib/screens/gdpr/view/gdpr_screen.dart` | BlocConsumerのlistener実装（49-129行目） |

**主要処理フロー**:
1. **49行目**: BlocConsumer<GdprBloc, GdprState> でState変化を監視
2. **89-101行目**: CreateGdprRequestState の処理（成功/失敗でSnackBar表示）
3. **103-116行目**: RevokeGdprRequestState の処理（成功/失敗でSnackBar表示）
4. **66-88行目**: GdprSearchRequestState の処理（失敗時のみSnackBar表示）
5. **118-128行目**: GdprPdfRequestState の処理（失敗時のみSnackBar表示）

#### Step 3: ビジネスロジック層を理解する

GdprBlocでのイベント処理とAPI呼び出しを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | gdpr_bloc.dart | `lib/screens/gdpr/bloc/gdpr_bloc.dart` | mapEventToStateでのイベントハンドリング（28-91行目） |
| 3-2 | gdpr_repository.dart | `lib/screens/gdpr/bloc/gdpr_repository.dart` | API呼び出しの実装（16-76行目） |

**主要処理フロー**:
- **gdpr_bloc.dart 38-51行目**: CreateGdprRequest イベント処理
- **gdpr_bloc.dart 53-63行目**: RevokeGdprRequest イベント処理
- **gdpr_repository.dart 30-39行目**: createGdprRequest API呼び出し
- **gdpr_repository.dart 42-51行目**: revokeGdprRequest API呼び出し

#### Step 4: リクエスト作成UIを理解する

CreateRequestModalダイアログの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create_request.dart | `lib/screens/gdpr/view/widget/create_request.dart` | ダイアログUIとフォームバリデーション（11-179行目） |

**主要処理フロー**:
- **6-9行目**: GdprRequestType enum定義（DELETE, UPDATE）
- **25-37行目**: createGdprRequest関数でBlocにイベント送信
- **67-143行目**: フォーム実装（タイプ選択、メッセージ入力）
- **134-140行目**: メッセージのバリデーション

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

```
GdprScreen
    │
    ├─ CreateRequestModal
    │      └─ createGdprRequest()
    │             └─ GdprBloc.add(CreateGdprRequest)
    │                    └─ GdprRequestRepo.createGdprRequest()
    │                           └─ ApiClient().createGdprRequest()
    │                    └─ emit(CreateGdprRequestState)
    │
    ├─ RevokeボタンonPressed
    │      └─ GdprBloc.add(RevokeGdprRequest)
    │             └─ GdprRequestRepo.revokeGdprRequest()
    │                    └─ ApiClient().revokeGdprRequest()
    │             └─ emit(RevokeGdprRequestState)
    │
    └─ BlocConsumer.listener
           └─ ScaffoldMessenger.showSnackBar()
```

### データフロー図

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

ユーザー操作 ───▶ GdprBloc.add(Event) ───▶ GdprLoadingState
                        │
                        ▼
                 Repository.apiCall()
                        │
                        ▼
                 ApiClient().request()
                        │
                        ▼
               ┌────────┴────────┐
               │                 │
           API成功            API失敗
               │                 │
               ▼                 ▼
    State.success emit    State.fail emit
               │                 │
               └────────┬────────┘
                        ▼
             BlocConsumer.listener
                        │
                        ▼
           ScaffoldMessenger.showSnackBar()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| gdpr_screen.dart | `lib/screens/gdpr/view/gdpr_screen.dart` | ソース | GDPR画面メインUI |
| create_request.dart | `lib/screens/gdpr/view/widget/create_request.dart` | ソース | リクエスト作成ダイアログ |
| gdpr_bloc.dart | `lib/screens/gdpr/bloc/gdpr_bloc.dart` | ソース | GDPRビジネスロジック（Bloc） |
| gdpr_event.dart | `lib/screens/gdpr/bloc/gdpr_event.dart` | ソース | GDPRイベント定義 |
| gdpr_state.dart | `lib/screens/gdpr/bloc/gdpr_state.dart` | ソース | GDPRステート定義 |
| gdpr_repository.dart | `lib/screens/gdpr/bloc/gdpr_repository.dart` | ソース | GDPRリポジトリ（API呼び出し） |
| string_constants.dart | `lib/utils/string_constants.dart` | ソース | 文字列定数定義 |
| drawer_list_view.dart | `lib/screens/drawer/drawer_list_view.dart` | ソース | ドロワーメニュー（GDPR画面への遷移） |
