# 画面設計書 23-ウィッシュリストページ

## 概要

本ドキュメントは、Shopware 6 Storefrontのウィッシュリストページ（Wishlist Page）の画面設計書である。顧客がお気に入りに追加した商品を一覧表示・管理するための画面について記載する。

### 本画面の処理概要

ウィッシュリストページは、顧客が興味を持った商品を保存し、後から確認・購入できるようにするための機能を提供する。

**業務上の目的・背景**：顧客は商品を検討段階で保存しておき、後日購入を決定したいというニーズがある。ウィッシュリスト機能により、顧客は複数の商品を比較検討でき、購入意欲の維持とコンバージョン向上に寄与する。また、ゲストユーザーでもローカルストレージを利用した一時的なウィッシュリスト機能が提供される。

**画面へのアクセス方法**：
- ヘッダーのウィッシュリストアイコンをクリック
- マイアカウントメニューから「ウィッシュリスト」を選択
- 商品詳細ページで「お気に入りに追加」後のリダイレクト

**主要な操作・処理内容**：
1. お気に入り商品の一覧表示（ページネーション付き）
2. 商品のウィッシュリストからの削除
3. 商品をカートに追加
4. 商品詳細ページへの遷移
5. ゲストユーザーのウィッシュリストをログイン時にマージ

**画面遷移**：
- 遷移元：ホームページ、商品詳細ページ、アカウントホームページ
- 遷移先：商品詳細ページ、カートページ

**権限による表示制御**：
- ログインユーザー：サーバーサイドでウィッシュリストを管理
- ゲストユーザー：クライアントサイド（localStorage）でウィッシュリストを管理、ログイン時にマージ可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 50 | お気に入り一覧 | 主機能 | お気に入り商品一覧表示（WishlistController::index） |
| 52 | お気に入り削除 | 補助機能 | 商品をお気に入りから削除（WishlistController::ajaxRemove） |
| 53 | お気に入りマージ | 補助機能 | ゲストのお気に入りをマージ（WishlistController::ajaxMerge） |

## 画面種別

一覧

## URL/ルーティング

| ルート名 | URL | HTTPメソッド | 説明 |
|----------|-----|--------------|------|
| frontend.wishlist.page | /wishlist | GET | ウィッシュリストページ表示 |
| frontend.wishlist.guestPage.pagelet | /wishlist/guest-pagelet | POST | ゲスト用ページレット取得 |
| widgets.wishlist.pagelet | /widgets/wishlist | GET, POST | ウィッシュリストウィジェット |
| frontend.wishlist.product.list | /wishlist/list | GET | お気に入り商品ID一覧取得 |
| frontend.wishlist.product.delete | /wishlist/product/delete/{id} | POST, DELETE | 商品削除（フォーム用） |
| frontend.wishlist.product.add | /wishlist/add/{productId} | POST | 商品追加（AJAX） |
| frontend.wishlist.product.remove | /wishlist/remove/{productId} | POST | 商品削除（AJAX） |
| frontend.wishlist.add.after.login | /wishlist/add-after-login/{productId} | GET | ログイン後の商品追加 |
| frontend.wishlist.product.merge | /wishlist/merge | POST | ゲストウィッシュリストのマージ |
| frontend.wishlist.product.merge.pagelet | /wishlist/merge/pagelet | GET, POST | マージ後のページレット取得 |

## 入出力項目

### 入力項目

| 項目名 | パラメータ名 | 型 | 必須 | 説明 |
|--------|-------------|-----|------|------|
| ページ番号 | p | integer | - | ページネーション用 |
| 商品ID | productId | string(UUID) | - | 追加/削除対象商品 |
| 商品ID | id | string(UUID) | - | 削除対象商品（フォーム用） |
| 商品IDリスト | productIds | array | - | マージ対象商品リスト |

### 出力項目

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | WishlistPage / GuestWishlistPage | ウィッシュリストページ |
| page.wishlist | LoadWishlistRouteResponse | ウィッシュリストデータ |
| page.wishlist.productListing | EntitySearchResult | 商品一覧 |

## 表示項目

