# 機能設計書 3-BOM（部品表）管理

## 概要

本ドキュメントは、InvenTreeシステムにおけるBOM（Bill of Materials：部品表）管理機能の設計を記述する。BOMは製品を構成する部品の階層的な関係を定義し、製造計画、原価計算、在庫管理の基盤となる。

### 本機能の処理概要

**業務上の目的・背景**：
製造業において、製品を構成する部品と数量を正確に管理することは、生産計画の立案、原価計算、部品調達において不可欠である。BOM管理機能は、親部品（組立品）と子部品（コンポーネント）の関係を定義し、各部品の必要数量、オプション/消耗品フラグ、代替部品、ロス率などを管理する。これにより、正確な製造所要量計算（MRP）、原価積み上げ、在庫引当が可能となる。

**機能の利用シーン**：
- 新製品の設計時に、構成部品と数量を定義する場面
- 既存BOMの変更・最適化を行う場面
- 製造オーダー作成時に、必要部品と数量を自動計算する場面
- 代替部品を登録し、部品不足時の柔軟な製造対応を可能にする場面
- BOMのバリデーション（検証）を行い、設計変更の影響を確認する場面

**主要な処理内容**：
1. BOMアイテムの作成・編集・削除
2. 必要数量の計算（ロス率、セットアップ数量、丸め倍数を考慮）
3. 代替部品（BomItemSubstitute）の管理
4. BOMの検証とチェックサム管理
5. バリアント部品への継承制御
6. 循環参照チェック

**関連システム・外部連携**：
- Part（部品）との連携（親部品・子部品の参照）
- Build（製造オーダー）との連携（BuildLineへの展開）
- StockItem（在庫）との連携（引当計算）
- PartPricing（価格）との連携（BOMコスト計算）

**権限による制御**：
- BOMの閲覧は一般ユーザーでも可能
- BOMの作成・編集にはpart.change_bomitem権限が必要
- ロックされた組立品のBOMは編集不可

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | 部品詳細画面 | 主機能 | BOMタブでのBOMアイテム一覧表示・編集 |
| 20 | 製造オーダー詳細画面 | 参照画面 | BOMに基づく製造ライン表示 |

## 機能種別

CRUD操作 / 階層データ管理 / 数量計算

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| part | integer | Yes | 親部品（組立品）ID | assembly=Trueの部品のみ |
| sub_part | integer | Yes | 子部品（コンポーネント）ID | component=Trueの部品のみ |
| quantity | decimal | Yes | 必要数量 | 0以上、trackable部品は整数 |
| reference | string | No | 参照情報（基板のリファレンス等） | 最大5000文字 |
| optional | boolean | No | オプション部品フラグ | デフォルトFalse |
| consumable | boolean | No | 消耗品フラグ | デフォルトFalse |
| setup_quantity | decimal | No | セットアップ数量 | 0以上 |
| attrition | decimal | No | ロス率（%） | 0-100 |
| rounding_multiple | decimal | No | 丸め倍数 | 0以上 |
| note | string | No | メモ | 最大500文字 |
| inherited | boolean | No | バリアント継承フラグ | デフォルトFalse |
| allow_variants | boolean | No | バリアント許可フラグ | デフォルトFalse |

### 入力データソース

- 画面入力（BOMアイテム作成・編集フォーム）
- REST API（api-bom-list）
- CSVインポート

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pk | integer | BOMアイテムの主キー |
| part | integer | 親部品ID |
| part_detail | object | 親部品詳細情報 |
| sub_part | integer | 子部品ID |
| sub_part_detail | object | 子部品詳細情報 |
| quantity | decimal | 必要数量 |
| reference | string | 参照情報 |
| optional | boolean | オプションフラグ |
| consumable | boolean | 消耗品フラグ |
| setup_quantity | decimal | セットアップ数量 |
| attrition | decimal | ロス率 |
| rounding_multiple | decimal | 丸め倍数 |
| validated | boolean | 検証済みフラグ |
| checksum | string | チェックサム |
| inherited | boolean | 継承フラグ |
| allow_variants | boolean | バリアント許可フラグ |
| price_range | string | 価格範囲 |
| substitutes | array | 代替部品リスト |

### 出力先

- 画面表示（BOM一覧、BOM詳細）
- REST API レスポンス
- 製造オーダー展開

## 処理フロー

### 処理シーケンス

```
1. BOMアイテム作成
   └─ 循環参照チェック → バリデーション → 親部品ロックチェック → DB保存 → BOM有効性更新

2. BOMアイテム編集
   └─ 親部品ロックチェック → バリデーション → DB更新 → BOM有効性更新 → 価格再計算

3. BOMアイテム削除
   └─ 親部品ロックチェック → 関連BuildLine更新 → DB削除 → BOM有効性更新

4. 必要数量計算
   └─ 基本数量 × 製造数 × (1 + ロス率) + セットアップ数量 → 丸め処理

5. BOM検証
   └─ 各BOMアイテムのハッシュ計算 → 全体チェックサム生成 → 検証状態更新
```

