# 帳票設計書 3-商品インポートCSVテンプレート

## 概要

本ドキュメントは、Medusaプラットフォームにおける商品インポート機能で使用するCSVテンプレートの帳票設計書である。商品データを一括でインポートするための標準フォーマットを定義し、新規商品の作成および既存商品の更新に使用される。

### 本帳票の処理概要

商品インポートCSVテンプレートは、管理者が商品データを一括でMedusaにインポートするためのCSVファイル形式を定義する。このテンプレートに従ってCSVファイルを作成し、インポート機能を使用することで、商品の新規作成と既存商品の更新を効率的に行うことができる。インポート処理は2段階で実行され、まずCSVの解析と検証が行われ、その後ユーザーの確認を経て実際のインポートが実行される。

**業務上の目的・背景**：ECサイト運営において、大量の商品データを効率的に登録・更新する必要がある。手動での個別登録は時間がかかり、入力ミスのリスクも高い。本テンプレートを使用することで、Excelや外部システムで作成した商品データを一括でMedusaにインポートでき、商品マスタの初期構築、季節商品の一括登録、価格の一括更新、他プラットフォームからのデータ移行などの業務を効率化する。また、商品エクスポート機能との互換性を持つため、エクスポートしたCSVを編集して再インポートすることで商品の一括更新が可能である。

**帳票の利用シーン**：新規ECサイト立ち上げ時の商品マスタ初期投入、季節商品・キャンペーン商品の一括登録時、他ECプラットフォーム（Shopify、WooCommerce等）からのデータ移行時、サプライヤー提供の商品データの一括取込時、価格改定や説明文更新の一括適用時に利用される。

**主要な入力内容**：
1. 商品基本情報（ハンドル、タイトル、説明、ステータス等）
2. 商品属性情報（重量、寸法、HSコード、原産国等）
3. バリアント情報（タイトル、SKU、バーコード、在庫管理設定等）
4. 価格情報（通貨別価格）
5. オプション情報（サイズ、色などのバリアントオプション）
6. 関連情報（画像URL、タグID、カテゴリID、セールスチャネルID）

**帳票の入力タイミング**：管理画面の商品インポート機能からCSVファイルをアップロードする際に使用する。

**帳票の利用者**：ECサイト管理者、商品管理担当者、データ移行担当者

## 帳票種別

テンプレート（インポート用CSVフォーマット定義）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 入力操作 |
|--------|--------|-----------------|---------|
| 5 | 商品一覧画面 | /admin/products | インポートボタン |
| 19 | 商品インポート画面 | /admin/products/import | CSVファイルアップロード |

## 入力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | CSV |
| ファイル名 | 任意（拡張子: .csv） |
| 文字コード | UTF-8 |
| 区切り文字 | カンマ（,）またはセミコロン（;） |
| 囲み文字 | ダブルクォート（"） |
| ヘッダー行 | 必須 |

### CSV固有設定

| 項目 | 内容 |
|-----|------|
| 改行コード | LF または CRLF |
| 必須カラム | Product Handle または Product Id（少なくとも一方） |
| 大文字小文字 | カラム名は大文字小文字を区別しない |

## 帳票レイアウト

### レイアウト概要

CSVファイルは1行目にヘッダー、2行目以降にデータ行を記載する。各バリアントが1行として入力され、同じ商品の複数バリアントは商品情報を重複して記載する。

```
┌─────────────────────────────────────────────────────────────┐
│ ヘッダー行（カラム名）                                        │
├─────────────────────────────────────────────────────────────┤
│ データ行1（商品A - バリアント1）                               │
│ データ行2（商品A - バリアント2）                               │
│ データ行3（商品B - バリアント1）                               │
│ ...                                                         │
│ データ行N                                                    │
└─────────────────────────────────────────────────────────────┘
```

### ヘッダー部（CSVカラム定義）

#### 商品識別情報（必須）

| No | 項目名 | 説明 | 必須 | 入力形式 | 備考 |
|----|-------|------|-----|---------|------|
| 1 | Product Id | 商品の一意識別子（更新時に必要） | 条件付き | 文字列（prod_xxxxx形式） | 更新時は必須、新規作成時は不要 |
| 2 | Product Handle | URLスラッグ | 条件付き | 文字列（半角英数字、ハイフン） | Product Idがない場合は必須 |

#### 商品基本情報

