# 機能設計書 2-部品マスタ管理

## 概要

本ドキュメントは、InvenTreeシステムにおける部品マスタ管理機能の設計を記述する。部品マスタはシステムの中核となるエンティティであり、在庫管理、発注、製造のすべての基盤となる。

### 本機能の処理概要

**業務上の目的・背景**：
製造業・組立業において、部品情報の一元管理は業務効率化の要である。部品マスタ管理機能は、部品の基本情報（名前、説明、IPN、リビジョン等）を登録・編集・削除し、部品画像、タグ、メタデータも含めた包括的な部品情報管理を実現する。これにより、部品の重複登録防止、正確な在庫追跡、BOM管理の基盤構築が可能となる。また、部品のバリアント（派生品）管理やリビジョン管理にも対応し、製品ライフサイクル全体を通じた部品情報の追跡を可能にする。

**機能の利用シーン**：
- 新規部品の登録時に、基本情報・属性・画像を設定する場面
- 既存部品の情報を更新・修正する場面
- 部品のバリアント（色違い、サイズ違い等）を管理する場面
- 部品のリビジョン（設計変更）を追跡する場面
- 部品の有効/無効化、ロック状態の管理を行う場面
- 部品を検索・一覧表示する場面

**主要な処理内容**：
1. 部品の作成・編集・削除（バリデーション付き）
2. 部品画像の登録・更新・削除
3. バリアント関係の設定（テンプレート部品との関連）
4. リビジョン管理（revision_of関連）
5. 部品属性の設定（assembly, component, trackable, purchaseable, salable等）
6. タグ・キーワードによる検索性向上
7. ユーザー購読機能

**関連システム・外部連携**：
- PartCategory（部品カテゴリ）との連携
- StockLocation（デフォルト保管場所）との連携
- SupplierPart（デフォルトサプライヤー）との連携
- BomItem（BOM管理）との連携
- StockItem（在庫管理）との連携
- 価格管理システム（PartPricing）との連携

**権限による制御**：
- 部品の閲覧は一般ユーザーでも可能
- 部品の作成にはpart.add_part権限が必要
- 部品の編集にはpart.change_part権限が必要（ロック状態の部品は編集不可）
- 部品の削除にはpart.delete_part権限が必要（アクティブな部品は削除不可）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | 部品詳細画面 | 主機能 | 部品情報の表示・編集・削除、部品画像の管理 |
| 13 | 部品カテゴリ一覧・詳細画面 | 参照画面 | カテゴリ内部品一覧から部品詳細への遷移 |
| 15 | 在庫ロケーション一覧・詳細画面 | 参照画面 | デフォルト部品一覧の表示 |

## 機能種別

CRUD操作 / マスタ管理 / 階層データ管理（バリアント）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | 部品名 | 最大100文字、空文字不可 |
| description | string | No | 部品説明 | 最大250文字 |
| category | integer | No | カテゴリID | 有効なPartCategoryのID、構造的カテゴリ不可 |
| IPN | string | No | 内部部品番号 | 最大100文字、重複チェック設定可能 |
| revision | string | No | リビジョン | 最大100文字 |
| revision_of | integer | No | リビジョン元部品ID | 有効なPartのID |
| variant_of | integer | No | テンプレート部品ID | is_template=Trueの部品のみ |
| is_template | boolean | No | テンプレートフラグ | デフォルト設定から取得 |
| keywords | string | No | キーワード | 最大250文字 |
| link | string | No | 外部URL | URL形式、最大2000文字 |
| default_location | integer | No | デフォルト保管場所 | 有効なStockLocationのID |
| default_supplier | integer | No | デフォルトサプライヤー | 有効なSupplierPartのID |
| default_expiry | integer | No | デフォルト有効期限（日数） | 0以上の整数 |
| minimum_stock | decimal | No | 最小在庫数 | 0以上の数値 |
| units | string | No | 単位 | 最大20文字、物理単位形式 |
| assembly | boolean | No | 組立品フラグ | |
| component | boolean | No | コンポーネントフラグ | |
| trackable | boolean | No | 追跡可能フラグ | |
| purchaseable | boolean | No | 購入可能フラグ | |
| salable | boolean | No | 販売可能フラグ | |
| active | boolean | No | 有効フラグ | デフォルトTrue |
| locked | boolean | No | ロックフラグ | |
| virtual | boolean | No | 仮想部品フラグ | |
| image | file | No | 部品画像 | 画像ファイル |

### 入力データソース

- 画面入力（部品作成・編集フォーム）
- REST API（api-part-list）
- CSVインポート

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pk | integer | 部品の主キー |
| name | string | 部品名 |
| full_name | string | フォーマット済み完全名 |
| description | string | 説明 |
| category | integer | カテゴリID |
| category_path | string | カテゴリパス文字列 |
| IPN | string | 内部部品番号 |
| revision | string | リビジョン |
| revision_of | integer | リビジョン元部品ID |
| variant_of | integer | テンプレート部品ID |
| is_template | boolean | テンプレートフラグ |
| image | string | 画像URL |
| thumbnail | string | サムネイルURL |
| total_stock | decimal | 総在庫数 |
| available_stock | decimal | 利用可能在庫数 |
| on_order | decimal | 発注中数量 |
| pricing | object | 価格情報 |
| starred | boolean | ユーザーの購読状態 |

