# 機能設計書 46-プロモーションコード追加

## 概要

本ドキュメントは、ショッピングカートにプロモーションコード（クーポンコード）を適用する機能の設計を定義するものである。

### 本機能の処理概要

顧客が入力したプロモーションコードをカートに追加し、条件が合致すれば割引を適用する機能である。

**業務上の目的・背景**：ECサイトにおいてプロモーションコード（クーポンコード、割引コード）は、販売促進の重要な手段である。顧客にコードを入力させることで、特定のキャンペーンや顧客セグメント向けの割引を適用できる。この機能により、マーケティング施策の効果測定や、顧客ロイヤリティの向上が期待できる。

**機能の利用シーン**：カートページやチェックアウトページで「プロモーションコード」入力欄にコードを入力し、「適用」ボタンをクリックした際に実行される。メールマガジンやSNS、紙媒体などで配布されたコードを顧客が入力する。

**主要な処理内容**：
1. リクエストからプロモーションコードを取得
2. コードの空白トリミングとバリデーション
3. PromotionItemBuilderでプレースホルダーラインアイテムを生成
4. CartServiceを通じてカートにプロモーションを追加
5. カート計算時にプロモーションが有効かどうかを評価
6. 成功/エラーメッセージをフラッシュメッセージまたはカートエラーとして設定
7. レスポンスを返却

**関連システム・外部連携**：カート永続化システム、価格計算エンジン、プロモーションルールエンジン

**権限による制御**：特別な権限制御はなく、カートの所有者であれば実行可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | カートページ | 主画面 | プロモーションコード入力・適用（CartLineItemController::addPromotion） |
| 10 | 注文確認ページ | 補助画面 | プロモーションコード入力・適用 |
| 45 | プロモーション管理 | 管理画面 | 割引・クーポンの設定 |

## 機能種別

CRUD操作（Create）/ ビジネスロジック処理 / バリデーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| code | string | Yes | プロモーションコード | 空でないこと、mb_trim適用 |

### 入力データソース

- POSTリクエストボディ: `code` パラメータ
- Cartオブジェクト: セッションまたは永続化ストレージから自動取得
- SalesChannelContext: リクエストから自動解決

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| redirect | Response | リダイレクトレスポンス |
| flash_message | string | 成功時: "checkout.codeAddedSuccessful"、エラー時: "error.message-default" |
| cart_error | Error | プロモーションが無効な場合のカートエラー |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POSTメソッドで /checkout/promotion/add にアクセス
2. コード取得
   └─ リクエストからcodeパラメータを取得
   └─ mb_trim()で空白をトリミング
3. バリデーション
   └─ コードが空の場合はRoutingException発生
4. プレースホルダー生成
   └─ PromotionItemBuilder::buildPlaceholderItem()でラインアイテム生成
5. カートに追加
   └─ CartService::add()でプレースホルダーをカートに追加
6. カート計算
   └─ PromotionProcessorがプレースホルダーを実際の割引に変換
7. 結果処理
   └─ PromotionCartAddedInformationErrorがあれば成功メッセージに変換
   └─ その他のエラーはカートエラーとして表示