| No | 項目名 | 説明 | 必須 | 入力形式 | 備考 |
|----|-------|------|-----|---------|------|
| 3 | Product Title | 商品タイトル | No | 文字列 | - |
| 4 | Product Subtitle | 商品サブタイトル | No | 文字列 | - |
| 5 | Product Description | 商品説明 | No | 文字列（改行可） | 改行は\nで記載 |
| 6 | Product Status | ステータス | No | draft/proposed/published/rejected | - |
| 7 | Product Thumbnail | サムネイルURL | No | URL文字列 | - |
| 8 | Product External Id | 外部システムID | No | 文字列 | - |
| 9 | Product Collection Id | コレクションID | No | 文字列（pcol_xxxxx形式） | - |
| 10 | Product Type Id | 商品タイプID | No | 文字列（ptyp_xxxxx形式） | - |
| 11 | Product Discountable | 割引適用可否 | No | true/false | - |
| 12 | Shipping Profile Id | 配送プロファイルID | No | 文字列（sp_xxxxx形式） | - |

#### 商品物理属性

| No | 項目名 | 説明 | 必須 | 入力形式 | 備考 |
|----|-------|------|-----|---------|------|
| 13 | Product Weight | 重量 | No | 数値 | - |
| 14 | Product Height | 高さ | No | 数値 | - |
| 15 | Product Width | 幅 | No | 数値 | - |
| 16 | Product Length | 長さ | No | 数値 | - |
| 17 | Product Hs Code | HSコード（関税分類） | No | 文字列 | - |
| 18 | Product Origin Country | 原産国 | No | 国コード（2文字） | - |
| 19 | Product Mid Code | MIDコード | No | 文字列 | - |
| 20 | Product Material | 素材 | No | 文字列 | - |
| 21 | Product Metadata | メタデータ | No | JSON文字列 | - |

#### 商品関連情報（動的カラム）

| No | 項目名パターン | 説明 | 必須 | 入力形式 | 備考 |
|----|--------------|------|-----|---------|------|
| 22+ | Product Image 1, 2, 3... | 商品画像URL | No | URL文字列 | 番号は1から連続 |
| 22+ | Product Tag 1, 2, 3... | タグID | No | 文字列 | 既存タグのIDを指定 |
| 22+ | Product Sales Channel 1, 2... | セールスチャネルID | No | 文字列 | 既存チャネルのIDを指定 |
| 22+ | Product Category 1, 2... | カテゴリID | No | 文字列（pcat_xxxxx形式） | 既存カテゴリのIDを指定 |

#### バリアント識別情報

| No | 項目名 | 説明 | 必須 | 入力形式 | 備考 |
|----|-------|------|-----|---------|------|
| V-1 | Variant Id | バリアントの一意識別子（更新時） | No | 文字列（variant_xxxxx形式） | 更新時に使用 |

#### バリアント基本情報

| No | 項目名 | 説明 | 必須 | 入力形式 | 備考 |
|----|-------|------|-----|---------|------|
| V-2 | Variant Title | バリアントタイトル | No | 文字列 | - |
| V-3 | Variant Sku | SKU | No | 文字列 | - |
| V-4 | Variant Barcode | バーコード | No | 文字列 | - |
| V-5 | Variant Ean | EANコード | No | 文字列 | - |
| V-6 | Variant Upc | UPCコード | No | 文字列 | - |
| V-7 | Variant Hs Code | HSコード | No | 文字列 | - |
| V-8 | Variant Mid Code | MIDコード | No | 文字列 | - |
| V-9 | Variant Manage Inventory | 在庫管理有無 | No | true/false | - |
| V-10 | Variant Allow Backorder | バックオーダー許可 | No | true/false | - |
| V-11 | Variant Variant Rank | 表示順序 | No | 数値 | - |

#### バリアント物理属性

| No | 項目名 | 説明 | 必須 | 入力形式 | 備考 |
|----|-------|------|-----|---------|------|
| V-12 | Variant Weight | 重量 | No | 数値 | - |
| V-13 | Variant Height | 高さ | No | 数値 | - |
| V-14 | Variant Width | 幅 | No | 数値 | - |
| V-15 | Variant Length | 長さ | No | 数値 | - |
| V-16 | Variant Origin Country | 原産国 | No | 国コード（2文字） | - |
| V-17 | Variant Material | 素材 | No | 文字列 | - |
| V-18 | Variant Metadata | メタデータ | No | JSON文字列 | - |

#### バリアント価格（動的カラム）

| No | 項目名パターン | 説明 | 必須 | 入力形式 | 備考 |
|----|--------------|------|-----|---------|------|
| P-1 | Variant Price {CURRENCY} | 通貨別価格（例：Variant Price USD） | No | 数値 | 通貨コードは大文字 |

