# 機能設計書 43-数量変更

## 概要

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

### 本機能の処理概要

カート内の特定商品の数量を指定した値に変更し、カートの再計算を行う機能である。

**業務上の目的・背景**：顧客が購入を検討している商品の購入数量を調整できるようにすることで、柔軟な購買体験を提供する。商品を一度カートに追加した後でも、必要な数量に変更できることは、ECサイトの基本的な機能要件である。これにより、顧客は同じ商品を複数回追加する手間なく、希望する数量を設定できる。

**機能の利用シーン**：カートページやオフキャンバスカートで、商品の数量入力フィールドを変更した際に実行される。顧客が購入予定数を増減させたい場合や、複数個購入したい場合に使用される。数量は通常、ドロップダウンや数値入力フィールドから選択・入力される。

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

**関連システム・外部連携**：カート永続化システム（Redis/Database）との連携、価格計算エンジンとの連携、在庫管理システムとの連携（数量上限チェック）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | カートページ | 主画面 | カート内商品の数量変更（CartLineItemController::changeQuantity） |
| 8 | オフキャンバスカート | 参照画面 | サイドバー形式のカートで数量変更 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | 数量変更対象のラインアイテムID | URLパラメータとして必須、カート内に存在すること |
| quantity | int | Yes | 新しい数量 | リクエストパラメータとして必須、整数に変換される |

### 入力データソース

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

## 出力仕様

### 出力データ

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

### 出力先

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

## 処理フロー

### 処理シーケンス

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

### フローチャート

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

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-43-01 | 数量パラメータ必須 | quantityパラメータが存在しない場合はエラー | 常時 |
| BR-43-02 | カート内存在確認 | 変更対象の商品がカート内に存在しない場合はエラー | 常時 |
| BR-43-03 | 数量型変換 | 数量は整数型に変換される | 常時 |
| BR-43-04 | カート再計算 | 数量変更後は必ずカート全体を再計算する | 変更成功時 |
| BR-43-05 | 在庫チェック | 数量変更時に在庫数量を超えていないかチェック | カート計算時 |

### 計算ロジック

数量変更後、CartCalculatorにより以下が再計算される：
- 対象商品の小計（単価 x 新数量）
- カート小計（税抜き/税込み）
- 適用可能なプロモーション割引（数量条件付きプロモーションの再評価）
- 配送料（重量や金額に基づく場合）
- 税額
- 総合計

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

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

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

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

#### cart

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | line_item | 数量更新後のラインアイテムJSON | LineItem::quantityが更新される |
| UPDATE | price | 再計算後の価格情報 | CartPrice オブジェクト |
| UPDATE | updated_at | 現在時刻 | 自動更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| FRAMEWORK__MISSING_REQUEST_PARAMETER | パラメータエラー | quantityパラメータが存在しない | エラーメッセージを表示 |
| CHECKOUT__LINE_ITEM_NOT_FOUND | 業務エラー | 指定されたIDがカート内に存在しない | エラーメッセージを表示 |
| CHECKOUT__CART_INVALID_LINE_ITEM_QUANTITY | 業務エラー | 数量が無効（0以下、在庫超過など） | エラーメッセージを表示 |
| - | システムエラー | 予期しない例外発生時 | 汎用エラーメッセージを表示 |

### リトライ仕様

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

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

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

## パフォーマンス要件

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

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

- セッションベースのカート所有者確認（トークン）
- CSRFトークン検証（Symfony標準）
- XmlHttpRequest ヘッダーによるAJAXリクエスト識別
- 数量の型検証（整数変換）による入力サニタイズ

## 備考

- 数量を0に変更した場合の動作は、商品の設定（removable属性）に依存する
- 在庫数量を超える数量への変更は、カート計算時にエラーとなる可能性がある

---

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

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

### 推奨読解順序

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

