# 機能設計書 41-商品削除

## 概要

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

### 本機能の処理概要

カート内の特定の商品を削除し、カートの再計算を行う機能である。

**業務上の目的・背景**：顧客がショッピングカートに追加した商品を購入前に取り消したい場合に、不要な商品をカートから除外できるようにする必要がある。これにより、顧客は購入意思のある商品のみを注文に含めることができ、誤って追加した商品や検討後に不要になった商品を簡単に除去できる。

**機能の利用シーン**：顧客がカートページやオフキャンバスカートを閲覧中に、個別の商品の削除ボタンをクリックした際に実行される。商品詳細ページで追加した後に気が変わった場合や、複数商品を比較検討した結果不要と判断した商品を除外する際に使用される。

**主要な処理内容**：
1. リクエストから削除対象の商品ID（ラインアイテムID）を取得
2. カート内に該当商品が存在するかを検証
3. CartServiceを通じてカートから商品を削除
4. カートを再計算して合計金額等を更新
5. 成功/エラーメッセージをフラッシュメッセージとして設定
6. レスポンスを返却

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

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

## 関連画面

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

## 機能種別

CRUD操作（Delete）/ データ更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | 削除対象のラインアイテムID | URLパラメータとして必須、カート内に存在すること |

### 入力データソース

- URLパラメータ: `/checkout/line-item/delete/{id}`
- Cartオブジェクト: セッションまたは永続化ストレージから自動取得
- SalesChannelContext: リクエストから自動解決

## 出力仕様

### 出力データ

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

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POSTまたはDELETEメソッドで /checkout/line-item/delete/{id} にアクセス
2. パラメータ取得
   └─ URLからラインアイテムIDを取得
3. カート内存在チェック
   └─ $cart->has($id) でカート内に該当商品が存在するか確認
4. 商品削除実行
   └─ CartService::remove() を呼び出してカートから商品を削除
5. カート再計算
   └─ CartCalculator::calculate() でカート合計を再計算
6. 永続化
   └─ CartPersister::save() で更新されたカートを保存
7. イベント発火
   └─ BeforeLineItemRemovedEvent、AfterLineItemRemovedEvent、CartChangedEvent を発火
8. フラッシュメッセージ設定
   └─ 成功時は "checkout.cartUpdateSuccess"、エラー時は "error.message-default"
