# 画面設計書 33-GDPRリクエスト画面

## 概要

本ドキュメントは、Bagisto eコマースモバイルアプリにおける「GDPRリクエスト画面」の画面設計書です。本画面は、GDPR（一般データ保護規則）に基づくユーザーのデータリクエスト管理機能を提供します。

### 本画面の処理概要

本画面は、ユーザーが自身の個人データに関するGDPRリクエストを作成、一覧表示、検索、取り消し、およびデータのエクスポート（PDF/HTML）を行うための機能を提供します。GDPRに準拠したデータ主体の権利（アクセス権、訂正権、削除権等）を行使するためのインターフェースを提供します。

**業務上の目的・背景**：EU一般データ保護規則（GDPR）により、企業はユーザーからのデータアクセス要求、データ修正要求、データ削除要求に対応する義務があります。本画面は、ユーザーがセルフサービスでこれらのリクエストを管理できるようにすることで、GDPRコンプライアンスを実現し、サポートチームの負荷を軽減します。

**画面へのアクセス方法**：アカウント情報画面からGDPRメニューをタップ、またはダッシュボードのサイドドロワーメニューから「GDPR」をタップすることでアクセスします。ルーティングは`gdpr`という名前で定義されています。ログイン状態が必要です。

**主要な操作・処理内容**：
1. 画面表示時にGDPRリクエスト一覧を取得して表示
2. 「PDF」ボタンでユーザーデータをPDF形式でダウンロード
3. 「HTML」ボタンで外部ブラウザでHTMLビューを表示
4. 「Create Request」ボタンでリクエスト作成ダイアログを表示
5. 検索フィールドでリクエストIDによる検索が可能
6. 各リクエストカードの「Revoke」ボタンでリクエストを取り消し
7. リクエスト一覧には、ID、ステータス、タイプ、メッセージ、日付が表示

**画面遷移**：
- HTMLエクスポート: 外部ブラウザで`$baseDomain/customer/account/gdpr/html-view`を開く
- リクエスト作成: CreateRequestModalダイアログを表示（画面内モーダル）

**権限による表示制御**：本画面はログインユーザーのみがアクセス可能です。各ユーザーは自身のGDPRリクエストのみを閲覧・管理できます。リクエストのステータスが「REVOKED」の場合、Revokeボタンは無効化されます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 63 | GDPRリクエスト一覧 | 主機能 | GDPRデータリクエスト一覧表示 |
| 64 | GDPRリクエスト作成 | 補助機能 | GDPRデータリクエストの新規作成処理 |
| 65 | GDPRリクエスト取消 | 補助機能 | GDPRデータリクエストの取消処理 |
| 66 | GDPRデータダウンロード | 補助機能 | GDPRに基づくユーザーデータのダウンロード処理 |

## 画面種別

一覧（リスト表示画面）+ 登録機能（モーダルダイアログ）

## URL/ルーティング

- ルート名: `gdpr`
- ルート定数: `const String gdpr = "gdpr";`
- ナビゲーション: `Navigator.pushNamed(context, gdpr)`

## 入出力項目

| 項目名 | 項目ID | 入力/出力 | データ型 | 必須 | バリデーション | 備考 |
|--------|--------|----------|----------|------|----------------|------|
| 検索ID | searchId | 入力 | String | 任意 | 数値のみ | リクエストID検索用 |
| リクエストタイプ | type | 入力（モーダル） | Enum | 必須 | DELETE/UPDATE | GdprRequestType |
| メッセージ | message | 入力（モーダル） | String | 必須 | 空でないこと | リクエスト内容 |

## 表示項目

| 項目名 | 表示位置 | データソース | 表示形式 | 備考 |
|--------|----------|-------------|----------|------|
| 画面タイトル | AppBar | 固定値 | テキスト | 「GDPR Data Requests」 |
| PDFボタン | アクションバー | - | OutlinedButton | PDFエクスポート |
| HTMLボタン | アクションバー | - | OutlinedButton | HTMLビュー表示 |
| 作成ボタン | アクションバー | StringConstants.createRequest | ElevatedButton | リクエスト作成 |
| 検索フィールド | フォームエリア | - | TextField | ID検索用 |
| リクエストID | カード内 | gdprRequests[].id | テキスト | - |
| ステータス | カード内 | gdprRequests[].status | ラベル | 黄色背景 |
| タイプ | カード内 | gdprRequests[].type | テキスト | 大文字表示 |
| メッセージ | カード内 | gdprRequests[].message | テキスト | - |
| 日付 | カード内 | gdprRequests[].date | テキスト | - |
| 取り消しボタン | カード内 | - | ElevatedButton | Revoke/Revoked |

