# 機能設計書 32-商品比較一覧表示

## 概要

本ドキュメントは、比較リストに追加された商品を一覧表示し、商品の仕様を横並びで比較できる機能の設計を記述する。

### 本機能の処理概要

本機能は、ユーザーが複数の商品を比較検討するために、比較リストに追加された商品を横スクロール可能な一覧形式で表示する機能である。

**業務上の目的・背景**：ECサイトにおいて、ユーザーは複数の類似商品の中から最適な商品を選択したいというニーズがある。本機能により、商品のSKU、説明、価格、評価などを横並びで比較でき、購入意思決定を支援する。これにより、ユーザー満足度の向上とコンバージョン率の改善が期待できる。

**機能の利用シーン**：ユーザーがホーム画面や商品一覧画面で複数の商品を比較リストに追加した後、ナビゲーションドロワーやボトムナビゲーションから比較画面にアクセスした際に本機能が実行される。

**主要な処理内容**：
1. 比較画面表示時にGraphQL APIで比較商品一覧を取得
2. 商品情報（画像、価格、名前、SKU、説明、評価）を横スクロールリストで表示
3. 各商品からカート追加、ウィッシュリスト追加/削除、比較リストからの削除が可能
4. 無限スクロールによる追加データ取得に対応
5. 全削除ボタンによる比較リストの一括クリア

**関連システム・外部連携**：GraphQL APIを介したBagistoバックエンドとの通信

**権限による制御**：ログインユーザー、ゲストユーザー共に利用可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 商品比較画面 | 主画面 | 比較リストに追加された商品の比較表示 |

## 機能種別

データ取得・表示処理（READ操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| page | int | Yes | ページ番号 | 1以上の整数 |
| first | int | Yes | 1ページあたりの取得件数 | デフォルト10 |

### 入力データソース

- 画面初期化時に自動取得
- スクロールによるページネーション

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| data | List<CompareProducts> | 比較商品リスト |
| data[].productId | String | 商品ID |
| data[].product | Product | 商品詳細情報 |
| data[].product.name | String | 商品名 |
| data[].product.sku | String | SKUコード |
| data[].product.description | String | 商品説明（HTML） |
| data[].product.priceHtml | PriceHtml | 価格HTML |
| data[].product.averageRating | double | 平均評価 |
| data[].product.images | List<Images> | 商品画像リスト |
| data[].product.isSaleable | bool | 販売可能フラグ |
| data[].product.isInWishlist | bool | ウィッシュリスト登録済フラグ |

### 出力先

- 画面表示: 横スクロール可能な比較リストUI
- カート数バッジ: GlobalData.cartCountController

## 処理フロー

### 処理シーケンス

```
1. 比較画面表示時にCompareScreenFetchEvent発火
   └─ initState内でBloc.add実行
2. Blocがリポジトリを通じてAPI呼び出し
   └─ CompareScreenRepository.callCompareApi
3. ApiClient.getCompareProductsでGraphQL通信
   └─ compareProducts queryを実行
4. レスポンスをCompareProductsDataにパース
   └─ 商品リストを状態として保持
5. CompareView/CompareListウィジェットで表示
   └─ 横スクロールリストとして描画
6. スクロール終端で追加データ取得
   └─ ページ番号をインクリメントして再取得
```

### フローチャート

