# 機能設計書 21-住所マネージャー

## 概要

本ドキュメントは、Shopwareストアフロントにおける住所マネージャー機能の設計を記述したものである。住所マネージャーは、チェックアウト時に顧客が請求先住所および配送先住所を選択・管理するためのモーダルウィジェットを提供する機能である。

### 本機能の処理概要

住所マネージャー機能は、チェックアウトプロセス中に顧客が住所を効率的に管理できるようにするためのモーダルインターフェースを提供する。

**業務上の目的・背景**：ECサイトにおいて、顧客が注文確定前に配送先や請求先を柔軟に変更できることは、ユーザー体験の向上と注文完了率の向上に直結する。特に複数の住所を持つ顧客（自宅・勤務先など）にとって、注文ごとに適切な住所を選択できることは重要な機能である。また、新規住所の登録もモーダル内で完結させることで、ページ遷移による離脱を防ぎ、スムーズなチェックアウトフローを実現する。

**機能の利用シーン**：
- 注文確認ページで配送先住所を変更したい場合
- 注文確認ページで請求先住所を変更したい場合
- チェックアウト中に新しい住所を追加登録したい場合
- ゲスト顧客が住所を編集したい場合

**主要な処理内容**：
1. ログイン済み顧客の住所一覧をモーダルで表示
2. 配送先住所または請求先住所の切り替え
3. 新規住所の作成（モーダル内フォーム）
4. 既存住所の選択とコンテキストへの適用
5. フラッシュメッセージによる操作結果の通知

**関連システム・外部連携**：
- AbstractListAddressRoute: 住所一覧取得API
- AbstractUpsertAddressRoute: 住所作成・更新API
- AbstractContextSwitchRoute: 販売チャネルコンテキスト切り替えAPI
- CartService: カート情報取得

**権限による制御**：
- ログイン必須（LOGIN_REQUIRED属性）
- ゲスト顧客もアクセス可能（LOGIN_REQUIRED_ALLOW_GUEST属性）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | 注文確認ページ | 主画面 | 配送先・請求先住所の選択（AddressController::addressManager） |
| 19 | 住所作成ページ | 参照画面 | 新しい住所の登録フォーム |
| 20 | 住所編集ページ | 参照画面 | 既存住所の編集フォーム |

## 機能種別

CRUD操作 / データ連携 / バリデーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| addressId | string | No | 編集対象の住所ID | UUID形式 |
| type | string | Yes | 住所タイプ（billing/shipping） | "billing" または "shipping" |
| redirectTo | string | No | 処理後のリダイレクト先 | ルート名 |
| redirectParameters | string | No | リダイレクトパラメータ | JSON形式 |
| address | RequestDataBag | No | 住所データ（新規作成・更新時） | 住所バリデーションルール適用 |

#### 住所データの内部パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | No | 住所ID（更新時） | UUID形式 |
| firstName | string | Yes | 名 | NotBlank |
| lastName | string | Yes | 姓 | NotBlank |
| street | string | Yes | 番地 | NotBlank |
| city | string | Yes | 市区町村 | NotBlank |
| zipcode | string | No | 郵便番号 | 国によって必須 |
| countryId | string | Yes | 国ID | UUID形式、EntityExists |
| countryStateId | string | No | 州・都道府県ID | UUID形式 |
| salutationId | string | No | 敬称ID | UUID形式 |
| company | string | No | 会社名 | - |
| department | string | No | 部署 | - |
| phoneNumber | string | No | 電話番号 | - |

### 入力データソース

- HTTPリクエストパラメータ（Query/POST）
- SalesChannelContext（顧客情報、セッション情報）
- CustomerEntity（ログイン顧客エンティティ）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | AddressListingPage | 住所一覧ページオブジェクト |
| page.addresses | CustomerAddressCollection | 顧客の住所コレクション |
| page.countries | CountryCollection | 利用可能な国一覧 |
| page.salutations | SalutationCollection | 敬称一覧 |
| page.cart | Cart | 現在のカート情報 |
| redirectTo | string | リダイレクト先ルート |
| redirectParameters | string | リダイレクトパラメータ |

### 出力先

- HTMLレスポンス（Twigテンプレート: `@Storefront/storefront/component/address/address-manager-modal.html.twig`）
- NoContentResponse（住所作成成功時）
- フラッシュメッセージ（セッション）

## 処理フロー

### 処理シーケンス

```
1. addressManager（GET）- モーダル表示
   └─ AddressListingPageLoaderで住所一覧ページを読み込み
   └─ AddressEditorModalStructにページデータをセット
   └─ address-manager-modal.html.twigをレンダリング
   └─ x-robots-tagヘッダーを設定（noindex）

2. addressManagerUpsert（POST）- 住所作成/更新
   └─ typeパラメータでchangeShipping/changeBillingをセット
   └─ addressIdがあれば既存住所を取得
   └─ addressデータがあれば住所を作成/更新
   └─ 成功時はNoContentResponseを返却
   └─ バリデーションエラー時はフォームを再表示

3. addressManagerSwitch（POST）- 住所切り替え
   └─ 配送先/請求先住所IDをリクエストから取得
   └─ ContextSwitchRouteでコンテキストを切り替え
   └─ フラッシュメッセージを設定
   └─ 指定されたルートにリダイレクト
```

