# 機能設計書 54-価格計算

## 概要

本ドキュメントは、Medusa ECプラットフォームにおける価格計算機能の設計を記述する。通貨・地域に応じた価格計算機能の詳細仕様を定義する。

### 本機能の処理概要

価格計算機能は、PriceSetに対して通貨、地域、顧客グループなどのコンテキストを適用し、最適な価格を計算する機能である。通常価格（defaultPrice）と価格リスト価格（priceListPrice）を評価し、タイプに応じて計算価格（calculatedPrice）と元価格（originalPrice）を決定する。また、税込み/税抜きの価格設定（PricePreference）も考慮する。

**業務上の目的・背景**：ECサイトでは、顧客の属性や購入条件によって表示価格が変わる。地域による通貨の違い、会員ランクによる割引、セール期間中の特別価格など、複雑な価格ルールを一元的に計算する仕組みが必要である。

**機能の利用シーン**：
1. 商品一覧表示時の価格取得
2. 商品詳細ページでの価格表示
3. カート内商品の価格計算
4. 注文確定時の最終価格計算
5. 管理画面での価格プレビュー

**主要な処理内容**：
1. コンテキスト（通貨、地域、顧客グループ等）の取得
2. 該当するPriceSetの価格候補を取得
3. 価格リストの評価（SALE/OVERRIDE）
4. 最適価格の選択と計算
5. 税込み/税抜き設定の適用
6. CalculatedPriceSetの返却

**関連システム・外部連携**：価格計算結果はカートモジュール、注文モジュール、商品表示で利用される。税モジュールとも連携し、税込み価格の計算に使用される。

**権限による制御**：価格計算は認証不要で実行可能（Store API）。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | 商品価格編集画面 | 補助機能 | 通貨・地域に応じた価格表示 |
| 141 | 配送オプション料金設定画面 | 補助機能 | 通貨別料金の計算 |

## 機能種別

計算処理 / データ取得

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| pricingFilters.id | string[] | Yes | PriceSetのID配列 | - |
| context.currency_code | string | No | 通貨コード | ISO 4217形式 |
| context.region_id | string | No | 地域ID | - |
| context.customer_group_id | string | No | 顧客グループID | - |
| context.quantity | number | No | 数量（数量割引用） | 0以上の数値 |

### 入力データソース

- リクエストパラメータ（API経由）
- カート/注文からのコンテキスト情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | PriceSetのID |
| is_calculated_price_price_list | boolean | 計算価格が価格リストからか |
| is_calculated_price_tax_inclusive | boolean | 計算価格が税込みか |
| calculated_amount | number | 計算された価格 |
| raw_calculated_amount | BigNumber | 計算された価格（Raw） |
| is_original_price_price_list | boolean | 元価格が価格リストからか |
| is_original_price_tax_inclusive | boolean | 元価格が税込みか |
| original_amount | number | 元の価格 |
| raw_original_amount | BigNumber | 元の価格（Raw） |
| currency_code | string | 通貨コード |
| calculated_price | object | 計算価格の詳細 |
| original_price | object | 元価格の詳細 |

### 出力先

- API レスポンス（JSON形式）
- 呼び出し元のサービス/ワークフロー

## 処理フロー

### 処理シーケンス

```
1. コンテキストの正規化
   └─ setupCalculatedPriceConfig_()でコンテキストを準備
2. 価格候補の取得
   └─ pricingRepository_.calculatePrices()でDB問い合わせ
3. 価格リストの評価
   └─ OVERRIDE/SALEタイプに応じた価格選択
4. 税込み設定の適用
   └─ PricePreferenceを取得して適用
5. 結果の構築
   └─ CalculatedPriceSetを構築して返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[コンテキストの正規化]
    B --> C[価格候補の取得]
    C --> D{価格リスト価格が存在?}
    D -->|No| E[通常価格を使用]
    D -->|Yes| F{タイプがOVERRIDE?}
    F -->|Yes| G[価格リスト価格で上書き]
    F -->|No| H{価格リスト価格が低い?}
    H -->|Yes| I[価格リスト価格を使用]
    H -->|No| J[通常価格を使用]
    E --> K[税込み設定を適用]
    G --> K
    I --> K
    J --> K
    K --> L[CalculatedPriceSetを返却]
    L --> M[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-54-01 | OVERRIDE優先 | OVERRIDEタイプの価格リストは通常価格を完全に上書き | 価格リストタイプがOVERRIDEの場合 |
| BR-54-02 | SALE最低価格 | SALEタイプは通常価格とセール価格の低い方を適用 | 価格リストタイプがSALEの場合 |
| BR-54-03 | 複数価格リスト | 複数の価格リストがある場合は最低額を選択 | 複数の価格リスト価格が該当する場合 |
| BR-54-04 | 地域優先 | 地域設定がある場合は地域のPricePreferenceを優先 | region_idが指定された場合 |
| BR-54-05 | 通貨フォールバック | 地域設定がない場合は通貨のPricePreferenceを使用 | region_idが未指定の場合 |

### 計算ロジック

```typescript
// 価格選択ロジック
if (priceListPrice) {
  switch (priceListPrice.price_list_type) {
    case PriceListType.OVERRIDE:
      calculatedPrice = priceListPrice
      originalPrice = priceListPrice
      break
    case PriceListType.SALE:
      if (defaultPrice?.amount && priceListPrice.amount) {
        calculatedPrice = MathBN.lte(priceListPrice.amount, defaultPrice.amount)
          ? priceListPrice
          : defaultPrice
      }
      originalPrice = defaultPrice
      break
  }
} else {
  calculatedPrice = defaultPrice
  originalPrice = defaultPrice
}

