# 機能設計書 18-住所編集

## 概要

本ドキュメントは、Shopwareストアフロントにおける「住所編集」機能の設計内容を記載する。顧客が既存の配送先または請求先住所を編集する機能であり、住所詳細画面の表示からバリデーション、データベースへの更新までを行う。

### 本機能の処理概要

**業務上の目的・背景**：住所情報は変更されることがあり、顧客が登録済みの住所を簡単に更新できることは重要である。引っ越し、会社名変更、電話番号変更など、様々な理由で住所情報の更新が必要になる。本機能は、既存の住所情報を安全に更新する手段を提供する。

**機能の利用シーン**：
- 引っ越しにより住所が変わった場合
- 電話番号が変わった場合
- 会社名が変わった場合
- チェックアウト中に住所を修正する場合

**主要な処理内容**：
1. ログイン認証の確認
2. 住所所有権の確認
3. 住所詳細フォームの表示（GETリクエスト）
4. 入力データのバリデーション
5. 住所の更新（upsert処理）
6. 成功・エラーメッセージの表示とリダイレクト

**関連システム・外部連携**：Store API（AbstractUpsertAddressRoute）を経由してデータを更新。CustomerAddressValidationTraitで住所所有権を検証。

**権限による制御**：ログイン必須（`LOGIN_REQUIRED`属性）。ゲストユーザーも許可（`LOGIN_REQUIRED_ALLOW_GUEST`）。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | 住所編集ページ | 主画面 | 既存住所の編集フォーム |
| 18 | 住所帳ページ | 遷移元/遷移先 | 住所一覧からの遷移、保存後のリダイレクト先 |

## 機能種別

CRUD操作（Update） / バリデーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| addressId | string | Yes | 編集対象の住所ID | 有効なUUID、顧客所有確認 |
| address.id | string | Yes | 住所ID（フォーム内） | addressIdと一致 |
| address.salutationId | string | No | 敬称ID | 未指定時はNOT_SPECIFIED |
| address.firstName | string | Yes | 名 | NotBlank |
| address.lastName | string | Yes | 姓 | NotBlank |
| address.street | string | Yes | 通り | NotBlank |
| address.city | string | Yes | 市区町村 | NotBlank |
| address.zipcode | string | Yes | 郵便番号 | CustomerZipCode（国別検証） |
| address.countryId | string | Yes | 国ID | NotBlank、有効なUUID |
| address.countryStateId | string | No | 州/県ID | 有効なUUID（存在する場合） |
| address.company | string | No（business時必須） | 会社名 | accountType=businessの場合NotBlank |
| address.department | string | No | 部署名 | なし |
| address.title | string | No | 肩書き | なし |
| address.phoneNumber | string | No | 電話番号 | なし |
| address.additionalAddressLine1 | string | No | 住所補足1 | なし |
| address.additionalAddressLine2 | string | No | 住所補足2 | なし |
| address.customFields | RequestDataBag | No | カスタムフィールド | StoreApiCustomFieldMapperによるマッピング |
| accountType | string | No | アカウント種別 | CustomerEntity::ACCOUNT_TYPE_*の値 |
| redirectTo | string | No | リダイレクト先 | 有効なルート名 |

### 入力データソース

- URLパラメータ（addressId）
- フォーム入力（POSTリクエスト）
- システム設定（core.loginRegistration.showAccountTypeSelection）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | AddressDetailPage | 住所詳細ページオブジェクト（GET時） |
| flashMessage | string | 成功/エラーメッセージ |
| UpsertAddressRouteResponse | Response | 更新された住所情報を含むレスポンス（POST時） |

### 出力先

- 画面表示（GETリクエスト時、`@Storefront/storefront/page/account/addressbook/edit.html.twig`）
- データベース（customer_address テーブル、POSTリクエスト時）
- セッション（フラッシュメッセージ）
- 画面リダイレクト（frontend.account.address.page または redirectTo先）

## 処理フロー

### 処理シーケンス

```
1. ルーティング処理
   └─ /account/address/{addressId} パスへのGET/POSTリクエストを受信

2-A. 編集フォーム表示（GETリクエスト）
   ├─ AddressDetailPageLoaderで住所詳細ページを構築
   └─ Twigテンプレートをレンダリング

2-B. 住所更新処理（POSTリクエスト）
   ├─ UpsertAddressRouteを呼び出し
   ├─ 住所所有権の検証（validateAddress）
   ├─ バリデーション定義の取得（update用）
   ├─ バリデーション実行
   ├─ DataMappingEventの発火
   └─ addressリポジトリのupsert実行

3. レスポンス生成
   ├─ 成功時：成功メッセージ設定、住所一覧ページへリダイレクト
   └─ エラー時：フォーム再表示（バリデーションエラー付き）
```

### フローチャート

