# 業務ロジック — VisitController.processNewVisitForm() [12 LOC]

| 項目 | 値 |
|-------|-------|
| 完全修飾名 | `org.springframework.samples.petclinic.owner.VisitController` |
| レイヤー | Controller |
| モジュール | `owner` (パッケージ: `org.springframework.samples.petclinic.owner`) |

## 1. 役割

### VisitController.processNewVisitForm()

本メソッドは、ペットクリニックにおける「獣医診察訪問（Visit）」の新規予約入力フォームの送信を受け付け、訪問記録を永続化するPOSTエンドポイントである。クライアントから送信された診察日（`date`）および診察内容（`description`）を含む `Visit` オブジェクトを検証し、バリデーションエラーが存在する場合は入力画面へ戻す。エラーがない場合は、対応する飼い主（`Owner`）とそのペット（`Pet`）に訪問レコードを関連付けた上で `OwnerRepository` を介して永続化し、成功メッセージをフラッシュ属性として付与した後に飼い主詳細ページへリダイレクトする。

本メソッドは **Spring MVC の POST リクエストハンドラーパターン** を実装しており、`@ModelAttribute` を介したモデル束縛と `@Valid` による Bean バリデーションを組み合わせた標準的なフォーム処理フローを辿る。また、**デlegation（委譲）パターン** により、`Owner.addVisit()` → `Pet.addVisit()` と階層的にドメインモデル内部に処理を委譲し、`OwnerRepository.save()` で持久層へ保存する。

システム全体における役割は、ペットクリニック管理画面における「新規診察予約」機能の**終端コントローラー**として、Web層からドメイン層・永続化層までの責任をシームレスに繋ぐことである。このメソッドは HTTP POST のみを受容し、GET リクエストでの直接アクセスは想定されない（GET は `initNewVisitForm()` が担当）。

制御分岐は1つだけ存在する：バリデーションエラーの有無による二分岐。エラー時は入力画面へ復帰し、正常時は訪問登録→リダイレクトの単一パスを処理する。

## 2. 処理パターン（詳細業務ロジック）

```mermaid
flowchart TD
    START(["processNewVisitForm 呼出"])
    COND["result.hasErrors()?"]
    EARLY_RET["pets/createOrUpdateVisitForm を返却"]
    ADD_VISIT["owner.addVisit(petId, visit)"]
    SAVE_OWNER["this.owners.save(owner)"]
    FLASH_MSG["redirectAttributes.addFlashAttribute"]
    REDIRECT_RET["redirect:/owners/{ownerId} を返却"]
    FINAL(["ページ遷移完了"])

    START --> COND
    COND -->|true| EARLY_RET
    COND -->|false| ADD_VISIT
    ADD_VISIT --> SAVE_OWNER
    SAVE_OWNER --> FLASH_MSG
    FLASH_MSG --> REDIRECT_RET
    REDIRECT_RET --> FINAL
```

## 3. パラメータ分析

| No | パラメータ名 | 型 | 業務的説明 |
|----|---------------|------|---------------------|
| 1 | `owner` | `@ModelAttribute Owner` | ペットを所有する飼い主情報。フォームからバインドされた所有者の氏名、住所、市区町村、電話番号などの属性を保持する。 |
| 2 | `petId` | `@PathVariable int` | URLパス `owners/{ownerId}/pets/{petId}/visits/new` から取得されたペットの一意ID。どのペットの診察予約かを特定するための識別子。 |
| 3 | `visit` | `@Valid Visit` | フォームから送信された診察予約情報。診察日（`date`）と診察内容説明（`description`）を含む。`@Valid` アノテーションにより Bean バリデーションが実行される（例：`description` は必須）。 |
| 4 | `result` | `BindingResult` | `visit` パラメータに対するバリデーション結果。`hasErrors()` メソッドでエラーの有無を判定し、エラー時は入力画面へリターンする。 |
| 5 | `redirectAttributes` | `RedirectAttributes` | リダイレクト送信先のページへ一時的に渡すフラッシュ属性。成功メッセージ「Your visit has been booked」をセットし、リダイレクト後にもメッセージを表示可能にする。 |

**追加 — 内部状態:**

