# 機能設計書 51-お気に入り追加

## 概要

本ドキュメントは、Shopware Storefrontにおける「お気に入り追加」機能の設計仕様を定義する。顧客が商品をお気に入りリスト（ウィッシュリスト）に追加するためのAjax APIを提供する。

### 本機能の処理概要

**業務上の目的・背景**：ECサイトにおいて、顧客が興味のある商品を後で確認・購入できるようにお気に入りリストに保存する機能は、購買意欲の維持と顧客体験向上に不可欠である。本機能により、顧客は商品詳細ページや商品一覧から簡単にお気に入りに追加でき、後日再訪問時に購入検討を再開できる。

**機能の利用シーン**：顧客が商品詳細ページや商品一覧ページを閲覧中に、ハートアイコン等のUIボタンをクリックした際に本機能が呼び出される。ログイン済みの顧客のみが利用可能であり、未ログイン状態でクリックした場合はログインページへリダイレクトされる。

**主要な処理内容**：
1. ログイン状態およびウィッシュリスト機能有効化の確認
2. 指定された商品IDの存在検証
3. 顧客のウィッシュリスト取得（存在しない場合は新規作成）
4. ウィッシュリストへの商品追加（upsert処理）
5. イベント発火による拡張ポイント提供
6. 処理結果のJSONレスポンス返却

**関連システム・外部連携**：本機能は Store API (`/store-api/customer/wishlist/add/{productId}`) を内部的に利用し、データベースへの永続化を行う。また、`WishlistProductAddedEvent`イベントを発火し、プラグインによる拡張を可能にする。

**権限による制御**：本機能はログイン必須（`LOGIN_REQUIRED`属性）であり、未認証ユーザーはアクセスできない。また、システム設定`core.cart.wishlistEnabled`が無効の場合はエラーを返却する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 商品詳細ページ | 主画面 | ハートアイコンクリックでお気に入り追加 |
| 4 | 商品クイックビュー | 参照画面 | クイックビュー内からのお気に入り追加 |
| 23 | ウィッシュリストページ | 結果表示画面 | 追加後のリダイレクト先 |

## 機能種別

CRUD操作（Create）/ Ajax API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| productId | string (UUID) | Yes | お気に入りに追加する商品のID | UUID形式、存在する商品であること |

### 入力データソース

- URLパスパラメータ: `/wishlist/add/{productId}`
- SalesChannelContext: 顧客情報、販売チャネル情報を含む

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| success | boolean | 処理成功時true、失敗時false |

### 出力先

- HTTPレスポンス: JSON形式 `{"success": true}` または `{"success": false}`

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ WishlistController::ajaxAdd が呼び出される

2. Store API呼び出し
   └─ AddWishlistProductRoute::add を実行

3. ウィッシュリスト機能有効性チェック
   └─ core.cart.wishlistEnabled システム設定を確認
   └─ 無効の場合は CustomerException::customerWishlistNotActivated をスロー

4. 商品存在チェック
   └─ productRepository で商品IDの存在を検証
   └─ 存在しない場合は CustomerException::productNotFound をスロー

5. ウィッシュリストID取得
   └─ 顧客ID + 販売チャネルIDでウィッシュリストを検索
   └─ 存在しない場合は新規UUIDを生成

6. データ永続化
   └─ wishlistRepository.upsert でウィッシュリストと商品関連を保存

7. イベント発火
   └─ WishlistProductAddedEvent をディスパッチ

8. レスポンス返却
   └─ SuccessResponse を返却（Controller側でJSON変換）
```

### フローチャート

```mermaid
flowchart TD
    A[開始: ajaxAdd呼び出し] --> B{ウィッシュリスト機能有効?}
    B -->|No| C[例外: WishlistNotActivated]
    B -->|Yes| D{商品は存在する?}
    D -->|No| E[例外: ProductNotFound]
    D -->|Yes| F[顧客のウィッシュリストID取得]
    F --> G{既存ウィッシュリストあり?}
    G -->|Yes| H[既存IDを使用]
    G -->|No| I[新規UUIDを生成]
    H --> J[Upsert処理実行]
    I --> J
    J --> K[WishlistProductAddedEvent発火]
    K --> L[成功レスポンス返却]
    C --> M[例外キャッチ]
    E --> M
    M --> N[失敗レスポンス返却]
    L --> O[終了]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | ログイン必須 | ログイン済み顧客のみが利用可能 | 全リクエスト |
| BR-51-02 | ウィッシュリスト有効化 | システム設定でウィッシュリストが有効である必要がある | 全リクエスト |
| BR-51-03 | 商品存在チェック | SalesChannelで有効な商品のみ追加可能 | 商品追加時 |
| BR-51-04 | 重複許容 | 同一商品を複数回追加してもエラーにならない（upsert） | 商品追加時 |
| BR-51-05 | 販売チャネル分離 | ウィッシュリストは販売チャネル毎に分離管理される | 全リクエスト |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ウィッシュリスト取得/作成 | customer_wishlist | SELECT/INSERT | 顧客のウィッシュリストを検索、なければ作成 |
| 商品関連追加 | customer_wishlist_product | INSERT | ウィッシュリストと商品の関連を追加 |
| 商品存在確認 | product | SELECT | 指定商品IDの存在を確認 |

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

#### customer_wishlist

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | customer_id = ? AND sales_channel_id = ? | 既存ウィッシュリスト検索 |
| INSERT/UPDATE | id, customer_id, sales_channel_id | 新規または既存のID、顧客ID、販売チャネルID | upsertで処理 |

