# 画面設計書 139-配送オプション作成画面

## 概要

本ドキュメントは、Medusa Admin Dashboardにおける配送オプション作成画面の設計仕様を定義するものである。

### 本画面の処理概要

配送オプション作成画面は、サービスゾーン内に新しい配送オプション（配送方法）を作成するための画面である。

**業務上の目的・背景**：ECサイト運営において、各サービスゾーンで利用可能な配送方法を定義する必要がある。例えば「国内配送ゾーン」に対して「通常配送」「翌日配送」「クール便」などの選択肢を提供する。配送オプションには料金タイプ（固定料金/計算式）、配送プロファイル、フルフィルメントプロバイダー、価格設定などを指定できる。また、返品用の配送オプションとピックアップオプションも同様の画面で作成できる。

**画面へのアクセス方法**：ロケーション詳細画面 > サービスゾーン内の「Add Shipping Option」または「Add Pickup Option」ボタンをクリックすることでアクセスできる。返品用は「?is_return」パラメータを付与してアクセスする。URLパスは `/settings/locations/{location_id}/fulfillment-set/{fset_id}/service-zone/{zone_id}/shipping-option/create`。

**主要な操作・処理内容**：
1. 料金タイプの選択（固定料金/計算式）※Pickup以外
2. 配送オプション名の入力
3. 配送プロファイルの選択
4. 配送オプションタイプの選択
5. フルフィルメントプロバイダーの選択
6. フルフィルメントオプションの選択
7. ストア表示設定
8. 価格設定（通貨別/リージョン別）※固定料金の場合
9. 「保存」ボタンで配送オプションを作成

**画面遷移**：
- 遷移元：ロケーション詳細画面（`/settings/locations/{location_id}`）
- 遷移先：作成完了後、ロケーション詳細画面へ自動的に戻る

**権限による表示制御**：管理者権限を持つユーザーのみがこの画面にアクセスし、配送オプションを作成できる。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 43 | 配送オプション管理 | 主機能 | 配送オプションの作成 |
| 46 | 配送プロバイダー管理 | 補助機能 | プロバイダーの選択 |

## 画面種別

登録画面（フルページモーダル形式・ステップ形式）

## URL/ルーティング

- パス: `/settings/locations/:location_id/fulfillment-set/:fset_id/service-zone/:zone_id/shipping-option/create`
- クエリパラメータ: `is_return` - 返品用配送オプションの場合に付与
- コンポーネント: `LocationServiceZoneShippingOptionCreate`
- ルートファイル: `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx`

## 入出力項目

### 詳細タブ（Details）

| 項目名 | 項目ID | 入出力 | 型 | 必須 | バリデーション | 説明 |
|--------|--------|--------|-----|------|---------------|------|
| 料金タイプ | price_type | 入力 | enum | Yes | - | `flat_rate`または`calculated` |
| 配送オプション名 | name | 入力 | string | Yes | 1文字以上 | 配送オプションの名称 |
| 配送プロファイル | shipping_profile_id | 入力 | string | Yes | 1文字以上 | 配送プロファイルのID |
| 配送オプションタイプ | shipping_option_type_id | 入力 | string | Yes | 1文字以上 | 配送オプションタイプのID |
| フルフィルメントプロバイダー | provider_id | 入力 | string | Yes | 1文字以上 | プロバイダーID |
| フルフィルメントオプション | fulfillment_option_id | 入力 | string | Yes | 1文字以上 | プロバイダーが提供するオプションのID |
| ストア表示 | enabled_in_store | 入力 | boolean | Yes | - | ストアで表示するかどうか |

### 価格タブ（Pricing）

| 項目名 | 項目ID | 入出力 | 型 | 必須 | バリデーション | 説明 |
|--------|--------|--------|-----|------|---------------|------|
| 通貨別価格 | currency_prices | 入力 | object | No | - | 通貨コードをキー、価格を値とするオブジェクト |
| リージョン別価格 | region_prices | 入力 | object | No | - | リージョンIDをキー、価格を値とするオブジェクト |
| 条件付き通貨別価格 | conditional_currency_prices | 入力 | object | No | - | 条件付き価格ルール |
| 条件付きリージョン別価格 | conditional_region_prices | 入力 | object | No | - | 条件付き価格ルール |

## 表示項目

| 項目名 | 説明 |
|--------|------|
| ヘッダータイトル | タイプに応じた動的タイトル（Shipping/Pickup/Returns） |
| ヒントメッセージ | 配送オプション作成についてのヒント |
| プログレスタブ | Details → Pricingの2ステップ表示（計算式の場合は1ステップ） |

### 料金タイプ選択（Shipping/Returns時のみ）

| 項目名 | 説明 |
|--------|------|
| 固定料金 | `flat_rate` - 通貨/リージョン別に固定料金を設定 |
| 計算式 | `calculated` - プロバイダーの計算ロジックを使用 |

## イベント仕様

### 1-保存ボタン押下

**トリガー**: 「Save」ボタンのクリック、または `Cmd/Ctrl + Enter` キーボードショートカット（価格タブまたは計算式選択時）

