# 機能設計書 9-在庫移動・調整

## 概要

本ドキュメントは、InvenTreeシステムにおける在庫移動・調整機能の設計を記述する。在庫移動・調整は在庫アイテムのロケーション変更、数量の増減、在庫分割・統合、顧客への割当・返却などの在庫操作を管理する。

### 本機能の処理概要

**業務上の目的・背景**：
製造業・物流業において、在庫の移動や数量調整は日常的な業務である。在庫移動・調整機能は、ロケーション間の在庫移動、棚卸による数量調整、在庫の分割・統合、顧客への出荷と返品、製造工程での消費と産出などを正確に記録し、在庫の正確性とトレーサビリティを維持する。すべての在庫操作は履歴として記録され、監査や分析に活用できる。

**機能の利用シーン**：
- 在庫をある保管場所から別の保管場所へ移動する場面
- 棚卸結果に基づいて在庫数量を調整する場面
- 大量在庫を複数に分割して異なる場所に配置する場面
- 同一部品の複数在庫アイテムを統合する場面
- 顧客への在庫出荷と返品受入を行う場面
- 製造工程で在庫を消費し、完成品を産出する場面

**主要な処理内容**：
1. 在庫移動（ロケーション変更）
2. 在庫数量の調整（カウント、追加、削除）
3. 在庫分割（splitStock）
4. 在庫統合（merge_stock_items）
5. 顧客割当と返品（allocateToCustomer, return_from_customer）
6. 製造への割当と消費
7. バリアント変換（convert_to_variant）

**関連システム・外部連携**：
- StockItem（在庫アイテム）との連携
- StockLocation（ロケーション）との連携
- StockItemTracking（追跡履歴）との連携
- SalesOrder（受注）との連携
- Build（製造オーダー）との連携

**権限による制御**：
- 在庫移動にはstock.change_stockitem権限が必要
- オーナー設定時はSTOCK_OWNERSHIP_CONTROL設定による追加制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | 在庫アイテム詳細画面 | 主機能 | 在庫移動・調整・分割・統合操作 |
| 15 | 在庫ロケーション一覧・詳細画面 | 参照画面 | 移動先ロケーション選択 |

## 機能種別

在庫操作 / 数量管理 / 履歴管理

## 入力仕様

### 在庫移動

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| items | array | Yes | 移動対象在庫アイテムリスト | |
| location | integer | Yes | 移動先ロケーションID | structural=Falseのロケーションのみ |
| notes | string | No | 備考 | |

### 在庫数量調整

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| items | array | Yes | 調整対象在庫アイテムリスト | |
| quantity | decimal | Yes | 調整後数量またはカウント結果 | 0以上 |
| notes | string | No | 備考 | |

### 在庫分割

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| quantity | decimal | Yes | 分割する数量 | 元在庫の数量未満 |
| location | integer | No | 分割後のロケーション | |
| notes | string | No | 備考 | |

### 在庫統合

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| items | set | Yes | 統合対象在庫アイテムセット | 同一部品、同一バッチ、同一ロケーション |
| location | integer | No | 統合後のロケーション | |
| notes | string | No | 備考 | |

### 入力データソース

- 画面入力（在庫操作フォーム）
- REST API（各種在庫操作エンドポイント）
- 受注出荷処理
- 製造工程処理

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pk | integer | 在庫アイテムの主キー |
| quantity | decimal | 操作後の数量 |
| location | integer | 操作後のロケーションID |
| tracking_entry | object | 追跡履歴エントリ |

### 出力先

- 画面表示（在庫詳細の更新）
- REST API レスポンス
- 追跡履歴テーブル

## 処理フロー

### 処理シーケンス

```
1. 在庫移動
   └─ 移動先検証 → 各アイテムのロケーション更新 → 追跡エントリ作成

2. 在庫数量調整
   ├─ カウント：現在数量との差分を計算 → 数量更新 → 追跡エントリ
   ├─ 追加：数量加算 → 追跡エントリ
   └─ 削除：数量減算 → ゼロ時の処理（削除 or 保持）→ 追跡エントリ

3. 在庫分割
   └─ 分割数量検証 → 元在庫の数量減算 → 新在庫アイテム作成 → 追跡エントリ（両方）

4. 在庫統合
   └─ 統合条件検証 → 数量集計 → 1つに統合 → 他を削除 → 追跡エントリ

5. 顧客割当
   └─ 顧客検証 → 数量分割（必要時）→ ステータス更新 → 追跡エントリ
```

### フローチャート