### フローチャート

```mermaid
flowchart TD
    A[住所マネージャー呼び出し] --> B{リクエストタイプ}
    B -->|GET| C[住所一覧取得]
    C --> D[AddressListingPageLoader.load]
    D --> E[モーダルHTML生成]
    E --> F[レスポンス返却]

    B -->|POST with address| G{住所データあり?}
    G -->|Yes| H[住所バリデーション]
    H --> I{バリデーション成功?}
    I -->|Yes| J[住所Upsert]
    J --> K[コンテキスト切り替え]
    K --> L[NoContentResponse]
    I -->|No| M[エラーフォーム表示]

    G -->|No| N[住所作成フォーム表示]

    B -->|POST switch| O[住所ID取得]
    O --> P[ContextSwitchRoute.switchContext]
    P --> Q[フラッシュメッセージ]
    Q --> R[リダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-21-01 | ログイン必須 | 住所マネージャーはログイン済み顧客のみ利用可能 | 常時 |
| BR-21-02 | ゲストアクセス許可 | ゲスト顧客もアクセス可能 | チェックアウト中 |
| BR-21-03 | 住所タイプ必須 | billing/shippingのいずれかを指定 | 住所作成・切り替え時 |
| BR-21-04 | 顧客所有確認 | 操作対象の住所は当該顧客が所有するもののみ | 編集・選択時 |
| BR-21-05 | 国・州整合性 | 選択された州は選択された国に属している必要がある | 住所作成・更新時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 住所一覧取得 | customer_address | SELECT | 顧客の住所一覧を取得 |
| 国一覧取得 | country | SELECT | 利用可能な国一覧を取得 |
| 州一覧取得 | country_state | SELECT | 国に紐づく州一覧を取得 |
| 敬称一覧取得 | salutation | SELECT | 敬称一覧を取得 |
| 住所作成 | customer_address | INSERT | 新規住所を登録 |
| 住所更新 | customer_address | UPDATE | 既存住所を更新 |

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

#### customer_address

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | customerId = {ログイン顧客ID} | 顧客の全住所を取得 |
| INSERT | 全項目 | フォーム入力値 | 新規住所作成時 |
| UPDATE | 更新対象項目 | フォーム入力値 | id = {addressId} AND customerId = {顧客ID} |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ROUTING_INVALID_REQUEST_PARAMETER | バリデーションエラー | typeパラメータが不正 | エラーメッセージを表示 |
| CUSTOMER_ADDRESS_NOT_FOUND | 住所不在 | 指定されたaddressIdが存在しない | エラーメッセージを表示 |
| UUID_INVALID | UUID形式エラー | addressIdがUUID形式でない | 例外をスロー |
| CONSTRAINT_VIOLATION | バリデーション失敗 | 住所データのバリデーション失敗 | フォームにエラーを表示 |

### リトライ仕様

リトライ処理は行わない。バリデーションエラー時はフォームを再表示し、ユーザーに修正を促す。

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

住所の作成・更新処理はDAL（Data Abstraction Layer）経由で実行され、自動的にトランザクション管理される。コンテキスト切り替えとの一貫性は、処理の順序制御により担保する。

## パフォーマンス要件

- モーダル表示: 500ms以内
- 住所切り替え: 300ms以内
- 住所作成・更新: 1秒以内

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

- ログイン認証必須（XmlHttpRequest + LOGIN_REQUIRED属性）
- CSRF保護（Shopwareの標準CSRF保護機能）
- 顧客IDによるアクセス制御（他顧客の住所にはアクセス不可）
- x-robots-tag: noindex（検索エンジンへのインデックス防止）
- 入力値のサニタイズ（DALによる自動エスケープ）

## 備考

- 本機能はXmlHttpRequest（AJAX）として動作し、ページ全体の再読み込みなしに住所管理が可能
- デフォルトのリダイレクト先は`frontend.checkout.confirm.page`（注文確認ページ）
- ゲスト顧客の場合、作成した住所は注文完了後に破棄される可能性がある

---

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

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

### 推奨読解順序

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

まず、住所マネージャーで扱うデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerAddressEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressEntity.php` | 住所エンティティの構造、プロパティ（firstName, lastName, street, city, zipcode, countryId等） |
| 1-2 | AddressEditorModalStruct.php | `src/Storefront/Page/Address/AddressEditorModalStruct.php` | モーダル表示用のビューモデル構造 |
| 1-3 | AddressListingPage.php | `src/Storefront/Page/Address/Listing/AddressListingPage.php` | 住所一覧ページのデータ構造 |

**読解のコツ**: CustomerAddressEntityは親クラスEntityを継承しており、id, createdAt, updatedAtなどの共通プロパティは親クラスで定義されている。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AddressController.php | `src/Storefront/Controller/AddressController.php` | 住所マネージャーのエントリーポイント |