まず、ラインアイテムの数量関連属性を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | LineItem.php | `src/Core/Checkout/Cart/LineItem/LineItem.php` | quantity属性、setQuantity()メソッド |
| 1-2 | Cart.php | `src/Core/Checkout/Cart/Cart.php` | カートの基本構造、has()メソッド |

**読解のコツ**: LineItemクラスのquantity属性は整数型で、stackable属性がtrueの場合のみ数量変更が有効となる。

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

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

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

**主要処理フロー**:
1. **150-176行目**: changeQuantityメソッド全体
2. **153-175行目**: Profiler::trace内の実処理
3. **155-158行目**: quantityパラメータの取得と存在チェック
4. **160-163行目**: カート内存在チェック
5. **165行目**: CartService::changeQuantity呼び出し
6. **167-169行目**: エラーチェックとフラッシュメッセージ

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

CartServiceがどのように数量変更を実行するかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CartService.php | `src/Core/Checkout/Cart/SalesChannel/CartService.php` | changeQuantity()メソッドがupdate()に委譲 |

**主要処理フロー**:
- **103-111行目**: changeQuantity()メソッド
- **105-110行目**: update()への委譲、idとquantityの配列形式
- **118-126行目**: update()メソッドの実装

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

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

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

**主要処理フロー**:
- **43-64行目**: change()メソッド全体
- **45行目**: CartLockerによる排他制御開始
- **46行目**: リクエストからitemsを取得
- **49-51行目**: lineItemFactoryで更新処理
- **55行目**: カート再計算
- **56行目**: カート永続化
- **58-59行目**: AfterLineItemQuantityChangedEvent、CartChangedEvent発火

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

```
CartLineItemController::changeQuantity()
    │
    ├─ RequestParamHelper::get('quantity') - 数量取得
    │
    ├─ Cart::has() - カート内存在チェック
    │
    ├─ CartService::changeQuantity()
    │      │
    │      └─ CartService::update()
    │             │
    │             └─ CartItemUpdateRoute::change()
    │                    │
    │                    ├─ CartLocker::locked() - 排他制御
    │                    │
    │                    ├─ LineItemFactoryRegistry::update() - ラインアイテム更新
    │                    │
    │                    ├─ Cart::markModified() - 変更マーク
    │                    │
    │                    ├─ CartCalculator::calculate() - 再計算
    │                    │
    │                    ├─ CartPersister::save() - 永続化
    │                    │
    │                    └─ EventDispatcher::dispatch
    │                           ├─ AfterLineItemQuantityChangedEvent
    │                           └─ CartChangedEvent
    │
    ├─ traceErrors() - エラーチェック
    │
    └─ createActionResponse() - レスポンス生成
```

### データフロー図

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

ラインアイテムID ───────▶ CartLineItemController        ───▶ リダイレクトレスポンス
quantity ──────────────▶       │                                    │
        │                      ▼                                    │
        │               CartService::changeQuantity()               │
        │                      │                                    │
        │                      ▼                                    │
        │               CartItemUpdateRoute::change()               │
        │                      │                                    │
        ▼                      ▼                                    ▼
Cart (セッション) ───▶ LineItem.quantity更新       ───▶ 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` | サービス | カート操作のファサード |
| CartItemUpdateRoute.php | `src/Core/Checkout/Cart/SalesChannel/CartItemUpdateRoute.php` | ルート | Store API実装、イベント発火 |
| LineItemFactoryRegistry.php | `src/Core/Checkout/Cart/LineItemFactoryRegistry.php` | ファクトリ | ラインアイテムの更新処理 |
| 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` | サービス | カート操作の排他制御 |
| AfterLineItemQuantityChangedEvent.php | `src/Core/Checkout/Cart/Event/AfterLineItemQuantityChangedEvent.php` | イベント | 数量変更後イベント |
| CartChangedEvent.php | `src/Core/Checkout/Cart/Event/CartChangedEvent.php` | イベント | カート変更イベント |
