# 機能設計書 10-パーツ価格表示

## 概要

本ドキュメントは、InvenTreeモバイルアプリにおけるパーツ価格表示機能の設計を記載する。パーツに関連する各種価格情報（購入価格、内部コスト、BOMコスト、販売価格など）を一覧表示する機能を提供する。

### 本機能の処理概要

パーツの価格情報を詳細に表示する機能である。通貨、価格レンジ、オーバーライド価格、内部コスト、BOMコスト、購入価格、サプライヤー価格、販売価格、販売履歴などを表示する。

**業務上の目的・背景**：在庫管理において、パーツの原価把握は重要な経営判断材料である。購入価格、製造コスト（BOM積み上げ）、販売価格を把握することで、利益率の分析や価格設定の意思決定に活用できる。

**機能の利用シーン**：パーツの原価確認、価格設定の参考、利益率分析、購入先選定などで利用される。

**主要な処理内容**：
1. パーツの価格情報取得（API経由）
2. 通貨の表示
3. 総合価格レンジの表示
4. オーバーライド価格の表示
5. 内部コストの表示
6. バリアントコストの表示（テンプレートパーツの場合）
7. BOMコストの表示（アセンブリパーツの場合）
8. 購入価格・サプライヤー価格の表示（購入可能パーツの場合）
9. 販売価格・販売履歴の表示（販売可能パーツの場合）

**関連システム・外部連携**：InvenTree REST API（part/{pk}/pricing/エンドポイント）

**権限による制御**：partロールのview権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 22 | 部品詳細画面 | 遷移元画面 | 価格タイルタップ時に価格画面へ遷移 |

## 機能種別

データ参照 / 詳細表示

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| part | InvenTreePart | Yes | 対象パーツ | null不可 |
| partPricing | InvenTreePartPricing? | No | 価格情報オブジェクト | nullの場合は「価格なし」表示 |

### 入力データソース

- パーツ詳細画面から渡されるパーツオブジェクトと価格情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| currency | String | 通貨コード |
| overallMin | double? | 総合最低価格 |
| overallMax | double? | 総合最高価格 |
| overrideMin | double? | オーバーライド最低価格 |
| overrideMax | double? | オーバーライド最高価格 |
| internalCostMin | double? | 内部コスト最低 |
| internalCostMax | double? | 内部コスト最高 |
| variantCostMin | double? | バリアントコスト最低 |
| variantCostMax | double? | バリアントコスト最高 |
| bomCostMin | double? | BOMコスト最低 |
| bomCostMax | double? | BOMコスト最高 |
| purchaseCostMin | double? | 購入コスト最低 |
| purchaseCostMax | double? | 購入コスト最高 |
| supplierPriceMin | double? | サプライヤー価格最低 |
| supplierPriceMax | double? | サプライヤー価格最高 |
| salePriceMin | double? | 販売価格最低 |
| salePriceMax | double? | 販売価格最高 |
| saleHistoryMin | double? | 販売履歴最低 |
| saleHistoryMax | double? | 販売履歴最高 |

### 出力先

- UI（価格詳細画面）

## 処理フロー

### 処理シーケンス

```
1. 価格画面表示
   └─ パーツ情報ヘッダー表示

2. 価格情報チェック
   └─ null: 「価格情報なし」表示
   └─ 有効: 価格タイル構築

3. 価格タイル構築
   └─ 通貨表示
   └─ 総合価格レンジ表示
   └─ オーバーライド価格表示（存在時）
   └─ 内部コスト表示
   └─ バリアントコスト表示（isTemplate時）
   └─ BOMコスト表示（isAssembly時）
   └─ 購入価格・サプライヤー価格表示（isPurchaseable時）
   └─ 販売価格・販売履歴表示（isSalable時）
```

### フローチャート

```mermaid
flowchart TD
    A[価格画面表示] --> B[パーツ情報ヘッダー]
    B --> C{partPricing != null?}
    C -->|No| D[価格情報なしメッセージ]
    C -->|Yes| E[通貨タイル]
    E --> F[総合価格レンジタイル]
    F --> G{overallMin != null?}
    G -->|Yes| H[オーバーライド最低タイル]
    G -->|No| I{overrideMax != null?}
    H --> I
    I -->|Yes| J[オーバーライド最高タイル]
    I -->|No| K[内部コストタイル]
    J --> K
    K --> L{isTemplate?}
    L -->|Yes| M[バリアントコストタイル]
    L -->|No| N{isAssembly?}
    M --> N
    N -->|Yes| O[BOMコストタイル]
    N -->|No| P{isPurchaseable?}
    O --> P
    P -->|Yes| Q[購入価格タイル]
    Q --> R[サプライヤー価格タイル]
    P -->|No| S{isSalable?}
    R --> S
    S -->|Yes| T[販売価格タイル]
    T --> U[販売履歴タイル]
    S -->|No| V[表示完了]
    U --> V
    D --> V
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 価格なし表示 | partPricingがnullの場合は「価格情報なし」 | 価格情報取得失敗時 |
| BR-02 | 通貨デフォルト | 未設定の場合はUSD | 通貨未指定時 |
| BR-03 | バリアントコスト表示 | テンプレートパーツのみ表示 | isTemplate=true |
| BR-04 | BOMコスト表示 | アセンブリパーツのみ表示 | isAssembly=true |
| BR-05 | 購入関連表示 | 購入可能パーツのみ表示 | isPurchaseable=true |
| BR-06 | 販売関連表示 | 販売可能パーツのみ表示 | isSalable=true |

### 計算ロジック

**価格レンジフォーマット**:
```
if (minPrice == maxPrice):
  return renderCurrency(minPrice, currency)
