# 画面設計書 20-住所編集ページ

## 概要

本ドキュメントは、Shopware 6のStorefrontにおける「住所編集ページ」の画面設計書です。ログイン済み顧客が既存の住所を編集するためのフォーム画面の仕様を記載しています。

### 本画面の処理概要

住所編集ページは、ログイン済み顧客が既存の住所を編集するためのフォーム画面です。住所帳から編集対象の住所を選択してアクセスすると、現在の住所情報がフォームに事前入力された状態で表示されます。個人情報と住所情報を編集し、変更を保存できます。ゲスト顧客もこの画面にアクセス可能です。

**業務上の目的・背景**：顧客の住所情報は時間とともに変更されることがあります（引っ越し、会社名変更など）。既存住所の編集機能により、顧客は新しい住所を作成せずに情報を更新でき、住所帳の管理が容易になります。

**画面へのアクセス方法**：
- 住所帳ページの住所カードから「編集」リンクをクリック
- チェックアウト時の住所編集
- 直接URL（/account/address/{addressId}）にアクセス

**主要な操作・処理内容**：
1. 既存住所情報の表示（事前入力）
2. 個人情報の編集（敬称、氏名）
3. 住所情報の編集（住所行、郵便番号、市区町村、国、州、会社情報）
4. 住所の更新保存
5. 住所帳ページへの戻り

**画面遷移**：
- 遷移元：住所帳ページ、チェックアウト
- 遷移先（成功時）：住所帳ページ（デフォルト）、またはredirectToパラメータで指定されたページ
- 遷移先（エラー時）：同画面にエラーメッセージ表示
- 遷移先（戻る）：住所帳ページ

**権限による表示制御**：ログイン必須。ゲスト顧客も利用可能（LOGIN_REQUIRED_ALLOW_GUEST属性）。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 15 | 住所編集 | 主機能 | 既存住所の編集（AddressController::accountEditAddress、saveAddress） |

## 画面種別

フォーム入力

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| URL（フォーム表示） | /account/address/{addressId} |
| ルート名（フォーム表示） | frontend.account.address.edit.page |
| HTTPメソッド | GET |
| 必須パラメータ | addressId（パスパラメータ） |
| URL（住所保存） | /account/address/{addressId} |
| ルート名（住所保存） | frontend.account.address.edit.save |
| HTTPメソッド | POST |

## 入出力項目

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| addressId | string | Yes | 編集対象の住所ID（パスパラメータ、UUIDv4形式） |
| address[id] | string | Yes | 住所ID（hidden） |
| address[salutationId] | string | 設定依存 | 敬称ID |
| address[firstName] | string | Yes | 名 |
| address[lastName] | string | Yes | 姓 |
| address[company] | string | No | 会社名 |
| address[department] | string | No | 部署 |
| address[street] | string | Yes | 住所（番地含む） |
| address[additionalAddressLine1] | string | No | 住所追加行1 |
| address[additionalAddressLine2] | string | No | 住所追加行2 |
| address[zipcode] | string | Yes | 郵便番号 |
| address[city] | string | Yes | 市区町村 |
| address[countryId] | string | Yes | 国ID |
| address[countryStateId] | string | 国依存 | 州/県ID |
| address[phoneNumber] | string | 設定依存 | 電話番号 |
| redirectTo | string | No | 保存後のリダイレクト先ルート |

## 表示項目

| セクション | 表示項目 | データソース |
|------------|----------|--------------|
| タイトル | ページタイトル | 固定テキスト（account.addressEditMetaTitle） |
| 個人情報フォーム | 敬称選択（事前選択あり） | page.salutations、page.address |
| 個人情報フォーム | 名前入力欄（事前入力あり） | page.address |
| 住所フォーム | 会社名入力欄（事前入力あり） | page.address |
| 住所フォーム | 住所入力欄（事前入力あり） | page.address |
| 住所フォーム | 郵便番号入力欄（事前入力あり） | page.address |
| 住所フォーム | 市区町村入力欄（事前入力あり） | page.address |
| 住所フォーム | 国選択（事前選択あり） | page.countries、page.address |
| 住所フォーム | 州/県選択（事前選択あり） | page.countries.states、page.address |
| アクション | 戻るボタン | 固定（住所帳ページへ） |
| アクション | 保存ボタン | 固定 |

## イベント仕様

### 1-ページ読み込み時（GET）