### フローチャート

```mermaid
flowchart TD
    A[BOM操作開始] --> B{操作種別}
    B -->|作成| C[循環参照チェック]
    C --> D{循環あり?}
    D -->|Yes| E[ValidationError]
    D -->|No| F[親部品ロックチェック]
    F --> G{ロック状態?}
    G -->|Yes| E
    G -->|No| H[バリデーション実行]
    H --> I{trackable整合性?}
    I -->|NG| J[親部品をtrackable化]
    I -->|OK| K[DB保存]
    J --> K
    K --> L[BOM有効性更新]

    B -->|編集| F

    B -->|削除| M[親部品ロックチェック]
    M --> N{ロック状態?}
    N -->|Yes| E
    N -->|No| O[関連BuildLine更新]
    O --> P[DB削除]
    P --> L

    L --> Q[価格再計算スケジュール]
    Q --> R[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 循環参照禁止 | 部品は自身または自身を含むBOMに追加不可 | BOMアイテム作成・編集時 |
| BR-002 | ロック部品BOM編集禁止 | ロックされた部品のBOMは編集不可 | part.locked=True |
| BR-003 | trackable整合性 | trackable部品をBOMに追加すると親もtrackable化 | sub_part.trackable=True |
| BR-004 | trackable数量制約 | trackable部品の数量は整数のみ | sub_part.trackable=True |
| BR-005 | 組立品制約 | BOMは組立品（assembly=True）のみ定義可能 | part.assembly |
| BR-006 | コンポーネント制約 | BOMの子部品はcomponent=Trueの部品のみ | sub_part.component |
| BR-007 | バリアント継承 | inherited=TrueのBOMアイテムはバリアントにも適用 | inherited=True |
| BR-008 | 代替部品制約 | 代替部品は元部品と同じではいけない | substitute.part != bom_item.sub_part |

### 計算ロジック

```python
# 必要数量の計算
def get_required_quantity(self, build_quantity: float) -> float:
    # 基本数量
    required = self.quantity * build_quantity

    # ロス率の適用
    if self.attrition > 0:
        attrition = Decimal(self.attrition) / Decimal(100)
        required *= 1 + attrition

    # セットアップ数量の加算
    if self.setup_quantity > 0:
        required += Decimal(self.setup_quantity)

    # 丸め処理
    if self.rounding_multiple and self.rounding_multiple > 0:
        value = Decimal(required)
        value = math.ceil(value / round_up) * round_up
        required = float(value)

    return required

# 製造可能数量の計算
def can_build_quantity(self, available_stock: float) -> int:
    # セットアップ数量を差し引く
    available_stock = Decimal(max(0, available_stock - self.setup_quantity))
    quantity_decimal = Decimal(self.quantity)
    attrition_decimal = Decimal(self.attrition) / 100
    n = quantity_decimal * (1 + attrition_decimal)

    if n <= 0:
        return 0.0

    return int(available_stock / n)

# チェックサムハッシュの計算
def get_item_hash(self) -> str:
    result_hash = hashlib.md5(b'')
    for field in self.hash_fields():
        value = getattr(self, field, None)
        if value is None:
            value = ''
        # 数値正規化
        if field in ['quantity', 'attrition', 'setup_quantity', 'rounding_multiple']:
            value = normalize(value)
            if not value or value <= 0:
                continue
        result_hash.update(str(value).encode())
    return str(result_hash.digest())
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| BOMアイテム作成 | part_bomitem | INSERT | 新規BOMアイテムレコード挿入 |
| BOMアイテム編集 | part_bomitem | UPDATE | BOMアイテム情報更新 |
| BOMアイテム削除 | part_bomitem | DELETE | BOMアイテムレコード削除 |
| 代替部品追加 | part_bomitemsubstitute | INSERT | 代替部品レコード挿入 |
| BOM検証更新 | part_bomitem | UPDATE | checksum, validatedフィールド更新 |
| 価格更新 | part_partpricing | UPDATE | bom_cost_min, bom_cost_max更新 |

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

#### part_bomitem

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | part_id, sub_part_id, quantity, reference, optional, consumable, setup_quantity, attrition, rounding_multiple, note, inherited, allow_variants, checksum, validated | フォーム入力値、計算値 | 循環参照チェック後 |
| UPDATE | 上記カラム | フォーム入力値 | ロックチェック後 |
| DELETE | - | pk指定 | ロックチェック後 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ValidationError | バリデーションエラー | 循環参照が検出された | BOM構造を見直す |
| ValidationError | バリデーションエラー | ロックされた部品のBOMを編集 | ロック解除が必要 |
| ValidationError | バリデーションエラー | trackable部品に非整数数量 | 整数値を指定 |
| ValidationError | バリデーションエラー | 子部品が未指定 | 子部品を選択 |
| ValidationError | バリデーションエラー | 代替部品が元部品と同一 | 別の部品を選択 |

### リトライ仕様

特になし（トランザクション管理による整合性確保）

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