| No | フィールド名 | 型 | 業務的説明 |
|----|---------------|------|---------------------|
| 1 | `owners` | `OwnerRepository` | Spring Data JPA による永続化レイヤーへのアクセス用リポジトリ。`save()` メソッドで `Owner` および関連エンティティ（`Pet`, `Visit`）を `owners` テーブルおよび結合テーブルへ永続化する。 |

## 4. CRUD操作／呼び出しサービス

### コード解析グラフからの事前抽出エビデンス:

| CRUD | SC / CBS | SCコード | エンティティ / DB | 操作の説明 |
|------|----------|---------|-------------|----------------------|
| C | `Owner.addVisit` | - | `owners` | `Owner` ドメイン内で指定された `petId` に該当するペットを検索し、新しい `Visit` をそのペットの診察履歴に関連付ける |
| C | `Pet.addVisit` | - | `visits` | ペットの診察記録リスト（`Collection<Visit>`）に新しい `Visit` オブジェクトを追加 |
| C | `OwnerRepository.save` | - | `owners`, `pets`, `visits` | `Owner` エンティティ全体を永続化。`cascade = CascadeType.ALL` により、関連する `Pet` および `Visit` も合わせて INSERT/UPDATE される |

**操作分類の詳細:**

| CRUD | SC / CBS | SCコード | エンティティ / DB | 操作の説明 |
|------|----------|---------|-------------|----------------------|
| C | `owner.addVisit` | - | `visits` (関連付け) | 飼い主配下の指定ペットに新たな診察予約レコードを関連付ける（ドメインモデル内での関連付け追加のみ） |
| C | `pet.addVisit` | - | `visits` | ペットエンティティ内の `visits` コレクションに `Visit` オブジェクトを追加。JPA により外部キーが自動設定される |
| C | `this.owners.save` | - | `owners`, `pets`, `visits` | Spring Data JPA によるトランザクショナルな永続化。`FetchType.EAGER` により `Owner→Pet→Visit` の全階層が同時に DB へ書き出される |

**CRUD分類の補足:**
- **C (Create)**: 本メソッドは新規診察予約の登録のみを行い、既存データの読み取り・更新・削除は行わない。
- `OwnerRepository.save()` は JPA の `save()` であるが、このメソッドの文脈では新規 `Visit` の追加伴随する `Owner` エンティティ全体の保存であり、実質的には Create 操作に分類される。
- `cascade = CascadeType.ALL` および `FetchType.EAGER` により、`Owner` の保存時に `Pet` と `Visit` も合わせてデータベースに INSERT される。

## 5. 依存関係トレース

| # | 呼び出し元（画面/バッチ） | 呼び出しチェーン（本メソッドまでの全経路） | 終端（SC / CRUD / エンティティ） |
|---|----------------------|--------------------------------------|-------------------------------|
| 1 | HTTP POST: `/owners/{ownerId}/pets/{petId}/visits/new` | `DispatcherServlet` → `VisitController.processNewVisitForm` | `owner.addVisit [C] visits`, `owners.save [C] owners, pets, visits` |
| 2 | テストケース: `VisitControllerTests.processNewVisitFormSuccess()` | `MockMvc.perform(post(...))` → `VisitController.processNewVisitForm` | `owner.addVisit [C] visits`, `owners.save [C] owners, pets, visits` |
| 3 | テストケース: `VisitControllerTests.processNewVisitFormHasErrors()` | `MockMvc.perform(post(...))` → `VisitController.processNewVisitForm` | `result.hasErrors()` → 早期リターン（CRUD なし） |

**補足:**
- 本メソッドは `@PostMapping` により HTTP POST リクエストを直接受信するコントローラーエントリーポイントである。
- 呼び出し元として明確に確認できたのは、上記の直接 HTTP 呼出とユニットテストの2種類のみである。

## 6. 分岐ごとの詳細ブロック

**ブロック 1** — [IF] `(result.hasErrors())` (L93)

> バリデーションエラーのチェック。`@Valid` による Bean バリデーションでエラーが見つかった場合、入力画面へ復帰する。

| # | 種別 | コード |
|---|------|------|
| 1 | EXEC | `result.hasErrors()` // Visit オブジェクトに対するバリデーション結果を確認 |
| 2 | RETURN | `return "pets/createOrUpdateVisitForm";` // エラー時: 入力画面へ復帰 |

**ブロック 2** — [ELSE 相当] `(!result.hasErrors())` (L95)