### 商品一覧

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| 商品名 | product.name | 商品名 |
| 商品画像 | product.cover | 商品サムネイル |
| 価格 | product.calculatedPrice | 販売価格 |
| メーカー | product.manufacturer | メーカー情報 |
| バリアント | product.options | サイズ・カラー等 |
| 在庫状況 | product.available | 在庫有無 |

## イベント仕様

### 1-商品削除

削除ボタンクリックで該当商品をウィッシュリストから削除。AJAX通信で削除処理を実行し、成功時にフラッシュメッセージ表示。

### 2-カートに追加

「カートに追加」ボタンクリックで商品をショッピングカートに追加。

### 3-ページネーション

1ページあたり24件（DEFAULT_LIMIT）で表示。AJAX通信でページ切り替え。

### 4-ゲストウィッシュリストマージ

ゲストユーザーがログインした際、localStorageのウィッシュリストをサーバーサイドにマージ。マージ完了後にフラッシュメッセージ表示。

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 商品追加 | customer_wishlist_product | INSERT | ウィッシュリストに商品追加 |
| 商品削除 | customer_wishlist_product | DELETE | ウィッシュリストから商品削除 |
| マージ | customer_wishlist_product | INSERT | ゲストの商品をマージ |

### テーブル別更新項目詳細

#### customer_wishlist_product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | customer_wishlist_id | 顧客のウィッシュリストID | 商品追加時 |
| INSERT | product_id | 追加する商品ID | 商品追加時 |
| INSERT | product_version_id | 商品バージョンID | 商品追加時 |
| DELETE | id | 削除対象のレコードID | 商品削除時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| wishlist.itemAddedSuccess | 成功 | 商品をウィッシュリストに追加しました | 追加成功時 |
| wishlist.itemDeleteSuccess | 成功 | 商品をウィッシュリストから削除しました | 削除成功時 |
| wishlist.duplicateItemError | 警告 | この商品は既にウィッシュリストに存在します | 重複追加時 |
| wishlist.wishlistMergeHint | 情報 | ウィッシュリストがマージされました | マージ成功時 |
| error.message-default | エラー | エラーが発生しました | 処理失敗時 |

## 例外処理

| 例外 | 原因 | 処理内容 |
|------|------|----------|
| CustomerWishlistNotFoundException | ウィッシュリストが存在しない | 空のウィッシュリストを表示 |
| DuplicateWishlistProductException | 商品が既に存在 | 警告メッセージ表示 |
| NotFoundHttpException | ゲスト用ページレットをログインユーザーがアクセス | 404エラー |
| RoutingException | 商品IDが未指定 | エラーメッセージ表示 |

## 備考

- ログインユーザーとゲストユーザーで異なるローダー（WishlistPageLoader / GuestWishlistPageLoader）を使用
- ゲストユーザーはJavaScript（data-guest-wishlist-page）でlocalStorageを利用
- 商品はwishlists.updatedAtで昇順ソート
- WishListPageProductCriteriaEventでCriteriaのカスタマイズが可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | WishlistPage.php | `src/Storefront/Page/Wishlist/WishlistPage.php` | wishlistプロパティ |
| 1-2 | GuestWishlistPage.php | `src/Storefront/Page/Wishlist/GuestWishlistPage.php` | ゲスト用ページ構造 |
| 1-3 | LoadWishlistRouteResponse.php | `src/Core/Checkout/Customer/SalesChannel/LoadWishlistRouteResponse.php` | レスポンス構造 |

**読解のコツ**: ログインユーザーとゲストユーザーで異なるPage/Loaderが使用される点に注意。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WishlistController.php | `src/Storefront/Controller/WishlistController.php` | index(), guestPagelet(), ajaxAdd(), ajaxRemove(), ajaxMerge() |

**主要処理フロー**:
1. **57-77行**: index() - メインページ表示（ログイン/ゲスト分岐）
2. **79-101行**: guestPagelet() - ゲスト用ページレット
3. **187-199行**: ajaxAdd() - 商品追加
4. **201-223行**: ajaxRemove() - 商品削除
5. **247-270行**: ajaxMerge() - ウィッシュリストマージ