1. 顧客のログイン状態を確認（LOGIN_REQUIRED、ゲスト許可）
2. AddressDetailPageLoaderでページデータを取得
3. 敬称一覧、国一覧、対象住所を取得
4. 住所が見つからない場合はAddressNotFoundException
5. AddressDetailPageLoadedHookを発火
6. テンプレートをレンダリング（redirectToをクエリパラメータから取得して渡す）

### 2-住所保存処理（POST）

1. RequestDataBagからaddressデータを取得
2. AbstractUpsertAddressRouteで住所を更新（id指定で更新）
3. 成功時：成功メッセージを表示してリダイレクト
   - redirectTo指定あり：指定されたルートへリダイレクト
   - redirectTo指定なし：住所帳ページへリダイレクト
   - ゲスト顧客でredirectToなし：カートページへリダイレクト
4. バリデーションエラー時：エラーを表示して編集ページへフォワード

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ページ表示 | customer_address | SELECT | 対象住所の取得 |
| ページ表示 | country | SELECT | 国一覧の取得 |
| ページ表示 | salutation | SELECT | 敬称一覧の取得 |
| 住所保存 | customer_address | UPDATE | 住所レコードの更新 |

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

#### customer_address

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | addressId（パスパラメータ） | 対象住所の取得 |
| UPDATE | salutation_id | フォーム入力値 | 敬称ID |
| UPDATE | first_name | フォーム入力値 | 名 |
| UPDATE | last_name | フォーム入力値 | 姓 |
| UPDATE | company | フォーム入力値 | 会社名 |
| UPDATE | department | フォーム入力値 | 部署 |
| UPDATE | street | フォーム入力値 | 住所 |
| UPDATE | additional_address_line1 | フォーム入力値 | 住所追加行1 |
| UPDATE | additional_address_line2 | フォーム入力値 | 住所追加行2 |
| UPDATE | zipcode | フォーム入力値 | 郵便番号 |
| UPDATE | city | フォーム入力値 | 市区町村 |
| UPDATE | country_id | フォーム入力値 | 国ID |
| UPDATE | country_state_id | フォーム入力値 | 州/県ID |
| UPDATE | phone_number | フォーム入力値 | 電話番号 |

## メッセージ仕様

| メッセージID | 種別 | 表示条件 | メッセージ内容 |
|-------------|------|----------|---------------|
| account.addressEditMetaTitle | 情報 | 常時 | ページタイトル |
| general.requiredFields | 情報 | 常時 | 必須フィールド説明 |
| general.back | 情報 | 常時 | 戻るボタンラベル |
| account.addressSaveChange | 情報 | 常時 | 保存ボタンラベル |
| account.addressSaved | 成功 | 住所保存成功時 | 住所保存完了 |

## 例外処理

| 例外 | 発生条件 | 処理内容 |
|------|----------|----------|
| CustomerNotLoggedInException | 未ログイン状態でアクセス | ログインページへリダイレクト |
| AddressNotFoundException | 指定された住所が見つからない | 404エラーまたはエラーメッセージ表示 |
| UuidException | addressIdが不正なUUID形式 | invalidUuidエラー |
| ConstraintViolationException | バリデーションエラー | エラーを表示して編集ページへフォワード |

## 備考

- SEO設定：seo=false（検索エンジンにインデックスさせない）
- キャッシュ：無効化（PlatformRequest::ATTRIBUTE_NO_STORE = true）
- ゲスト顧客も利用可能（LOGIN_REQUIRED_ALLOW_GUEST属性）
- address-personal.html.twigとaddress-form.html.twigコンポーネントを使用
- redirectToパラメータにより、チェックアウトフローからの編集にも対応
- フォームには既存の住所データが事前入力される

---

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

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

### 推奨読解順序

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

住所詳細ページのPageクラスを確認します（作成ページと共用）。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AddressDetailPage.php | `src/Storefront/Page/Address/Detail/AddressDetailPage.php` | address/salutations/countriesプロパティ |

**読解のコツ**: AddressDetailPageは住所データ、敬称一覧、国一覧を保持するPageクラスです。編集時はaddressに既存データが設定されます。

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

処理の起点となるコントローラーメソッドを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AddressController.php | `src/Storefront/Controller/AddressController.php` | accountEditAddress（115-136行目）、saveAddress（207-261行目） |

**主要処理フロー（accountEditAddressメソッド）**:
1. **128行目**: AddressDetailPageLoaderでページデータ取得（addressIdあり）
2. **130行目**: AddressDetailPageLoadedHookを発火
3. **132-135行目**: テンプレートをレンダリング（redirectToをクエリパラメータから取得）