## イベント仕様

### 1-画面初期化（initState）

画面表示時に自動的に以下の処理が実行されます：

1. GdprBlocを`context.read<GdprBloc>()`で取得
2. `FetchGdprRequestsList`イベントをBlocに送信
3. `appStoragePref.getCustomerEmail()`で顧客メールアドレスを取得
4. APIからGDPRリクエスト一覧を取得し、`gdprRequests`リストに格納

### 2-PDFボタン押下

1. `GdprPdfRequest`イベントをBlocに送信
2. APIからBase64エンコードされたPDFデータを取得
3. `DownloadFile().saveBase64String()`でファイルとして保存

### 3-HTMLボタン押下

1. `launchUrl()`で外部ブラウザを起動
2. URL: `$baseDomain/customer/account/gdpr/html-view`
3. `LaunchMode.externalApplication`で外部アプリとして開く

### 4-リクエスト作成ボタン押下

1. `showDialog()`でCreateRequestModalを表示
2. ユーザーがタイプ（DELETE/UPDATE）を選択
3. メッセージを入力して保存ボタンを押下
4. `CreateGdprRequest`イベントをBlocに送信
5. 成功後、ダイアログを閉じてリスト再取得

### 5-検索フィールド送信（onSubmitted）

1. 入力値が空の場合: `FetchGdprRequestsList`で全件再取得
2. 入力値が数値でない場合: SnackBarでエラーメッセージ表示
3. 入力値が数値の場合: `GdprSearchRequest`イベントで検索実行

### 6-Revokeボタン押下

1. ステータスが「REVOKED」の場合は処理しない（早期リターン）
2. `RevokeGdprRequest`イベントをBlocに送信（リクエストIDを引数）
3. 成功後、リスト再取得

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示 | gdpr_requests（サーバー側） | SELECT | GDPRリクエスト一覧の取得 |
| リクエスト作成 | gdpr_requests（サーバー側） | INSERT | 新規GDPRリクエストの作成 |
| リクエスト取り消し | gdpr_requests（サーバー側） | UPDATE | ステータスをREVOKEDに更新 |
| リクエスト検索 | gdpr_requests（サーバー側） | SELECT | 特定IDのリクエスト取得 |
| PDFダウンロード | customer_data（サーバー側） | SELECT | 顧客データの抽出・PDF生成 |

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

#### GraphQL Query: gdprRequests

| 操作 | 項目（パラメータ） | 取得条件 | 備考 |
|-----|-----------------|---------|------|
| SELECT | id | customerId | リクエストID |
| SELECT | status | customerId | ステータス（PENDING/REVOKED等） |
| SELECT | type | customerId | リクエストタイプ（DELETE/UPDATE） |
| SELECT | message | customerId | リクエストメッセージ |
| SELECT | createdAt | customerId | 作成日時 |

#### GraphQL Mutation: createGdprRequest

| 操作 | 項目（パラメータ） | 更新値 | 備考 |
|-----|-----------------|--------|------|
| INSERT | type | DELETE/UPDATE | リクエストタイプ |
| INSERT | message | ユーザー入力値 | リクエスト内容 |

#### GraphQL Mutation: revokeGdprRequest

| 操作 | 項目（パラメータ） | 更新値 | 備考 |
|-----|-----------------|--------|------|
| UPDATE | id | リクエストID | 対象リクエスト |
| UPDATE | status | REVOKED | ステータス更新 |

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容 | 表示方法 |
|-------------|---------------|----------|---------------|----------|
| MSG001 | 成功 | リクエスト作成成功 | 「Request created」 | SnackBar |
| MSG002 | エラー | リクエスト作成失敗 | エラーメッセージ（デフォルト:「Failed to create request」） | SnackBar |
| MSG003 | 成功 | リクエスト取り消し成功 | サーバーからのメッセージ | SnackBar |
| MSG004 | エラー | リクエスト取り消し失敗 | エラーメッセージ（デフォルト:「Failed to revoke request」） | SnackBar |
| MSG005 | エラー | 検索失敗 | エラーメッセージ（デフォルト:「Failed to fetch search results」） | SnackBar |
| MSG006 | エラー | 検索ID不正 | 「Please enter a valid numeric ID」 | SnackBar |
| MSG007 | エラー | PDFエクスポート失敗 | エラーメッセージ（デフォルト:「Failed to export PDF」） | SnackBar |
| MSG008 | バリデーション | メッセージ未入力 | 「Message is required.」 | フォームエラー |