### 出力先

- 画面表示（部品一覧、部品詳細）
- REST API レスポンス
- レポート・ラベル出力

## 処理フロー

### 処理シーケンス

```
1. 部品作成
   └─ バリデーション実行 → 一意性チェック → 画像処理 → DB保存 → 価格計算スケジュール

2. 部品編集
   └─ ロック状態チェック → バリデーション → 画像差分処理 → DB更新 → 関連更新

3. 部品削除
   ├─ アクティブ状態チェック（アクティブなら削除不可）
   ├─ ロック状態チェック（ロックなら削除不可）
   └─ BOM使用チェック → DB削除

4. バリアント作成
   └─ テンプレート部品の検証 → 親子関係設定 → MPTT更新
```

### フローチャート

```mermaid
flowchart TD
    A[部品操作開始] --> B{操作種別}
    B -->|作成| C[バリデーション実行]
    C --> D[一意性チェック]
    D --> E{IPN重複?}
    E -->|Yes & 設定で禁止| F[ValidationError]
    E -->|No| G[画像処理]
    G --> H[DB保存]
    H --> I[価格計算スケジュール]

    B -->|編集| J{ロック状態?}
    J -->|Yes| K[ValidationError]
    J -->|No| C

    B -->|削除| L{アクティブ?}
    L -->|Yes| M[ValidationError]
    L -->|No| N{ロック状態?}
    N -->|Yes| K
    N -->|No| O{BOMで使用中?}
    O -->|Yes & 設定で禁止| P[ValidationError]
    O -->|No| Q[DB削除]

    I --> R[終了]
    Q --> R
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | アクティブ部品削除禁止 | アクティブな部品は削除できない | active=True |
| BR-002 | ロック部品編集禁止 | ロックされた部品は編集・削除できない | locked=True |
| BR-003 | IPN重複チェック | IPNの重複は設定により許可/禁止 | PART_ALLOW_DUPLICATE_IPN設定 |
| BR-004 | 構造的カテゴリ禁止 | 構造的カテゴリには部品を配置できない | category.structural=True |
| BR-005 | トラッカブル継承 | trackable部品をBOMに使用すると親も自動的にtrackableになる | sub_part.trackable=True |
| BR-006 | BOM使用中削除制限 | BOMで使用中の部品は設定により削除制限 | PART_ALLOW_DELETE_FROM_ASSEMBLY設定 |
| BR-007 | リビジョン制約 | リビジョン部品は別のリビジョンの子になれない | revision_of.revision_of が存在する場合 |
| BR-008 | シリアル番号一意性 | シリアル番号は部品ツリー内で一意（設定によりグローバル一意） | SERIAL_NUMBER_GLOBALLY_UNIQUE設定 |

### 計算ロジック

```python
# 利用可能在庫の計算
@property
def available_stock(self):
    total = self.total_stock
    total -= self.allocation_count()
    return max(total, 0)

# フル部品名の生成
@property
def full_name(self) -> str:
    return part_helpers.render_part_full_name(self)

# 発注中数量の計算
@property
def on_order(self):
    quantity = 0
    for sp in self.supplier_parts.all():
        lines = sp.purchase_order_line_items.filter(
            order__status__in=PurchaseOrderStatusGroups.OPEN,
            quantity__gt=F('received'),
        )
        for line in lines:
            remaining = line.quantity - line.received
            if remaining > 0:
                quantity += sp.base_quantity(remaining)
    return quantity
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 部品作成 | part_part | INSERT | 新規部品レコード挿入 |
| 部品編集 | part_part | UPDATE | 部品情報更新 |
| 部品削除 | part_part | DELETE | 部品レコード削除 |
| 画像更新 | part_part | UPDATE | imageフィールド更新 |
| 購読追加 | part_partstar | INSERT | ユーザー購読レコード挿入 |
| 価格更新 | part_partpricing | INSERT/UPDATE | 価格キャッシュ更新 |

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

#### part_part

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name, description, category_id, IPN, revision, variant_of_id, is_template, ... | フォーム入力値 | MPTTによるバリアント階層管理 |
| UPDATE | 上記カラム | フォーム入力値 | locked=Trueの場合は更新不可 |
| DELETE | - | pk指定 | active=False, locked=False が条件 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ValidationError | バリデーションエラー | アクティブ部品を削除しようとした | 先に非アクティブ化が必要 |
| ValidationError | バリデーションエラー | ロック部品を編集しようとした | ロック解除が必要 |
| ValidationError | バリデーションエラー | IPNが重複（設定で禁止時） | 別のIPNを使用 |
| ValidationError | バリデーションエラー | 構造的カテゴリに部品を配置 | 別のカテゴリを選択 |
| ValidationError | バリデーションエラー | 自己参照のリビジョン | 別の部品を指定 |

### リトライ仕様

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

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

