# 機能設計書 24-クーポン適用

## 概要

本ドキュメントは、ECモバイルアプリにおける割引クーポンコードをカートに適用する機能について設計仕様を定義する。

### 本機能の処理概要

本機能は、ユーザーが保有するクーポンコードをショッピングカートに適用し、割引を反映する機能である。

**業務上の目的・背景**：ECサイトにおいて、クーポン割引は顧客獲得・リテンションの重要な施策である。キャンペーンや会員特典として配布されるクーポンコードを適用することで、ユーザーは割引価格で商品を購入できる。本機能により、マーケティング施策の効果を最大化し、顧客満足度を向上させる。

**機能の利用シーン**：ユーザーがカート画面でチェックアウト前に、保有しているクーポンコードを入力して割引を受けたい場合に利用される。メールやSNSで配布されたプロモーションコード、会員限定クーポンなどを適用する際に活用される。

**主要な処理内容**：
1. カート画面のクーポン入力欄にコードを入力
2. 「Apply」ボタンを押下
3. 入力値のバリデーション（空チェック）
4. AddCouponCartEventを発行し、GraphQL API経由でサーバーに送信
5. サーバーでクーポンの有効性を検証
6. 成功時はカートに割引を適用し、合計金額を再計算
7. 成功メッセージを表示し、クーポンコードを表示

**関連システム・外部連携**：Bagisto GraphQL APIとの連携により、`applyCoupon` mutationを使用してクーポンを適用する。

**権限による制御**：ゲストユーザー・ログインユーザーいずれも本機能を利用可能。クーポンによっては会員限定など適用条件がある場合がある。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | カート画面 | 主画面 | 割引クーポンコードの適用処理 |

## 機能種別

CRUD操作（Create/Update）/ バリデーション / 計算処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| code | String | Yes | クーポンコード | 空でないこと |

### 入力データソース

画面入力（ApplyCouponViewウィジェットのテキストフィールド）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| success | bool | 処理成功可否 |
| message | String | 処理結果メッセージ |

### 出力先

- 画面表示（クーポン適用状態の表示、割引額の反映）
- カートデータの更新（couponCodeフィールド）
- 合計金額表示の更新（割引額を反映）

## 処理フロー

### 処理シーケンス

```
1. ユーザーがクーポンコードを入力
   └─ TextEditingControllerに値を保持
2. 「Apply」ボタンを押下
   └─ バリデーション実行（空チェック）
3. バリデーション成功時、AddCouponCartEventを発行
   └─ コード文字列をパラメータとして渡す
4. CartScreenBlocがイベントを受信
   └─ repository.addCouponを呼び出し
5. CartScreenRepositoryがApiClientを経由でAPI呼び出し
   └─ GraphQL mutation applyCouponを実行
6. サーバーでクーポン検証
   └─ 有効性、適用条件、使用回数等をチェック
7. 成功時: AddCouponState.successを発行
   └─ カートデータを再取得し割引を反映
8. 成功メッセージを表示、ボタン表示を「Remove」に変更
```

### フローチャート