```mermaid
flowchart TD
    A[/account/address/{addressId}] --> B{ログイン済み?}
    B -->|No| C[ログイン画面へリダイレクト]
    B -->|Yes| D{HTTPメソッド?}
    D -->|GET| E[AddressDetailPageLoader::load]
    E --> F[Twigテンプレートレンダリング]
    D -->|POST| G[UpsertAddressRoute::upsert]
    G --> H[validateAddress - 所有権確認]
    H --> I{所有権OK?}
    I -->|No| J[CustomerException発生]
    I -->|Yes| K[バリデーション定義取得 update用]
    K --> L{accountType = business?}
    L -->|Yes| M[company必須バリデーション追加]
    L -->|No| N[郵便番号バリデーション設定]
    M --> N
    N --> O[バリデーション実行]
    O --> P{バリデーション成功?}
    P -->|No| Q[ConstraintViolationException]
    Q --> R[フォーム再表示]
    P -->|Yes| S[DataMappingEvent発火]
    S --> T[addressRepository.upsert]
    T --> U[成功メッセージ設定]
    U --> V{redirectTo指定?}
    V -->|Yes| W[redirectTo先へ遷移]
    V -->|No| X[住所一覧へリダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-18-01 | ログイン必須 | 未ログインユーザーはアクセス不可 | 全リクエスト |
| BR-18-02 | ゲスト許可 | ゲストユーザーも利用可能 | LOGIN_REQUIRED_ALLOW_GUEST設定時 |
| BR-18-03 | 所有権確認 | 編集対象の住所が自分のものであることを確認 | 全リクエスト |
| BR-18-04 | 必須フィールド | firstName, lastName, street, city, zipcode, countryIdは必須 | 全リクエスト |
| BR-18-05 | 法人必須フィールド | accountType=businessの場合、companyは必須 | 法人アカウント |
| BR-18-06 | 郵便番号検証 | 国別の郵便番号形式に従う | CustomerZipCode制約 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 住所更新 | customer_address | UPDATE | 既存住所レコードの更新 |

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

#### customer_address

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | id | 既存のaddressId | WHERE条件 |
| UPDATE | salutation_id | 入力値またはNOT_SPECIFIED | 外部キー |
| UPDATE | first_name | 入力値 | 必須 |
| UPDATE | last_name | 入力値 | 必須 |
| UPDATE | street | 入力値 | 必須 |
| UPDATE | city | 入力値 | 必須 |
| UPDATE | zipcode | 入力値 | 必須 |
| UPDATE | country_id | 入力値 | 外部キー |
| UPDATE | country_state_id | 入力値またはnull | 外部キー |
| UPDATE | company | 入力値またはnull | businessの場合必須 |
| UPDATE | department | 入力値またはnull | 任意 |
| UPDATE | title | 入力値またはnull | 任意 |
| UPDATE | phone_number | 入力値またはnull | 任意 |
| UPDATE | additional_address_line1 | 入力値またはnull | 任意 |
| UPDATE | additional_address_line2 | 入力値またはnull | 任意 |
| UPDATE | custom_fields | マッピングされた値またはnull | 任意 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | CustomerException::addressNotFound | 住所が存在しないまたは所有権なし | エラーメッセージ表示 |
| - | UuidException::invalidUuid | 無効なUUID形式 | エラーメッセージ表示 |
| - | ConstraintViolationException | バリデーション失敗 | フォームにエラー内容を表示、再入力を促す |

### リトライ仕様

特になし（ユーザーによる再入力）

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

DAL（Data Abstraction Layer）のupsertメソッドにより、1件のUPDATEがトランザクション内で実行される。

## パフォーマンス要件

- 単一レコードの更新のため、特別なパフォーマンス要件なし

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

- LOGIN_REQUIRED属性によりログイン必須を強制
- CustomerAddressValidationTraitにより住所所有権を検証
- 他の顧客の住所は編集不可
- カスタムフィールドはStoreApiCustomFieldMapperでフィルタリング

## 備考

- 同一ルート（saveAddress）がcreateとeditの両方を処理（addressIdの有無で判定）
- DataMappingEvent（CustomerEvents::MAPPING_ADDRESS_CREATE）により、プラグインがデータマッピングを拡張可能
- BuildValidationEventにより、プラグインがバリデーションルールを拡張可能

---

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

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

### 推奨読解順序

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

住所エンティティと住所詳細ページの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerAddressEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressEntity.php` | 住所エンティティのフィールド定義を確認 |
| 1-2 | AddressDetailPage.php | `src/Storefront/Page/Address/Detail/AddressDetailPage.php` | 住所詳細ページの構造を確認 |

**読解のコツ**: AddressDetailPageは編集対象の住所情報を保持し、フォームの初期値として使用される。

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

コントローラーでのリクエスト処理を理解する。

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