#### バリアントオプション（動的カラム）

| No | 項目名パターン | 説明 | 必須 | 入力形式 | 備考 |
|----|--------------|------|-----|---------|------|
| O-1 | Variant Option 1 Name | オプション1の名前（例：size） | 条件付き | 文字列 | 値を設定する場合は必須 |
| O-2 | Variant Option 1 Value | オプション1の値（例：large） | 条件付き | 文字列 | 名前を設定した場合は必須 |
| O-3 | Variant Option 2 Name | オプション2の名前 | No | 文字列 | - |
| O-4 | Variant Option 2 Value | オプション2の値 | 条件付き | 文字列 | 名前を設定した場合は必須 |

#### 無視されるカラム（エクスポートに含まれるが、インポート時は無視）

| No | 項目名 | 説明 | 備考 |
|----|-------|------|------|
| I-1 | Product Is Giftcard | ギフトカード商品フラグ | 無視される |
| I-2 | Product Created At | 作成日時 | 無視される |
| I-3 | Product Updated At | 更新日時 | 無視される |
| I-4 | Product Deleted At | 削除日時 | 無視される |
| I-5 | Variant Product Id | 所属商品ID | 無視される |
| I-6 | Variant Created At | 作成日時 | 無視される |
| I-7 | Variant Updated At | 更新日時 | 無視される |
| I-8 | Variant Deleted At | 削除日時 | 無視される |

### 明細部

該当なし（各バリアントが1行として入力される）

### フッター部

該当なし（CSVファイルのためフッターなし）

## 入力条件

### 必須条件

| 条件名 | 説明 |
|-------|------|
| 識別子必須 | Product IdまたはProduct Handleのいずれかが必須 |
| オプション値必須 | Variant Option N Nameを指定した場合、Variant Option N Valueも必須 |

### 新規作成 vs 更新の判定

| 条件 | 動作 |
|------|------|
| Product Idが存在 | 既存商品の更新として処理 |
| Product Idが存在しない（Handleのみ） | 新規商品の作成として処理 |

### バリデーション

| 項目 | バリデーション | エラーメッセージ例 |
|------|--------------|------------------|
| 未知のカラム | 存在しないカラム名はエラー | `Invalid column name(s) "Unknown Column"` |
| 識別子欠如 | Product IdとHandleの両方が欠如 | `Row X: Missing product id and handle. One of these columns are required to process the row` |
| 価格形式 | 数値以外の値 | `Row X: Invalid value provided for "Variant Price USD". Expected value to be a number, received "abc"` |
| オプション値欠如 | 名前があるが値がない | `Row X: Missing option value for "Variant Option 1 Name"` |
| JSON形式 | 不正なJSON文字列 | `Row X: Invalid value provided for "Product Metadata". Expected a valid JSON string, received "..."` |

## 処理フロー

### インポートフロー

