# 機能設計書 25-クーポン削除

## 概要

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

### 本機能の処理概要

本機能は、ショッピングカートに適用されているクーポンコードを削除し、割引を解除する機能である。

**業務上の目的・背景**：ECサイトにおいて、ユーザーが適用したクーポンを取り消したい場合がある。別のクーポンを使用したい場合、クーポンなしの正規価格で購入したい場合、または誤って適用した場合に、クーポンを簡単に削除できる機能が必要である。本機能により、柔軟なクーポン管理を実現する。

**機能の利用シーン**：ユーザーがカート画面で既に適用されているクーポンを取り消したい場合に利用される。より割引率の高い別のクーポンを適用したい場合や、クーポンの適用条件を満たさなくなった場合などに活用される。

**主要な処理内容**：
1. カート画面にてクーポン適用済み状態で「Remove」ボタンを押下
2. RemoveCouponCartEventを発行
3. GraphQL API経由でサーバーにクーポン削除リクエストを送信
4. サーバーでクーポンを解除し、割引を取り消し
5. 成功時はカートの割引を解除し、合計金額を再計算
6. 成功メッセージを表示し、クーポン入力欄をクリア

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

**権限による制御**：ゲストユーザー・ログインユーザーいずれも本機能を利用可能。自身のカートに適用されたクーポンのみ削除可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | カート画面 | 主画面 | 適用済みクーポンの削除処理 |

## 機能種別

CRUD操作（Delete）/ 計算処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| cartDetailsModel | CartModel | Yes | 現在のカートデータ（内部使用） | - |

### 入力データソース

画面操作（「Remove」ボタン押下）

## 出力仕様

### 出力データ

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

### 出力先

- 画面表示（クーポン入力欄をクリア、ボタンを「Apply」に戻す）
- カートデータの更新（couponCodeをnullに設定）
- 合計金額表示の更新（割引額を解除）

## 処理フロー

### 処理シーケンス

```
1. ユーザーがクーポン適用済み状態で「Remove」ボタンを押下
   └─ ApplyCouponView.onPressed処理
2. RemoveCouponCartEventを発行
   └─ cartDetailsModelをパラメータとして渡す
3. CartScreenBlocがイベントを受信
   └─ repository.removeCouponを呼び出し
4. CartScreenRepositoryがApiClientを経由でAPI呼び出し
   └─ GraphQL mutation removeCouponを実行
5. サーバーでクーポンを解除
   └─ カートからクーポン情報を削除、割引額を0に
6. 成功時: RemoveCouponCartState.successを発行
   └─ カートデータを再取得し画面更新
7. クーポン入力欄をクリア、ボタンを「Apply」に変更
8. 成功メッセージを表示
```

### フローチャート