- 部品の作成・更新・削除は単一トランザクション内で実行
- 画像の削除は部品保存後に実行（孤立画像の削除）
- 価格更新はバックグラウンドタスクで非同期実行

## パフォーマンス要件

- 部品一覧取得: 1000件で1秒以内
- 部品詳細取得: 500ms以内
- 価格計算: バックグラウンドで実行、UIをブロックしない

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

- 画像ファイルはサイズ・形式のバリデーション
- URL入力はInvenTreeURLFieldでバリデーション
- ユーザー権限に基づくアクセス制御

## 備考

- 部品削除は論理削除ではなく物理削除
- 削除前に非アクティブ化を推奨
- MPTTライブラリによるバリアント階層管理

---

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

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

### 推奨読解順序

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

Partモデルの構造と多数の属性を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models.py | `src/backend/InvenTree/part/models.py` | Partクラスの定義（464-2700行目） |
| 1-2 | models.py | `src/backend/InvenTree/InvenTree/models.py` | 各種Mixinクラスの理解 |

**読解のコツ**:
- Partは多数のMixinを継承（PluginValidationMixin, InvenTreeParameterMixin, InvenTreeAttachmentMixin, InvenTreeBarcodeMixin, InvenTreeNotesMixin, InvenTreeReportMixin, InvenTreeImageMixin, MetadataMixin, InvenTreeTree）
- `NODE_PARENT_KEY = 'variant_of'` はバリアント階層の親子関係を定義
- フィールド属性（assembly, component, trackable等）は部品の用途を決定

**主要処理フロー**:
- **464-516行目**: クラス定義とドキュメント（属性一覧）
- **582-602行目**: deleteメソッド（削除制約のチェック）
- **604-639行目**: saveメソッド（画像の孤立チェック、clean実行）
- **720-769行目**: validate_ipnメソッド（IPN検証ロジック）
- **814-916行目**: validate_serial_numberメソッド（シリアル番号検証）
- **1064-1096行目**: cleanメソッド（全体バリデーション）
- **1107-1319行目**: フィールド定義（name, is_template, variant_of, description, keywords, category, IPN, revision等）
- **1374-1390行目**: available_stockプロパティ
- **1549-1559行目**: net_stockプロパティ
- **2596-2619行目**: on_orderプロパティ

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

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

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

#### Step 3: 価格計算を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | models.py | `src/backend/InvenTree/part/models.py` | PartPricingクラス（2729-3468行目） |

**主要処理フロー**:
- **2871-2927行目**: update_pricingメソッド（価格再計算のメインロジック）
- **2962-3040行目**: update_bom_costメソッド（BOMコスト計算）
- **3042-3106行目**: update_purchase_costメソッド（購入コスト計算）

#### Step 4: 購読機能を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | models.py | `src/backend/InvenTree/part/models.py` | get_subscribers, is_starred_by, set_starredメソッド（1561-1617行目） |
| 4-2 | models.py | `src/backend/InvenTree/part/models.py` | PartStarクラス（3567-3594行目） |

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

```
REST API Request
    │
    ├─ PartList (api.py)
    │      ├─ PartSerializer
    │      │      └─ Part.save()
    │      │             ├─ full_clean()
    │      │             │      ├─ clean()
    │      │             │      │      ├─ validate_revision()
    │      │             │      │      ├─ validate_ipn()
    │      │             │      │      └─ validate_name()
    │      │             │      └─ validate_unique()
    │      │             ├─ 画像処理
    │      │             └─ ensure_trackable()
    │      │
    │      └─ Part.delete()
    │             ├─ アクティブチェック
    │             ├─ ロックチェック
    │             └─ BOM使用チェック
    │
    └─ PartDetail (api.py)
           └─ 同上
```

### データフロー図

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

フォーム入力 ───▶ PartSerializer ───▶ JSONレスポンス
                        │
                        ▼
                 Part.clean()
                        │
                        ▼
                 Part.save()
                        │
                        ├─▶ part_part テーブル
                        │
                        └─▶ PartPricing.schedule_for_update()
                                   │
                                   ▼
                           バックグラウンドタスク
                                   │
                                   ▼
                           part_partpricing テーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| models.py | `src/backend/InvenTree/part/models.py` | ソース | Part, PartPricing, PartStarモデル定義 |
| api.py | `src/backend/InvenTree/part/api.py` | ソース | REST APIビュー定義 |
| serializers.py | `src/backend/InvenTree/part/serializers.py` | ソース | シリアライザ定義 |
| helpers.py | `src/backend/InvenTree/part/helpers.py` | ソース | ヘルパー関数（render_part_full_name等） |
| settings.py | `src/backend/InvenTree/part/settings.py` | ソース | 部品関連のデフォルト設定 |
| PartDetail.tsx | `src/frontend/src/pages/part/PartDetail.tsx` | ソース | フロントエンド詳細画面 |
| admin.py | `src/backend/InvenTree/part/admin.py` | ソース | 管理画面設定 |
| tasks.py | `src/backend/InvenTree/part/tasks.py` | ソース | バックグラウンドタスク |
