# 機能設計書 50-お気に入り一覧

## 概要

本ドキュメントは、顧客のお気に入り（ウィッシュリスト）商品一覧を表示する機能の設計を定義するものである。

### 本機能の処理概要

顧客が保存したお気に入り商品の一覧を表示するページ機能である。

**業務上の目的・背景**：顧客が興味を持った商品を保存し、後から確認・購入検討できるようにすることで、購買意欲の維持と再訪問を促進する。ゲスト顧客の場合はローカルストレージに保存されたお気に入り情報を元にクライアント側で表示を行う。

**機能の利用シーン**：顧客がナビゲーションメニューやヘッダーのお気に入りアイコンをクリックしてアクセスする。商品詳細ページや商品一覧ページでお気に入りに追加した商品を確認する際に使用される。

**主要な処理内容**：
1. 顧客のログイン状態を判定
2. 会員顧客の場合はデータベースからお気に入り情報を取得
3. ゲスト顧客の場合は空のページを返し、クライアント側でローカルストレージから取得
4. ページデータを構築してテンプレートをレンダリング

**関連システム・外部連携**：商品管理システム（商品情報の取得）

**権限による制御**：お気に入り機能は`core.cart.wishlistEnabled`設定で有効/無効を制御。会員顧客とゲスト顧客で処理が分岐する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | お気に入り一覧ページ | 主画面 | お気に入り商品の一覧表示（WishlistController::index） |
| 1 | TOPページ | 遷移元 | ヘッダーのお気に入りアイコンから遷移 |

## 機能種別

画面表示 / データ取得

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| p | int | No | ページ番号 | 正の整数、デフォルト1 |

### 入力データソース

- クエリパラメータ: `p`（ページ番号）
- SalesChannelContext: 顧客情報
- Request: HTTPリクエストオブジェクト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | WishlistPage / GuestWishlistPage | ページデータオブジェクト |
| page.wishlist | LoadWishlistRouteResponse | お気に入りデータ（会員のみ） |
| page.wishlist.productListing | EntitySearchResult | 商品一覧 |

### 出力先

- HTMLレスポンス（Twigテンプレートをレンダリング）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ GETメソッドで /wishlist にアクセス
2. 顧客状態判定
   └─ ログイン済み会員か、ゲストかを判定
3. 会員の場合
   └─ WishlistPageLoader::load() を呼び出し
   └─ LoadWishlistRoute経由でお気に入りデータ取得
   └─ WishlistPageLoadedHook発火
4. ゲストの場合
   └─ GuestWishlistPageLoader::load() を呼び出し
   └─ 空のページデータを構築
   └─ GuestWishlistPageLoadedHook発火