- BOMアイテムの作成・更新・削除は単一トランザクション内で実行
- BOM検証更新はバックグラウンドタスクで実行
- 価格更新はバックグラウンドタスクで非同期実行

## パフォーマンス要件

- BOM一覧取得: 500件で1秒以内
- 必要数量計算: 100ms以内
- BOM検証: バックグラウンドで実行

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

- ユーザー権限に基づくアクセス制御
- ロック状態によるBOM保護
- APIエンドポイントは認証必須

## 備考

- BOMの検証状態はチェックサムベースで管理
- バリアントへの継承はinheritedフラグで制御
- consumable部品は製造オーダーで追跡されない

---

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

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

### 推奨読解順序

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

BomItemモデルの構造と関連モデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models.py | `src/backend/InvenTree/part/models.py` | BomItemクラスの定義（3788-4300行目） |
| 1-2 | models.py | `src/backend/InvenTree/part/models.py` | BomItemSubstituteクラスの定義（4353-4408行目） |

**読解のコツ**:
- BomItemはpartとsub_partの2つの外部キーを持つ
- partはassembly=Trueの部品のみ、sub_partはcomponent=Trueの部品のみを参照可能
- hash_fields()メソッドで検証対象フィールドを定義

**主要処理フロー**:
- **3788-3809行目**: クラス定義とドキュメント（属性一覧）
- **3825-3832行目**: get_assembliesメソッド（継承を考慮した組立品リスト取得）
- **3834-3885行目**: get_valid_parts_for_allocationメソッド（引当可能部品リスト）
- **3899-3912行目**: deleteメソッド（ロックチェック、関連更新）
- **3914-3954行目**: saveメソッド（ロックチェック、検証状態更新）
- **3956-3978行目**: check_part_lockメソッド（ロック状態確認）
- **4097-4114行目**: hash_fields()メソッド
- **4116-4149行目**: get_item_hash()メソッド（チェックサム計算）
- **4174-4213行目**: cleanメソッド（循環参照チェック、trackable整合性）
- **4215-4234行目**: can_build_quantityメソッド（製造可能数量計算）
- **4236-4276行目**: get_required_quantityメソッド（必要数量計算）

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

APIエンドポイントとシリアライザを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | api.py | `src/backend/InvenTree/part/api.py` | BomListとBomDetailビュー |
| 2-2 | serializers.py | `src/backend/InvenTree/part/serializers.py` | BomItemSerializerの定義 |

#### Step 3: BOMコスト計算を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | models.py | `src/backend/InvenTree/part/models.py` | PartPricing.update_bom_costメソッド（2962-3040行目） |

**主要処理フロー**:
- **2972-2981行目**: 非組立品の場合はスキップ
- **2991-3040行目**: BOMアイテムごとの価格積み上げ

#### Step 4: 循環参照チェックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | models.py | `src/backend/InvenTree/part/models.py` | Part.check_add_to_bomメソッド |

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

```
REST API Request
    │
    ├─ BomList (api.py)
    │      ├─ BomItemSerializer
    │      │      └─ BomItem.save()
    │      │             ├─ clean()
    │      │             │      ├─ sub_part.check_add_to_bom() - 循環参照チェック
    │      │             │      └─ trackable整合性チェック
    │      │             ├─ check_part_lock() - ロック確認
    │      │             ├─ validated = is_line_valid - 検証状態
    │      │             └─ super().save()
    │      │
    │      └─ BomItem.delete()
    │             ├─ check_part_lock()
    │             └─ super().delete()
    │
    └─ BomDetail (api.py)
           └─ 同上
```

### データフロー図

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

フォーム入力 ───▶ BomItemSerializer ───▶ JSONレスポンス
                        │
                        ▼
                 BomItem.clean()
                        │
                        ├─ 循環参照チェック
                        │
                        ├─ trackable整合性
                        │
                        └─ ロックチェック
                                │
                                ▼
                        BomItem.save()
                                │
                                ├─▶ part_bomitem テーブル
                                │
                                └─▶ part_tasks.check_bom_valid()
                                           │
                                           ▼
                                   バックグラウンドタスク
                                           │
                                           ├─▶ BOM検証更新
                                           │
                                           └─▶ PartPricing.schedule_for_update()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| models.py | `src/backend/InvenTree/part/models.py` | ソース | BomItem, BomItemSubstituteモデル定義 |
| api.py | `src/backend/InvenTree/part/api.py` | ソース | REST APIビュー定義 |
| serializers.py | `src/backend/InvenTree/part/serializers.py` | ソース | シリアライザ定義 |
| tasks.py | `src/backend/InvenTree/part/tasks.py` | ソース | check_bom_valid等のバックグラウンドタスク |
| test_bom_item.py | `src/backend/InvenTree/part/test_bom_item.py` | テスト | BOMアイテムのユニットテスト |
| admin.py | `src/backend/InvenTree/part/admin.py` | ソース | 管理画面設定 |
| PartDetail.tsx | `src/frontend/src/pages/part/PartDetail.tsx` | ソース | フロントエンドBOMタブ |