```mermaid
flowchart TD
    A[在庫操作開始] --> B{操作種別}
    B -->|移動| C[移動先ロケーション検証]
    C --> D{構造的ロケーション?}
    D -->|Yes| E[エラー]
    D -->|No| F[各アイテムのロケーション更新]
    F --> G[追跡エントリ作成]

    B -->|数量調整| H{調整種別}
    H -->|カウント| I[差分計算]
    H -->|追加| J[数量加算]
    H -->|削除| K[数量減算]
    I --> L[数量更新]
    J --> L
    K --> M{数量=0?}
    M -->|Yes| N{delete_on_deplete?}
    N -->|Yes| O[在庫削除]
    N -->|No| L
    M -->|No| L
    L --> G

    B -->|分割| P[分割数量検証]
    P --> Q{数量 < 元在庫?}
    Q -->|No| E
    Q -->|Yes| R[元在庫の数量減算]
    R --> S[新在庫アイテム作成]
    S --> T[両方に追跡エントリ]

    B -->|統合| U[統合条件検証]
    U --> V{同一部品・バッチ・ロケーション?}
    V -->|No| E
    V -->|Yes| W[数量集計]
    W --> X[統合先に数量設定]
    X --> Y[他の在庫を削除]
    Y --> G

    G --> Z[終了]
    O --> Z
    T --> Z
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 構造的ロケーション移動禁止 | 構造的ロケーションへの移動は不可 | location.structural |
| BR-002 | 分割数量制限 | 分割数量は元在庫の数量未満 | splitStock |
| BR-003 | シリアル化在庫分割禁止 | シリアル化された在庫は分割不可 | serialized=True |
| BR-004 | 統合条件 | 統合は同一部品、同一バッチ、同一ロケーションのみ | merge_stock_items |
| BR-005 | シリアル化在庫統合禁止 | シリアル化された在庫は統合不可 | serialized=True |
| BR-006 | ゼロ在庫処理 | delete_on_deplete=Trueかつ数量=0で自動削除 | 数量調整後 |
| BR-007 | 顧客割当時ロケーション | 顧客に割り当てられた在庫のロケーションはNullになる | allocateToCustomer |
| BR-008 | 割当クリア | 顧客・受注への割当時、既存のビルド/受注割当をクリア | clearAllocations |

### 計算ロジック

```python
# 在庫移動
@transaction.atomic
def move(self, location, user, **kwargs):
    notes = kwargs.get('notes', '')
    self.location = location
    self.save()
    self.add_tracking_entry(
        StockHistoryCode.STOCK_MOVE,
        user,
        deltas={'location': location.pk},
        location=location,
        notes=notes,
    )

# 在庫分割
@transaction.atomic
def splitStock(self, quantity, location, user, **kwargs):
    # 元在庫から数量を減らす
    self.take_stock(quantity, user, notes=notes, **kwargs)

    # 新しい在庫アイテムを作成
    new_stock = StockItem.objects.create(
        part=self.part,
        quantity=quantity,
        location=location or self.location,
        batch=self.batch,
        # ... その他の属性をコピー
    )

    # 追跡エントリ
    new_stock.add_tracking_entry(
        StockHistoryCode.SPLIT_FROM_PARENT,
        user,
        deltas={'stockitem': self.pk},
    )
    self.add_tracking_entry(
        StockHistoryCode.SPLIT_CHILD_ITEM,
        user,
        deltas={'stockitem': new_stock.pk},
    )

    return new_stock

# 在庫統合
@transaction.atomic
def merge_stock_items(self, items: set, user=None, **kwargs):
    # 数量を集計
    total_quantity = sum(item.quantity for item in items)

    # 自身に数量を設定
    self.quantity = total_quantity
    self.save()

    # 他の在庫を削除
    for item in items:
        if item != self:
            item.delete()

    self.add_tracking_entry(
        StockHistoryCode.MERGED_STOCK_ITEMS,
        user,
        deltas={'quantity': float(total_quantity)},
    )
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 在庫移動 | stock_stockitem | UPDATE | locationフィールド更新 |
| 数量調整 | stock_stockitem | UPDATE | quantityフィールド更新 |
| 在庫分割 | stock_stockitem | UPDATE + INSERT | 元在庫更新、新在庫作成 |
| 在庫統合 | stock_stockitem | UPDATE + DELETE | 統合先更新、他を削除 |
| 追跡エントリ | stock_stockitemtracking | INSERT | 履歴レコード挿入 |

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

