# 機能設計書 12-プロフィール編集

## 概要

本ドキュメントは、Shopwareストアフロントにおける「プロフィール編集」機能の設計内容を記載する。ログイン中の顧客が自身の基本プロフィール情報（名前、敬称、会社情報、生年月日等）を更新する機能である。

### 本機能の処理概要

**業務上の目的・背景**：ECサイトにおいて、顧客が自身の登録情報を最新の状態に保つことは重要である。引っ越しや名前の変更、法人顧客の会社情報更新など、顧客情報は変化するため、顧客自身が簡単に更新できる機能が必要である。本機能は、セキュリティを確保しながら顧客が自分のプロフィール情報を変更できるようにする。

**機能の利用シーン**：
- 顧客が名前を変更した場合（結婚等）
- 法人顧客が会社名や税番号（VAT ID）を更新する場合
- 敬称や肩書きを変更したい場合
- 生年月日を登録・修正したい場合
- カスタムフィールドの情報を更新したい場合

**主要な処理内容**：
1. ログイン認証の確認
2. 入力データのバリデーション（必須項目、形式チェック）
3. 法人アカウントの場合は会社名・VAT IDの追加バリデーション
4. 顧客情報の更新（customerリポジトリへの書き込み）
5. 成功・エラーメッセージの表示とリダイレクト

**関連システム・外部連携**：Store API（ChangeCustomerProfileRoute）を経由してデータを更新。イベントディスパッチャーによるデータマッピングイベント発火。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | プロフィールページ | 主画面 | プロフィール情報の表示と更新フォーム |
| 66 | プロフィール | 参照画面 | 管理者用プロフィール編集との関連 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| salutationId | string | Yes | 敬称ID | 有効なUUID形式、存在確認 |
| firstName | string | Yes | 名 | NotBlank |
| lastName | string | Yes | 姓 | NotBlank |
| accountType | string | No | アカウント種別（private/business） | CustomerEntity::ACCOUNT_TYPE_*の値 |
| company | string | No（business時必須） | 会社名 | accountType=businessの場合NotBlank |
| vatIds | array | No | VAT ID（税番号）リスト | CustomerVatIdentification制約 |
| title | string | No | 肩書き | なし |
| birthdayDay | int | No | 誕生日（日） | 数値 |
| birthdayMonth | int | No | 誕生日（月） | 数値 |
| birthdayYear | int | No | 誕生日（年） | 数値 |
| customFields | RequestDataBag | No | カスタムフィールド | StoreApiCustomFieldMapperによるマッピング |

### 入力データソース

- フォーム入力（POSTリクエスト）
- SalesChannelContextから取得される販売チャネル設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| flashMessage | string | 成功/エラーメッセージ |
| redirectResponse | Response | プロフィールページへのリダイレクト |

### 出力先

- セッション（フラッシュメッセージ）
- 画面リダイレクト（frontend.account.profile.page）

## 処理フロー

### 処理シーケンス

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

2. コントローラー処理 (AccountProfileController::saveProfile)
   └─ ChangeCustomerProfileRouteを呼び出し

3. プロフィール変更ルート処理 (ChangeCustomerProfileRoute::change)
   ├─ accountTypeの検証と処理
   ├─ business typeの場合、companyとvatIds検証を追加
   ├─ バリデーション実行
   ├─ データマッピングイベント発火
   └─ customerリポジトリの更新

4. レスポンス生成
   ├─ 成功時：成功メッセージ設定、プロフィールページへリダイレクト
   └─ エラー時：エラーメッセージ設定、フォーム再表示