else:
  return "${renderCurrency(minPrice)} - ${renderCurrency(maxPrice)}"
```

**通貨フォーマット**:
- CurrencyFormatterパッケージを使用
- 無効な値（null、NaN、Infinite）は"-"を返す

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 価格取得 | - | - | サーバーAPIからのみ取得（ローカルDB操作なし） |

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

ローカルデータベース操作なし。サーバーAPIからのデータ取得のみ。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| - | 価格情報なし | partPricing=null | 「価格情報なし」メッセージ表示 |
| 401/403 | 認証/権限エラー | partロールなし | エラーメッセージ表示 |
| 404 | 未検出エラー | 価格情報が存在しない | 「価格情報なし」表示 |
| 500+ | サーバーエラー | サーバー内部エラー | 「価格情報なし」表示 |

### リトライ仕様

- 価格情報取得エラー時はpartPricing=nullとして処理
- 画面リフレッシュによる手動リトライ

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

- 読み取り専用（トランザクションなし）

## パフォーマンス要件

- 価格情報はパーツ詳細読み込み時に非同期取得
- サムネイル画像はキャッシュ利用

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

- partロールのview権限が必要
- 認証トークンによるAPI認証

## 備考

- 価格情報はpart/{pk}/pricing/エンドポイントから取得
- 通貨フォーマットはcurrency_formatterパッケージを使用
- パーツタイプ（テンプレート、アセンブリ、購入可能、販売可能）により表示項目が変化

---

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

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

### 推奨読解順序

#### Step 1: データモデルを理解する

まず、価格情報のデータモデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | part.dart | `lib/inventree/part.dart` | InvenTreePartPricingクラス（498-545行目） |

**読解のコツ**:
- **504-505行目**: `rolesRequired` - partロールが必要
- **512行目**: `currency` - 通貨コード（デフォルト: USD）
- **514-515行目**: `overallMin`/`overallMax` - 総合価格レンジ
- **517-523行目**: `overrideMin`/`overrideMax` - オーバーライド価格
- **525-526行目**: `bomCostMin`/`bomCostMax` - BOMコスト
- **528-529行目**: `purchaseCostMin`/`purchaseCostMax` - 購入コスト
- **531-532行目**: `internalCostMin`/`internalCostMax` - 内部コスト
- **534-535行目**: `supplierPriceMin`/`supplierPriceMax` - サプライヤー価格
- **537-538行目**: `variantCostMin`/`variantCostMax` - バリアントコスト
- **540-541行目**: `salePriceMin`/`salePriceMax` - 販売価格
- **543-544行目**: `saleHistoryMin`/`saleHistoryMax` - 販売履歴

#### Step 2: 価格取得処理を理解する

価格情報のAPI取得処理を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | part.dart | `lib/inventree/part.dart` | getPricingメソッド（239-255行目） |

**主要処理フロー**:
- **241行目**: `/api/part/${pk}/pricing/`エンドポイント呼び出し
- **242-248行目**: レスポンス検証とInvenTreePartPricingオブジェクト生成
- **249-252行目**: 例外処理とSentryレポート
- **254行目**: エラー時はnull返却

#### Step 3: 価格表示ウィジェットを理解する

価格表示画面の全体構造を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | part_pricing.dart | `lib/widget/part/part_pricing.dart` | PartPricingWidgetクラス（9-195行目） |

**主要処理フロー**:
- **10-16行目**: コンストラクタ - part、partPricingパラメータ
- **24-26行目**: `getAppBarTitle` - タイトル（L10().partPricing）
- **29-194行目**: `getTiles` - 価格タイル構築
- **31-38行目**: パーツ情報ヘッダー
- **40-49行目**: partPricing==null時の「価格なし」表示
- **53-55行目**: 通貨タイル
- **57-68行目**: 総合価格レンジタイル
- **70-90行目**: オーバーライド価格タイル
- **92-103行目**: 内部コストタイル
- **105-118行目**: バリアントコストタイル（isTemplate条件）
- **120-133行目**: BOMコストタイル（isAssembly条件）
- **135-161行目**: 購入価格・サプライヤー価格タイル（isPurchaseable条件）
- **163-191行目**: 販売価格・販売履歴タイル（isSalable条件）

#### Step 4: 価格フォーマット処理を理解する

通貨表示のヘルパー関数を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | helpers.dart | `lib/helpers.dart` | renderCurrency関数（131-144行目） |
| 4-2 | helpers.dart | `lib/helpers.dart` | formatPriceRange関数（153-179行目） |

**主要処理フロー**:
- **131-144行目**: `renderCurrency` - 通貨フォーマット
- **132行目**: null、Infinite、NaN チェック
- **138-139行目**: CurrencyFormat取得
- **141行目**: CurrencyFormatter.format呼び出し
- **153-179行目**: `formatPriceRange` - 価格レンジフォーマット
- **159-161行目**: 両方無効の場合"-"
- **165-166行目**: 同値の場合は単一価格
- **168行目**: 異なる場合はレンジ表示

#### Step 5: 呼び出し元を理解する

パーツ詳細画面からの遷移を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | part_detail.dart | `lib/widget/part/part_detail.dart` | 価格取得（263-273行目） |
| 5-2 | part_detail.dart | `lib/widget/part/part_detail.dart` | 価格タイル（565-591行目） |

**主要処理フロー**:
- **65行目**: `InvenTreePartPricing? partPricing`変数
- **263-273行目**: `part.getPricing()`で価格取得（非同期）
- **565行目**: `showPricing && partPricing != null`条件
- **566-570行目**: `formatPriceRange`でサブタイトル生成
- **584-585行目**: `PartPricingWidget`遷移

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

```
PartDetailWidget (part_detail.dart)
    │
    ├─ request() [263-273行目]
    │      └─ part.getPricing()
    │             └─ api.get("/api/part/${pk}/pricing/")
    │                    └─ InvenTreePartPricing.fromJson()
    │
    └─ partTiles() [565-591行目]
           └─ 価格タイル
                  └─ PartPricingWidget(part, partPricing)

