# 機能設計書 8-在庫アイテム管理

## 概要

本ドキュメントは、InvenTreeシステムにおける在庫アイテム管理機能の設計を記述する。在庫アイテムは部品の物理的なインスタンスを表し、数量、ロケーション、シリアル番号、バッチコード、有効期限などの属性を管理する。

### 本機能の処理概要

**業務上の目的・背景**：
製造業・在庫管理において、部品の物理的な在庫を正確に追跡することは、生産計画、発注管理、顧客対応において極めて重要である。在庫アイテム管理機能は、各在庫アイテムの数量、保管場所、ステータス、シリアル番号、有効期限などを管理し、在庫の可視化とトレーサビリティを実現する。シリアル化された部品の個体追跡、バッチ単位での品質管理、有効期限管理にも対応する。

**機能の利用シーン**：
- 入荷した部品を在庫として登録する場面
- 在庫の数量、ロケーション、ステータスを更新する場面
- シリアル番号付き部品を個別に追跡する場面
- バッチコードで品質管理を行う場面
- 有効期限管理で期限切れ在庫を特定する場面
- 在庫の親子関係（組み込み部品）を管理する場面

**主要な処理内容**：
1. 在庫アイテムの作成・編集・削除
2. シリアル番号の割り当てと検証
3. バッチコードの管理と検証
4. 有効期限の設定と期限切れ判定
5. ステータス管理（OK、要注意、損傷、紛失等）
6. 親子在庫の関係管理（belongs_to）
7. 顧客への割り当て管理

**関連システム・外部連携**：
- Part（部品）との連携（部品インスタンス）
- StockLocation（ロケーション）との連携
- SupplierPart（サプライヤー部品）との連携
- PurchaseOrder（発注）との連携
- SalesOrder（受注）との連携
- Build（製造オーダー）との連携

**権限による制御**：
- 在庫の閲覧は一般ユーザーでも可能
- 在庫の作成・編集にはstock.change_stockitem権限が必要
- オーナー設定時はSTOCK_OWNERSHIP_CONTROL設定による追加制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | 在庫アイテム詳細画面 | 主機能 | 在庫アイテムの表示・編集・削除 |
| 14 | 部品詳細画面 | 参照画面 | 部品の在庫一覧表示 |
| 15 | 在庫ロケーション一覧・詳細画面 | 参照画面 | ロケーション内在庫一覧表示 |

## 機能種別

CRUD操作 / 在庫管理 / シリアル/バッチ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| part | integer | Yes | 部品ID | virtual=Falseの部品のみ |
| quantity | decimal | Yes | 数量 | 0以上、シリアル化時は1 |
| location | integer | No | ロケーションID | structural=Falseのロケーションのみ |
| serial | string | No | シリアル番号 | 最大100文字、部品ツリー内で一意 |
| batch | string | No | バッチコード | 最大100文字 |
| status | integer | No | ステータス | StockStatusの有効値 |
| expiry_date | date | No | 有効期限 | |
| supplier_part | integer | No | サプライヤー部品ID | partと整合性チェック |
| purchase_price | money | No | 購入価格 | |
| packaging | string | No | 梱包形態 | 最大50文字 |
| link | string | No | 外部URL | URL形式、最大2000文字 |
| owner | integer | No | オーナーID | 有効なOwnerのID |
| delete_on_deplete | boolean | No | 枯渇時削除フラグ | シリアル化時はFalse |

### 入力データソース

- 画面入力（在庫アイテム作成・編集フォーム）
- REST API（api-stock-list）
- 発注入荷処理
- 製造完了処理

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pk | integer | 在庫アイテムの主キー |
| part | integer | 部品ID |
| part_detail | object | 部品詳細情報 |
| quantity | decimal | 数量 |
| location | integer | ロケーションID |
| location_detail | object | ロケーション詳細情報 |
| serial | string | シリアル番号 |
| serial_int | integer | シリアル番号の数値表現 |
| batch | string | バッチコード |
| status | integer | ステータス |
| status_text | string | ステータス表示名 |
| expiry_date | date | 有効期限 |
| supplier_part | integer | サプライヤー部品ID |
| purchase_price | money | 購入価格 |
| in_stock | boolean | 在庫中フラグ |
| expired | boolean | 期限切れフラグ |
| stale | boolean | 期限間近フラグ |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. 在庫アイテム作成
   └─ バリデーション → シリアル番号検証 → バッチコード検証 → DB保存 → 追跡エントリ作成

2. 在庫アイテム編集
   └─ バリデーション → ステータス変更検出 → DB更新 → 追跡エントリ作成

3. シリアル番号割り当て
   └─ シリアル範囲解析 → 一意性検証 → 在庫分割 → 各アイテムにシリアル付与