> バリデーションクリア後の訪問登録処理。エラーがない場合のみ実行される主パス。

| # | 種別 | コード |
|---|------|------|
| 1 | CALL | `owner.addVisit(petId, visit);` // Owner ドメインメソッドを呼び出し、指定ペットに Visit を関連付け |
| 2 | CALL | `this.owners.save(owner);` // OwnerRepository.save() で Owner および関連 Pet/Visit を永続化 |
| 3 | EXEC | `redirectAttributes.addFlashAttribute("message", "Your visit has been booked");` // リダイレクト先へ成功メッセージを渡す |
| 4 | RETURN | `return "redirect:/owners/{ownerId}";` // 飼い主詳細ページへリダイレクト指示を返す |

**ブロック 2.1** — [内部: `Owner.addVisit()`] (Owner.java, L164)

> `addVisit` メソッド内部での検証と関連付け処理。

| # | 種別 | コード |
|---|------|------|
| 1 | EXEC | `Assert.notNull(petId, "Pet identifier must not be null!");` // petId が null なら例外 |
| 2 | EXEC | `Assert.notNull(visit, "Visit must not be null!");` // visit が null なら例外 |
| 3 | CALL | `Pet pet = getPet(petId);` // Owner 配下のペットを ID で検索 |
| 4 | EXEC | `Assert.notNull(pet, "Invalid Pet identifier!");` // ペットが存在しないなら例外 |
| 5 | CALL | `pet.addVisit(visit);` // ペットエンティティの診察リストに Visit を追加 |

**ブロック 2.1.1** — [内部: `Pet.addVisit()`] (Pet.java, L81)

> ペットエンティティ内での診察記録追加処理。

| # | 種別 | コード |
|---|------|------|
| 1 | SET | `getVisits().add(visit);` // `Collection<Visit>` へVisitを直接追加（`visits` テーブルへは save 時に反映） |

## 7. 用語集

| 用語 | 種別 | 業務的意味 |
|------|------|------------------|
| `Visit` | Entity | 診察予約エンティティ。ペットの獣医受診記録を表し、診察日（`date`）と診察内容（`description`）を属性として持つ。DB テーブル `visits` にマッピングされる。 |
| `Owner` | Entity | ペットの飼い主エンティティ。氏名、住所、市区町村、電話番号を属性とし、複数の `Pet` を所有できる。DB テーブル `owners` にマッピングされる。 |
| `Pet` | Entity | ペットエンティティ。飼い主（`Owner`）に属し、複数の診察記録（`Visit`）を保持できる。 |
| `OwnerRepository` | Interface | Spring Data JPA リポジトリインタフェース。`Owner` エンティティの永続化（CRUD）操作を提供する。 |
| `cascade = CascadeType.ALL` | JPA 属性 | 親エンティティ（`Owner`）の保存時に関連エンティティ（`Pet`, `Visit`）も自動的に保存される設定。 |
| `FetchType.EAGER` | JPA 属性 | 親エンティティ参照時に関連エンティティを同時に読み込むフェッチ戦略。`Owner` から `Pet` 一覧が即座に取得可能。 |
| `@Valid` | アノテーション | Bean Validation による入力バリデーションをトリガーする。`Visit` オブジェクトの制約注釈（`@NotBlank` など）が実行される。 |
| `BindingResult` | Spring 型 | バリデーションの結果を格納するオブジェクト。`hasErrors()` でエラーの有無を判定できる。 |
| `RedirectAttributes` | Spring 型 | リダイレクト送信先に一時的なフラッシュ属性（`message` など）を渡すためのオブジェクト。 |
| `flash attribute` | 概念 | リダイレクト後のリクエストのみ有効な一時的なセッションデータ。ページ遷移後のメッセージ表示に使用される。 |
| `pets/createOrUpdateVisitForm` | ビュー名 | 診察予約入力フォームの Thymeleaf テンプレートパス。バリデーションエラー時に表示される。 |
| `redirect:/owners/{ownerId}` | 遷移先 | 飼い主詳細ページへのリダイレクト先URL。成功メッセージ付きで表示される。 |
| `@PostMapping` | アノテーション | HTTP POST リクエストを受信するマッピング注釈。URL パス `/owners/{ownerId}/pets/{petId}/visits/new` にバインドされる。 |