## 例外処理

| 例外状況 | 対応処理 | ユーザーへの表示 |
|---------|---------|-----------------|
| ネットワークエラー（一覧取得） | catch句でエラーキャッチ | エラーメッセージをSnackBarで表示 |
| ネットワークエラー（リクエスト作成） | catch句でエラーキャッチ | 「Failed to create request」SnackBar |
| ネットワークエラー（取り消し） | catch句でエラーキャッチ | 「Failed to revoke request」SnackBar |
| 検索結果なし | 空の配列を設定 | 空のリストを表示 |
| PDFダウンロード失敗 | catch句でエラーキャッチ | 「Failed to export PDF」SnackBar |
| 無効なステータスでRevoke | 早期リターン | ボタンがグレーアウト |

## 備考

- GDPRリクエストタイプは2種類: DELETE（データ削除要求）、UPDATE（データ修正要求）
- ステータスが「REVOKED」のリクエストはRevokeボタンがグレーアウトされ操作不可
- PDFエクスポートはBase64エンコードされたデータとして返却され、ローカルファイルとして保存
- HTMLビューは外部ブラウザで開くため、認証Cookieの引き継ぎが必要
- 検索は数値IDのみ受け付け、文字列検索は不可
- 顧客IDはappStoragePref.getCustomerId()から取得

---

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

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

### 推奨読解順序

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

まず、GDPRリクエストのデータモデルと状態管理の構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | gdpr_event.dart | `lib/screens/gdpr/bloc/gdpr_event.dart` | 各イベントクラス（FetchGdprRequestsList, CreateGdprRequest, RevokeGdprRequest, GdprSearchRequest, GdprPdfRequest）を確認 |
| 1-2 | gdpr_state.dart | `lib/screens/gdpr/bloc/gdpr_state.dart` | 各状態クラス（FetchGdprRequestsState, CreateGdprRequestState, RevokeGdprRequestState等）を確認 |
| 1-3 | create_request.dart | `lib/screens/gdpr/view/widget/create_request.dart` | GdprRequestType列挙型（DELETE, UPDATE）とCreateRequestModalの構造を確認 |

**読解のコツ**: GDPRの各操作は別々のイベント/ステートで管理されています。各操作の成功/失敗状態を個別に追跡する設計です。

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

処理の起点となるViewファイルを特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | gdpr_screen.dart | `lib/screens/gdpr/view/gdpr_screen.dart` | ウィジェット構造、各イベントハンドリング、BlocConsumerの使い方 |

**主要処理フロー**:
1. **行26-31**: initStateでGdprBlocを取得し、FetchGdprRequestsListイベントを送信
2. **行48-302**: BlocConsumerで各状態を監視
3. **行50-128**: listenerで各状態変化に応じた処理（SnackBar表示、データ更新等）
4. **行136-162**: アクションボタン行（PDF, HTML, Create Request）
5. **行164-207**: 検索フィールドとバリデーション
6. **行213-289**: リクエスト一覧のListView.builder

#### Step 3: モーダルダイアログを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create_request.dart | `lib/screens/gdpr/view/widget/create_request.dart` | CreateRequestModalのフォーム構成とバリデーション |

**主要処理フロー**:
- **行6-8**: GdprRequestType列挙型の定義
- **行25-37**: createGdprRequest()でリクエスト作成処理
- **行71-104**: DropdownButtonFormFieldでタイプ選択
- **行109-141**: TextFormFieldでメッセージ入力

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | gdpr_bloc.dart | `lib/screens/gdpr/bloc/gdpr_bloc.dart` | 各イベントのハンドリングとAPI呼び出し |