4. 在庫ステータス判定
   └─ 数量確認 → 割当確認 → 有効期限確認 → ステータス返却
```

### フローチャート

```mermaid
flowchart TD
    A[在庫アイテム操作開始] --> B{操作種別}
    B -->|作成| C[バリデーション実行]
    C --> D{仮想部品?}
    D -->|Yes| E[ValidationError]
    D -->|No| F{構造的ロケーション?}
    F -->|Yes| E
    F -->|No| G{シリアル指定?}
    G -->|Yes| H[シリアル検証]
    H --> I{一意?}
    I -->|No| E
    I -->|Yes| J[数量=1に設定]
    G -->|No| K[バッチコード検証]
    J --> K
    K --> L[DB保存]
    L --> M[追跡エントリ作成]

    B -->|編集| N{ステータス変更?}
    N -->|Yes| O[ステータス追跡エントリ]
    N -->|No| P[バリデーション]
    O --> P
    P --> L

    M --> Q[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 仮想部品制約 | 仮想部品（virtual=True）には在庫アイテム作成不可 | part.virtual |
| BR-002 | 構造的ロケーション制約 | 構造的ロケーションには在庫直接配置不可 | location.structural |
| BR-003 | シリアル数量制約 | シリアル番号付き在庫の数量は1固定 | serial設定時 |
| BR-004 | シリアル一意性 | シリアル番号は部品ツリー内で一意（設定によりグローバル） | SERIAL_NUMBER_GLOBALLY_UNIQUE設定 |
| BR-005 | シリアル削除禁止 | シリアル化在庫はdelete_on_deplete=False | serialized=True |
| BR-006 | サプライヤー整合性 | supplier_part.partは在庫のpartと一致が必要 | supplier_part設定時 |
| BR-007 | trackable数量 | trackable部品の数量は整数のみ | part.trackable |
| BR-008 | 自己参照禁止 | belongs_toに自身は指定不可 | belongs_to設定時 |
| BR-009 | 製造参照整合性 | is_building=Trueの場合、buildは必須 | is_building |

### 計算ロジック

```python
# 在庫中判定
IN_STOCK_FILTER = Q(
    quantity__gt=0,
    sales_order=None,
    belongs_to=None,
    customer=None,
    consumed_by=None,
    is_building=False,
    status__in=StockStatusGroups.AVAILABLE_CODES,
)

# シリアル番号の数値変換
def convert_serial_to_int(serial: str) -> int | None:
    # プラグインによるカスタム変換を優先
    for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
        serial_int = plugin.convert_serial_to_int(serial)
        if serial_int is not None:
            return min(abs(serial_int), 0x7FFFFFFF)
    # デフォルト：数値部分を抽出
    return InvenTree.helpers.extract_int(serial)

# 期限切れ判定
def is_expired(self):
    if self.expiry_date is None:
        return False
    if not self.in_stock:
        return False
    return self.expiry_date < current_date()

# 期限間近判定
def is_stale(self):
    if self.expiry_date is None:
        return False
    if not self.in_stock:
        return False
    stale_days = get_global_setting('STOCK_STALE_DAYS')
    if stale_days <= 0:
        return False
    expiry_threshold = current_date() + timedelta(days=stale_days)
    return self.expiry_date < expiry_threshold
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 在庫作成 | stock_stockitem | INSERT | 新規在庫アイテムレコード挿入 |
| 在庫編集 | stock_stockitem | UPDATE | 在庫アイテム情報更新 |
| 在庫削除 | stock_stockitem | DELETE | 在庫アイテムレコード削除 |
| 追跡エントリ | stock_stockitemtracking | INSERT | 変更履歴レコード挿入 |
| シリアル一括作成 | stock_stockitem | BULK INSERT | 複数シリアル在庫の一括作成 |

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

#### stock_stockitem

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | part_id, quantity, location_id, serial, serial_int, batch, status, expiry_date, supplier_part_id, purchase_price, ... | 入力値、計算値 | MPTT対応（親子関係） |
| UPDATE | 上記カラム | 入力値 | ステータス変更は追跡エントリも作成 |
| DELETE | - | pk指定 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ValidationError | バリデーションエラー | 仮想部品に在庫作成 | 仮想でない部品を選択 |
| ValidationError | バリデーションエラー | 構造的ロケーションに配置 | 別のロケーションを選択 |
| ValidationError | バリデーションエラー | シリアル番号が重複 | 別のシリアル番号を使用 |
| ValidationError | バリデーションエラー | シリアル化で数量>1 | 数量を1に設定 |
| ValidationError | バリデーションエラー | サプライヤー部品と部品の不整合 | 正しいサプライヤー部品を選択 |
| ValidationError | バリデーションエラー | trackable部品に非整数数量 | 整数値を指定 |

### リトライ仕様

特になし

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

- 在庫アイテムの作成・更新・削除は単一トランザクション内で実行
- シリアル番号の一括作成はbulk_createで効率化
- 追跡エントリの作成は在庫操作と同一トランザクション

## パフォーマンス要件

- 在庫一覧取得: 1000件で1秒以内
- シリアル一括作成: 100件で5秒以内
- 在庫検索: インデックスを活用した高速検索

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

- ユーザー権限に基づくアクセス制御
- オーナー権限制御（STOCK_OWNERSHIP_CONTROL）
- シリアル番号のプラグインバリデーション

## 備考

- StockItemはMPTTを使用（親子関係の効率的管理）
- シリアル番号のソート用にserial_intフィールドを自動生成
- バーコード型コードは'SI'
- delete_on_deplete設定でゼロ在庫時の自動削除を制御

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models.py | `src/backend/InvenTree/stock/models.py` | StockItemクラス（399-999行目） |

**読解のコツ**:
- StockItemは多数のMixinを継承（PluginValidationMixin, InvenTreeAttachmentMixin, InvenTreeBarcodeMixin, InvenTreeNotesMixin, StatusCodeMixin, InvenTreeReportMixin, MetaMixin, MetadataMixin, InvenTreeTree）
- IN_STOCK_FILTERクエリフィルタで「在庫中」の定義を理解
- serialとserial_intの関係を理解

**主要処理フロー**:
- **399-435行目**: クラス定義とドキュメント（属性一覧）
- **509-519行目**: IN_STOCK_FILTER定義
- **528-652行目**: _create_serial_numbers（シリアル一括作成）
- **654-687行目**: convert_serial_to_int（シリアル数値化）
- **768-842行目**: saveメソッド（ステータス変更追跡、追跡エントリ作成）
- **858-873行目**: validate_unique（シリアル一意性検証）
- **893-921行目**: cleanメソッド（構造的ロケーション、シリアル数量等のバリデーション）
- **1014-1092行目**: フィールド定義（part, location, quantity, serial, batch等）

#### Step 2: ステータス管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | status_codes.py | `src/backend/InvenTree/stock/status_codes.py` | StockStatus, StockStatusGroups定義 |

**主要処理フロー**:
- **8-27行目**: StockStatus列挙型（OK, ATTENTION, DAMAGED, DESTROYED, REJECTED, LOST, QUARANTINED, RETURNED）
- **29-38行目**: StockStatusGroups（AVAILABLE_CODES）

#### Step 3: シリアル番号管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | models.py | `src/backend/InvenTree/stock/models.py` | update_serial_number, get_next_serialized_itemメソッド |

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

```
REST API Request
    │
    ├─ StockItemList (api.py)
    │      ├─ StockItemSerializer
    │      │      └─ StockItem.save()
    │      │             ├─ validate_unique()
    │      │             │      └─ シリアル一意性検証
    │      │             ├─ clean()
    │      │             │      ├─ 構造的ロケーションチェック
    │      │             │      ├─ シリアル数量チェック
    │      │             │      └─ trackable数量チェック
    │      │             ├─ update_serial_number()
    │      │             └─ add_tracking_entry()
    │      │
    │      └─ StockItem._create_serial_numbers()
    │             └─ bulk_create()
    │
    └─ StockItemDetail (api.py)
           └─ 同上
```

### データフロー図

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

フォーム入力 ───▶ StockItemSerializer ───▶ JSONレスポンス
                        │
                        ▼
                 StockItem.clean()
                        │
                        ├─ 構造的ロケーションチェック
                        │
                        ├─ シリアル/数量整合性
                        │
                        └─ サプライヤー整合性
                                │
                                ▼
                        StockItem.save()
                                │
                                ├─ update_serial_number()
                                │
                                ├─ ステータス変更追跡
                                │
                                └─ add_tracking_entry()
                                        │
                                        ├─▶ stock_stockitem テーブル
                                        │
                                        └─▶ stock_stockitemtracking テーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| models.py | `src/backend/InvenTree/stock/models.py` | ソース | StockItemモデル定義 |
| status_codes.py | `src/backend/InvenTree/stock/status_codes.py` | ソース | StockStatus, StockHistoryCode定義 |
| api.py | `src/backend/InvenTree/stock/api.py` | ソース | REST APIビュー定義 |
| serializers.py | `src/backend/InvenTree/stock/serializers.py` | ソース | シリアライザ定義 |
| generators.py | `src/backend/InvenTree/stock/generators.py` | ソース | バッチコード生成 |
| StockDetail.tsx | `src/frontend/src/pages/stock/StockDetail.tsx` | ソース | フロントエンド詳細画面 |
