# 画面設計書 62-キャンペーン追加画面

## 概要

本ドキュメントは、Medusa Admin Dashboard のキャンペーン追加画面について、画面仕様・機能・データ処理を定義した画面設計書です。

### 本画面の処理概要

この画面では、プロモーションをキャンペーンに関連付けることができます。既存のキャンペーンを選択するか、キャンペーンとの関連付けを解除することができます。

**業務上の目的・背景**：キャンペーンは複数のプロモーションをグループ化し、期間や予算を一元管理するための機能です。プロモーションをキャンペーンに関連付けることで、マーケティング施策の効果測定や予算管理を効率的に行えます。また、キャンペーンを通じてプロモーションの使用回数や支出額を制限できます。

**画面へのアクセス方法**：プロモーション詳細画面から「キャンペーンを編集」アクションをクリックすることで、右側からスライドインするドロワー形式でこの画面が表示されます。URL: `/promotions/:id/add-campaign`

**主要な操作・処理内容**：
1. キャンペーンとの関連付け方法を選択（なし/既存のキャンペーン）
2. 「既存のキャンペーン」を選択した場合、コンボボックスからキャンペーンを検索・選択
3. 選択したキャンペーンの詳細情報（予算情報等）を確認
4. 保存ボタンで変更を確定

**画面遷移**：
- 遷移元：プロモーション詳細画面（`/promotions/:id`）
- 遷移先：保存成功時はプロモーション詳細画面に戻る、キャンセル時も同様

**権限による表示制御**：認証済みの管理者ユーザーのみがアクセス可能です。プロモーション管理権限を持つユーザーが操作できます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 38 | キャンペーン管理 | 主機能 | プロモーションをキャンペーンに追加 |

## 画面種別

編集（モーダル/ドロワー形式）

## URL/ルーティング

`/promotions/:id/add-campaign`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | 説明 |
|--------|--------|-----|------|-----------|------|
| キャンペーン選択方法 | campaign_choice | enum | ○ | 入力 | none/existing |
| キャンペーンID | campaign_id | string | - | 入力 | campaign_choiceがexistingの場合に必要 |

## 表示項目

| 項目名 | 説明 |
|--------|------|
| 画面タイトル | 「キャンペーンを編集」 |
| キャンペーン選択方法 | RadioGroupで「なし」/「既存のキャンペーン」を選択 |
| キャンペーン選択 | コンボボックスで既存キャンペーンを検索・選択 |
| キャンペーン詳細 | 選択したキャンペーンの予算情報等を表示 |
| キャンセルボタン | ドロワーを閉じる |
| 保存ボタン | 変更を保存 |

## イベント仕様

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

**処理フロー**：
1. フォームバリデーションを実行
2. `useUpdatePromotion` フックを使用してAPIを呼び出し
3. 成功時：トースト通知を表示し、ドロワーを閉じる
4. 失敗時：エラーメッセージをトーストで表示

**API呼び出し**：
- エンドポイント: `POST /admin/promotions/:id`
- リクエストボディ:
```json
{
  "campaign_id": string | null
}
```

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

**処理フロー**：
1. フォームの変更を破棄
2. ドロワーを閉じてプロモーション詳細画面に戻る

### 3-キャンペーン選択方法変更

**処理フロー**：
1. 「なし」を選択した場合：campaign_idをnullに設定
2. 「既存のキャンペーン」を選択した場合：元のキャンペーンIDを設定

### 4-キャンペーン選択変更

**処理フロー**：
1. コンボボックスで選択したキャンペーンのIDをフォームに設定
2. 選択したキャンペーンの詳細情報を取得・表示

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | promotion | UPDATE | キャンペーンIDの更新 |

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

#### promotion

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | campaign_id | フォームの campaign_id | null可能（関連付け解除） |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG-001 | 成功 | キャンペーンが更新されました | 保存成功時 |
| MSG-002 | エラー | {エラーメッセージ} | API通信エラー時 |

## 例外処理

| 例外 | 処理内容 |
|------|---------|
| プロモーションが見つからない | エラーをスローしてエラー画面を表示 |
| API通信エラー | エラーメッセージをtoastで表示 |

## 備考