```mermaid
flowchart TD
    A[Removeボタン押下] --> B[RemoveCouponCartEvent発行]
    B --> C[CartScreenBloc処理]
    C --> D[API呼び出し]
    D --> E{成功?}
    E -->|Yes| F[RemoveCouponCartState.success]
    F --> G[cartDetailsModel.couponCode = null]
    G --> H[カートデータ再取得]
    H --> I[入力欄クリア]
    I --> J[ボタンをApplyに変更]
    J --> K[成功メッセージ表示]
    E -->|No| L[RemoveCouponCartState.fail]
    L --> M[エラーメッセージ表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-025-01 | 適用済み条件 | クーポンが適用されている場合のみ削除可能 | Remove押下時 |
| BR-025-02 | 即座反映 | 削除成功時は即座に割引を解除 | API成功時 |
| BR-025-03 | UI状態変更 | 削除後はボタンが「Apply」に戻る | 削除成功時 |
| BR-025-04 | 入力欄クリア | 削除後はクーポン入力欄を空にする | 削除成功時 |

### 計算ロジック

削除後合計 = 商品合計 + 配送料 + 税額（割引額を0に戻す）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| クーポン削除 | carts | UPDATE | カートからクーポンコードを削除 |
| 割引額リセット | carts | UPDATE | 割引額を0に戻す |

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

#### carts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | coupon_code | null | クーポンコードを削除 |
| UPDATE | discount_amount | 0 | 割引額をリセット |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ネットワークエラー | API通信失敗 | エラーメッセージを表示 |
| - | セッション切れ | カートセッションが無効 | エラー表示 |

### リトライ仕様

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

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

サーバー側でトランザクション管理。クーポン削除と割引額再計算は同一トランザクション内で実行。

## パフォーマンス要件

- API応答時間: 2秒以内を目標

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

- 認証: ゲストユーザーはセッションベース、ログインユーザーはトークン認証
- アクセス権限: 自身のカートのクーポンのみ削除可能

## 備考

クーポン削除後は、同じクーポンまたは別のクーポンを再度適用可能。

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | apply_coupon.dart | `lib/screens/cart_screen/cart_model/apply_coupon.dart` | ApplyCouponクラス（削除時も同じモデルを使用） |
| 1-2 | cart_data_model.dart | `lib/screens/cart_screen/cart_model/cart_data_model.dart` | CartModel.couponCodeフィールド |

**読解のコツ**: removeCouponもApplyCouponモデルを返す点に注意。

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

Removeボタン押下からイベント発行までの流れを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | apply_coupon_view.dart | `lib/screens/cart_screen/widget/apply_coupon_view.dart` | Removeボタンの条件分岐と処理 |

**主要処理フロー**:
1. **103-104行目**: クーポン適用済みかどうかの判定
2. **109-112行目**: RemoveCouponCartEvent発行
3. **116-126行目**: ボタン表示の切り替え（Apply/Remove）

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cart_screen_event.dart | `lib/screens/cart_screen/bloc/cart_screen_event.dart` | RemoveCouponCartEventの定義（17-21行目） |
| 3-2 | cart_screen_state.dart | `lib/screens/cart_screen/bloc/cart_screen_state.dart` | RemoveCouponCartStateの定義（34-44行目） |
| 3-3 | cart_screen_bloc.dart | `lib/screens/cart_screen/bloc/cart_screen_bloc.dart` | RemoveCouponCartEventの処理（70-82行目） |

**主要処理フロー**:
- **70-82行目**: RemoveCouponCartEventを受信し、cartDetailsModel.couponCodeをnullに設定後、repository経由でAPI呼び出し

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cart_screen_repository.dart | `lib/screens/cart_screen/bloc/cart_screen_repository.dart` | removeCouponメソッド（84-94行目） |
| 4-2 | api_client.dart | `lib/services/api_client.dart` | removeCoupon API呼び出し（542-554行目） |
| 4-3 | mutation_query.dart | `lib/services/mutation_query.dart` | GraphQL mutation定義（411-419行目） |

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

```
ApplyCouponView (apply_coupon_view.dart)
    │
    └─ MaterialButton.onPressed (couponCode != null の場合)
           │
           └─ RemoveCouponCartEvent(cartDetailsModel)発行
                  │
                  └─ CartScreenBloc.mapEventToState
                         │
                         ├─ cartDetailsModel.couponCode = null (ローカル更新)
                         │
                         └─ CartScreenRepository.removeCoupon
                                │
                                └─ ApiClient.removeCoupon
                                       │
                                       └─ MutationQuery.removeCoupon
```

### データフロー図

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

Removeボタン押下 ───▶ RemoveCouponCartEvent ───▶ CartScreenBloc
      (UI)             (イベント発行)           (状態管理)
                                                     │
                           ┌─────────────────────────┘
                           ▼
                 couponCode = null (ローカル)
                           │
                           ▼
                 CartScreenRepository
                           │
                           ▼
                     ApiClient
                           │
                           ▼
              GraphQL API (removeCoupon)
                           │
                           ▼
                 ApplyCoupon ───▶ RemoveCouponCartState ───▶ 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、Remove処理 |
| 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` | ソース | クーポン応答モデル |
| api_client.dart | `lib/services/api_client.dart` | ソース | API通信クライアント |
| mutation_query.dart | `lib/services/mutation_query.dart` | ソース | GraphQLクエリ定義 |