PartPricingWidget (part_pricing.dart)
    │
    ├─ getAppBarTitle() [24-26行目]
    │      └─ L10().partPricing
    │
    └─ getTiles() [29-194行目]
           ├─ パーツ情報ヘッダー [31-38行目]
           │
           ├─ partPricing==null → 価格なし表示 [40-49行目]
           │
           ├─ 通貨タイル [53-55行目]
           │
           ├─ 総合価格レンジタイル [57-68行目]
           │      └─ formatPriceRange(overallMin, overallMax)
           │
           ├─ オーバーライド価格タイル [70-90行目]
           │      └─ renderCurrency()
           │
           ├─ 内部コストタイル [92-103行目]
           │
           ├─ [isTemplate] バリアントコストタイル [105-118行目]
           │
           ├─ [isAssembly] BOMコストタイル [120-133行目]
           │
           ├─ [isPurchaseable] 購入価格タイル [135-147行目]
           ├─ [isPurchaseable] サプライヤー価格タイル [149-161行目]
           │
           ├─ [isSalable] 販売価格タイル [163-177行目]
           └─ [isSalable] 販売履歴タイル [179-191行目]
```

### データフロー図

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

InvenTreePart ───▶ getPricing() ───▶ InvenTreePartPricing
    │                  │                      │
    └─ pk              │                      ├─ currency
                       ▼                      ├─ overallMin/Max
              /api/part/{pk}/pricing/         ├─ overrideMin/Max
                       │                      ├─ internalCostMin/Max
                       ▼                      ├─ variantCostMin/Max
              JSON Response                   ├─ bomCostMin/Max
                       │                      ├─ purchaseCostMin/Max
                       ▼                      ├─ supplierPriceMin/Max
              PartPricingWidget               ├─ salePriceMin/Max
                       │                      └─ saleHistoryMin/Max
                       ▼
              formatPriceRange() ───▶ "¥100 - ¥200"
              renderCurrency()   ───▶ "¥150"
                       │
                       ▼
              価格タイル一覧 ───▶ UI表示
                                    ├─ 通貨
                                    ├─ 価格レンジ
                                    ├─ オーバーライド
                                    ├─ 内部コスト
                                    ├─ バリアントコスト*
                                    ├─ BOMコスト*
                                    ├─ 購入価格*
                                    ├─ サプライヤー価格*
                                    ├─ 販売価格*
                                    └─ 販売履歴*
                                    (* パーツタイプにより条件表示)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| part_pricing.dart | `lib/widget/part/part_pricing.dart` | ソース | 価格表示画面ウィジェット |
| part.dart | `lib/inventree/part.dart` | ソース | InvenTreePartPricing、getPricingメソッド |
| part_detail.dart | `lib/widget/part/part_detail.dart` | ソース | 価格タイル（遷移元） |
| helpers.dart | `lib/helpers.dart` | ソース | renderCurrency、formatPriceRange |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | RefreshableState基底クラス |
| api.dart | `lib/api.dart` | ソース | API呼び出し |