5. レスポンス返却
   └─ テンプレートをレンダリングして返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: index] --> B{顧客判定}
    B -->|会員| C[WishlistPageLoader::load]
    B -->|ゲスト/未ログイン| D[GuestWishlistPageLoader::load]
    C --> E[createCriteria]
    E --> F[WishListPageProductCriteriaEvent発火]
    F --> G[GenericPageLoader::load]
    G --> H[LoadWishlistRoute::load]
    H --> I{ウィッシュリスト存在?}
    I -->|Yes| J[商品データ取得]
    I -->|No| K[空のウィッシュリスト作成]
    J --> L[WishlistPageLoadedEvent発火]
    K --> L
    L --> M[WishlistPageLoadedHook発火]
    D --> N[GenericPageLoader::load]
    N --> O[GuestWishlistPageLoadedEvent発火]
    O --> P[GuestWishlistPageLoadedHook発火]
    M --> Q[テンプレートレンダリング]
    P --> Q
    Q --> R[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | 機能有効チェック | お気に入り機能は設定で有効/無効を制御 | core.cart.wishlistEnabled |
| BR-50-02 | 会員/ゲスト分岐 | 会員はDB、ゲストはローカルストレージ | ログイン状態による |
| BR-50-03 | ページネーション | 1ページあたり24件表示 | DEFAULT_LIMIT定数 |
| BR-50-04 | ソート順 | 更新日時の降順で表示 | wishlists.updatedAt DESC |

### 計算ロジック

ページングのオフセット計算：
- offset = limit * (page - 1)
- 例: page=2, limit=24 の場合、offset=24

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ウィッシュリスト取得 | customer_wishlist | SELECT | 顧客のウィッシュリスト取得 |
| 商品取得 | product | SELECT | お気に入り商品一覧取得 |

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

#### customer_wishlist（読み取りのみ）

検索条件：
- customerId = 顧客ID
- salesChannelId = 販売チャネルID

#### product（読み取りのみ）

関連テーブルとして以下を含めて取得：
- `manufacturer` - 製造元情報
- `options.group` - バリアントオプション
- `wishlists` - ウィッシュリスト関連（フィルタ用）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CUSTOMER__WISHLIST_IS_NOT_ACTIVATED | 業務エラー | お気に入り機能が無効 | 例外をスロー |
| CUSTOMER__WISHLIST_NOT_FOUND | 業務エラー | ウィッシュリストが存在しない | 空のウィッシュリストを作成 |

### リトライ仕様

エラー発生時は例外をスローするか、空のデータを返す。

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

参照処理のみのため、特別なトランザクション制御は不要。

## パフォーマンス要件

- レスポンス時間: 1000ms以内
- ページネーション: 1ページあたり24件（DEFAULT_LIMIT）
- 在庫切れ商品の非表示設定対応（hideCloseoutProductsWhenOutOfStock）

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

- robots noindex,follow設定（SEO対策）
- キャッシュ無効化（PlatformRequest::ATTRIBUTE_NO_STORE）
- 会員は自分のお気に入りのみ閲覧可能（customerId照合）

## 備考

- ゲスト顧客のお気に入りはローカルストレージに保存され、クライアント側JavaScriptで処理される
- ログイン後にローカルストレージのお気に入りをDBにマージする機能あり（ajaxMerge）
- 商品がウィッシュリストに存在しない場合は空のコレクションを返す

---

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

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

### 推奨読解順序

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

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

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

**主要処理フロー**:
1. **57-63行目**: Route属性（パス、メソッド、オプション）
2. **64-77行目**: indexメソッド全体
3. **66行目**: 顧客情報取得
4. **68行目**: 会員顧客判定（getGuest() === false）
5. **69行目**: WishlistPageLoader::load呼び出し
6. **70行目**: WishlistPageLoadedHook発火
7. **72行目**: ゲスト用GuestWishlistPageLoader::load呼び出し
8. **73行目**: GuestWishlistPageLoadedHook発火
9. **76行目**: テンプレートレンダリング

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

実際のデータ取得処理を行うページローダーを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WishlistPageLoader.php | `src/Storefront/Page/Wishlist/WishlistPageLoader.php` | データ取得ロジック |

**主要処理フロー**:
- **30-32行目**: 定数定義（DEFAULT_LIMIT=24, DEFAULT_PAGE=1）
- **50-83行目**: load()メソッド全体
- **52行目**: Criteria作成
- **53行目**: WishListPageProductCriteriaEvent発火
- **55行目**: GenericPageLoader::load呼び出し
- **56行目**: robots設定（noindex,follow）
- **60-76行目**: ウィッシュリストデータ取得（例外処理含む）
- **78-80行目**: WishlistPageLoadedEvent発火
- **85-100行目**: createCriteriaメソッド（ページング、ソート、関連設定）

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

お気に入りデータ取得のAPIルートを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | LoadWishlistRoute.php | `src/Core/Checkout/Customer/SalesChannel/LoadWishlistRoute.php` | データ取得API |

**主要処理フロー**:
- **64-78行目**: load()メソッド全体
- **70-72行目**: wishlistEnabled設定チェック
- **74行目**: loadWishlist()でウィッシュリストエンティティ取得
- **75行目**: loadProducts()で商品データ取得
- **80-95行目**: loadWishlistメソッド（顧客ID、販売チャネルIDでフィルタ）
- **100-123行目**: loadProductsメソッド（商品検索、在庫切れフィルタ、イベント発火）

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

```
WishlistController::index()
    │
    ├─ context->getCustomer() - 顧客取得
    │
    ├─ 【会員の場合】
    │      │
    │      ├─ WishlistPageLoader::load()
    │      │      │
    │      │      ├─ createCriteria() - ページング設定
    │      │      │
    │      │      ├─ WishListPageProductCriteriaEvent発火
    │      │      │
    │      │      ├─ GenericPageLoader::load() - 基本ページ情報
    │      │      │
    │      │      ├─ setRobots('noindex,follow') - SEO設定
    │      │      │
    │      │      ├─ LoadWishlistRoute::load()
    │      │      │      │
    │      │      │      ├─ wishlistEnabled設定チェック
    │      │      │      │
    │      │      │      ├─ loadWishlist() - ウィッシュリスト取得
    │      │      │      │      │
    │      │      │      │      └─ wishlistRepository::search()
    │      │      │      │
    │      │      │      └─ loadProducts() - 商品データ取得
    │      │      │             │
    │      │      │             ├─ CustomerWishlistLoaderCriteriaEvent発火
    │      │      │             │
    │      │      │             ├─ productRepository::search()
    │      │      │             │
    │      │      │             └─ CustomerWishlistProductListingResultEvent発火
    │      │      │
    │      │      └─ WishlistPageLoadedEvent発火
    │      │
    │      └─ hook(WishlistPageLoadedHook)
    │
    ├─ 【ゲストの場合】
    │      │
    │      ├─ GuestWishlistPageLoader::load()
    │      │      │
    │      │      ├─ GenericPageLoader::load() - 基本ページ情報
    │      │      │
    │      │      ├─ setRobots('noindex,follow') - SEO設定
    │      │      │
    │      │      └─ GuestWishlistPageLoadedEvent発火
    │      │
    │      └─ hook(GuestWishlistPageLoadedHook)
    │
    └─ renderStorefront() - テンプレートレンダリング
```

### データフロー図

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

Request ────────────────▶ WishlistController              ───▶ HTMLレスポンス
  │                              │                                    │
  │                              ▼                                    │
  │                      顧客状態判定                                  │
  │                              │                                    │
  │                    ┌─────────┴─────────┐                          │
  │                    ▼                   ▼                          │
  │             【会員】              【ゲスト】                        │
  │                    │                   │                          │
  │                    ▼                   ▼                          │
  │          WishlistPageLoader   GuestWishlistPageLoader             │
  │                    │                   │                          │
  │                    ▼                   │                          │
  │          LoadWishlistRoute             │                          │
  │                    │                   │                          │
  │                    ▼                   │                          │
  │          loadWishlist()                │                          │
  │                    │                   │                          │
  ▼                    ▼                   ▼                          ▼
customer_wishlist ──▶ CustomerWishlistEntity         GuestWishlistPage
  │                    │                                              │
  │                    ▼                                              │
product ───────────▶ loadProducts()                                   │
  │                    │                                              │
  ├─ manufacturer      ▼                                              │
  │              ProductCollection                                    │
  ├─ options           │                                              │
  │                    ▼                                              │
  └─ wishlists    WishlistPage ────────────────────────────────────▶ page
                       │
                       ├─ wishlist.productListing
                       │
                       └─ metaInformation (robots: noindex,follow)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WishlistController.php | `src/Storefront/Controller/WishlistController.php` | コントローラー | HTTPリクエスト処理 |
| 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` | ページエンティティ | ゲスト用ページデータ構造 |
| LoadWishlistRoute.php | `src/Core/Checkout/Customer/SalesChannel/LoadWishlistRoute.php` | ルート | ウィッシュリスト取得API |
| AbstractLoadWishlistRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractLoadWishlistRoute.php` | 抽象クラス | ルートインターフェース |
| GenericPageLoaderInterface.php | `src/Storefront/Page/GenericPageLoaderInterface.php` | インターフェース | 汎用ページ読み込み |
| WishlistPageLoadedHook.php | `src/Storefront/Page/Wishlist/WishlistPageLoadedHook.php` | フック | 会員ページ読み込み後フック |
| GuestWishlistPageLoadedHook.php | `src/Storefront/Page/Wishlist/GuestWishlistPageLoadedHook.php` | フック | ゲストページ読み込み後フック |
| WishListPageProductCriteriaEvent.php | `src/Storefront/Page/Wishlist/WishListPageProductCriteriaEvent.php` | イベント | 商品検索条件カスタマイズ |
| CustomerWishlistLoaderCriteriaEvent.php | `src/Core/Checkout/Customer/Event/CustomerWishlistLoaderCriteriaEvent.php` | イベント | ローダー検索条件イベント |
| index.html.twig | `src/Storefront/Resources/views/storefront/page/wishlist/index.html.twig` | テンプレート | 画面レンダリング |