```mermaid
flowchart TD
    A[比較画面表示] --> B[CompareScreenFetchEvent発火]
    B --> C[API呼び出し]
    C --> D{データ取得成功?}
    D -->|成功| E[商品一覧表示]
    D -->|失敗| F[エラーメッセージ表示]
    E --> G{スクロール終端?}
    G -->|Yes| H{追加データあり?}
    H -->|Yes| B
    H -->|No| I[現在のリスト維持]
    G -->|No| E
    E --> J{商品タップ?}
    J -->|Yes| K[商品詳細画面へ遷移]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-32-01 | ページング | 10件ずつ取得、無限スクロール対応 | 画面表示・スクロール時 |
| BR-32-02 | 横スクロール表示 | 商品を横並びで表示、画面幅の50%ずつ | 常時 |
| BR-32-03 | 販売不可商品 | isSaleable=falseの場合、カート追加ボタン無効化 | 商品表示時 |
| BR-32-04 | 空リスト処理 | 比較リストが空の場合はEmptyDataView表示 | データ取得後 |

### 計算ロジック

- 表示幅: MediaQuery.of(context).size.width / 2
- 評価表示: averageRatingをint変換して星表示

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 一覧表示 | compare_items | SELECT | 比較リスト取得 |
| 一覧表示 | products | SELECT | 商品詳細取得（JOIN） |

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

#### compare_items

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | customer_id, product_id | 現在のユーザーID | バックエンドで処理 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | graphqlErrors | API通信失敗 | エラーメッセージ表示 |
| - | ネットワークエラー | 通信不可 | エラーメッセージ表示 |
| - | データなし | 比較リストが空 | EmptyDataView表示 |

### リトライ仕様

自動リトライなし。画面再表示で再取得される。

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

読み取り専用のため、トランザクション管理は不要。

## パフォーマンス要件

- API応答時間: 3秒以内
- ページング: 10件ずつ取得で負荷分散
- 画像: 非同期読み込みでUI応答性確保

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

- セッションまたは認証トークンによるユーザー識別
- 他ユーザーの比較リストは参照不可

## 備考

- HTML形式の商品説明はHtmlWidgetで描画
- 評価色はReviewColorHelper.getColorで動的に設定

---

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

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

### 推奨読解順序

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

比較商品データのモデル構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | compare_product_model.dart | `lib/screens/compare/data_model/compare_product_model.dart` | CompareProductsData、CompareProductsクラスの構造 |

**読解のコツ**: CompareProductsはproductフィールドを持ち、これがProduct型で商品詳細を保持する。

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

画面表示の起点となるUI部分を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | compare_screen.dart | `lib/screens/compare/view/compare_screen.dart` | 画面初期化とBloc連携 |

**主要処理フロー**:
1. **34-47行目**: initStateでBlocイベント発火、スクロールリスナー設定
2. **129-202行目**: BlocConsumerによる状態監視
3. **271-310行目**: データ有無による表示切り替え

#### Step 3: Bloc（状態管理層）を理解する

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | compare_screen_event.dart | `lib/screens/compare/bloc/compare_screen_event.dart` | CompareScreenFetchEventの定義 |
| 3-2 | compare_screen_bloc.dart | `lib/screens/compare/bloc/compare_screen_bloc.dart` | ページング処理とAPI呼び出し |

**主要処理フロー**:
- **27-62行目**: CompareScreenFetchEventの処理、ページング制御

#### Step 4: Repository（データアクセス層）を理解する

API呼び出しの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | compare_screen_repository.dart | `lib/screens/compare/bloc/compare_screen_repository.dart` | callCompareApiメソッド |

**主要処理フロー**:
- **24-33行目**: getCompareProducts呼び出し

#### Step 5: 表示ウィジェットを理解する

実際の表示コンポーネントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | compare_view.dart | `lib/screens/compare/view/widget/compare_view.dart` | 横スクロールレイアウト |
| 5-2 | compare_list.dart | `lib/screens/compare/view/widget/compare_list.dart` | 商品カードUI |

**主要処理フロー**:
- compare_view.dart: SKU、説明セクションの横スクロール表示
- compare_list.dart **69-290行目**: 各商品カードの詳細表示

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

```
CompareScreen (UI)
    │
    ├─ initState: CompareScreenBloc.add(CompareScreenFetchEvent)
    │
    ├─ CompareScreenBloc
    │      │
    │      └─ CompareScreenRepository.callCompareApi(page, limit)
    │             │
    │             └─ ApiClient.getCompareProducts(page, limit)
    │                    │
    │                    └─ GraphQL query: compareProducts
    │
    └─ UI描画
           ├─ CompareView
           │      └─ CompareList（商品カード）
           │
           └─ FloatingActionButton（全削除）
```

### データフロー図

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

page, limit ────────▶ CompareScreenBloc ──────▶ 商品リスト表示
                          │
                    CompareScreenRepository    横スクロールUI
                          │
                    ApiClient                  SKU・説明表示
                          │
                    GraphQL Server             カート数バッジ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| compare_screen.dart | `lib/screens/compare/view/compare_screen.dart` | ソース | 比較画面本体 |
| compare_view.dart | `lib/screens/compare/view/widget/compare_view.dart` | ソース | 横スクロールレイアウト |
| compare_list.dart | `lib/screens/compare/view/widget/compare_list.dart` | ソース | 商品カードUI |
| compare_loader_view.dart | `lib/screens/compare/view/widget/compare_loader_view.dart` | ソース | ローディング表示 |
| compare_screen_bloc.dart | `lib/screens/compare/bloc/compare_screen_bloc.dart` | ソース | 状態管理Bloc |
| compare_screen_event.dart | `lib/screens/compare/bloc/compare_screen_event.dart` | ソース | イベント定義 |
| compare_screen_state.dart | `lib/screens/compare/bloc/compare_screen_state.dart` | ソース | 状態定義 |
| compare_screen_repository.dart | `lib/screens/compare/bloc/compare_screen_repository.dart` | ソース | リポジトリ層 |
| compare_product_model.dart | `lib/screens/compare/data_model/compare_product_model.dart` | ソース | データモデル |
| api_client.dart | `lib/services/api_client.dart` | ソース | API通信クライアント |
