# 機能設計書 42-複数商品削除

## 概要

本ドキュメントは、ショッピングカートから複数の商品（ラインアイテム）を一括で削除する機能の設計を定義するものである。

### 本機能の処理概要

カート内の複数の商品を一度のリクエストで削除し、カートの再計算を行う機能である。

**業務上の目的・背景**：顧客がカート内の複数商品を同時に削除したい場合に、個別に削除操作を繰り返す手間を省くことを目的としている。特にカート内に多くの商品がある場合や、まとめて商品を整理したい場合に効率的な操作を提供する。これにより、ユーザー体験の向上とサーバー負荷の軽減（複数リクエストの回避）を実現する。

**機能の利用シーン**：カートページで複数の商品を選択して一括削除する場合や、JavaScript処理で複数アイテムを同時に削除する必要がある場合に使用される。管理画面からのカート操作やAPIを通じた一括処理にも対応している。

**主要な処理内容**：
1. リクエストから削除対象の商品IDリスト（ラインアイテムIDの配列）を取得
2. IDリストの形式バリデーション（配列であること、各要素が文字列であること）
3. CartServiceを通じてカートから複数商品を一括削除
4. カートを再計算して合計金額等を更新
5. 成功/エラーメッセージをフラッシュメッセージとして設定
6. レスポンスを返却

**関連システム・外部連携**：カート永続化システム（Redis/Database）との連携、価格計算エンジンとの連携

**権限による制御**：特別な権限制御はなく、カートの所有者（セッション保持者）であれば実行可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | カートページ | 主画面 | カートから複数商品を一括削除 |
| 8 | オフキャンバスカート | 参照画面 | サイドバー形式のカートから複数商品削除 |

## 機能種別

CRUD操作（Delete）/ バッチ処理 / データ更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| ids | array | Yes | 削除対象のラインアイテムIDの配列 | 配列であること、空でないこと、各要素が文字列であること |

### 入力データソース

- POSTリクエストボディ: `ids` パラメータとして配列形式で送信
- Cartオブジェクト: セッションまたは永続化ストレージから自動取得
- SalesChannelContext: リクエストから自動解決

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| redirect | Response | リダイレクトレスポンス（通常はカートページまたは前のページ） |
| flash_message | string | 成功時: "checkout.cartUpdateSuccess"、失敗時: "error.message-default" |

### 出力先

- HTTPレスポンス（リダイレクトまたはAJAXレスポンス）
- フラッシュメッセージ（セッション経由でビューに表示）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POSTまたはDELETEメソッドで /checkout/line-item/delete にアクセス
2. パラメータ取得
   └─ RequestParamHelper::get()でidsパラメータを取得
3. バリデーション
   └─ idsが配列であること、空でないことを検証
   └─ 各IDが文字列であることを検証
4. 商品削除実行
   └─ CartService::removeItems() を呼び出してカートから複数商品を一括削除
5. カート再計算
   └─ CartCalculator::calculate() でカート合計を再計算
6. 永続化
   └─ CartPersister::save() で更新されたカートを保存
7. イベント発火
   └─ BeforeLineItemRemovedEvent（各アイテム）、AfterLineItemRemovedEvent、CartChangedEvent を発火
8. フラッシュメッセージ設定
   └─ 成功時は "checkout.cartUpdateSuccess"、エラー時は "error.message-default"