8. レスポンス返却
   └─ createActionResponse() でリダイレクトまたはJSONレスポンスを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: addPromotion] --> B[コードをmb_trimでトリミング]
    B --> C{コードが空?}
    C -->|Yes| D[RoutingException::missingRequestParameter]
    C -->|No| E[PromotionItemBuilder::buildPlaceholderItem]
    E --> F[CartService::add]
    F --> G[カート計算・プロモーション評価]
    G --> H{PromotionCartAddedInformationError?}
    H -->|Yes| I[成功フラッシュメッセージ]
    H -->|No| J{その他エラーあり?}
    J -->|Yes| K[カートエラーとして追加]
    J -->|No| L[処理完了]
    I --> L
    K --> L
    D --> M[例外ハンドリング]
    M --> N[エラーフラッシュメッセージ]
    N --> L
    L --> O[レスポンス返却]
    O --> P[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-46-01 | コード必須 | プロモーションコードは空でないこと | 常時 |
| BR-46-02 | コードトリミング | コードの前後空白はmb_trim()で除去 | 常時 |
| BR-46-03 | プレースホルダー方式 | コードは最初にプレースホルダーとしてカートに追加される | 常時 |
| BR-46-04 | 遅延評価 | プロモーションの有効性はカート計算時に評価される | カート計算時 |
| BR-46-05 | 重複防止 | 同じコードの重複追加は防止される | 追加時 |

### 計算ロジック

プロモーション追加後、PromotionProcessorにより以下が実行される：
1. プレースホルダーラインアイテムからコードを抽出
2. コードに対応するプロモーションをデータベースから検索
3. プロモーションの有効期限、利用条件をチェック
4. 条件合致時、割引ラインアイテムに変換
5. 割引タイプ（パーセント/絶対額/固定額）に応じて割引額を計算
6. カート合計に反映

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カート更新 | cart | UPDATE | プロモーションラインアイテムの追加 |
| プロモーション検索 | promotion | SELECT | コードに対応するプロモーションを検索 |

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

#### cart

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | line_item | プロモーションラインアイテムを追加 | type='promotion' |
| UPDATE | price | 割引適用後の価格情報 | CartPrice オブジェクト |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| FRAMEWORK__MISSING_REQUEST_PARAMETER | パラメータエラー | codeパラメータが空 | エラーメッセージを表示 |
| CHECKOUT__CODE_ALREADY_REDEEMED | 業務エラー | コードが既に使用済み | カートエラーとして表示 |
| CHECKOUT__PROMOTION_NOT_FOUND | 業務エラー | コードが存在しない | カートエラーとして表示 |
| CHECKOUT__PROMOTION_NOT_ELIGIBLE | 業務エラー | 条件を満たさない | カートエラーとして表示 |
| - | システムエラー | 予期しない例外発生時 | 汎用エラーメッセージを表示 |

### リトライ仕様

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

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

CartLockerを使用して、カート操作中の排他制御を実施。

## パフォーマンス要件

- レスポンス時間: 1000ms以内（プロモーション評価を含む）
- Profilerによるトレース: `cart::add-promotion` として計測

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

- セッションベースのカート所有者確認
- CSRFトークン検証
- コード入力のサニタイズ（mb_trim）
- プロモーション利用回数の制限（promotion テーブルで管理）

## 備考

- プロモーションコードは大文字小文字を区別しない場合が多い（設定による）
- 自動適用プロモーション（コード不要）は別のメカニズムで処理される

---

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

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

### 推奨読解順序

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

まず、プロモーションラインアイテムの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | LineItem.php | `src/Core/Checkout/Cart/LineItem/LineItem.php` | ラインアイテムの基本構造 |
| 1-2 | PromotionProcessor.php | `src/Core/Checkout/Promotion/Cart/PromotionProcessor.php` | LINE_ITEM_TYPE定数 |

**読解のコツ**: プロモーションラインアイテムはtype='promotion'で識別される。

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

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

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

**主要処理フロー**:
1. **126-148行目**: addPromotionメソッド全体
2. **129-147行目**: Profiler::trace内の実処理
3. **131行目**: mb_trim()でコードをトリミング
4. **133-134行目**: 空コードチェック
5. **137行目**: PromotionItemBuilder::buildPlaceholderItem()呼び出し
6. **139行目**: CartService::add()呼び出し
7. **141行目**: traceErrors()でエラー処理

#### Step 3: プレースホルダービルダーを理解する

プレースホルダーラインアイテムの生成処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PromotionItemBuilder.php | `src/Core/Checkout/Promotion/Cart/PromotionItemBuilder.php` | buildPlaceholderItem()メソッド |

**主要処理フロー**:
- **46-64行目**: buildPlaceholderItem()メソッド
- **50行目**: PLACEHOLDER_PREFIX + codeで一意キー生成
- **52行目**: PromotionProcessor::LINE_ITEM_TYPE でラインアイテム生成
- **57行目**: referencedIdにコードを設定
- **61行目**: 0%のPercentagePriceDefinitionを設定（副作用なし）

#### Step 4: 成功メッセージ変換を理解する

カートエラーを成功メッセージに変換する処理を理解する。

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

**主要処理フロー**:
- **412-420行目**: filterSuccessErrorMessages()メソッド
- **415行目**: PromotionCartAddedInformationErrorの検出
- **416行目**: 成功フラッシュメッセージに変換
- **417行目**: エラーコレクションから削除

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

```
CartLineItemController::addPromotion()
    │
    ├─ mb_trim(code) - 空白トリミング
    │
    ├─ PromotionItemBuilder::buildPlaceholderItem()
    │      │
    │      ├─ Uuid::fromStringToHex() - 一意ID生成
    │      │
    │      ├─ LineItem生成 (type='promotion')
    │      │
    │      └─ PercentagePriceDefinition(0) - 副作用なし価格設定
    │
    ├─ CartService::add()
    │      │
    │      └─ CartItemAddRoute::add()
    │             │
    │             ├─ CartCalculator::calculate()
    │             │      │
    │             │      └─ PromotionProcessor::process()
    │             │             │
    │             │             ├─ プロモーション検索・評価
    │             │             │
    │             │             └─ 割引ラインアイテムに変換
    │             │
    │             └─ CartPersister::save()
    │
    ├─ traceErrors() / filterSuccessErrorMessages()
    │      │
    │      └─ PromotionCartAddedInformationError → 成功メッセージ
    │
    └─ createActionResponse() - レスポンス生成
```

### データフロー図

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

code ─────────────────▶ CartLineItemController        ───▶ リダイレクトレスポンス
  │                            │                                    │
  │                            ▼                                    │
  │                     mb_trim(code)                               │
  │                            │                                    │
  │                            ▼                                    │
  │                     PromotionItemBuilder                        │
  │                     ::buildPlaceholderItem()                    │
  │                            │                                    │
  │                            ▼                                    │
  │                     LineItem(type='promotion')                  │
  │                            │                                    │
  │                            ▼                                    │
  │                     CartService::add()                          │
  │                            │                                    │
  ▼                            ▼                                    ▼
Cart ─────────────────▶ PromotionProcessor      ───────▶ Cart (割引適用済)
                               │
                               ▼
                       promotion テーブル検索
                               │
                               ▼
                       条件評価・割引計算
                               │
                               ▼
                       CartPersister::save()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| CartLineItemController.php | `src/Storefront/Controller/CartLineItemController.php` | コントローラー | HTTPリクエスト処理 |
| PromotionItemBuilder.php | `src/Core/Checkout/Promotion/Cart/PromotionItemBuilder.php` | ビルダー | プレースホルダー生成 |
| PromotionProcessor.php | `src/Core/Checkout/Promotion/Cart/PromotionProcessor.php` | プロセッサー | プロモーション評価・適用 |
| CartService.php | `src/Core/Checkout/Cart/SalesChannel/CartService.php` | サービス | カート操作のファサード |
| CartCalculator.php | `src/Core/Checkout/Cart/CartCalculator.php` | サービス | カート価格計算 |
| PromotionCartAddedInformationError.php | `src/Core/Checkout/Promotion/Cart/PromotionCartAddedInformationError.php` | エラー | 成功情報を伝えるエラー |
| LineItem.php | `src/Core/Checkout/Cart/LineItem/LineItem.php` | エンティティ | ラインアイテムデータ構造 |
