# 機能設計書 11-シリアル番号管理

## 概要

本ドキュメントは、InvenTreeシステムにおけるシリアル番号管理機能の設計内容を記述した機能設計書である。シリアル化された在庫アイテムの個体管理、シリアル番号の自動生成、検索、追跡機能について詳細を定義する。

### 本機能の処理概要

**業務上の目的・背景**：
製造業や在庫管理において、個々の製品や部品を一意に識別・追跡するためにシリアル番号が必要である。特に高価値な製品、保証対象品、トレーサビリティが求められる部品では、個体レベルでの管理が不可欠である。本機能は、シリアル番号の付与・管理・追跡を統合的に行うことで、品質管理、保証対応、リコール対応などの業務を支援する。

**機能の利用シーン**：
- 製造工程で完成品にシリアル番号を付与する場面
- 入荷時にサプライヤーから受領した部品にシリアル番号を登録する場面
- 顧客からの問い合わせ時にシリアル番号で製品を特定する場面
- 製造オーダーの出力時にシリアル番号を自動生成する場面
- 在庫棚卸しでシリアル番号による個体確認を行う場面

**主要な処理内容**：
1. シリアル番号の登録・バリデーション処理
2. シリアル番号の自動生成（連番、パターンベース）
3. シリアル番号の一意性検証（部品単位/部品ツリー単位）
4. シリアル番号による在庫アイテム検索
5. シリアル番号の整数変換によるソート最適化
6. シリアル化在庫の分割・統合制限

**関連システム・外部連携**：
- プラグインシステム（バリデーションMixin、シリアル番号変換カスタマイズ）
- バーコードスキャン機能との連携
- 製造オーダー機能との連携（出力時のシリアル番号生成）

**権限による制御**：
- 在庫アイテムの作成・編集権限を持つユーザーのみがシリアル番号を設定可能
- シリアル番号の変更は在庫追跡履歴として記録される

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | 在庫アイテム詳細画面 | 主機能 | シリアル番号の検索・シリアライズ処理 |
| 14 | 部品詳細画面 | 参照画面 | 部品に関連するシリアル番号の一覧表示 |
| 18 | 製造オーダー詳細画面 | 結果表示画面 | 製造出力時のシリアル番号生成 |

## 機能種別

データ登録・バリデーション / 検索処理 / 自動採番処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| serial | CharField | No | シリアル番号（最大100文字） | 文字列前後の空白除去、部品ツリー内での一意性検証 |
| part | ForeignKey | Yes | 関連部品 | trackable=Trueの部品のみ |
| quantity | DecimalField | Yes | 数量 | シリアル番号が設定されている場合は必ず1 |
| serials | List[str] | No | 一括登録用シリアル番号リスト | 重複チェック、パターン展開対応 |

### 入力データソース

- 画面入力（在庫アイテム登録・編集フォーム）
- 製造オーダー完了時の自動生成
- 発注受領処理での入力
- APIリクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| serial | str | シリアル番号文字列 |
| serial_int | int | シリアル番号の整数表現（ソート用） |
| serialized | bool | シリアル化されているかどうかのフラグ |

### 出力先

- StockItemテーブルのserial, serial_intフィールド
- 在庫追跡履歴（StockItemTracking）

## 処理フロー

### 処理シーケンス

```
1. シリアル番号の入力受付
   └─ 画面入力、API、または自動生成から取得

2. シリアル番号のバリデーション
   └─ 空白トリム
   └─ 部品ツリー内での一意性検証
   └─ プラグインによるカスタムバリデーション

3. シリアル番号の整数変換
   └─ プラグインによるカスタム変換を試行
   └─ デフォルトの数値抽出処理

4. 在庫アイテムの保存
   └─ serial, serial_intフィールドの更新
   └─ 数量を1に固定
   └─ delete_on_depleteをFalseに設定

5. 追跡履歴の記録
   └─ StockHistoryCodeの生成
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{シリアル番号入力あり?}
    B -->|No| Z[通常在庫として保存]
    B -->|Yes| C[空白をトリム]
    C --> D{一意性検証}
    D -->|重複| E[ValidationError]
    D -->|OK| F[整数変換処理]
    F --> G{プラグイン変換成功?}
    G -->|Yes| H[プラグイン結果を使用]
    G -->|No| I[デフォルト抽出処理]
    H --> J[数量を1に設定]
    I --> J
    J --> K[delete_on_deplete=False]
    K --> L[在庫アイテム保存]
    L --> M[追跡履歴記録]
    M --> N[終了]
    E --> N
    Z --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-11-01 | シリアル番号一意性 | 同一部品ツリー内でシリアル番号は一意でなければならない | trackable部品に対してシリアル番号を設定する場合 |
| BR-11-02 | 数量制約 | シリアル番号が設定された在庫アイテムの数量は必ず1 | シリアル番号が設定されている場合 |
| BR-11-03 | 削除禁止 | シリアル化在庫はdelete_on_deplete=Falseとなり、数量0でも自動削除されない | シリアル番号が設定されている場合 |
| BR-11-04 | trackable要件 | シリアル番号を設定できるのはtrackable=Trueの部品のみ | シリアル番号設定時 |

### 計算ロジック

**シリアル番号の整数変換（serial_int）**:
```python
# プラグインによるカスタム変換を優先
for plugin in registry.with_mixin(VALIDATION):
    serial_int = plugin.convert_serial_to_int(serial)
    if serial_int is not None:
        return min(abs(serial_int), 0x7FFFFFFF)