9. レスポンス返却
   └─ createActionResponse() でリダイレクトまたはJSONレスポンスを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: deleteLineItem] --> B{カートに商品が存在?}
    B -->|No| C[CartException::lineItemNotFound]
    B -->|Yes| D[CartService::remove 呼び出し]
    D --> E[CartCalculator::calculate]
    E --> F[CartPersister::save]
    F --> G[イベント発火]
    G --> H{エラーあり?}
    H -->|Yes| I[エラーフラッシュメッセージ]
    H -->|No| J[成功フラッシュメッセージ]
    I --> K[レスポンス返却]
    J --> K
    C --> L[例外ハンドリング]
    L --> I
    K --> M[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-41-01 | カート内存在確認 | 削除対象の商品がカート内に存在しない場合はエラーとする | 常時 |
| BR-41-02 | カート再計算 | 商品削除後は必ずカート全体を再計算する | 削除成功時 |
| BR-41-03 | 永続化必須 | 削除結果は必ず永続化ストレージに保存する | 削除成功時 |

### 計算ロジック

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

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

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

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

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

#### cart

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CHECKOUT__LINE_ITEM_NOT_FOUND | 業務エラー | 指定されたIDがカート内に存在しない | エラーメッセージを表示 |
| - | システムエラー | 予期しない例外発生時 | 汎用エラーメッセージを表示 |

### リトライ仕様

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

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

CartLockerを使用して、カート操作中の排他制御を実施。同一カートに対する同時操作を防止する。

## パフォーマンス要件

- レスポンス時間: 500ms以内
- Profilerによるトレース: `cart::delete-line-item` として計測

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

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

## 備考

- 商品削除はソフトデリートではなく、カートデータからの完全削除
- 削除後のカートが空になった場合も正常処理として扱う

---

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

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

### 推奨読解順序

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

まず、カートとラインアイテムのデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Cart.php | `src/Core/Checkout/Cart/Cart.php` | カートの基本構造、has()メソッド、remove()メソッドの実装 |
| 1-2 | LineItem.php | `src/Core/Checkout/Cart/LineItem/LineItem.php` | ラインアイテムの構造、IDの扱い |

**読解のコツ**: Cartクラスは内部にLineItemCollectionを保持しており、has()やremove()はこのコレクションに対する操作である。

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

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

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

**主要処理フロー**:
1. **52-72行目**: deleteLineItemメソッド全体
2. **55-71行目**: Profiler::trace内の実処理
3. **57-58行目**: カート内存在チェック
4. **61行目**: CartService::remove呼び出し
5. **63-65行目**: エラーチェックとフラッシュメッセージ
6. **70行目**: レスポンス生成

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CartService.php | `src/Core/Checkout/Cart/SalesChannel/CartService.php` | remove()メソッドがremoveItems()を呼び出す構造 |

**主要処理フロー**:
- **131-134行目**: remove()メソッドがremoveItems()に委譲
- **141-149行目**: removeItems()がitemRemoveRouteを呼び出し

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | CartItemRemoveRoute.php | `src/Core/Checkout/Cart/SalesChannel/CartItemRemoveRoute.php` | 実際の削除処理、イベント発火 |

**主要処理フロー**:
- **45-78行目**: remove()メソッド全体
- **47行目**: CartLockerによる排他制御開始
- **56-63行目**: ラインアイテムの取得と削除
- **65行目**: BeforeLineItemRemovedEvent発火
- **70行目**: カート再計算
- **71行目**: カート永続化
- **73-74行目**: AfterLineItemRemovedEvent、CartChangedEvent発火

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

```
CartLineItemController::deleteLineItem()
    │
    ├─ Cart::has() - カート内存在チェック
    │
    ├─ CartService::remove()
    │      │
    │      └─ CartService::removeItems()
    │             │
    │             └─ CartItemRemoveRoute::remove()
    │                    │
    │                    ├─ CartLocker::locked() - 排他制御
    │                    │
    │                    ├─ Cart::get() - ラインアイテム取得
    │                    │
    │                    ├─ Cart::remove() - カートから削除
    │                    │
    │                    ├─ EventDispatcher::dispatch(BeforeLineItemRemovedEvent)
    │                    │
    │                    ├─ CartCalculator::calculate() - 再計算
    │                    │
    │                    ├─ CartPersister::save() - 永続化
    │                    │
    │                    └─ EventDispatcher::dispatch(AfterLineItemRemovedEvent, CartChangedEvent)
    │
    ├─ traceErrors() - エラーチェック
    │
    └─ createActionResponse() - レスポンス生成
```

### データフロー図

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

ラインアイテムID ───────▶ CartLineItemController        ───▶ リダイレクトレスポンス
        │                       │                                    │
        │                       ▼                                    │
        │               CartService::remove()                        │
        │                       │                                    │
        │                       ▼                                    │
        │               CartItemRemoveRoute::remove()                │
        │                       │                                    │
        ▼                       ▼                                    ▼
Cart (セッション) ───▶ CartCalculator::calculate()  ───▶ Cart (永続化)
                                │
                                ▼
                        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` | サービス | カート操作の排他制御 |
| BeforeLineItemRemovedEvent.php | `src/Core/Checkout/Cart/Event/BeforeLineItemRemovedEvent.php` | イベント | 削除前イベント |
| AfterLineItemRemovedEvent.php | `src/Core/Checkout/Cart/Event/AfterLineItemRemovedEvent.php` | イベント | 削除後イベント |
| CartChangedEvent.php | `src/Core/Checkout/Cart/Event/CartChangedEvent.php` | イベント | カート変更イベント |
