# 機能設計書 16-ダウンロード可能商品

## 概要

本ドキュメントは、Eコマースモバイルアプリケーションにおけるダウンロード可能商品機能の設計仕様を定義する。本機能は、ユーザーが購入したダウンロード可能商品（デジタルコンテンツ）の一覧表示と実際のダウンロード処理を提供する。

### 本機能の処理概要

**業務上の目的・背景**：
ECサイトにおいて、物理的な配送を必要としないデジタルコンテンツ（ソフトウェア、音楽、電子書籍等）の販売は重要な収益源である。ユーザーが購入したデジタルコンテンツを簡単に確認・ダウンロードできる機能を提供することで、顧客満足度を向上させる。

**機能の利用シーン**：
- マイアカウントから「ダウンロード可能商品」メニューを選択した際
- 購入したデジタルコンテンツをダウンロードしたい際
- ダウンロード履歴を確認したい際

**主要な処理内容**：
1. 購入済みダウンロード可能商品の一覧取得（ページネーション対応）
2. フィルター機能（タイトル、ステータス、注文ID、注文日範囲）
3. ダウンロード回数の表示（購入回数、使用回数、残り回数）
4. 実際のファイルダウンロード処理
5. Base64エンコードされたファイルデータの取得とローカル保存

**関連システム・外部連携**：
- Bagisto GraphQL API（ダウンロード可能商品取得、ダウンロードリンク取得）
- DownloadFile（ファイル保存ユーティリティ）

**権限による制御**：
- ログイン必須（顧客固有のダウンロード可能商品を表示）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | ダウンロード可能商品画面 | 主画面 | ダウンロード可能商品一覧表示、ダウンロード |

## 機能種別

CRUD操作（Read）/ ファイルダウンロード / フィルタリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| page | int | Yes | ページ番号 | 1以上 |
| limit | int | Yes | 1ページあたりの件数 | デフォルト10 |
| title | String? | No | 商品名フィルター | - |
| status | String? | No | ステータスフィルター | PENDING/AVAILABLE/EXPIRED |
| orderId | String? | No | 注文IDフィルター | - |
| orderDateFrom | String? | No | 注文日開始フィルター | 日付形式 |
| orderDateTo | String? | No | 注文日終了フィルター | 日付形式 |

### 入力データソース

- ユーザー入力（フィルター条件）
- GraphQL API（ダウンロード可能商品データ）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| productsList | DownloadableProductModel? | ダウンロード可能商品一覧 |
| downloadLink | Download? | ダウンロードリンク情報 |
| downloadLinkProduct | DownloadLinkDataModel? | Base64エンコードされたファイルデータ |

### 出力先

- 画面表示（商品一覧）
- ローカルストレージ（ダウンロードファイル）

## 処理フロー

### 処理シーケンス

```
1. 画面初期化（initState）
   └─ DownloadableProductsCustomerEvent発行（page:1, limit:10）
2. 商品一覧取得
   └─ DownloadableProductsRepositoryImp.getDownloadableProductCustomerData()
3. 商品一覧表示
   └─ ListView.builderでDownloadProductItem表示
4. スクロールリスナー
   └─ 追加ページ読み込み（hasMoreData判定）
5. フィルター適用
   └─ フィルターモーダルから条件設定
6. ダウンロード処理
   ├─ DownloadProductEvent発行
   ├─ DownloadBase64ProductEvent発行
   └─ DownloadFile.saveBase64String()でファイル保存
```

### フローチャート