**処理フロー**:
1. フォームバリデーションを実行
2. 価格データを適切な形式に変換
3. `useCreateShippingOptions`フックを通じてAPIを呼び出し
4. APIリクエスト: `POST /admin/shipping-options`
5. 成功時: トースト通知を表示し、ロケーション詳細画面へ遷移
6. 失敗時: エラーメッセージをトースト通知で表示

**送信データ**:
```typescript
{
  name: string,
  price_type: "flat_rate" | "calculated",
  service_zone_id: string,
  shipping_profile_id: string,
  provider_id: string,
  prices: Array<{
    currency_code?: string,
    region_id?: string,
    amount: number,
    rules?: Array<{...}>
  }>,
  data: Record<string, unknown>,  // fulfillmentOptionData
  rules: [
    { value: string, attribute: "is_return", operator: "eq" },
    { value: string, attribute: "enabled_in_store", operator: "eq" }
  ],
  type_id: string
}
```

### 2-キャンセルボタン押下

**トリガー**: 「Cancel」ボタンのクリック

**処理フロー**:
1. モーダルを閉じる
2. ロケーション詳細画面に戻る

### 3-続行ボタン押下（詳細タブ）

**トリガー**: 「Continue」ボタンのクリック、または `Cmd/Ctrl + Enter`（詳細タブ表示時）

**処理フロー**:
1. 詳細タブのバリデーションを実行（`CreateShippingOptionDetailsSchema`）
2. バリデーション成功時、価格タブへ遷移
3. バリデーション失敗時、最初のエラーにフォーカス

### 4-プロバイダー変更

**トリガー**: フルフィルメントプロバイダーのコンボボックス変更

**処理フロー**:
1. `provider_id`を更新
2. `fulfillment_option_id`をリセット（空文字）
3. `useFulfillmentProviderOptions`で新しいプロバイダーのオプションを取得

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | shipping_option | INSERT | 配送オプションレコードの作成 |
| 保存ボタン押下 | shipping_option_price | INSERT | 価格レコードの作成（複数） |
| 保存ボタン押下 | shipping_option_rule | INSERT | ルールレコードの作成（is_return, enabled_in_store） |

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

#### shipping_option

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | システム自動生成（UUID） | 主キー |
| INSERT | name | フォーム入力値 | 配送オプション名 |
| INSERT | price_type | フォーム入力値 | `flat_rate`または`calculated` |
| INSERT | service_zone_id | URLパラメータのzone_id | 外部キー |
| INSERT | shipping_profile_id | フォーム入力値 | 外部キー |
| INSERT | provider_id | フォーム入力値 | 外部キー |
| INSERT | type_id | フォーム入力値 | 配送オプションタイプ |
| INSERT | data | fulfillmentOptionData | プロバイダー固有データ |

#### shipping_option_price

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | システム自動生成（UUID） | 主キー |
| INSERT | shipping_option_id | 作成されたshipping_optionのid | 外部キー |
| INSERT | currency_code | 通貨コード（該当時） | 例：USD, JPY |
| INSERT | region_id | リージョンID（該当時） | 外部キー |
| INSERT | amount | 価格（整数） | 最小通貨単位 |

#### shipping_option_rule

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | システム自動生成（UUID） | 主キー |
| INSERT | shipping_option_id | 作成されたshipping_optionのid | 外部キー |
| INSERT | attribute | `is_return` または `enabled_in_store` | ルール属性 |
| INSERT | operator | `eq` | 演算子 |
| INSERT | value | `true` または `false` | ルール値 |

## メッセージ仕様

| メッセージ種別 | メッセージキー | 表示条件 | 表示内容 |
|--------------|---------------|----------|---------|
| 成功（配送） | stockLocations.shippingOptions.create.shipping.successToast | 配送オプション作成成功時 | 「{name} created successfully」 |
| 成功（返品） | stockLocations.shippingOptions.create.returns.successToast | 返品オプション作成成功時 | 「{name} created successfully」 |
| エラー | - | API呼び出し失敗時 | APIから返却されたエラーメッセージ |
| バリデーション | - | 必須項目未入力時 | 該当フィールドにエラー表示 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| フルフィルメントセットが見つからない | 404エラーをスロー |
| サービスゾーンが見つからない | 404エラーをスロー |
| ネットワークエラー | トースト通知でエラーメッセージを表示 |
| サーバーエラー（5xx） | トースト通知でエラーメッセージを表示 |
| 認証エラー（401） | ログイン画面へリダイレクト |

## 備考

- 画面はフルページモーダル（`RouteFocusModal`）として実装されている
- フォームは`react-hook-form`と`zod`によるバリデーションを使用
- ステップ形式のUI（`ProgressTabs`）で詳細→価格の2ステップ
- 料金タイプが「計算式」の場合、価格タブはスキップされる
- Pickupタイプの場合、料金タイプ選択は表示されない（自動的にfixed）
- フルフィルメントプロバイダー選択時、そのプロバイダーのオプションを動的に取得
- 返品用オプションは`is_return`クエリパラメータで判定
- 価格はcurrency_prices（通貨別）とregion_prices（リージョン別）の両方で設定可能
- 条件付き価格ルール（conditional_*_prices）で数量や重量に応じた価格設定が可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.ts | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/schema.ts` | L9-17: `CreateShippingOptionDetailsSchema`で詳細フィールドを定義、L34-40: `CreateShippingOptionSchema`で全体スキーマを定義 |
| 1-2 | constants.ts | `packages/admin/dashboard/src/routes/locations/common/constants.ts` | `ShippingOptionPriceType`でflat_rate/calculatedを定義 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | location-service-zone-shipping-option-create.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx` | L9-55: URLパラメータ取得、is_return判定、データフェッチ |

