# 機能設計書 57-税計算

## 概要

本ドキュメントは、Medusa ECプラットフォームにおける税計算機能の設計を記述する。商品・配送に対する税金計算機能の詳細仕様を定義する。

### 本機能の処理概要

税計算機能は、商品や配送料に対して適用される税率を決定し、税額を計算する機能である。住所情報（国コード、州/都道府県コード）に基づいて該当する税地域を特定し、商品や配送オプションに適用される税率を決定する。また、結合可能な税率（州税+国税など）の処理や、税計算プロバイダーへの委譲も行う。

**業務上の目的・背景**：ECサイトでは、商品や配送料に対して正確な税額を計算し、顧客に表示する必要がある。国や地域によって税率が異なり、また商品カテゴリによって軽減税率が適用される場合もあるため、複雑な税計算ロジックが必要である。

**機能の利用シーン**：
1. カート内商品の税額計算
2. 配送料の税額計算
3. 注文確定時の最終税額計算
4. 見積もり時の税額表示

**主要な処理内容**：
1. 住所から該当税地域の特定
2. 商品・配送オプションに適用される税率の決定
3. 税率の優先度評価（商品固有 > 商品タイプ > デフォルト）
4. 結合可能税率の処理
5. 税計算プロバイダーへの委譲

**関連システム・外部連携**：税計算結果はカートモジュール、注文モジュールで使用される。外部の税計算プロバイダー（TaxJar等）とも連携可能。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | カート画面 | API連携 | 商品・配送料の税額計算 |
| - | 注文確認画面 | API連携 | 最終税額の計算・表示 |

## 機能種別

計算処理 / データ取得

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| items | TaxableItemDTO[] | Yes | 課税対象アイテムの配列 | - |
| items[].product_id | string | Yes | 商品ID | 存在チェック |
| items[].product_type_id | string | No | 商品タイプID | - |
| items[].quantity | number | Yes | 数量 | 1以上 |
| items[].unit_price | number | Yes | 単価 | 0以上 |
| shippingItems | TaxableShippingDTO[] | No | 配送オプションの配列 | - |
| shippingItems[].shipping_option_id | string | Yes | 配送オプションID | - |
| context.address.country_code | string | Yes | 国コード | ISO 3166-1 alpha-2 |
| context.address.province_code | string | No | 州/都道府県コード | - |

### 入力データソース

- リクエストパラメータ（API経由）
- カート/注文からの商品情報・住所情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ItemTaxLineDTO[] | array | 商品の税額明細 |
| ShippingTaxLineDTO[] | array | 配送の税額明細 |
| [].rate_id | string | 適用された税率ID |
| [].rate | number | 税率 |
| [].name | string | 税率名 |
| [].code | string | 税率コード |
| [].line_item_id | string | 対象アイテムID |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. コンテキストの正規化
   └─ normalizeTaxCalculationContext()で住所コードを正規化
2. 該当税地域の取得
   └─ 国・州/都道府県に一致するTaxRegionを取得
3. 税率候補の取得
   └─ 税地域に紐づく全ての税率とルールを取得
4. アイテムごとの税率決定
   └─ getTaxRatesForItem()で最適な税率を決定
5. 税計算プロバイダーへの委譲
   └─ getTaxLinesFromProvider()で税額を計算
6. 結果の返却
   └─ 税額明細を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[コンテキストの正規化]
    B --> C[該当税地域の取得]
    C --> D{親地域が存在?}
    D -->|No| E[空の結果を返却]
    D -->|Yes| F[税率候補の取得]
    F --> G[アイテムごとに税率決定]
    G --> H[税率の優先度評価]
    H --> I{結合可能?}
    I -->|Yes| J[親地域の税率も追加]
    I -->|No| K[税計算プロバイダーに委譲]
    J --> K
    K --> L[税額明細を返却]
    E --> M[終了]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-57-01 | 税地域必須 | 該当する税地域がない場合は空の結果を返却 | 税計算時 |
| BR-57-02 | 優先度評価 | 商品固有 > 商品タイプ > デフォルトの順で税率を評価 | 税率決定時 |
| BR-57-03 | 州/都道府県優先 | 州/都道府県の税率は国の税率より優先 | 同一優先度の場合 |
| BR-57-04 | 結合可能税率 | is_combinable=trueの場合は親地域の税率と結合 | 州/都道府県税の場合 |
| BR-57-05 | プロバイダー委譲 | 税地域にプロバイダーが設定されている場合は委譲 | 外部プロバイダー使用時 |

### 計算ロジック