```mermaid
flowchart TD
    A[クーポンコード入力] --> B[Applyボタン押下]
    B --> C{バリデーション}
    C -->|空| D[エラー表示]
    C -->|有効| E[AddCouponCartEvent発行]
    E --> F[CartScreenBloc処理]
    F --> G[API呼び出し]
    G --> H{サーバー検証}
    H -->|成功| I[AddCouponState.success]
    I --> J[カートデータ再取得]
    J --> K[割引額を反映]
    K --> L[成功メッセージ表示]
    L --> M[ボタンをRemoveに変更]
    H -->|失敗| N[AddCouponState.fail]
    N --> O[エラーメッセージ表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-024-01 | 空入力禁止 | クーポンコードは空で送信できない | Apply押下時 |
| BR-024-02 | クーポン有効性 | サーバー側でクーポンの有効性を検証 | API呼び出し時 |
| BR-024-03 | 単一クーポン | 一度に適用できるクーポンは1つのみ | クーポン適用時 |
| BR-024-04 | 適用後表示変更 | クーポン適用後はボタンが「Remove」に変わる | 適用成功時 |

### 計算ロジック

割引後合計 = 商品合計 - クーポン割引額 + 配送料 + 税額

※クーポンの割引方式（定額割引/パーセント割引）はサーバー側で計算

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| クーポン適用 | carts | UPDATE | カートにクーポンコードを設定 |
| 割引額計算 | carts | UPDATE | 割引額を再計算して更新 |

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

#### carts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | coupon_code | ユーザー入力値 | クーポンコードを保存 |
| UPDATE | discount_amount | 計算結果 | 割引額を更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 入力エラー | クーポンコードが空 | バリデーションエラー表示 |
| - | クーポン無効 | 存在しないコード | サーバーエラーメッセージ表示 |
| - | クーポン期限切れ | 有効期限を過ぎている | サーバーエラーメッセージ表示 |
| - | 適用条件不足 | 最低購入金額未満など | サーバーエラーメッセージ表示 |
| - | 使用回数超過 | 使用上限に達している | サーバーエラーメッセージ表示 |

### リトライ仕様

自動リトライは行わない。ユーザーに再入力を促す。

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

サーバー側でトランザクション管理。クーポン検証と適用は同一トランザクション内で実行。

## パフォーマンス要件

- API応答時間: 2秒以内を目標
- バリデーション: 即座に実行

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

- クーポンコードの検証はサーバー側で実施
- 不正なクーポン使用の防止はサーバー側で制御

## 備考

適用済みクーポンは画面上に表示され、「Remove」ボタンで削除可能。

---

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

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

### 推奨読解順序

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

API応答のデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apply_coupon.dart | `lib/screens/cart_screen/cart_model/apply_coupon.dart` | ApplyCouponクラス（success, messageフィールド） |
| 1-2 | cart_data_model.dart | `lib/screens/cart_screen/cart_model/cart_data_model.dart` | CartModel.couponCodeフィールド |

**読解のコツ**: ApplyCouponモデルはシンプルなsuccess/message構造。カートモデルのcouponCodeで適用状態を管理。

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

クーポン入力UIからイベント発行までの流れを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | apply_coupon_view.dart | `lib/screens/cart_screen/widget/apply_coupon_view.dart` | クーポン入力UI、バリデーション、Apply/Removeボタン |

**主要処理フロー**:
1. **64-80行目**: バリデーション付きテキストフィールド
2. **71-79行目**: 空チェックバリデータ
3. **98-114行目**: Apply/Removeボタン押下時の処理分岐
4. **105-107行目**: AddCouponCartEvent発行

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

状態管理とビジネスロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cart_screen_event.dart | `lib/screens/cart_screen/bloc/cart_screen_event.dart` | AddCouponCartEventの定義（23-26行目） |
| 3-2 | cart_screen_state.dart | `lib/screens/cart_screen/bloc/cart_screen_state.dart` | AddCouponStateの定義（21-32行目） |
| 3-3 | cart_screen_bloc.dart | `lib/screens/cart_screen/bloc/cart_screen_bloc.dart` | AddCouponCartEventの処理（57-69行目） |

**主要処理フロー**:
- **57-69行目**: AddCouponCartEventを受信し、repository経由でAPI呼び出し、success判定で状態分岐

#### Step 4: リポジトリ層とAPI層を理解する

実際のAPI呼び出し処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cart_screen_repository.dart | `lib/screens/cart_screen/bloc/cart_screen_repository.dart` | addCouponメソッド（72-82行目） |
| 4-2 | api_client.dart | `lib/services/api_client.dart` | applyCoupon API呼び出し（528-540行目） |
| 4-3 | mutation_query.dart | `lib/services/mutation_query.dart` | GraphQL mutation定義（398-409行目） |

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

```
ApplyCouponView (apply_coupon_view.dart)
    │
    ├─ TextFormField.validator - 空チェック
    │
    └─ MaterialButton.onPressed
           │
           └─ _discountCouponFormKey.currentState.validate()
                  │
                  └─ (couponCode == null) ? AddCouponCartEvent : RemoveCouponCartEvent
                         │
                         └─ CartScreenBloc.mapEventToState
                                │
                                └─ CartScreenRepository.addCoupon
                                       │
                                       └─ ApiClient.applyCoupon
                                              │
                                              └─ MutationQuery.applyCoupon
```

### データフロー図

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

クーポンコード入力 ───▶ バリデーション ───▶ AddCouponCartEvent
     (UI)              (空チェック)         (イベント発行)
                                                  │
                        ┌─────────────────────────┘
                        ▼
              CartScreenBloc
                        │
                        ▼
              CartScreenRepository
                        │
                        ▼
                  ApiClient
                        │
                        ▼
           GraphQL API (applyCoupon)
                        │
                        ▼
              ApplyCoupon ───▶ AddCouponState ───▶ UI更新
              (応答データ)       (状態通知)      (割引反映)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cart_screen.dart | `lib/screens/cart_screen/cart_screen.dart` | ソース | カート画面メインWidget |
| apply_coupon_view.dart | `lib/screens/cart_screen/widget/apply_coupon_view.dart` | ソース | クーポン入力UI |
| cart_screen_bloc.dart | `lib/screens/cart_screen/bloc/cart_screen_bloc.dart` | ソース | カート画面BLoC |
| cart_screen_event.dart | `lib/screens/cart_screen/bloc/cart_screen_event.dart` | ソース | BLoCイベント定義 |
| cart_screen_state.dart | `lib/screens/cart_screen/bloc/cart_screen_state.dart` | ソース | BLoC状態定義 |
| cart_screen_repository.dart | `lib/screens/cart_screen/bloc/cart_screen_repository.dart` | ソース | カート画面リポジトリ |
| apply_coupon.dart | `lib/screens/cart_screen/cart_model/apply_coupon.dart` | ソース | クーポン応答モデル |
| cart_data_model.dart | `lib/screens/cart_screen/cart_model/cart_data_model.dart` | ソース | カートデータモデル |
| api_client.dart | `lib/services/api_client.dart` | ソース | API通信クライアント |
| mutation_query.dart | `lib/services/mutation_query.dart` | ソース | GraphQLクエリ定義 |