// 税込み判定
isTaxInclusive = regionPreference?.is_tax_inclusive || currencyPreference?.is_tax_inclusive || false
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 価格計算 | price | SELECT | 価格候補の取得 |
| 価格計算 | price_list | SELECT | 価格リストの取得 |
| 価格計算 | price_rule | SELECT | 価格ルールの取得 |
| 価格計算 | price_preference | SELECT | 税込み設定の取得 |

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

#### price

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, amount, currency_code, price_set_id, price_list_id, min_quantity, max_quantity | WHERE price_set_id IN (:ids) AND (price_list条件) | 複雑なクエリ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 空配列 | 該当する価格がない | 空の結果を返却（エラーではない） |

### リトライ仕様

読み取り専用操作のため、特別なリトライ仕様は不要。

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

読み取り専用操作のため、トランザクション管理は不要。@InjectManagerデコレータで読み取りコンテキストを使用。

## パフォーマンス要件

- 単一PriceSet計算: 50ms以内
- 複数PriceSet計算（10件）: 200ms以内
- インデックス: price_set_id, price_list_id, currency_codeを使用

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

- 認証: Store APIでは認証不要
- 認可: 公開情報のため制限なし
- キャッシュ: 価格計算結果のキャッシュは呼び出し元で実装

## 備考

- 価格計算はステートレスな操作
- 大量の計算が必要な場合はバッチ処理またはキャッシュを検討

---

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

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

### 推奨読解順序

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

まず、価格計算に関連する型定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types/pricing.ts | `packages/core/types/src/pricing/` | PricingContext, CalculatedPriceSet型を確認 |

**読解のコツ**: CalculatedPriceSetの構造を理解することで、出力データの形式が明確になる。

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

価格計算のメインメソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | pricing-module.ts | `packages/modules/pricing/src/services/pricing-module.ts` | calculatePricesメソッドの実装 |

**主要処理フロー**:
1. **388-398行目**: calculatePrices - メインエントリーポイント
2. **400-453行目**: 価格リスト評価ロジック（SALE/OVERRIDE）
3. **456-460行目**: 価格ルールの取得
4. **466-486行目**: PricePreferenceの取得
5. **488-554行目**: CalculatedPriceSetの構築

#### Step 3: 税込み判定を理解する

税込み/税抜きの判定ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | pricing-module.ts | `packages/modules/pricing/src/services/pricing-module.ts` | isTaxInclusive関数の実装 |

**主要処理フロー**:
- **1781-1808行目**: isTaxInclusive - 税込み判定ロジック

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

```
PricingModuleService.calculatePrices()
    │
    ├─ pricingRepository_.calculatePrices()
    │      └─ DB問い合わせで価格候補を取得
    │
    ├─ groupBy(results, "price_set_id")
    │      └─ PriceSetごとに価格をグループ化
    │
    ├─ 価格選択ロジック
    │      ├─ OVERRIDEの場合は上書き
    │      └─ SALEの場合は最低価格を選択
    │
    ├─ listPriceRules()
    │      └─ 価格ルールを取得
    │
    ├─ listPricePreferences_()
    │      └─ 税込み設定を取得
    │
    └─ isTaxInclusive()
           └─ 税込み判定
```

### データフロー図

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

PricingFilters ───▶ pricingRepository_        ───▶ 価格候補
                    .calculatePrices()
        │
        ▼
PricingContext ───▶ 価格選択ロジック          ───▶ calculatedPrice
                    (OVERRIDE/SALE判定)              originalPrice
        │
        ▼
region_id     ───▶ listPricePreferences_()   ───▶ is_tax_inclusive
currency_code
        │
        ▼
全体          ───▶ CalculatedPriceSet構築    ───▶ CalculatedPriceSet[]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| pricing-module.ts | `packages/modules/pricing/src/services/pricing-module.ts` | ソース | 価格モジュールのメインサービス |
| pricing-repository.ts | `packages/modules/pricing/src/repositories/pricing.ts` | ソース | 価格計算のDBアクセス |
| price-preference.ts | `packages/modules/pricing/src/models/price-preference.ts` | ソース | 税込み設定のモデル |