```

### フローチャート

```mermaid
flowchart TD
    A[POST /account/profile] --> B{ログイン済み?}
    B -->|No| C[ログイン画面へリダイレクト]
    B -->|Yes| D[ChangeCustomerProfileRoute::change]
    D --> E{accountType = business?}
    E -->|Yes| F[company必須バリデーション追加]
    F --> G[vatIds検証追加]
    G --> H[バリデーション実行]
    E -->|No| I[company/vatIds クリア]
    I --> H
    H --> J{バリデーション成功?}
    J -->|No| K[ConstraintViolationException]
    K --> L[エラーメッセージ設定]
    L --> M[フォーム再表示]
    J -->|Yes| N[DataMappingEvent発火]
    N --> O[customerリポジトリ更新]
    O --> P[成功メッセージ設定]
    P --> Q[プロフィールページへリダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-12-01 | ログイン必須 | 未ログインユーザーはアクセス不可 | 全リクエスト |
| BR-12-02 | ゲスト許可 | ゲストユーザーも利用可能 | LOGIN_REQUIRED_ALLOW_GUEST設定時 |
| BR-12-03 | 法人必須フィールド | accountType=businessの場合、companyは必須 | 法人アカウント |
| BR-12-04 | VAT ID検証 | VAT IDは国別の形式に従う必要がある | vatIdsが入力された場合 |
| BR-12-05 | VAT ID必須 | 請求先住所の国がvatIdRequired=trueの場合、VAT IDは必須 | 該当国設定時 |
| BR-12-06 | 敬称デフォルト | salutationIdが未指定の場合、NOT_SPECIFIEDを設定 | salutationId空の場合 |
| BR-12-07 | 個人情報クリア | private typeの場合、company/vatIdsはクリア | 個人アカウント |

### 計算ロジック

**生年月日の計算**:
```php
// birthdayDay, birthdayMonth, birthdayYearが全て数値の場合に日付を生成
if (is_numeric($birthdayDay) && is_numeric($birthdayMonth) && is_numeric($birthdayYear)) {
    $birthday = new DateTime("$birthdayYear-$birthdayMonth-$birthdayDay");
}
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| プロフィール更新 | customer | UPDATE | 顧客プロフィール情報の更新 |

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

#### customer

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | first_name | 入力値 | 必須 |
| UPDATE | last_name | 入力値 | 必須 |
| UPDATE | salutation_id | 入力値または"not_specified" | 必須 |
| UPDATE | title | 入力値 | 任意 |
| UPDATE | company | 入力値（business）またはnull（private） | accountTypeによる |
| UPDATE | account_type | 入力値 | private/business |
| UPDATE | vat_ids | 入力値（business）またはnull（private） | accountTypeによる |
| UPDATE | birthday | 計算された日付値 | 任意 |
| UPDATE | custom_fields | マッピングされた値 | 任意 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ConstraintViolationException | バリデーション失敗 | フォームにエラー内容を表示、再入力を促す |
| - | Exception | その他の例外 | エラーログ出力、汎用エラーメッセージ表示 |

### リトライ仕様

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

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

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

## パフォーマンス要件

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

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

- LOGIN_REQUIRED属性によりログイン必須を強制
- 顧客は自分自身のプロフィールのみ更新可能（customer.idはセッションから取得）
- VAT IDはCustomerVatIdentification制約で国別形式を検証
- カスタムフィールドはStoreApiCustomFieldMapperでフィルタリング

## 備考

- DataMappingEvent（CustomerEvents::MAPPING_CUSTOMER_PROFILE_SAVE）により、プラグインがデータマッピングを拡張可能
- BuildValidationEventにより、プラグインがバリデーションルールを拡張可能

---

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

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

### 推奨読解順序

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

顧客エンティティのフィールド構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerEntity.php | `src/Core/Checkout/Customer/CustomerEntity.php` | firstName, lastName, company, vatIds等のフィールドを確認 |
| 1-2 | RequestDataBag.php | `src/Core/Framework/Validation/DataBag/RequestDataBag.php` | フォームデータのラッパークラスの構造を理解 |

**読解のコツ**: CustomerEntityのACCOUNT_TYPE_PRIVATE/ACCOUNT_TYPE_BUSINESS定数がaccountTypeの有効値である。

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

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

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

**主要処理フロー**:
1. **98行目**: `$this->changeCustomerProfileRoute->change($data, $context, $customer)` でプロフィール変更ルート呼び出し
2. **100行目**: 成功時、成功メッセージをフラッシュに設定
3. **101-102行目**: ConstraintViolationException時、フォームに戻る
4. **103-105行目**: その他例外時、エラーログ出力とエラーメッセージ設定
5. **108行目**: プロフィールページへリダイレクト

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

実際のプロフィール更新ロジックを理解する。

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

**主要処理フロー**:
- **78行目**: バリデーション定義の取得
- **80-82行目**: 空のaccountTypeの処理
- **84-93行目**: business typeの場合のバリデーション追加
- **90-93行目**: private typeの場合のデータクリア
- **105-107行目**: バリデーション実行
- **109-133行目**: 顧客データの構築
- **135-138行目**: DataMappingEventの発火
- **142行目**: customerリポジトリの更新

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | CustomerVatIdentification.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerVatIdentification.php` | VAT ID検証制約 |
| 4-2 | DataValidationFactoryInterface.php | `src/Core/Framework/Validation/DataValidationFactoryInterface.php` | バリデーション定義ファクトリ |

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

```
AccountProfileController::saveProfile
    │
    ├─ ChangeCustomerProfileRoute::change
    │      │
    │      ├─ customerProfileValidationFactory.update (バリデーション定義取得)
    │      │
    │      ├─ addVatIdsValidation (VAT ID検証追加)
    │      │
    │      ├─ dispatchValidationEvent (BuildValidationEvent)
    │      │
    │      ├─ validator.validate (バリデーション実行)
    │      │
    │      ├─ getBirthday (生年月日計算)
    │      │
    │      ├─ storeApiCustomFieldMapper.map (カスタムフィールドマッピング)
    │      │
    │      ├─ eventDispatcher.dispatch (DataMappingEvent)
    │      │
    │      └─ customerRepository.update (DB更新)
    │
    └─ addFlash (フラッシュメッセージ設定)
```

### データフロー図

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

POST /account/profile
      │
      ▼
RequestDataBag ─────────▶ AccountProfileController::saveProfile
      │                         │
      │                         ▼
      │               ChangeCustomerProfileRoute::change
      │                         │
      │               ┌─────────┴─────────┐
      │               ▼                   ▼
      │         バリデーション        DataMappingEvent
      │               │                   │
      │               ▼                   │
      │         成功/失敗判定 ◀───────────┘
      │               │
      │      ┌────────┴────────┐
      │      ▼                 ▼
      │   成功時            失敗時
      │      │                 │
      │      ▼                 ▼
      │ customerRepository  ConstraintViolation
      │    .update             Exception
      │      │                 │
      └──────┴─────────────────┘
                    │
                    ▼
         フラッシュメッセージ設定
                    │
                    ▼
         プロフィールページへリダイレクト
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AccountProfileController.php | `src/Storefront/Controller/AccountProfileController.php` | コントローラー | HTTPリクエスト処理のエントリーポイント |
| ChangeCustomerProfileRoute.php | `src/Core/Checkout/Customer/SalesChannel/ChangeCustomerProfileRoute.php` | Store API | プロフィール更新ビジネスロジック |
| AbstractChangeCustomerProfileRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractChangeCustomerProfileRoute.php` | 抽象クラス | Store APIの抽象定義 |
| CustomerEntity.php | `src/Core/Checkout/Customer/CustomerEntity.php` | エンティティ | 顧客データモデル |
| CustomerVatIdentification.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerVatIdentification.php` | 制約 | VAT ID検証ロジック |
| StoreApiCustomFieldMapper.php | `src/Core/System/SalesChannel/StoreApiCustomFieldMapper.php` | マッパー | カスタムフィールドのフィルタリング |
| DataMappingEvent.php | `src/Core/Framework/Event/DataMappingEvent.php` | イベント | データマッピング拡張ポイント |
| BuildValidationEvent.php | `src/Core/Framework/Validation/BuildValidationEvent.php` | イベント | バリデーション拡張ポイント |
| index.html.twig | `src/Storefront/Resources/views/storefront/page/account/profile/index.html.twig` | テンプレート | プロフィール編集フォーム |