```mermaid
flowchart TD
    A[ダウンロード可能商品画面] --> B[DownloadableProductsCustomerEvent発行]
    B --> C[API呼び出し]
    C --> D{成功?}
    D -->|Yes| E[商品一覧表示]
    D -->|No| F[エラー表示]
    E --> G{ユーザー操作}
    G -->|スクロール| H{さらにデータあり?}
    G -->|フィルター| I[フィルターモーダル表示]
    G -->|ダウンロード| J[DownloadProductEvent発行]
    H -->|Yes| K[page++で追加取得]
    H -->|No| L[追加読み込みなし]
    I --> M[フィルター条件設定]
    M --> B
    J --> N[DownloadBase64ProductEvent発行]
    N --> O[Base64データ取得]
    O --> P[DownloadFile.saveBase64String]
    P --> Q[ファイル保存完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ログイン必須 | ダウンロード可能商品の閲覧はログインユーザーのみ | 常時 |
| BR-02 | ダウンロード回数制限 | downloadBought - downloadUsed = 残りダウンロード回数 | ダウンロード時 |
| BR-03 | ページネーション | 10件ずつ取得、スクロールで追加読み込み | 一覧表示時 |
| BR-04 | ステータスフィルター | PENDING/AVAILABLE/EXPIRED（大文字変換） | フィルター適用時 |

### 計算ロジック

- 残りダウンロード回数: `downloadBought - downloadUsed`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 商品一覧取得 | downloadable_link_purchases | SELECT | 顧客のダウンロード可能商品取得 |
| ダウンロードリンク取得 | downloadable_links | SELECT | ダウンロードリンク情報取得 |
| ダウンロード使用回数更新 | downloadable_link_purchases | UPDATE | downloadUsedをインクリメント |

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

#### downloadable_link_purchases（GraphQL経由）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全フィールド | customer_id = ログインユーザー | GraphQL: customerDownloadableProducts |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | API失敗 | ダウンロード商品取得エラー | DownloadableProductsCustomerDataState.failでエラー表示 |
| - | ダウンロード失敗 | ファイルダウンロードエラー | ShowMessage.errorNotificationでエラー表示 |

### リトライ仕様

- Pull-to-Refresh対応（RefreshIndicator）
- スクロールによる追加読み込み

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

ダウンロード使用回数の更新はバックエンドでトランザクション制御。

## パフォーマンス要件

- 商品一覧取得: 2秒以内
- ファイルダウンロード: ファイルサイズに依存
- SkeletonLoaderでローディング表示

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

- ログイン必須チェック
- 顧客固有のダウンロード可能商品のみ表示
- ダウンロード回数制限の適用

## 備考

- Base64エンコードされたファイルデータをデコードして保存
- DownloadFile.saveBase64String()でファイル名を指定して保存

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | downloadable_product_model.dart | `lib/screens/downloadable_products/data_model/downloadable_product_model.dart` | DownloadableProductModel, DownloadableLinkPurchasesの構造 |
| 1-2 | download_product_Image_model.dart | `lib/screens/downloadable_products/data_model/download_product_Image_model.dart` | Download, DownloadLinkDataModelの構造 |

**読解のコツ**: DownloadableLinkPurchasesにdownloadBought（購入回数）とdownloadUsed（使用回数）があり、残り回数を計算できる。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | downloadable_products.dart | `lib/screens/downloadable_products/view/downloadable_products.dart` | DownLoadableScreenクラス |

**主要処理フロー**:
1. **38-44行目**: initState()でBLoC取得、初期イベント発行、スクロールリスナー設定
2. **46-60行目**: _setItemScrollListener()で無限スクロール実装
3. **62-65行目**: hasMoreData()で追加データ有無判定
4. **107-134行目**: BlocConsumerでダウンロード結果を処理
5. **173-206行目**: downloadableList()で商品一覧表示

#### Step 3: BLoC層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | downloadable_products_bloc.dart | `lib/screens/downloadable_products/bloc/downloadable_products_bloc.dart` | イベント処理ロジック |
| 3-2 | dowbloadable_products_repository.dart | `lib/screens/downloadable_products/bloc/dowbloadable_products_repository.dart` | API呼び出し |

**主要処理フロー（downloadable_products_bloc.dart）**:
- **25-40行目**: DownloadableProductsCustomerEventの処理（商品一覧取得）
- **41-53行目**: DownloadProductEventの処理（ダウンロードリンク取得）
- **54-68行目**: DownloadBase64ProductEventの処理（Base64データ取得）

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

```
DownLoadableScreen (view/downloadable_products.dart)
    │
    ├─ DownloadableProductsBloc (bloc/downloadable_products_bloc.dart)
    │      │
    │      └─ DownloadableProductsRepositoryImp (bloc/dowbloadable_products_repository.dart)
    │             │
    │             ├─ ApiClient.getCustomerDownloadableProducts() → 商品一覧取得
    │             ├─ ApiClient.downloadLinksProduct() → ダウンロードリンク取得
    │             └─ ApiClient.downloadLinksProductAPI() → Base64データ取得
    │
    ├─ DownloadableOrderFilters (view/widgets/downloadable_order_filter.dart)
    │      └─ フィルター条件入力
    │
    ├─ DownloadProductItem (view/widgets/download_product_item.dart)
    │      └─ 商品カード表示
    │
    └─ DownloadFile (utils/download_file.dart)
           └─ saveBase64String() → ファイル保存
```

### データフロー図

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

page, limit ────────────▶ DownloadableProductsCustomerEvent
(+ フィルター)                │
                              ▼
                         DownloadableProductsRepositoryImp.getDownloadableProductCustomerData()
                              │
                              ▼
                         ApiClient.getCustomerDownloadableProducts()
                              │
                              ▼
                         GraphQL API
                              │
                              ▼
                         DownloadableProductModel ───▶ 商品一覧表示

ダウンロードボタン ─────▶ DownloadProductEvent
                              │
                              ▼
                         ApiClient.downloadLinksProduct()
                              │
                              ▼
                         Download ──────────────────▶ DownloadBase64ProductEvent
                                                            │
                                                            ▼
                                                    ApiClient.downloadLinksProductAPI()
                                                            │
                                                            ▼
                                                    DownloadLinkDataModel
                                                            │
                                                            ▼
                                                    DownloadFile.saveBase64String()
                                                            │
                                                            ▼
                                                    ファイル保存完了
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| downloadable_products.dart | `lib/screens/downloadable_products/view/downloadable_products.dart` | ソース | ダウンロード可能商品画面UI |
| downloadable_products_bloc.dart | `lib/screens/downloadable_products/bloc/downloadable_products_bloc.dart` | ソース | 状態管理BLoC |
| downloadable_products_event.dart | `lib/screens/downloadable_products/bloc/downloadable_products_event.dart` | ソース | イベント定義 |
| downloadable_products_state.dart | `lib/screens/downloadable_products/bloc/downloadable_products_state.dart` | ソース | 状態定義 |
| dowbloadable_products_repository.dart | `lib/screens/downloadable_products/bloc/dowbloadable_products_repository.dart` | ソース | APIリポジトリ |
| downloadable_product_model.dart | `lib/screens/downloadable_products/data_model/downloadable_product_model.dart` | ソース | 商品データモデル |
| download_product_Image_model.dart | `lib/screens/downloadable_products/data_model/download_product_Image_model.dart` | ソース | ダウンロードデータモデル |
| downloadable_order_filter.dart | `lib/screens/downloadable_products/view/widgets/downloadable_order_filter.dart` | ソース | フィルターWidget |
| download_product_item.dart | `lib/screens/downloadable_products/view/widgets/download_product_item.dart` | ソース | 商品アイテムWidget |
