# 機能設計書 44-明細更新

## 概要

本ドキュメントは、ショッピングカート内の複数のラインアイテムを一括で更新する機能の設計を定義するものである。

### 本機能の処理概要

カート内の複数商品の属性（数量など）を一度のリクエストで一括更新し、カートの再計算を行う機能である。

**業務上の目的・背景**：顧客がカート内の複数商品を効率的に更新できるようにすることで、ユーザー体験を向上させる。個別の更新リクエストを複数回送信するよりも、一括更新によりサーバー負荷を軽減し、レスポンス時間を短縮できる。特にカートページでフォーム送信時に、複数商品の数量変更を一度に処理する際に使用される。

**機能の利用シーン**：カートページでフォーム全体を送信する際に使用される。複数の商品の数量を変更した後、「カートを更新」ボタンをクリックした際に実行される。バッチ処理やAPIからの一括更新にも対応している。

**主要な処理内容**：
1. リクエストからlineItemsパラメータ（更新対象のラインアイテム配列）を取得
2. パラメータの形式バリデーション（RequestDataBagであること）
3. 各ラインアイテムデータを標準形式に変換
4. CartServiceを通じてカートを一括更新
5. カートを再計算して合計金額等を更新
6. 成功/エラーメッセージをフラッシュメッセージとして設定
7. レスポンスを返却

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

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | カートページ | 主画面 | カートの明細を一括更新（CartLineItemController::updateLineItems） |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| lineItems | RequestDataBag | Yes | 更新対象のラインアイテム情報の配列 | RequestDataBag形式であること |
| lineItems[*].id | string | Yes | ラインアイテムID | 各ラインアイテムに必須 |
| lineItems[*].quantity | int | No | 新しい数量 | 整数に変換される |
| lineItems[*].stackable | bool | No | スタック可能フラグ | ブール値に変換される |
| lineItems[*].removable | bool | No | 削除可能フラグ | ブール値に変換される |

### 入力データソース

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

### 入力データ形式例

```php
'lineItems' => [
    'anyKey' => [
        'id' => 'someKey',
        'quantity' => 2,
    ],
    'randomKey' => [
        'id' => 'otherKey',
        'quantity' => 3,
    ]
]
```

## 出力仕様

### 出力データ

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

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POSTまたはPATCHメソッドで /checkout/line-item/update にアクセス
2. パラメータ取得
   └─ RequestDataBagからlineItemsパラメータを取得
3. パラメータバリデーション
   └─ lineItemsがRequestDataBag形式であることを検証
4. ラインアイテム変換
   └─ 各lineItemDataをgetLineItemArray()で標準配列形式に変換
5. 一括更新実行
   └─ CartService::update() を呼び出してカートを一括更新
6. カート再計算
   └─ CartCalculator::calculate() でカート合計を再計算
7. 永続化
   └─ CartPersister::save() で更新されたカートを保存
8. イベント発火
   └─ AfterLineItemQuantityChangedEvent、CartChangedEvent を発火
9. フラッシュメッセージ設定
   └─ 成功時は "checkout.cartUpdateSuccess"、エラー時は "error.message-default"