**主要処理フロー（GET - 編集フォーム表示）**:
1. **128行目**: `$this->addressDetailPageLoader->load($request, $context, $customer)` でページローダーを呼び出し
2. **130行目**: `$this->hook(new AddressDetailPageLoadedHook($page, $context))` でフック実行
3. **132-135行目**: Twigテンプレートをレンダリング（redirectTo情報も渡す）

**主要処理フロー（POST - 住所更新）**:
1. **234-239行目**: `$this->updateAddressRoute->upsert($address->get('id'), ...)` で住所更新ルート呼び出し
2. **241行目**: 成功時、成功メッセージをフラッシュに設定
3. **256-260行目**: ConstraintViolationException時、編集画面に戻る

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

実際の住所更新ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | UpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/UpsertAddressRoute.php` | upsertメソッド（85-147行目）の処理フローを確認 |
| 3-2 | CustomerAddressValidationTrait.php | `src/Core/Checkout/Customer/SalesChannel/CustomerAddressValidationTrait.php` | 住所所有権検証の実装 |

**主要処理フロー**:
- **94-96行目**: addressIdが存在する場合、validateAddressで所有権確認
- **103-104行目**: バリデーション定義の取得（update用）
- **105行目**: バリデーション実行
- **141行目**: addressリポジトリのupsert実行

#### Step 4: 住所所有権検証を理解する

住所が顧客のものであることを検証するロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | CustomerAddressValidationTrait.php | `src/Core/Checkout/Customer/SalesChannel/CustomerAddressValidationTrait.php` | validateAddressメソッドの実装 |

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

```
AddressController::accountEditAddress (GET)
    │
    ├─ AddressDetailPageLoader::load
    │      ├─ GenericPageLoaderInterface::load
    │      └─ 住所詳細の取得
    │
    └─ AddressDetailPageLoadedHook (フック実行)

AddressController::saveAddress (POST)
    │
    ├─ addressパラメータ取得
    │
    └─ UpsertAddressRoute::upsert
           │
           ├─ validateAddress (所有権確認)
           │      └─ CustomerAddressValidationTrait
           │
           ├─ getValidationDefinition
           │      │
           │      ├─ addressValidationFactory.update
           │      │
           │      ├─ company必須追加 (business type)
           │      │
           │      ├─ CustomerZipCode設定
           │      │
           │      └─ BuildValidationEvent発火
           │
           ├─ validator.validate
           │
           ├─ eventDispatcher.dispatch (DataMappingEvent)
           │
           └─ addressRepository.upsert
```

### データフロー図

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

GET /account/address/{addressId}
         │
         ▼
AddressController::accountEditAddress ───▶ AddressDetailPageLoader
         │                                          │
         │                                          ▼
         │                                    AddressDetailPage
         │                                          │
         │                                          ▼
         │                                    Twigテンプレート
         │
─────────┼──────────────────────────────────────────────────────
         │
POST /account/address/{addressId}
         │
         ▼
RequestDataBag (address) ───▶ AddressController::saveAddress
         │                            │
         │                            ▼
         │                    UpsertAddressRoute::upsert
         │                            │
         │               ┌────────────┴────────────┐
         │               ▼                         ▼
         │        所有権確認               バリデーション
         │               │                         │
         │               └────────────┬────────────┘
         │                            │
         │                            ▼
         │                    成功/失敗判定
         │                            │
         │               ┌────────────┴────────────┐
         │               ▼                         ▼
         │           成功時                    失敗時
         │               │                         │
         │               ▼                         ▼
         │     addressRepository.upsert    ConstraintViolation
         │               │                     Exception
         └───────────────┴─────────────────────────┘
                                  │
                                  ▼
                     リダイレクト先決定
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AddressController.php | `src/Storefront/Controller/AddressController.php` | コントローラー | HTTPリクエスト処理のエントリーポイント |
| AddressDetailPageLoader.php | `src/Storefront/Page/Address/Detail/AddressDetailPageLoader.php` | ページローダー | 住所詳細ページの構築 |
| AddressDetailPage.php | `src/Storefront/Page/Address/Detail/AddressDetailPage.php` | ページモデル | 住所詳細ページオブジェクト |
| UpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/UpsertAddressRoute.php` | Store API | 住所更新ビジネスロジック |
| CustomerAddressValidationTrait.php | `src/Core/Checkout/Customer/SalesChannel/CustomerAddressValidationTrait.php` | トレイト | 住所所有権検証 |
| CustomerAddressEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressEntity.php` | エンティティ | 住所データモデル |
| CustomerZipCode.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerZipCode.php` | 制約 | 国別郵便番号検証 |
| edit.html.twig | `src/Storefront/Resources/views/storefront/page/account/addressbook/edit.html.twig` | テンプレート | 住所編集フォーム |