**主要処理フロー**:
1. **L10**: `useParams`でURLパラメータを取得
2. **L11-12**: `useSearchParams`でis_returnパラメータを取得
3. **L14-17**: `useStockLocation`でロケーションデータを取得
4. **L19-21**: fset_idに一致するフルフィルメントセットを検索
5. **L30**: zone_idに一致するサービスゾーンを検索
6. **L46-51**: フォームコンポーネントに必要なプロパティを渡す

#### Step 3: フォームコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create-shipping-options-form.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx` | L41-359: フォーム初期化、タブ管理、API呼び出し |

**主要処理フロー**:
- **L47-48**: アクティブタブとバリデーション状態を管理
- **L53-67**: `useForm`でフォーム状態を初期化
- **L69-77**: プロバイダー選択時にオプションを取得（`useFulfillmentProviderOptions`）
- **L82**: `useCreateShippingOptions`フックでAPI呼び出し関数を取得
- **L84-190**: `handleSubmit`で価格データを変換しAPI呼び出し
- **L192-232**: タブ切り替え時のバリデーション処理

#### Step 4: 詳細フォームを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create-shipping-option-details-form.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-option-details-form.tsx` | L27-297: 詳細入力フォームの実装 |

**主要処理フロー**:
- **L40-48**: `useComboboxData`で配送プロファイル一覧を取得
- **L50-58**: `useComboboxData`で配送オプションタイプ一覧を取得
- **L60-72**: `useComboboxData`でフルフィルメントプロバイダー一覧を取得
- **L97-141**: 料金タイプ選択（RadioGroup）
- **L209-242**: プロバイダー選択（変更時にfulfillment_option_idをリセット）

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

```
LocationServiceZoneShippingOptionCreate (location-service-zone-shipping-option-create.tsx)
    │
    ├─ useStockLocation (サービスゾーン情報取得)
    │
    └─ CreateShippingOptionsForm (create-shipping-options-form.tsx)
           │
           ├─ useForm (react-hook-form)
           │      └─ zodResolver (CreateShippingOptionSchema)
           │
           ├─ useFulfillmentProviderOptions (プロバイダーオプション取得)
           │
           ├─ ProgressTabs
           │      ├─ Tab.DETAILS
           │      │      └─ CreateShippingOptionDetailsForm
           │      │             ├─ useComboboxData (shippingProfiles)
           │      │             ├─ useComboboxData (shippingOptionTypes)
           │      │             └─ useComboboxData (fulfillmentProviders)
           │      │
           │      └─ Tab.PRICING
           │             └─ CreateShippingOptionsPricesForm
           │
           └─ useCreateShippingOptions
                  └─ POST /admin/shipping-options
```

### データフロー図

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

URLパラメータ取得 ───▶ zone, fset_id, is_return判定 ───▶ フォーム初期化
                                                              │
詳細入力 ──────────────▶ CreateShippingOptionDetailsSchema ───▶ バリデーション
      │                                                        │
      │                                    ┌───────────────────┘
      │                                    ▼
      └─(continue)──▶ 価格タブへ遷移 ───▶ 価格入力
                                              │
プロバイダー選択 ───▶ useFulfillmentProviderOptions ───▶ オプション一覧更新
                                              │
                                    ┌─────────┘
                                    ▼
価格データ ────────▶ castNumber変換 ───▶ prices配列構築
      │
      └─(save)──▶ useCreateShippingOptions
                          │
                          ├─(success)──▶ toast.success + 詳細画面遷移
                          │
                          └─(error)──▶ toast.error
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| location-service-zone-shipping-option-create.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/location-service-zone-shipping-option-create.tsx` | ソース | 画面エントリーポイント |
| create-shipping-options-form.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-form.tsx` | ソース | メインフォームコンポーネント |
| create-shipping-option-details-form.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-option-details-form.tsx` | ソース | 詳細入力フォーム |
| create-shipping-options-prices-form.tsx | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/create-shipping-options-prices-form.tsx` | ソース | 価格設定フォーム |
| schema.ts | `packages/admin/dashboard/src/routes/locations/location-service-zone-shipping-option-create/components/create-shipping-options-form/schema.ts` | ソース | バリデーションスキーマ |
| constants.ts | `packages/admin/dashboard/src/routes/locations/common/constants.ts` | ソース | 定数定義（ShippingOptionPriceType等） |
| shipping-options.tsx | `packages/admin/dashboard/src/hooks/api/shipping-options.tsx` | ソース | 配送オプションAPI |
