# 機能設計書 17-住所作成

## 概要

本ドキュメントは、Shopwareストアフロントにおける「住所作成」機能の設計内容を記載する。顧客が新しい配送先または請求先住所を登録する機能であり、フォーム入力からバリデーション、データベースへの保存までを行う。

### 本機能の処理概要

**業務上の目的・背景**：ECサイトにおいて、顧客が複数の配送先・請求先住所を管理できることは重要である。新規住所の追加により、贈り物の配送、職場への配送、法人顧客の複数拠点対応など、多様な利用シーンに対応する。本機能は、必要な住所情報を入力・検証し、安全にデータベースへ保存する。

**機能の利用シーン**：
- 新しい配送先住所を追加したい場合
- 新しい請求先住所を追加したい場合
- チェックアウト中に住所を追加する場合

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

**関連システム・外部連携**：Store API（AbstractUpsertAddressRoute）を経由してデータを保存。システム設定からバリデーションルールを取得。

**権限による制御**：ログイン必須（`LOGIN_REQUIRED`属性）。ゲストユーザーは住所帳での作成不可（カートエラー経由でのみ可能）。

## 関連画面

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

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| 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_*の値 |

### 入力データソース

- フォーム入力（POSTリクエスト）
- システム設定（core.loginRegistration.showAccountTypeSelection）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| flashMessage | string | 成功/エラーメッセージ |
| UpsertAddressRouteResponse | Response | 保存された住所情報を含むレスポンス |

### 出力先

- データベース（customer_address テーブル）
- セッション（フラッシュメッセージ）
- 画面リダイレクト（frontend.account.address.page または redirectTo先）

## 処理フロー

### 処理シーケンス

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

2. コントローラー処理 (AddressController::saveAddress)
   └─ UpsertAddressRouteを呼び出し

3. 住所登録ルート処理 (UpsertAddressRoute::upsert)
   ├─ addressIdがnullの場合、新規UUID生成
   ├─ salutationIdが未指定の場合、デフォルト設定
   ├─ バリデーション定義の取得（create用）
   ├─ accountType=businessの場合、company必須追加
   ├─ 郵便番号検証（国別）
   ├─ バリデーション実行
   ├─ DataMappingEventの発火
   └─ addressリポジトリのupsert実行

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

### フローチャート

```mermaid
flowchart TD
    A[POST /account/address/create] --> B{ログイン済み?}
    B -->|No| C[ログイン画面へリダイレクト]
    B -->|Yes| D[UpsertAddressRoute::upsert]
    D --> E[新規UUID生成]
    E --> F{salutationId指定?}
    F -->|No| G[デフォルト敬称設定]
    F -->|Yes| H[バリデーション定義取得]
    G --> H
    H --> I{accountType = business?}
    I -->|Yes| J[company必須バリデーション追加]
    I -->|No| K[郵便番号バリデーション設定]
    J --> K
    K --> L[バリデーション実行]
    L --> M{バリデーション成功?}
    M -->|No| N[ConstraintViolationException]
    N --> O[フォーム再表示]
    M -->|Yes| P[DataMappingEvent発火]
    P --> Q[addressRepository.upsert]
    Q --> R[成功メッセージ設定]
    R --> S{redirectTo指定?}
    S -->|Yes| T[redirectTo先へ遷移]
    S -->|No| U[住所一覧へリダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-17-01 | ログイン必須 | 未ログインユーザーはアクセス不可 | 全リクエスト |
| BR-17-02 | 必須フィールド | firstName, lastName, street, city, zipcode, countryIdは必須 | 全リクエスト |
| BR-17-03 | 法人必須フィールド | accountType=businessの場合、companyは必須 | 法人アカウント |
| BR-17-04 | 郵便番号検証 | 国別の郵便番号形式に従う | CustomerZipCode制約 |
| BR-17-05 | 敬称デフォルト | salutationIdが未指定の場合、NOT_SPECIFIEDを設定 | salutationId空の場合 |
| BR-17-06 | ゲストリダイレクト | ゲストユーザーはカートページへリダイレクト | ゲストユーザー |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 住所登録 | customer_address | INSERT | 新規住所レコードの作成 |

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

#### customer_address

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 新規生成UUID | 主キー |
| INSERT | customer_id | ログイン顧客ID | 外部キー |
| INSERT | salutation_id | 入力値またはNOT_SPECIFIED | 外部キー |
| INSERT | first_name | 入力値 | 必須 |
| INSERT | last_name | 入力値 | 必須 |
| INSERT | street | 入力値 | 必須 |
| INSERT | city | 入力値 | 必須 |
| INSERT | zipcode | 入力値 | 必須 |
| INSERT | country_id | 入力値 | 外部キー |
| INSERT | country_state_id | 入力値またはnull | 外部キー |
| INSERT | company | 入力値またはnull | businessの場合必須 |
| INSERT | department | 入力値またはnull | 任意 |
| INSERT | title | 入力値またはnull | 任意 |
| INSERT | phone_number | 入力値またはnull | 任意 |
| INSERT | additional_address_line1 | 入力値またはnull | 任意 |
| INSERT | additional_address_line2 | 入力値またはnull | 任意 |
| INSERT | custom_fields | マッピングされた値またはnull | 任意 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ConstraintViolationException | バリデーション失敗 | フォームにエラー内容を表示、再入力を促す |

### リトライ仕様

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

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

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

## パフォーマンス要件

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

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

- LOGIN_REQUIRED属性によりログイン必須を強制
- 住所はログイン顧客のcustomer_idに紐づけて保存
- カスタムフィールドは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 | CustomerAddressDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressDefinition.php` | DAL定義、フィールド制約を確認 |