#### customer_wishlist_product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | product_id, product_version_id, customer_wishlist_id | 商品ID、LIVE_VERSION、ウィッシュリストID | 商品関連追加 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CUSTOMER_WISHLIST_NOT_ACTIVATED | 機能無効 | core.cart.wishlistEnabled が false | システム設定を有効化 |
| PRODUCT_NOT_FOUND | 商品未存在 | 指定された商品IDが存在しない | 正しい商品IDを指定 |
| - | 認証エラー | 未ログイン状態でアクセス | ログインが必要 |

### リトライ仕様

特になし（同期処理のため）

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

- `wishlistRepository.upsert` 内でトランザクションが管理される
- ウィッシュリストと商品関連の登録は一つのトランザクション内で実行
- 失敗時は自動ロールバック

## パフォーマンス要件

- レスポンス時間: 500ms以内
- Ajax通信のため、非同期処理で画面遷移なし

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

- ログイン必須（`LOGIN_REQUIRED`属性による認証チェック）
- CSRF対策（Symfonyフレームワークによる自動保護）
- 商品IDのUUID形式による不正入力防止
- 他顧客のウィッシュリストへのアクセス不可（顧客ID紐付け）

## 備考

- ウィッシュリスト機能はShopware 6.3.4.0で導入
- ゲスト顧客はローカルストレージを使用した別機能（フロントエンドのみ）で対応

---

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

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

### 推奨読解順序

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

まず、ウィッシュリストのデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerWishlistDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlist/CustomerWishlistDefinition.php` | ウィッシュリストのエンティティ定義。customer_id、sales_channel_id、productsリレーションを確認 |

**読解のコツ**: Shopwareの`EntityDefinition`クラスは、データベーステーブルの構造とリレーションを定義する。`defineFields()`メソッドでフィールド定義、`OneToManyAssociationField`でリレーションを確認できる。

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

処理の起点となるコントローラーを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WishlistController.php | `src/Storefront/Controller/WishlistController.php` | Storefront側のエントリーポイント。ajaxAddメソッドがお気に入り追加の入口 |

**主要処理フロー**:
1. **187-199行目**: ajaxAddメソッドの定義。productIdとSalesChannelContext、CustomerEntityを受け取る
2. **189-194行目**: try-catchブロック内でaddWishlistRouteを呼び出し、例外時はsuccess=falseを返却
3. **196-198行目**: 成功時は`{"success": true}`のJSONレスポンスを返却

#### Step 3: Store API ルートを理解する

実際のビジネスロジックはStore APIルートに実装されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AddWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AddWishlistProductRoute.php` | 商品追加の核心ロジック。バリデーションとupsert処理 |

**主要処理フロー**:
- **57-83行目**: addメソッドの実装
- **59-61行目**: ウィッシュリスト機能の有効性チェック
- **63行目**: 商品存在チェック（validateProduct）
- **64行目**: ウィッシュリストID取得（getWishlistId）
- **66-78行目**: upsert処理でウィッシュリストと商品関連を永続化
- **80行目**: WishlistProductAddedEventイベント発火

#### Step 4: 補助メソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AddWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AddWishlistProductRoute.php` | getWishlistId、validateProductメソッド |

**主要処理フロー**:
- **85-95行目**: getWishlistIdメソッド。顧客ID+販売チャネルIDでウィッシュリストを検索、なければ新規UUIDを生成
- **97-103行目**: validateProductメソッド。SalesChannelRepositoryで商品存在を確認

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

```
WishlistController::ajaxAdd (Storefront)
    │
    └─ AddWishlistProductRoute::add (Store API)
           │
           ├─ SystemConfigService::get (設定確認)
           │
           ├─ validateProduct (商品存在確認)
           │      └─ SalesChannelRepository::searchIds
           │
           ├─ getWishlistId (ウィッシュリストID取得)
           │      └─ EntityRepository::searchIds
           │
           ├─ EntityRepository::upsert (データ永続化)
           │
           └─ EventDispatcher::dispatch (イベント発火)
                  └─ WishlistProductAddedEvent
```

### データフロー図

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

productId (UUID) ──────▶ WishlistController::ajaxAdd
                              │
SalesChannelContext ───────▶ │
                              │
CustomerEntity ────────────▶ │
                              ▼
                         AddWishlistProductRoute::add
                              │
                              ├─▶ customer_wishlist テーブル (SELECT/INSERT)
                              │
                              └─▶ customer_wishlist_product テーブル (INSERT)
                                          │
                                          ▼
                                   JsonResponse ──────▶ {"success": true/false}
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WishlistController.php | `src/Storefront/Controller/WishlistController.php` | ソース | Storefrontコントローラー、エントリーポイント |
| AddWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AddWishlistProductRoute.php` | ソース | Store APIルート、ビジネスロジック |
| AbstractAddWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractAddWishlistProductRoute.php` | ソース | 抽象クラス、デコレーションパターン用 |
| CustomerWishlistDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlist/CustomerWishlistDefinition.php` | ソース | ウィッシュリストエンティティ定義 |
| CustomerWishlistProductDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlistProduct/CustomerWishlistProductDefinition.php` | ソース | ウィッシュリスト商品関連エンティティ定義 |
| WishlistProductAddedEvent.php | `src/Core/Checkout/Customer/Event/WishlistProductAddedEvent.php` | ソース | 商品追加イベントクラス |
| index.html.twig | `src/Storefront/Resources/views/storefront/page/wishlist/index.html.twig` | テンプレート | ウィッシュリストページテンプレート |