# デフォルト: 文字列から数値を抽出
return extract_int(serial)
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| シリアル番号設定 | stock_stockitem | UPDATE | serial, serial_int, quantityフィールドの更新 |
| シリアル番号検索 | stock_stockitem | SELECT | serial, serial_intによる検索 |
| 一括作成 | stock_stockitem | INSERT | bulk_createによる複数シリアル番号の一括登録 |
| 追跡記録 | stock_stockitemtracking | INSERT | シリアル番号変更履歴の記録 |

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

#### stock_stockitem

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | serial | 入力されたシリアル番号（トリム済み） | VARCHAR(100) |
| UPDATE | serial_int | 整数変換されたシリアル番号 | ソート最適化用 |
| UPDATE | quantity | 1 | シリアル化時は固定 |
| UPDATE | delete_on_deplete | False | シリアル化時は固定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| serial_duplicate | ValidationError | 同一部品ツリー内でシリアル番号が重複 | 別のシリアル番号を指定 |
| quantity_invalid | ValidationError | シリアル番号設定時に数量が1以外 | 数量を1に修正 |
| part_not_trackable | ValidationError | trackable=Falseの部品にシリアル番号設定 | 部品のtrackable設定を確認 |

### リトライ仕様

シリアル番号の一意性エラーはユーザーによる再入力が必要であり、自動リトライは行わない。

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

- シリアル番号の一括作成（_create_serial_numbers）はトランザクション内で実行
- bulk_createによる一括登録後、必要に応じてMPTTツリーの再構築を実行
- 追跡履歴の記録は在庫アイテム保存と同一トランザクション内で実行

## パフォーマンス要件

- serial_intフィールドによるソートでインデックスを活用
- 一括作成時はbulk_createを使用して個別INSERTを回避
- 部品ツリー内での一意性検証はインデックスを活用

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

- シリアル番号の変更は追跡履歴に記録され、監査可能
- シリアル番号の設定は在庫アイテムの編集権限が必要
- プラグインによるカスタムバリデーションでセキュリティポリシーを適用可能

## 備考

- シリアル番号のパターン（例: "SN-001"〜"SN-100"）は範囲展開がサポートされている
- プラグインを使用することで、企業固有のシリアル番号形式やバリデーションルールを実装可能

---

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

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

### 推奨読解順序

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

まず、シリアル番号に関連するデータモデルの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models.py | `src/backend/InvenTree/stock/models.py` | StockItemクラスのserial, serial_intフィールド定義 |

**読解のコツ**:
- **1084-1092行目**: serialフィールドの定義（CharField, max_length=100, blank=True, null=True）
- **1092行目**: serial_intフィールド（IntegerField, default=0）はソート最適化用
- **849-856行目**: serializedプロパティでシリアル化判定

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

シリアル番号の設定・検証の起点となる処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | models.py | `src/backend/InvenTree/stock/models.py` | StockItem.save()メソッド |
| 2-2 | models.py | `src/backend/InvenTree/stock/models.py` | StockItem.validate_unique()メソッド |

**主要処理フロー**:
1. **768-843行目**: save()メソッドでupdate_serial_number()を呼び出し
2. **858-873行目**: validate_unique()でシリアル番号の一意性を検証
3. **893-1005行目**: clean()メソッドで数量=1の制約を検証

#### Step 3: シリアル番号変換処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | models.py | `src/backend/InvenTree/stock/models.py` | convert_serial_to_int()静的メソッド |
| 3-2 | models.py | `src/backend/InvenTree/stock/models.py` | update_serial_number()メソッド |

**主要処理フロー**:
- **654-687行目**: convert_serial_to_int()でプラグイン変換を試行後、デフォルト抽出
- **689-710行目**: update_serial_number()でserial_intフィールドを更新

#### Step 4: 一括作成処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | models.py | `src/backend/InvenTree/stock/models.py` | _create_serial_numbers()クラスメソッド |

**主要処理フロー**:
- **528-652行目**: 複数シリアル番号の一括作成処理
- **613-629行目**: 各シリアル番号に対してStockItemインスタンスを生成
- **632行目**: bulk_create()で一括登録

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

```
StockItem.save()
    │
    ├─ StockItem.validate_unique()
    │      └─ Part.validate_serial_number()
    │
    ├─ StockItem.clean()
    │      └─ quantity == 1 検証
    │
    └─ StockItem.update_serial_number()
           └─ StockItem.convert_serial_to_int()
                  ├─ Plugin.convert_serial_to_int()  [プラグイン]
                  └─ InvenTree.helpers.extract_int()  [デフォルト]
```

### データフロー図

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

シリアル番号     ───▶ バリデーション          ───▶ ValidationError
  (文字列)              │                           (エラー時)
                        │
                        ▼
                  整数変換処理              ───▶ serial_int
                        │                         (整数)
                        │
                        ▼
                  StockItem保存             ───▶ stock_stockitem
                        │                         テーブル
                        │
                        ▼
                  追跡履歴記録              ───▶ stock_stockitemtracking
                                                  テーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| models.py | `src/backend/InvenTree/stock/models.py` | ソース | StockItemモデル定義（シリアル番号フィールド含む） |
| api.py | `src/backend/InvenTree/stock/api.py` | ソース | シリアル番号関連のAPIエンドポイント |
| serializers.py | `src/backend/InvenTree/stock/serializers.py` | ソース | シリアル番号のシリアライズ処理 |
| helpers.py | `src/backend/InvenTree/InvenTree/helpers.py` | ソース | extract_int()等のヘルパー関数 |
| part/models.py | `src/backend/InvenTree/part/models.py` | ソース | Part.validate_serial_number()メソッド |