```mermaid
flowchart TD
    A[POST /admin/products/import] --> B[CSVファイル受信]
    B --> C[importProductsWorkflow開始]
    C --> D[normalizeCsvStep]
    D --> E{CSVパース成功?}
    E -->|No| F[エラーレスポンス返却]
    E -->|Yes| G[CSVNormalizer.preProcess]
    G --> H{バリデーション成功?}
    H -->|No| F
    H -->|Yes| I[CSVNormalizer.process]
    I --> J[toCreate/toUpdate分類]
    J --> K[サマリー返却 + transaction_id]
    K --> L[ユーザー確認待ち]
    L --> M[POST /admin/products/import/{id}/confirm]
    M --> N[waitConfirmationProductImportStep完了]
    N --> O[batchProductsWorkflow実行]
    O --> P{処理成功?}
    P -->|Yes| Q[sendNotificationsStep - 成功通知]
    P -->|No| R[notifyOnFailureStep - 失敗通知]
    Q --> S[完了]
    R --> S
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 未知のカラム | CSVに認識できないカラム名がある | `Invalid column name(s) "..."` | カラム名を修正 |
| 識別子欠如 | Product IdもHandleもない行がある | `Row X: Missing product id and handle...` | 識別子を追加 |
| 数値エラー | 数値が必要な項目に文字列がある | `Row X: Invalid value provided for "...". Expected value to be a number, received "..."` | 数値に修正 |
| オプション値欠如 | オプション名があるが値がない | `Row X: Missing option value for "..."` | オプション値を追加 |
| JSON形式エラー | メタデータが不正なJSON | `Row X: Invalid value provided for "...". Expected a valid JSON string, received "..."` | JSON形式を修正 |
| インポート失敗 | バックグラウンド処理での予期せぬエラー | `Failed to import products from file {filename}` | 通知フィードに表示、再試行 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千行 |
| 目標処理時間 | バックグラウンド処理のため制限なし |
| 同時インポート数上限 | ワークフローエンジンの設定に依存 |

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

- Admin API認証が必要（AuthenticatedMedusaRequest）
- CSVファイルはmultipart/form-dataで送信
- ファイル内容はメモリ上で処理される（file.buffer）
- 外部からのCSVファイルは信頼できるソースからのみ取り込むこと
- インポート前にプレビュー（toCreate/toUpdateのサマリー）が提供され、確認後に実行

## 備考

- エクスポート機能との互換性があり、エクスポートしたCSVを編集して再インポートが可能
- ただし、以下のカラムはインポート時に無視される：
  - Product Is Giftcard
  - Product Created At / Updated At / Deleted At
  - Variant Product Id
  - Variant Created At / Updated At / Deleted At
- カンマ区切り（,）とセミコロン区切り（;）の両方をサポート
- カラム名は大文字小文字を区別しない（内部で小文字に正規化）
- 2段階インポート：まずCSV解析・バリデーション、確認後に実際のインポート実行
- サマリーで「toCreate」（新規作成）と「toUpdate」（更新）の件数を確認可能

---

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

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

### 推奨読解順序

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

まず、インポート処理で使用されるデータ構造と型定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | csv-normalizer.ts | `packages/core/utils/src/product/csv-normalizer.ts` | CSVNormalizerクラス（行392-565）で入出力の構造を確認 |
| 1-2 | types | `@medusajs/framework/types` | WorkflowTypes.ProductWorkflow.ImportProductsDTOで入力パラメータの型を確認 |

**読解のコツ**: CSVNormalizerクラスの`#products`プロパティ（行441-455）を見ることで、`toCreate`と`toUpdate`の分類方法を理解できる。

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

処理の起点となるAPIルートを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/admin/products/import/route.ts` | POST関数（行12-35）がエントリーポイント |
| 2-2 | route.ts | `packages/medusa/src/api/admin/products/import/[transaction_id]/confirm/route.ts` | 確認エンドポイント |

**主要処理フロー**:
1. **行16-23**: アップロードされたファイルの検証
2. **行25-30**: importProductsWorkflowを実行
3. **行32-34**: トランザクションIDとサマリーをレスポンスとして返却（202 Accepted）

#### Step 3: ワークフローの構成を理解する

ワークフローの全体像と各ステップの役割を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | import-products.ts | `packages/core/core-flows/src/product/workflows/import-products.ts` | ワークフロー定義（行88-143）を確認 |

**主要処理フロー**:
- **行93**: normalizeCsvStepでCSVをパース・正規化
- **行95-100**: サマリー（toCreate/toUpdate件数）を生成
- **行102**: waitConfirmationProductImportStepで確認待ち
- **行122-124**: batchProductsWorkflowで実際のインポート処理
- **行126-140**: 成功時の通知送信

#### Step 4: CSV正規化ステップの詳細を理解する

CSVファイルの解析と正規化ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | normalize-products.ts | `packages/core/core-flows/src/product/steps/normalize-products.ts` | normalizeCsvStep（行19-54）でCSVパースと正規化を確認 |

**主要処理フロー**:
- **行22-23**: convertCsvToJsonでCSVをJSONに変換
- **行24-26**: CSVNormalizer.preProcessで各行を正規化
- **行27**: CSVNormalizer.processで商品オブジェクトに変換
- **行29-47**: toCreate/toUpdateに分類してバリデーション

#### Step 5: CSVNormalizerクラスの詳細を理解する

CSV列の処理ロジックとバリデーションを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | csv-normalizer.ts | `packages/core/utils/src/product/csv-normalizer.ts` | productStaticColumns（行192-236）で静的カラム定義を確認 |
| 5-2 | csv-normalizer.ts | 同上 | productWildcardColumns（行242-259）で動的カラム定義を確認 |
| 5-3 | csv-normalizer.ts | 同上 | variantStaticColumns（行265-306）でバリアントの静的カラム定義を確認 |
| 5-4 | csv-normalizer.ts | 同上 | variantWildcardColumns（行312-341）で価格カラムの処理を確認 |
| 5-5 | csv-normalizer.ts | 同上 | optionColumns（行347-373）でオプションカラムの処理を確認 |
| 5-6 | csv-normalizer.ts | 同上 | CSVNormalizer.preProcess（行400-437）でバリデーションを確認 |

**主要処理フロー**:
- **行400-437**: preProcessで未知カラムの検出と識別子必須チェック
- **行486-553**: processRowで各行を商品・バリアントに分解
- **行558-564**: processで全行を処理してtoCreate/toUpdateを返却

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

```
POST /admin/products/import (route.ts:12)
    │
    └─ importProductsWorkflow (import-products.ts:88)
           │
           ├─ normalizeCsvStep (normalize-products.ts:19)
           │      ├─ convertCsvToJson() - CSV→JSON変換
           │      └─ CSVNormalizer (csv-normalizer.ts:392)
           │             ├─ preProcess() - 正規化・バリデーション
           │             │      ├─ 未知カラムチェック
           │             │      └─ 識別子必須チェック
           │             └─ process() - 商品オブジェクト生成
           │                    ├─ productStaticColumns処理
           │                    ├─ productWildcardColumns処理
           │                    ├─ variantStaticColumns処理
           │                    ├─ variantWildcardColumns処理
           │                    └─ optionColumns処理
           │
           ├─ transform() - サマリー生成（toCreate/toUpdate件数）
           │
           ├─ waitConfirmationProductImportStep - 確認待ち
           │
           ├─ notifyOnFailureStep - 失敗時通知設定
           │
           ├─ batchProductsWorkflow [async, background] - 実際のインポート
           │
           └─ sendNotificationsStep - 成功通知送信