- キャンペーンの予算タイプが「spend」（支出額）で、プロモーションの通貨と異なる場合、そのキャンペーンは選択できない（disabled状態）
- プロモーションに通貨が設定されていない場合、支出額タイプのキャンペーンには追加できない
- 選択したキャンペーンの詳細情報（名前、識別子、予算タイプ、予算上限、使用済み予算等）がプレビューとして表示される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | add-campaign-promotion-form.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/components/add-campaign-promotion-form/add-campaign-promotion-form.tsx` | EditPromotionSchema（24-27行目）でフォームの構造を確認 |

**読解のコツ**: campaign_idとcampaign_choiceの2つのフィールドでキャンペーン関連付けを管理します。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | promotion-add-campaign.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/promotion-add-campaign.tsx` | usePromotionフックでデータ取得（12行目） |

**主要処理フロー**:
1. **10行目**: useParamsでURLからプロモーションIDを取得
2. **12行目**: usePromotionフックでプロモーションデータを取得
3. **24-25行目**: ローディング完了後にAddCampaignPromotionFormを表示

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | add-campaign-promotion-form.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/components/add-campaign-promotion-form/add-campaign-promotion-form.tsx` | AddCampaignPromotionFieldsとAddCampaignPromotionFormの実装 |

**主要処理フロー**:
- **51-66行目**: useComboboxDataでキャンペーン一覧を取得
- **68-74行目**: useCampaignで選択したキャンペーンの詳細を取得
- **183行目**: useUpdatePromotionでミューテーション準備
- **184-197行目**: handleSubmitで送信処理
- **204-212行目**: useEffectでcampaign_choiceの変更に応じてcampaign_idを更新

#### Step 4: キャンペーン詳細表示コンポーネント

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | campaign-details.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/components/add-campaign-promotion-form/campaign-details.tsx` | 選択したキャンペーンの詳細表示 |

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

```
PromotionAddCampaign (promotion-add-campaign.tsx)
    │
    ├─ usePromotion() - プロモーションデータ取得
    │      └─ sdk.admin.promotion.retrieve()
    │
    └─ AddCampaignPromotionForm
           │
           ├─ useForm() - フォーム状態管理
           │
           ├─ AddCampaignPromotionFields
           │      │
           │      ├─ useComboboxData() - キャンペーン一覧取得
           │      │      └─ sdk.admin.campaign.list()
           │      │
           │      ├─ useCampaign() - 選択キャンペーン詳細取得
           │      │      └─ sdk.admin.campaign.retrieve()
           │      │
           │      └─ CampaignDetails - キャンペーン詳細表示
           │
           └─ useUpdatePromotion() - 更新処理
                  └─ sdk.admin.promotion.update()
                         └─ POST /admin/promotions/:id
                                └─ updatePromotionsWorkflow
```

### データフロー図

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

URLパラメータ ────▶ usePromotion ────▶ プロモーションデータ
     (id)                                      ↓
                                         フォーム初期値
                                              ↓
キャンペーン選択 ──▶ useComboboxData ──▶ キャンペーン一覧
                                              ↓
キャンペーンID ────▶ useCampaign ────▶ キャンペーン詳細
                                              ↓
                                         CampaignDetails表示
                                              ↓
保存ボタン ──────▶ handleSubmit ───▶ バリデーション
                                              ↓
                  useUpdatePromotion ──▶ API更新
                                              ↓
                                        toast.success()
                                              ↓
                                        handleSuccess()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| promotion-add-campaign.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/promotion-add-campaign.tsx` | ソース | エントリーポイント |
| add-campaign-promotion-form.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/components/add-campaign-promotion-form/add-campaign-promotion-form.tsx` | ソース | フォームコンポーネント |
| campaign-details.tsx | `packages/admin/dashboard/src/routes/promotions/promotion-add-campaign/components/add-campaign-promotion-form/campaign-details.tsx` | ソース | キャンペーン詳細表示 |
| promotions.tsx | `packages/admin/dashboard/src/hooks/api/promotions.tsx` | ソース | プロモーションAPIフック |
| campaigns.tsx | `packages/admin/dashboard/src/hooks/api/campaigns.tsx` | ソース | キャンペーンAPIフック |
| route.ts | `packages/medusa/src/api/admin/promotions/[id]/route.ts` | ソース | プロモーションAPIエンドポイント |