**主要処理フロー**:
- **行29-37**: FetchGdprRequestsListの処理
- **行38-52**: CreateGdprRequestの処理
- **行53-63**: RevokeGdprRequestの処理
- **行65-79**: GdprSearchRequestの処理
- **行80-91**: GdprPdfRequestの処理

#### Step 5: データアクセス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | gdpr_repository.dart | `lib/screens/gdpr/bloc/gdpr_repository.dart` | 各APIメソッドの実装 |
| 5-2 | api_client.dart | `lib/services/api_client.dart` | GDPR関連API呼び出し（行1514-1591付近） |
| 5-3 | mutation_query.dart | `lib/services/mutation_query.dart` | GDPR関連GraphQLクエリ定義（行2100-2190付近） |

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

```
GdprScreen (gdpr_screen.dart)
    │
    ├─ initState() [行26-31]
    │      └─ GdprBloc.add(FetchGdprRequestsList)
    │
    ├─ _exportButton('PDF') [行307-324]
    │      └─ GdprBloc.add(GdprPdfRequest)
    │
    ├─ _exportButton('HTML') [行307-324]
    │      └─ launchUrl() [外部ブラウザ]
    │
    ├─ CreateRequestModal [行143-149]
    │      └─ GdprBloc.add(CreateGdprRequest)
    │
    ├─ 検索フィールド onSubmitted [行188-207]
    │      ├─ GdprBloc.add(FetchGdprRequestsList) [空の場合]
    │      └─ GdprBloc.add(GdprSearchRequest) [ID指定]
    │
    └─ Revokeボタン [行243-282]
           └─ GdprBloc.add(RevokeGdprRequest)
                  │
                  └─ GdprBloc (gdpr_bloc.dart)
                         │
                         └─ mapEventToState() [行28-91]
                                │
                                └─ GdprRequestRepository
                                       │
                                       └─ GdprRequestRepo (gdpr_repository.dart)
                                              │
                                              └─ ApiClient() [各APIメソッド]
```

### データフロー図

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

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ 画面初期化       │    │ FetchGdprRequests│    │ リクエスト一覧   │
│                 │───▶│ List Event       │───▶│ カード形式表示   │
└─────────────────┘    └──────────────────┘    └─────────────────┘

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ リクエスト作成   │    │ CreateGdprRequest│    │ 成功/失敗       │
│ - type (ENUM)   │───▶│ Event            │───▶│ SnackBar表示    │
│ - message       │    └──────────────────┘    │ リスト再取得    │
└─────────────────┘                            └─────────────────┘

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ Revokeボタン    │    │ RevokeGdprRequest│    │ ステータス更新   │
│ - id            │───▶│ Event            │───▶│ リスト再取得    │
└─────────────────┘    └──────────────────┘    └─────────────────┘

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ PDFボタン       │    │ GdprPdfRequest   │    │ PDF保存         │
│                 │───▶│ Event            │───▶│ ファイル保存    │
└─────────────────┘    └──────────────────┘    └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| gdpr_screen.dart | `lib/screens/gdpr/view/gdpr_screen.dart` | View | GDPRリクエスト一覧・操作UI |
| create_request.dart | `lib/screens/gdpr/view/widget/create_request.dart` | Widget | リクエスト作成モーダル |
| gdpr_bloc.dart | `lib/screens/gdpr/bloc/gdpr_bloc.dart` | Bloc | 状態管理とビジネスロジック |
| gdpr_event.dart | `lib/screens/gdpr/bloc/gdpr_event.dart` | Event | Blocイベント定義 |
| gdpr_state.dart | `lib/screens/gdpr/bloc/gdpr_state.dart` | State | Bloc状態定義 |
| gdpr_repository.dart | `lib/screens/gdpr/bloc/gdpr_repository.dart` | Repository | データアクセス層 |
| api_client.dart | `lib/services/api_client.dart` | Service | APIクライアント（行1514-1591） |
| mutation_query.dart | `lib/services/mutation_query.dart` | Query | GraphQLクエリ定義（行2100-2190） |
| file_download.dart | `lib/screens/product_screen/view/file_download.dart` | Utility | ファイルダウンロード処理 |
| app_navigation.dart | `lib/utils/app_navigation.dart` | Router | 画面ルーティング定義（行398-403） |
| route_constants.dart | `lib/utils/route_constants.dart` | Constants | ルート名定数（行44） |
