# 機能設計書 52-お気に入り削除

## 概要

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

### 本機能の処理概要

**業務上の目的・背景**：顧客がお気に入りリストに保存した商品を管理するために、不要になった商品を削除できる機能が必要である。購入済み商品や興味がなくなった商品をリストから除外することで、お気に入りリストの実用性を維持し、顧客体験を向上させる。

**機能の利用シーン**：顧客がウィッシュリストページを閲覧中に、特定商品の削除ボタン（ゴミ箱アイコン等）をクリックした際に本機能が呼び出される。また、商品詳細ページでお気に入り済み商品のハートアイコンを再クリックした場合にも利用される。ログイン済みの顧客のみが利用可能。

**主要な処理内容**：
1. ログイン状態およびウィッシュリスト機能有効化の確認
2. 顧客のウィッシュリスト存在確認
3. ウィッシュリスト内の指定商品関連レコード特定
4. 商品関連レコードの削除
5. イベント発火による拡張ポイント提供
6. 処理結果のJSONレスポンス返却

**関連システム・外部連携**：本機能は Store API (`/store-api/customer/wishlist/delete/{productId}`) を内部的に利用し、データベースから商品関連を削除する。また、`WishlistProductRemovedEvent`イベントを発火し、プラグインによる拡張を可能にする。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 23 | ウィッシュリストページ | 主画面 | 削除ボタンクリックでお気に入り削除 |
| 3 | 商品詳細ページ | 参照画面 | ハートアイコン再クリックで削除 |

## 機能種別

CRUD操作（Delete）/ Ajax API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| productId | string (UUID) | Yes | お気に入りから削除する商品のID | UUID形式、ウィッシュリストに存在すること |

### 入力データソース

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

## 出力仕様

### 出力データ

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

### 出力先

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

## 処理フロー

### 処理シーケンス

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

2. Store API呼び出し
   └─ RemoveWishlistProductRoute::delete を実行

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

4. ウィッシュリスト存在確認
   └─ 顧客ID + 販売チャネルIDでウィッシュリストを検索
   └─ 存在しない場合は CustomerException::customerWishlistNotFound をスロー

5. 商品関連レコード特定
   └─ ウィッシュリストID + 商品ID + LIVE_VERSION で検索
   └─ 存在しない場合は CustomerException::wishlistProductNotFound をスロー

6. データ削除
   └─ productRepository.delete で商品関連を削除

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

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

### フローチャート

```mermaid
flowchart TD
    A[開始: ajaxRemove呼び出し] --> B{ウィッシュリスト機能有効?}
    B -->|No| C[例外: WishlistNotActivated]
    B -->|Yes| D{ウィッシュリストは存在する?}
    D -->|No| E[例外: WishlistNotFound]
    D -->|Yes| F{商品はウィッシュリスト内に存在?}
    F -->|No| G[例外: WishlistProductNotFound]
    F -->|Yes| H[商品関連レコード削除]
    H --> I[WishlistProductRemovedEvent発火]
    I --> J[成功レスポンス返却]
    C --> K[例外キャッチ]
    E --> K
    G --> K
    K --> L[失敗レスポンス返却]
    J --> M[終了]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-52-01 | ログイン必須 | ログイン済み顧客のみが利用可能 | 全リクエスト |
| BR-52-02 | ウィッシュリスト有効化 | システム設定でウィッシュリストが有効である必要がある | 全リクエスト |
| BR-52-03 | 所有権チェック | 自分のウィッシュリスト内の商品のみ削除可能 | 商品削除時 |
| BR-52-04 | 存在チェック | ウィッシュリスト内に存在する商品のみ削除可能 | 商品削除時 |
| BR-52-05 | 販売チャネル分離 | 販売チャネル毎にウィッシュリストは分離管理 | 全リクエスト |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ウィッシュリスト取得 | customer_wishlist | SELECT | 顧客のウィッシュリストを検索 |
| 商品関連取得 | customer_wishlist_product | SELECT | 削除対象の商品関連レコードを特定 |
| 商品関連削除 | customer_wishlist_product | DELETE | ウィッシュリストから商品関連を削除 |

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

#### customer_wishlist

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | customer_id = ? AND sales_channel_id = ? | ウィッシュリスト存在確認 |

#### customer_wishlist_product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | wishlist_id = ? AND product_id = ? AND product_version_id = LIVE_VERSION | 削除対象特定 |
| DELETE | id | 上記で取得したID | 商品関連削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CUSTOMER_WISHLIST_NOT_ACTIVATED | 機能無効 | core.cart.wishlistEnabled が false | システム設定を有効化 |
| CUSTOMER_WISHLIST_NOT_FOUND | ウィッシュリスト未存在 | 顧客のウィッシュリストが存在しない | 先にお気に入り追加が必要 |
| WISHLIST_PRODUCT_NOT_FOUND | 商品未存在 | 指定商品がウィッシュリストにない | 正しい商品IDを指定 |
| - | 認証エラー | 未ログイン状態でアクセス | ログインが必要 |

### リトライ仕様

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

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

- `productRepository.delete` 内でトランザクションが管理される
- 失敗時は自動ロールバック

## パフォーマンス要件

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

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

- ログイン必須（`LOGIN_REQUIRED`属性による認証チェック）
- CSRF対策（Symfonyフレームワークによる自動保護）
- 所有権チェック（自分のウィッシュリストのみ操作可能）
- 他顧客のウィッシュリストへのアクセス不可

## 備考

- 削除はソフトデリートではなく物理削除
- ウィッシュリスト自体は商品がなくても残存する

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerWishlistDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlist/CustomerWishlistDefinition.php` | ウィッシュリストエンティティ定義。productsリレーションを確認 |
| 1-2 | CustomerWishlistProductDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlistProduct/CustomerWishlistProductDefinition.php` | 商品関連エンティティ定義。wishlistId、productId、productVersionIdを確認 |

**読解のコツ**: `CascadeDelete`フラグがついている場合、親エンティティ削除時に子も自動削除される。本機能では商品関連のみを削除するため、ウィッシュリスト自体は残る。

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

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

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

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

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | RemoveWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/RemoveWishlistProductRoute.php` | 商品削除の核心ロジック。存在チェックとdelete処理 |