**主要処理フロー（saveAddressメソッド）**:
1. **231行目**: addressデータをRequestDataBagから取得
2. **234-239行目**: AbstractUpsertAddressRouteで住所をupsert（idあり=更新）
3. **241行目**: 成功メッセージ
4. **243-246行目**: リダイレクト先の決定
5. **256-260行目**: ConstraintViolationExceptionで編集ページへフォワード

#### Step 3: PageLoaderを理解する

ページデータの組み立てロジックを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AddressDetailPageLoader.php | `src/Storefront/Page/Address/Detail/AddressDetailPageLoader.php` | loadメソッドで住所を取得 |

**主要処理フロー**:
- **58-76行目**: loadメソッド全体
- **69行目**: getAddressで住所を取得（addressIdパラメータから）
- **123-144行目**: getAddressメソッドでUUID検証、顧客IDフィルタ、住所取得

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

表示ロジックを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | edit.html.twig | `src/Storefront/Resources/views/storefront/page/account/addressbook/edit.html.twig` | メインテンプレート |

**主要構造**:
- **6-11行目**: ページタイトル（account.addressEditMetaTitle）
- **17-20行目**: フォームタグ（action=frontend.account.address.edit.save with addressId）
- **21-25行目**: address-personal.html.twigインクルード（data=page.address）
- **28-35行目**: address-form.html.twigインクルード（data=page.address）
- **44-46行目**: redirectTo hidden フィールド
- **48-66行目**: アクションボタン（戻る、保存）

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

```
AddressController::accountEditAddress() [GET]
    |
    +-- AddressDetailPageLoader::load()
    |       |
    |       +-- GenericPageLoader::load()
    |       |
    |       +-- AbstractSalutationRoute::load()
    |       |
    |       +-- AbstractCountryRoute::load()
    |       |
    |       +-- getAddress() -> CustomerAddressEntity
    |               |
    |               +-- AbstractListAddressRoute::load()
    |
    +-- AddressDetailPageLoadedHook
    |
    +-- renderStorefront()

AddressController::saveAddress() [POST]
    |
    +-- RequestDataBag取得
    |
    +-- AbstractUpsertAddressRoute::upsert()
    |       |
    |       +-- CustomerAddressEntity (更新)
    |
    +-- addFlash() / createActionResponse()
    |
    +-- [エラー時] forwardToRoute('frontend.account.address.edit.page')
```

### データフロー図

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

GET Request
  addressId ─────────> AddressController::accountEditAddress()
                              |
                              v
                        AddressDetailPageLoader
                              |
                        +-----+-----+-----+
                        |     |     |     |
                        v     v     v     v
                   Salutation Country getAddress
                     Route   Route  (既存)
                        |     |     |
                        +-----+-----+
                              |
                              v
                        AddressDetailPage ──────> Twig Template
                        (salutations, countries,       |
                         address=既存データ)           v
                                                  HTML Response

POST Request
  address[...] ──────> AddressController::saveAddress()
                              |
                              v
                        AbstractUpsertAddressRoute::upsert()
                              |
                              v
                        CustomerAddressEntity (更新)
                              |
                              v
                        Redirect (住所帳 or redirectTo指定先)
                        + Flash Message
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AddressController.php | `src/Storefront/Controller/AddressController.php` | コントローラー | ルーティングとリクエスト処理 |
| AddressDetailPageLoader.php | `src/Storefront/Page/Address/Detail/AddressDetailPageLoader.php` | PageLoader | ページデータの組み立て |
| AddressDetailPage.php | `src/Storefront/Page/Address/Detail/AddressDetailPage.php` | DTO | ページデータを保持 |
| AddressDetailPageLoadedHook.php | `src/Storefront/Page/Address/Detail/AddressDetailPageLoadedHook.php` | フック | 拡張ポイント |
| edit.html.twig | `src/Storefront/Resources/views/storefront/page/account/addressbook/edit.html.twig` | テンプレート | メイン画面 |
| address-personal.html.twig | `src/Storefront/Resources/views/storefront/component/address/address-personal.html.twig` | テンプレート | 個人情報フォーム |
| address-form.html.twig | `src/Storefront/Resources/views/storefront/component/address/address-form.html.twig` | テンプレート | 住所フォーム |
| AbstractUpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractUpsertAddressRoute.php` | Store API | 住所作成/更新API |
| AbstractListAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractListAddressRoute.php` | Store API | 住所取得API |