POST /admin/products/import/{id}/confirm (confirm/route.ts)
    │
    └─ workflowEngineService.setStepSuccess() - 確認ステップを完了
```

### データフロー図

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

CSVファイル           importProductsWorkflow                 サマリー + 通知
(multipart/form-data)  ┌──────────────────────┐
       │               │                      │
       ▼               │  normalizeCsvStep    │
┌────────────┐         │  ┌────────────────┐  │              ┌─────────────┐
│ CSVバイト   │──────────│▶│ CSV→JSON変換   │  │              │ サマリー     │
│ データ      │         │  └───────┬────────┘  │              │ (toCreate,  │
└────────────┘         │          │           │              │  toUpdate)  │
                       │          ▼           │              └──────┬──────┘
                       │  ┌────────────────┐  │                     │
                       │  │ CSVNormalizer  │  │                     │
                       │  │ preProcess     │──│─────────────────────┘
                       │  └───────┬────────┘  │
                       │          │           │
                       │          ▼           │
                       │  ┌────────────────┐  │
                       │  │ CSVNormalizer  │  │
                       │  │ process        │  │
                       │  └───────┬────────┘  │
                       │          │           │              ┌─────────────┐
                       │          │           │              │ transaction │
                       │          └───────────│─────────────▶│ _id         │
                       │                      │              └─────────────┘
                       │                      │
                       │  waitConfirmation    │◀─── 確認待ち
                       │          │           │
                       │          ▼           │
                       │  batchProductsWF     │              ┌─────────────┐
                       │  [background]        │              │ 商品データ   │
                       │          │           │──────────────│ (create/    │
                       │          │           │              │  update)    │
                       │          ▼           │              └─────────────┘
                       │  sendNotifications   │              ┌─────────────┐
                       │                      │──────────────│ 通知         │
                       └──────────────────────┘              │ (admin-ui)  │
                                                             └─────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| route.ts | `packages/medusa/src/api/admin/products/import/route.ts` | ソース | APIエンドポイント定義 |
| route.ts | `packages/medusa/src/api/admin/products/import/[transaction_id]/confirm/route.ts` | ソース | 確認APIエンドポイント |
| import-products.ts | `packages/core/core-flows/src/product/workflows/import-products.ts` | ソース | ワークフロー定義 |
| normalize-products.ts | `packages/core/core-flows/src/product/steps/normalize-products.ts` | ソース | CSV正規化ステップ |
| csv-normalizer.ts | `packages/core/utils/src/product/csv-normalizer.ts` | ソース | CSVパース・バリデーションロジック |
| csv-normalizer.spec.ts | `packages/core/utils/src/product/__tests__/csv-normalizer.spec.ts` | テスト | CSVNormalizerのユニットテスト |
| product-import.spec.ts | `integration-tests/http/__tests__/product/admin/product-import.spec.ts` | テスト | 統合テスト |
| products-comma.csv | `integration-tests/http/__tests__/product/admin/__fixtures__/products-comma.csv` | テストデータ | カンマ区切りサンプル |
| products-semicolon.csv | `integration-tests/http/__tests__/product/admin/__fixtures__/products-semicolon.csv` | テストデータ | セミコロン区切りサンプル |