**主要処理フロー**:
- **55-74行目**: deleteメソッドの実装
- **57-59行目**: ウィッシュリスト機能の有効性チェック
- **61行目**: ウィッシュリストID取得（getWishlistId）
- **63行目**: 商品関連レコードID取得（getWishlistProductId）
- **65-69行目**: delete処理で商品関連を削除
- **71行目**: WishlistProductRemovedEventイベント発火

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

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

**主要処理フロー**:
- **76-91行目**: getWishlistIdメソッド。顧客ID+販売チャネルIDでウィッシュリスト検索。存在しなければ例外
- **93-109行目**: getWishlistProductIdメソッド。ウィッシュリストID+商品ID+LIVE_VERSIONで商品関連検索。存在しなければ例外

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

```
WishlistController::ajaxRemove (Storefront)
    │
    └─ RemoveWishlistProductRoute::delete (Store API)
           │
           ├─ SystemConfigService::get (設定確認)
           │
           ├─ getWishlistId (ウィッシュリスト存在確認)
           │      └─ EntityRepository::searchIds
           │             └─ 例外: CustomerWishlistNotFound
           │
           ├─ getWishlistProductId (商品関連存在確認)
           │      └─ EntityRepository::searchIds
           │             └─ 例外: WishlistProductNotFound
           │
           ├─ EntityRepository::delete (データ削除)
           │
           └─ EventDispatcher::dispatch (イベント発火)
                  └─ WishlistProductRemovedEvent
```

### データフロー図

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

productId (UUID) ──────▶ WishlistController::ajaxRemove
                              │
SalesChannelContext ───────▶ │
                              │
CustomerEntity ────────────▶ │
                              ▼
                         RemoveWishlistProductRoute::delete
                              │
                              ├─▶ customer_wishlist テーブル (SELECT)
                              │
                              └─▶ customer_wishlist_product テーブル (SELECT → DELETE)
                                          │
                                          ▼
                                   JsonResponse ──────▶ {"success": true/false}
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WishlistController.php | `src/Storefront/Controller/WishlistController.php` | ソース | Storefrontコントローラー、エントリーポイント |
| RemoveWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/RemoveWishlistProductRoute.php` | ソース | Store APIルート、ビジネスロジック |
| AbstractRemoveWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractRemoveWishlistProductRoute.php` | ソース | 抽象クラス、デコレーションパターン用 |
| CustomerWishlistDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlist/CustomerWishlistDefinition.php` | ソース | ウィッシュリストエンティティ定義 |
| CustomerWishlistProductDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerWishlistProduct/CustomerWishlistProductDefinition.php` | ソース | ウィッシュリスト商品関連エンティティ定義 |
| WishlistProductRemovedEvent.php | `src/Core/Checkout/Customer/Event/WishlistProductRemovedEvent.php` | ソース | 商品削除イベントクラス |