**主要処理フロー**:
1. **323-354行目**: `addressManager`メソッド - モーダル表示処理
2. **356-427行目**: `addressManagerUpsert`メソッド - 住所作成/更新処理
3. **293-321行目**: `addressManagerSwitch`メソッド - 住所切り替え処理
4. **429-461行目**: `handleAddressCreation`メソッド - 住所作成のヘルパー
5. **463-483行目**: `getById`メソッド - 住所ID検索

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

住所一覧ページの読み込み処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AddressListingPageLoader.php | `src/Storefront/Page/Address/Listing/AddressListingPageLoader.php` | ページデータの組み立て |

**主要処理フロー**:
- **50-76行目**: `load`メソッド - ページデータの読み込み
- **57行目**: 敬称一覧取得
- **59行目**: 国一覧取得
- **61-63行目**: 住所一覧取得（firstName昇順でソート）
- **65行目**: カート情報取得

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

住所操作に使用されるAPIルートを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AbstractListAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractListAddressRoute.php` | 住所一覧取得の抽象クラス |
| 4-2 | AbstractUpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractUpsertAddressRoute.php` | 住所作成・更新の抽象クラス |
| 4-3 | AbstractContextSwitchRoute.php | `src/Core/System/SalesChannel/SalesChannel/AbstractContextSwitchRoute.php` | コンテキスト切り替えの抽象クラス |

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

```
AddressController
    │
    ├─ addressManager (GET /widgets/account/address-manager)
    │      ├─ AddressListingPageLoader.load
    │      │      ├─ GenericPageLoader.load
    │      │      ├─ AbstractSalutationRoute.load
    │      │      ├─ AbstractCountryRoute.load
    │      │      ├─ AbstractListAddressRoute.load
    │      │      └─ CartService.getCart
    │      └─ renderStorefront (address-manager-modal.html.twig)
    │
    ├─ addressManagerUpsert (POST /widgets/account/address-manager/{addressId?})
    │      ├─ getById (addressId指定時)
    │      │      └─ AbstractListAddressRoute.load
    │      ├─ handleAddressCreation
    │      │      ├─ AbstractUpsertAddressRoute.upsert
    │      │      └─ AbstractContextSwitchRoute.switchContext
    │      └─ renderStorefront (address-manager-modal-create-address.html.twig)
    │
    └─ addressManagerSwitch (POST /widgets/account/address-manager/switch)
           ├─ AbstractContextSwitchRoute.switchContext
           └─ createActionResponse (リダイレクト)
```

### データフロー図

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

HTTPリクエスト ─────────────────┬──────────────────────────────────► モーダルHTML
(GET /widgets/account/         │                                    (住所一覧表示)
 address-manager)              │
                               ▼
                    ┌─────────────────────┐
                    │ AddressController   │
                    │ ::addressManager    │
                    └─────────┬───────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ AddressListing      │
                    │ PageLoader          │◄──── customer_address
                    └─────────┬───────────┘      country
                              │                  salutation
                              ▼
                    ┌─────────────────────┐
                    │ AddressListingPage  │
HTTPリクエスト ─────┤ (ViewModel)         ├──────────────────────────► Twigテンプレート
(POST /widgets/     └─────────────────────┘
 address-manager)
      │
      ▼
┌─────────────────────┐
│ AddressController   │
│ ::addressManagerUpsert│
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ AbstractUpsert      │
│ AddressRoute        │────────────────────────────────────────────► customer_address
└─────────┬───────────┘                                              (INSERT/UPDATE)
          │
          ▼
┌─────────────────────┐
│ ContextSwitchRoute  │────────────────────────────────────────────► セッション更新
└─────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AddressController.php | `src/Storefront/Controller/AddressController.php` | ソース | コントローラー（エントリーポイント） |
| AddressListingPageLoader.php | `src/Storefront/Page/Address/Listing/AddressListingPageLoader.php` | ソース | ページデータ読み込み |
| AddressListingPage.php | `src/Storefront/Page/Address/Listing/AddressListingPage.php` | ソース | ページビューモデル |
| AddressEditorModalStruct.php | `src/Storefront/Page/Address/AddressEditorModalStruct.php` | ソース | モーダルビューモデル |
| CustomerAddressEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressEntity.php` | ソース | 住所エンティティ |
| AbstractListAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractListAddressRoute.php` | ソース | 住所一覧取得API |
| AbstractUpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractUpsertAddressRoute.php` | ソース | 住所作成・更新API |
| AbstractContextSwitchRoute.php | `src/Core/System/SalesChannel/SalesChannel/AbstractContextSwitchRoute.php` | ソース | コンテキスト切り替えAPI |
| address-manager-modal.html.twig | `src/Storefront/Resources/views/storefront/component/address/address-manager-modal.html.twig` | テンプレート | モーダル表示テンプレート |
| address-manager-modal-create-address.html.twig | `src/Storefront/Resources/views/storefront/component/address/address-manager-modal-create-address.html.twig` | テンプレート | 住所作成フォームテンプレート |