#### Step 3: ページローダーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | WishlistPageLoader.php | `src/Storefront/Page/Wishlist/WishlistPageLoader.php` | load(), createCriteria() |
| 3-2 | GuestWishlistPageLoader.php | `src/Storefront/Page/Wishlist/GuestWishlistPageLoader.php` | ゲスト用ローダー |

**主要処理フロー**:
- **50-83行**: load() - ページ読み込み
- **85-100行**: createCriteria() - 検索条件（24件、昇順ソート）

#### Step 4: テンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.html.twig | `src/Storefront/Resources/views/storefront/page/wishlist/index.html.twig` | メインテンプレート |
| 4-2 | wishlist-pagelet.html.twig | `src/Storefront/Resources/views/storefront/page/wishlist/wishlist-pagelet.html.twig` | 商品一覧部分 |

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

```
WishlistController::index()
    │
    ├─ [ログインユーザー]
    │      │
    │      ├─ WishlistPageLoader::load()
    │      │      ├─ GenericPageLoaderInterface::load()
    │      │      ├─ WishListPageProductCriteriaEvent dispatch
    │      │      ├─ AbstractLoadWishlistRoute::load()
    │      │      └─ WishlistPageLoadedEvent dispatch
    │      │
    │      └─ WishlistPageLoadedHook
    │
    └─ [ゲストユーザー]
           │
           ├─ GuestWishlistPageLoader::load()
           └─ GuestWishlistPageLoadedHook

WishlistController::ajaxAdd()
    │
    └─ AbstractAddWishlistProductRoute::add()

WishlistController::ajaxRemove()
    │
    └─ AbstractRemoveWishlistProductRoute::delete()

WishlistController::ajaxMerge()
    │
    └─ AbstractMergeWishlistProductRoute::merge()
```

### データフロー図

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

Request (p)
         │
         ▼
 WishlistController::index()
         │
         ├─► customer.guest判定
         │
         ├─► [ログインユーザー]
         │         │
         │         ▼
         │    WishlistPageLoader::load()
         │         │
         │         ├─► AbstractLoadWishlistRoute
         │         │         │
         │         │         ▼
         │         │    ProductCollection
         │         │
         │         └─► WishlistPage ────────────────► index.html.twig
         │
         └─► [ゲストユーザー]
                   │
                   ▼
              GuestWishlistPageLoader
                   │
                   └─► GuestWishlistPage
                            │
                            ▼
                       index.html.twig
                            │
                            ▼
                       data-guest-wishlist-page (JavaScript)
                            │
                            ▼
                       localStorage ──► guestPagelet ──► 商品表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WishlistController.php | `src/Storefront/Controller/WishlistController.php` | コントローラー | リクエスト処理 |
| WishlistPageLoader.php | `src/Storefront/Page/Wishlist/WishlistPageLoader.php` | ページローダー | ログインユーザー用 |
| GuestWishlistPageLoader.php | `src/Storefront/Page/Wishlist/GuestWishlistPageLoader.php` | ページローダー | ゲストユーザー用 |
| WishlistPage.php | `src/Storefront/Page/Wishlist/WishlistPage.php` | ページエンティティ | ログインユーザー用 |
| GuestWishlistPage.php | `src/Storefront/Page/Wishlist/GuestWishlistPage.php` | ページエンティティ | ゲストユーザー用 |
| AbstractLoadWishlistRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractLoadWishlistRoute.php` | Store API | ウィッシュリスト取得 |
| AbstractAddWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractAddWishlistProductRoute.php` | Store API | 商品追加 |
| AbstractRemoveWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractRemoveWishlistProductRoute.php` | Store API | 商品削除 |
| AbstractMergeWishlistProductRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractMergeWishlistProductRoute.php` | Store API | マージ |
| index.html.twig | `src/Storefront/Resources/views/storefront/page/wishlist/index.html.twig` | テンプレート | メイン画面 |
| wishlist-pagelet.html.twig | `src/Storefront/Resources/views/storefront/page/wishlist/wishlist-pagelet.html.twig` | テンプレート | 商品一覧 |
| guest-wishlist-page.plugin.js | `src/Storefront/Resources/app/storefront/src/plugin/wishlist/guest-wishlist-page.plugin.js` | JavaScript | ゲスト用処理 |