10. レスポンス返却
    └─ createActionResponse() でリダイレクトまたはJSONレスポンスを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: updateLineItems] --> B{lineItemsパラメータ取得}
    B --> C{RequestDataBag形式?}
    C -->|No| D[RoutingException::missingRequestParameter]
    C -->|Yes| E[各lineItemDataを変換]
    E --> F[CartService::update 呼び出し]
    F --> G[CartCalculator::calculate]
    G --> H[CartPersister::save]
    H --> I[イベント発火]
    I --> J{エラーあり?}
    J -->|Yes| K[エラーフラッシュメッセージ]
    J -->|No| L[成功フラッシュメッセージ]
    K --> M[レスポンス返却]
    L --> M
    D --> N[例外ハンドリング]
    N --> K
    M --> O[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-44-01 | lineItemsパラメータ形式 | lineItemsはRequestDataBag形式であること | 常時 |
| BR-44-02 | 数量型変換 | quantityは整数型に変換される | quantityが存在する場合 |
| BR-44-03 | ブール値変換 | stackable、removableはブール値に変換される | 各属性が存在する場合 |
| BR-44-04 | カート再計算 | 更新後は必ずカート全体を再計算する | 更新成功時 |
| BR-44-05 | 一括処理 | すべての更新処理は単一トランザクションで実行 | 常時 |

### 計算ロジック

更新後、CartCalculatorにより以下が再計算される：
- 各商品の小計（単価 x 新数量）
- カート小計（税抜き/税込み）
- 適用可能なプロモーション割引
- 配送料
- 税額
- 総合計

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

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

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

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

#### cart

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

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

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

CartLockerを使用して、カート操作中の排他制御を実施。同一カートに対する同時操作を防止する。複数ラインアイテムの更新は単一トランザクション内で実行される。

## パフォーマンス要件

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

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

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

## 備考

- このAPIは数量変更だけでなく、ラインアイテムのその他属性（stackable、removable等）も更新可能
- payloadフィールドの更新も可能だが、256KB以上のペイロードは拒否される

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | RequestDataBag.php | `src/Core/Framework/Validation/DataBag/RequestDataBag.php` | リクエストデータの格納形式 |
| 1-2 | LineItem.php | `src/Core/Checkout/Cart/LineItem/LineItem.php` | ラインアイテムの属性（quantity, stackable, removable等） |

**読解のコツ**: RequestDataBagはネストされたデータ構造を扱うためのSymfonyのParameterBag拡張クラスである。

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

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

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

**主要処理フロー**:
1. **191-217行目**: updateLineItemsメソッド全体
2. **194-216行目**: Profiler::trace内の実処理
3. **196-198行目**: lineItemsパラメータの取得とバリデーション
4. **201-204行目**: 各lineItemDataの配列変換ループ
5. **206行目**: CartService::update呼び出し
6. **208-210行目**: エラーチェックとフラッシュメッセージ

#### Step 3: ヘルパーメソッドを理解する

ラインアイテムデータの変換処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CartLineItemController.php | `src/Storefront/Controller/CartLineItemController.php` | getLineItemArrayメソッド |

**主要処理フロー**:
- **354-386行目**: getLineItemArray()メソッド全体
- **356-358行目**: payloadの正規化
- **365-367行目**: quantityの整数変換
- **369-371行目**: stackableのブール変換
- **373-375行目**: removableのブール変換

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

CartServiceがどのように一括更新を実行するかを理解する。

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

**主要処理フロー**:
- **118-126行目**: update()メソッド
- **120-121行目**: Requestオブジェクトの構築
- **123行目**: itemUpdateRouteの呼び出し

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

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

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

**主要処理フロー**:
- **43-64行目**: change()メソッド全体
- **46行目**: リクエストからitemsを取得
- **49-51行目**: 各アイテムをLineItemFactoryRegistryで更新
- **55行目**: カート再計算
- **56行目**: カート永続化
- **58-59行目**: イベント発火

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

```
CartLineItemController::updateLineItems()
    │
    ├─ requestDataBag->get('lineItems') - パラメータ取得
    │
    ├─ foreach (lineItems as lineItemData)
    │      │
    │      └─ getLineItemArray() - 配列形式に変換
    │             ├─ normalizePayload() - ペイロード正規化
    │             ├─ (int) quantity - 数量変換
    │             ├─ (bool) stackable - スタック可能フラグ変換
    │             └─ (bool) removable - 削除可能フラグ変換
    │
    ├─ CartService::update()
    │      │
    │      └─ CartItemUpdateRoute::change()
    │             │
    │             ├─ CartLocker::locked() - 排他制御
    │             │
    │             ├─ foreach (items as item)
    │             │      │
    │             │      └─ LineItemFactoryRegistry::update() - ラインアイテム更新
    │             │
    │             ├─ Cart::markModified() - 変更マーク
    │             │
    │             ├─ CartCalculator::calculate() - 再計算
    │             │
    │             ├─ CartPersister::save() - 永続化
    │             │
    │             └─ EventDispatcher::dispatch
    │                    ├─ AfterLineItemQuantityChangedEvent
    │                    └─ CartChangedEvent
    │
    ├─ traceErrors() - エラーチェック
    │
    └─ createActionResponse() - レスポンス生成
```

### データフロー図

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

lineItems配列 ────────────────▶ CartLineItemController        ───▶ リダイレクトレスポンス
  │                                   │                                    │
  ├─ anyKey:                          ▼                                    │
  │    id: 'someKey'          getLineItemArray() x N                       │
  │    quantity: 2                    │                                    │
  │                                   ▼                                    │
  └─ randomKey:               CartService::update()                        │
       id: 'otherKey'                 │                                    │
       quantity: 3                    ▼                                    │
        │                     CartItemUpdateRoute::change()                │
        │                             │                                    │
        ▼                             ▼                                    ▼
Cart (セッション) ───────▶ LineItem更新 x N          ───▶ 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` | ファクトリ | ラインアイテムの更新処理 |
| RequestDataBag.php | `src/Core/Framework/Validation/DataBag/RequestDataBag.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` | イベント | 数量変更後イベント |