#### stock_stockitem

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| 移動 | location_id | 新ロケーションID | |
| 数量調整 | quantity | 調整後数量 | |
| 分割（元） | quantity | 元数量 - 分割数量 | |
| 分割（新） | 全フィールド | 元在庫からコピー + 新数量 | INSERTとして |
| 統合 | quantity | 統合後合計数量 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ValidationError | バリデーションエラー | 構造的ロケーションへ移動 | 別のロケーションを選択 |
| ValidationError | バリデーションエラー | 分割数量が元在庫以上 | 分割数量を減らす |
| ValidationError | バリデーションエラー | シリアル化在庫を分割/統合 | シリアル化在庫は操作不可 |
| ValidationError | バリデーションエラー | 異なる部品/バッチ/ロケーションを統合 | 同一条件の在庫のみ統合可 |

### リトライ仕様

特になし

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

- すべての在庫操作はtransaction.atomic()内で実行
- 分割・統合は複数テーブルへの操作を単一トランザクションで実行
- 追跡エントリの作成は在庫操作と同一トランザクション

## パフォーマンス要件

- 単一在庫移動: 100ms以内
- バルク移動: 100件で3秒以内
- 在庫統合: 10件で1秒以内

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

- ユーザー権限に基づくアクセス制御
- オーナー権限制御（STOCK_OWNERSHIP_CONTROL）
- 操作履歴の完全記録

## 備考

- すべての操作はStockHistoryCodeで履歴に記録
- シリアル化された在庫は分割・統合不可
- trigger_eventでプラグインへのイベント通知

---

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

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

### 推奨読解順序

#### Step 1: 在庫操作メソッドを理解する

StockItemの各種操作メソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | models.py | `src/backend/InvenTree/stock/models.py` | 在庫移動・調整・分割・統合メソッド |

**読解のコツ**:
- すべての操作メソッドはtransaction.atomic()でラップ
- add_tracking_entry()で履歴を記録
- trigger_event()でプラグインに通知

**主要処理フロー**:
- **顧客割当**: allocateToCustomer（1331-1383行目）
- **顧客返品**: return_from_customer（1385-1408行目）
- **在庫返却**: return_to_stock（1410-1499行目）
- **在庫分割**: splitStock（対応するメソッド）
- **在庫統合**: merge_stock_items（対応するメソッド）

#### Step 2: 追跡コードを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | status_codes.py | `src/backend/InvenTree/stock/status_codes.py` | StockHistoryCode定義（41-98行目） |

**主要コード**:
- STOCK_COUNT (10): 棚卸
- STOCK_ADD (11): 数量追加
- STOCK_REMOVE (12): 数量削除
- STOCK_MOVE (20): 移動
- SPLIT_FROM_PARENT (40): 分割元
- SPLIT_CHILD_ITEM (42): 分割先
- MERGED_STOCK_ITEMS (45): 統合

#### Step 3: APIエンドポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | api.py | `src/backend/InvenTree/stock/api.py` | 在庫操作APIビュー |
| 3-2 | serializers.py | `src/backend/InvenTree/stock/serializers.py` | 操作用シリアライザ |

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

```
REST API Request
    │
    ├─ StockTransfer (api.py)
    │      └─ StockItem.move()
    │             ├─ location更新
    │             └─ add_tracking_entry(STOCK_MOVE)
    │
    ├─ StockCount / StockAdd / StockRemove (api.py)
    │      └─ StockItem.add_stock() / take_stock()
    │             ├─ quantity更新
    │             └─ add_tracking_entry(STOCK_COUNT/ADD/REMOVE)
    │
    ├─ StockMerge (api.py)
    │      └─ StockItem.merge_stock_items()
    │             ├─ 数量集計
    │             ├─ 他在庫削除
    │             └─ add_tracking_entry(MERGED_STOCK_ITEMS)
    │
    └─ StockSplit (api.py)
           └─ StockItem.splitStock()
                  ├─ 元在庫.take_stock()
                  ├─ 新在庫作成
                  └─ add_tracking_entry(SPLIT_*)
```

### データフロー図

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

移動リクエスト ───▶ StockTransferSerializer ───▶ JSONレスポンス
                        │
                        ▼
                 StockItem.move()
                        │
                        ├─ ロケーション更新
                        │
                        └─ add_tracking_entry()
                                │
                                ├─▶ stock_stockitem テーブル
                                │
                                └─▶ stock_stockitemtracking テーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| models.py | `src/backend/InvenTree/stock/models.py` | ソース | 在庫操作メソッド定義 |
| status_codes.py | `src/backend/InvenTree/stock/status_codes.py` | ソース | StockHistoryCode定義 |
| api.py | `src/backend/InvenTree/stock/api.py` | ソース | 在庫操作APIビュー定義 |
| serializers.py | `src/backend/InvenTree/stock/serializers.py` | ソース | 操作シリアライザ定義 |
| StockDetail.tsx | `src/frontend/src/pages/stock/StockDetail.tsx` | ソース | フロントエンド操作UI |