```typescript
// 優先度スコア計算
if ((isShippingMatch || isProductMatch) && isProvince) {
  priorityScore = 1
} else if (isProductTypeMatch && isProvince) {
  priorityScore = 2
} else if (isDefault && isProvince) {
  priorityScore = 3
} else if ((isShippingMatch || isProductMatch) && !isProvince) {
  priorityScore = 4
} else if (isProductTypeMatch && !isProvince) {
  priorityScore = 5
} else if (isDefault && !isProvince) {
  priorityScore = 6
}

// 結合可能税率の処理
if (rate.is_combinable && rate.tax_region.parent_id) {
  const parentRate = rates.find(r => r.tax_region.id === rate.tax_region.parent_id)
  if (parentRate) {
    applicableRates = [rate, parentRate]
  }
}
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 税計算 | tax_region | SELECT | 該当税地域の取得 |
| 税計算 | tax_rate | SELECT | 税率候補の取得 |
| 税計算 | tax_rate_rule | SELECT | 税率ルールの取得 |

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

#### tax_region

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, country_code, province_code, provider_id, parent_id | WHERE country_code = :countryCode AND (province_code = :provinceCode OR province_code IS NULL) | - |

#### tax_rate

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, rate, code, name, is_default, is_combinable, tax_region_id | WHERE tax_region_id IN (:regionIds) AND (is_default = true OR rules match) | - |

## エラー処理

### エラーケース一覧

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

### リトライ仕様

読み取り専用操作のため、特別なリトライ仕様は不要。プロバイダーエラー時はアプリケーション層でリトライ可能。

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

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

## パフォーマンス要件

- 単一アイテム税計算: 50ms以内
- 複数アイテム税計算（10件）: 200ms以内
- インデックス: IDX_tax_rate_tax_region_id, IDX_tax_region_unique_country_provinceを使用

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

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

## 備考

- 税地域が見つからない場合は空の結果を返却
- プロバイダー使用時は外部APIへのネットワーク遅延を考慮

---

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

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

### 推奨読解順序

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

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

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

**読解のコツ**: TaxableItemDTOとTaxableShippingDTOの違いに注目。shipping_option_idの有無で判別される。

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

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

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

**主要処理フロー**:
1. **407-509行目**: getTaxLines - メインエントリーポイント
2. **413-430行目**: 税地域の取得
3. **439-487行目**: 税率候補のバッチ取得
4. **489-501行目**: アイテムごとの税率マッチング
5. **502-508行目**: プロバイダーへの委譲

#### Step 3: 税率決定ロジックを理解する

税率の優先度評価と選択ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | tax-module-service.ts | `packages/modules/tax/src/services/tax-module-service.ts` | getTaxRatesForItem, prioritizeRatesの実装 |

**主要処理フロー**:
- **637-667行目**: getTaxRatesForItem - 適用税率の決定
- **802-840行目**: prioritizeRates - 優先度スコアの計算

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

```
TaxModuleService.getTaxLines()
    │
    ├─ normalizeTaxCalculationContext()
    │      └─ 住所コードの正規化
    │
    ├─ taxRegionService_.list()
    │      └─ 該当税地域の取得
    │
    ├─ taxRateService_.list()
    │      └─ 税率候補の取得
    │
    ├─ items.map()
    │      ├─ getTaxRateQueryForItem()
    │      │      └─ アイテム用のクエリ条件構築
    │      │
    │      └─ getTaxRatesForItem()
    │             ├─ prioritizeRates()
    │             │      └─ 優先度評価
    │             │
    │             └─ 結合可能税率の処理
    │
    └─ getTaxLinesFromProvider()
           └─ プロバイダーで税額計算
```

### データフロー図

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

TaxableItemDTO[] ───▶ normalizeTaxCalculation() ───▶ 正規化されたコンテキスト
TaxCalculationContext  Context()
        │
        ▼
country_code    ───▶ taxRegionService_.list()  ───▶ TaxRegion[]
province_code
        │
        ▼
regionIds       ───▶ taxRateService_.list()    ───▶ TaxRate[]（候補）
        │
        ▼
items + rates   ───▶ getTaxRatesForItem()      ───▶ 適用税率
                     prioritizeRates()
        │
        ▼
items + rates   ───▶ getTaxLinesFromProvider() ───▶ ItemTaxLineDTO[]
                                                    ShippingTaxLineDTO[]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tax-module-service.ts | `packages/modules/tax/src/services/tax-module-service.ts` | ソース | 税モジュールのメインサービス |
| tax-provider.ts | `packages/modules/tax/src/services/tax-provider.ts` | ソース | 税計算プロバイダーサービス |
| tax-rate.ts | `packages/modules/tax/src/models/tax-rate.ts` | ソース | TaxRateのデータモデル |
| tax-region.ts | `packages/modules/tax/src/models/tax-region.ts` | ソース | TaxRegionのデータモデル |