**読解のコツ**: CustomerAddressEntityには国・州へのアソシエーションがあり、countryId/countryStateIdで関連付けられる。

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

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

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

**主要処理フロー**:
1. **231行目**: `$address = $data->get('address')` でaddressパラメータ取得
2. **234-239行目**: `$this->updateAddressRoute->upsert(...)` で住所登録ルート呼び出し
3. **241行目**: 成功時、成功メッセージをフラッシュに設定
4. **243-246行目**: redirectToが未指定の場合、顧客タイプに応じてリダイレクト先を決定
5. **249行目**: ConstraintViolationException時、フォーム作成画面に戻る

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

実際の住所登録ロジックを理解する。

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

**主要処理フロー**:
- **91-97行目**: addressIdがnullの場合、新規UUIDを生成（isCreate=true）
- **99-101行目**: salutationIdが未指定の場合、デフォルト設定
- **103-104行目**: バリデーション定義の取得
- **105行目**: バリデーション実行
- **107-132行目**: 住所データの構築
- **134-136行目**: DataMappingEventの発火
- **138-141行目**: 住所データにid, customerIdを設定
- **141行目**: addressリポジトリのupsert実行

#### Step 4: バリデーションを理解する

バリデーションの定義と実行を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | UpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/UpsertAddressRoute.php` | getValidationDefinitionメソッド（149-173行目） |
| 4-2 | CustomerZipCode.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerZipCode.php` | 国別郵便番号検証制約 |

**バリデーション定義**:
- **155-158行目**: create用はaddressValidationFactory.createを使用
- **161-165行目**: business typeの場合、company必須を追加
- **167行目**: 郵便番号検証を設定（国IDを渡す）
- **169-170行目**: BuildValidationEventを発火

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

```
AddressController::saveAddress
    │
    ├─ addressパラメータ取得
    │
    └─ UpsertAddressRoute::upsert
           │
           ├─ Uuid::randomHex (新規ID生成)
           │
           ├─ getDefaultSalutationId (敬称デフォルト)
           │
           ├─ getValidationDefinition
           │      │
           │      ├─ addressValidationFactory.create
           │      │
           │      ├─ company必須追加 (business type)
           │      │
           │      ├─ CustomerZipCode設定
           │      │
           │      └─ BuildValidationEvent発火
           │
           ├─ validator.validate
           │
           ├─ storeApiCustomFieldMapper.map
           │
           ├─ eventDispatcher.dispatch (DataMappingEvent)
           │
           └─ addressRepository.upsert
```

### データフロー図

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

POST /account/address/create
         │
         ▼
RequestDataBag (address) ───▶ AddressController::saveAddress
         │                            │
         │                            ▼
         │                    UpsertAddressRoute::upsert
         │                            │
         │               ┌────────────┴────────────┐
         │               ▼                         ▼
         │        新規UUID生成             バリデーション
         │               │                         │
         │               └────────────┬────────────┘
         │                            │
         │                            ▼
         │                    成功/失敗判定
         │                            │
         │               ┌────────────┴────────────┐
         │               ▼                         ▼
         │           成功時                    失敗時
         │               │                         │
         │               ▼                         ▼
         │      DataMappingEvent発火      ConstraintViolation
         │               │                     Exception
         │               ▼                         │
         │     addressRepository.upsert            │
         │               │                         │
         └───────────────┴─────────────────────────┘
                                  │
                                  ▼
                     フラッシュメッセージ設定
                                  │
                                  ▼
                     リダイレクト先決定
                     (redirectTo or address list)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AddressController.php | `src/Storefront/Controller/AddressController.php` | コントローラー | HTTPリクエスト処理のエントリーポイント |
| UpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/UpsertAddressRoute.php` | Store API | 住所登録ビジネスロジック |
| AbstractUpsertAddressRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractUpsertAddressRoute.php` | 抽象クラス | Store APIの抽象定義 |
| CustomerAddressEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressEntity.php` | エンティティ | 住所データモデル |
| CustomerAddressDefinition.php | `src/Core/Checkout/Customer/Aggregate/CustomerAddress/CustomerAddressDefinition.php` | エンティティ定義 | DAL定義 |
| CustomerZipCode.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerZipCode.php` | 制約 | 国別郵便番号検証 |
| StoreApiCustomFieldMapper.php | `src/Core/System/SalesChannel/StoreApiCustomFieldMapper.php` | マッパー | カスタムフィールドフィルタリング |
| DataMappingEvent.php | `src/Core/Framework/Event/DataMappingEvent.php` | イベント | データマッピング拡張ポイント |
| create.html.twig | `src/Storefront/Resources/views/storefront/page/account/addressbook/create.html.twig` | テンプレート | 住所作成フォーム |