9. レスポンス返却
   └─ createActionResponse() でリダイレクトまたはJSONレスポンスを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: deleteLineItems] --> B{idsパラメータ取得}
    B --> C{配列かつ空でない?}
    C -->|No| D[RoutingException::missingRequestParameter]
    C -->|Yes| E{各IDが文字列?}
    E -->|No| F[RoutingException::invalidRequestParameter]
    E -->|Yes| G[CartService::removeItems 呼び出し]
    G --> H[CartCalculator::calculate]
    H --> I[CartPersister::save]
    I --> J[イベント発火]
    J --> K{エラーあり?}
    K -->|Yes| L[エラーフラッシュメッセージ]
    K -->|No| M[成功フラッシュメッセージ]
    L --> N[レスポンス返却]
    M --> N
    D --> O[例外ハンドリング]
    F --> O
    O --> L
    N --> P[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-01 | IDリスト形式検証 | idsパラメータは空でない配列であること | 常時 |
| BR-42-02 | ID要素型検証 | 配列の各要素は文字列であること | 常時 |
| BR-42-03 | カート内存在確認 | 削除対象の各商品がカート内に存在しない場合は例外 | 削除処理時 |
| BR-42-04 | カート再計算 | 商品削除後は必ずカート全体を再計算する | 削除成功時 |
| BR-42-05 | 一括処理 | すべての削除処理は単一トランザクションで実行 | 常時 |

### 計算ロジック

商品削除後、CartCalculatorにより以下が再計算される：
- 小計（税抜き/税込み）
- 適用可能なプロモーション割引
- 配送料
- 税額
- 総合計

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カート更新 | cart | UPDATE | カートデータの更新（line_itemフィールド） |

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

#### cart

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | line_item | 削除対象を除いたラインアイテムJSON | シリアライズされたCart::lineItems |
| UPDATE | price | 再計算後の価格情報 | CartPrice オブジェクト |
| UPDATE | updated_at | 現在時刻 | 自動更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| FRAMEWORK__MISSING_REQUEST_PARAMETER | パラメータエラー | idsパラメータがない、または空配列 | エラーメッセージを表示 |
| FRAMEWORK__INVALID_REQUEST_PARAMETER | パラメータエラー | ids配列内に文字列以外の要素が存在 | エラーメッセージを表示 |
| CHECKOUT__LINE_ITEM_NOT_FOUND | 業務エラー | 指定されたIDがカート内に存在しない | エラーメッセージを表示 |
| - | システムエラー | 予期しない例外発生時 | 汎用エラーメッセージを表示 |

### リトライ仕様

リトライは実装されていない。エラー発生時はユーザーに再操作を促す。

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

CartLockerを使用して、カート操作中の排他制御を実施。同一カートに対する同時操作を防止する。複数商品の削除は単一トランザクション内で実行され、一部失敗時は全体がロールバックされる。

## パフォーマンス要件

- レスポンス時間: 1000ms以内（商品数に依存）
- Profilerによるトレース: `cart::delete-line-items` として計測

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

- セッションベースのカート所有者確認（トークン）
- CSRFトークン検証（Symfony標準）
- XmlHttpRequest ヘッダーによるAJAXリクエスト識別
- IDの型検証によるインジェクション対策

## 備考

- 単一商品削除（No.41）との違いは、複数IDを配列で受け取る点
- 内部的にはCartService::removeItemsを使用しており、単一削除もこのメソッドに委譲されている

---

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

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

### 推奨読解順序

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

まず、リクエストパラメータの形式とカートのデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Cart.php | `src/Core/Checkout/Cart/Cart.php` | カートの基本構造、remove()メソッドの実装 |
| 1-2 | LineItemCollection.php | `src/Core/Checkout/Cart/LineItem/LineItemCollection.php` | ラインアイテムコレクションの操作 |

**読解のコツ**: 複数削除は各ラインアイテムに対してCart::remove()を順次呼び出す形で実装されている。

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

処理の起点となるコントローラーを特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | CartLineItemController.php | `src/Storefront/Controller/CartLineItemController.php` | deleteLineItemsメソッドがエントリーポイント |

**主要処理フロー**:
1. **90-120行目**: deleteLineItemsメソッド全体
2. **93-119行目**: Profiler::trace内の実処理
3. **95-98行目**: idsパラメータの取得と配列チェック
4. **100-107行目**: 各IDの型チェック
5. **109行目**: CartService::removeItems呼び出し
6. **111-113行目**: エラーチェックとフラッシュメッセージ

#### Step 3: サービス層を理解する

CartServiceがどのように削除処理を行うかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CartService.php | `src/Core/Checkout/Cart/SalesChannel/CartService.php` | removeItems()メソッドの実装 |

**主要処理フロー**:
- **141-149行目**: removeItems()メソッド
- **143-144行目**: Requestオブジェクトの構築
- **146行目**: itemRemoveRouteの呼び出し

#### Step 4: ルート実装を理解する

実際の削除処理とイベント発火を行うルートクラスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | CartItemRemoveRoute.php | `src/Core/Checkout/Cart/SalesChannel/CartItemRemoveRoute.php` | 複数削除の実装、ループ処理 |

**主要処理フロー**:
- **45-78行目**: remove()メソッド全体
- **48行目**: RequestParamHelper::get()でIDリスト取得
- **51-68行目**: 各IDに対するループ処理
- **56-63行目**: ラインアイテムの取得、削除、イベント発火
- **70行目**: カート再計算
- **71行目**: カート永続化
- **73-74行目**: AfterLineItemRemovedEvent、CartChangedEvent発火

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

```
CartLineItemController::deleteLineItems()
    │
    ├─ RequestParamHelper::get('ids') - IDリスト取得
    │
    ├─ バリデーション処理
    │      ├─ 配列チェック
    │      └─ 各要素の型チェック
    │
    ├─ CartService::removeItems()
    │      │
    │      └─ CartItemRemoveRoute::remove()
    │             │
    │             ├─ CartLocker::locked() - 排他制御
    │             │
    │             ├─ foreach ($ids as $id)
    │             │      │
    │             │      ├─ Cart::get() - ラインアイテム取得
    │             │      │
    │             │      ├─ Cart::remove() - カートから削除
    │             │      │
    │             │      └─ EventDispatcher::dispatch(BeforeLineItemRemovedEvent)
    │             │
    │             ├─ CartCalculator::calculate() - 再計算
    │             │
    │             ├─ CartPersister::save() - 永続化
    │             │
    │             └─ EventDispatcher::dispatch(AfterLineItemRemovedEvent, CartChangedEvent)
    │
    ├─ traceErrors() - エラーチェック
    │
    └─ createActionResponse() - レスポンス生成
```

### データフロー図

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

ids配列 ────────────────▶ CartLineItemController        ───▶ リダイレクトレスポンス
[id1, id2, ...]                 │                                    │
        │                       ▼                                    │
        │               バリデーション                                │
        │                       │                                    │
        │                       ▼                                    │
        │               CartService::removeItems()                   │
        │                       │                                    │
        │                       ▼                                    │
        │               CartItemRemoveRoute::remove()                │
        │                       │                                    │
        ▼                       ▼                                    ▼
Cart (セッション) ───▶ ループで各ID削除            ───▶ Cart (永続化)
                                │
                                ▼
                        CartCalculator::calculate()
                                │
                                ▼
                        CartPersister::save()
                                │
                                ▼
                        cart テーブル (DB/Redis)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| CartLineItemController.php | `src/Storefront/Controller/CartLineItemController.php` | コントローラー | HTTPリクエスト処理、パラメータバリデーション |
| CartService.php | `src/Core/Checkout/Cart/SalesChannel/CartService.php` | サービス | カート操作のファサード |
| CartItemRemoveRoute.php | `src/Core/Checkout/Cart/SalesChannel/CartItemRemoveRoute.php` | ルート | Store API実装、イベント発火 |
| Cart.php | `src/Core/Checkout/Cart/Cart.php` | エンティティ | カートデータ構造 |
| LineItem.php | `src/Core/Checkout/Cart/LineItem/LineItem.php` | エンティティ | ラインアイテムデータ構造 |
| CartCalculator.php | `src/Core/Checkout/Cart/CartCalculator.php` | サービス | カート価格計算 |
| AbstractCartPersister.php | `src/Core/Checkout/Cart/AbstractCartPersister.php` | サービス | カート永続化 |
| CartLocker.php | `src/Core/Checkout/Cart/CartLocker.php` | サービス | カート操作の排他制御 |
| RequestParamHelper.php | `src/Core/Framework/Adapter/Request/RequestParamHelper.php` | ユーティリティ | リクエストパラメータの取得 |
| RoutingException.php | `src/Core/Framework/Routing/RoutingException.php` | 例外 | パラメータエラー例外 |
