# 機能設計書 17-在庫数量調整

## 概要

本ドキュメントは、InvenTreeモバイルアプリにおける在庫数量調整機能の設計仕様を定義する。

### 本機能の処理概要

本機能は、在庫アイテムの数量を追加・削除・カウントする機能である。在庫の入出庫処理や棚卸し作業において、在庫数量を正確に管理するために使用される。

**業務上の目的・背景**：在庫管理業務において、在庫数量の調整は日常的に発生する。入荷による追加、出荷による削除、棚卸しによるカウント（実数確認）など、様々な理由で在庫数量を変更する必要がある。モバイルアプリから現場で直接数量調整を行うことで、リアルタイムな在庫管理が可能となる。

**機能の利用シーン**：
- 入荷時に在庫数量を追加する場合
- 出荷やスクラップで在庫を削除する場合
- 棚卸し時に実際の数量でカウントする場合
- 在庫の一部を消費した場合

**主要な処理内容**：
1. 追加（Add Stock）：既存の在庫に数量を追加
2. 削除（Remove Stock）：既存の在庫から数量を削除
3. カウント（Count Stock）：在庫数量を指定値に設定（棚卸し）

**関連システム・外部連携**：InvenTree APIの `stock/add/`、`stock/remove/`、`stock/count/` エンドポイント（POST）を使用する。

**権限による制御**：`stock` ロールの `change` 権限が必要である。シリアル化されたアイテムでは数量調整不可。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | 在庫アイテム詳細画面 | 主画面 | 追加/削除/カウントボタン押下時に数量調整フォーム表示 |

## 機能種別

データ更新（UPDATE）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| items[].pk | int | Yes | 在庫アイテムID | 正の整数 |
| items[].quantity | double | Yes | 調整数量 | 正の数値（追加/削除）または任意（カウント） |
| notes | String | No | 備考（調整理由等） | - |

### 入力データソース

- 在庫詳細画面に表示されている在庫アイテム
- ユーザーによる数量入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 更新後quantity | double | 調整後の在庫数量 |

### 出力先

- InvenTreeデータベース（StockItemテーブルのquantity更新）
- 在庫詳細画面の表示更新

## 処理フロー

### 処理シーケンス

```
1. アクションボタンの表示
   └─ シリアル化されていない在庫のみ表示
2. ユーザーがアクションを選択（追加/削除/カウント）
3. 調整ダイアログの表示
   └─ quantity, notesフィールドを表示
4. ユーザーによる入力
   └─ 調整数量とオプションの備考を入力
5. APIリクエストの実行
   ├─ 追加：POST stock/add/
   ├─ 削除：POST stock/remove/
   └─ カウント：POST stock/count/
6. レスポンスの処理
   ├─ 成功：画面をリフレッシュして更新後の数量を表示
   └─ 失敗：エラーメッセージを表示
```

### フローチャート

```mermaid
flowchart TD
    A[在庫詳細画面] --> B{シリアル化?}
    B -->|Yes| C[数量調整ボタン非表示]
    B -->|No| D[数量調整ボタン表示]
    D --> E{ユーザー選択}
    E -->|追加| F[_addStockDialog]
    E -->|削除| G[_removeStockDialog]
    E -->|カウント| H[_countStockDialog]
    F --> I[調整ダイアログ表示]
    G --> I
    H --> I
    I --> J[ユーザー入力]
    J --> K{入力検証}
    K -->|不正| L[エラー表示]
    L --> J
    K -->|正常| M{アクション種別}
    M -->|追加| N[POST stock/add/]
    M -->|削除| O[POST stock/remove/]
    M -->|カウント| P[POST stock/count/]
    N --> Q{成功?}
    O --> Q
    P --> Q
    Q -->|No| R[エラー表示]
    Q -->|Yes| S[画面リフレッシュ]
    S --> T[更新完了メッセージ]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-17-01 | シリアル制限 | シリアル化されたアイテムでは数量調整不可 | isSerialized() == true |
| BR-17-02 | 追加数量 | 追加する数量は正の値である必要がある | 追加操作時 |
| BR-17-03 | 削除数量 | 削除する数量は正の値で、現在数量以下である必要がある | 削除操作時 |
| BR-17-04 | カウント数量 | カウント値は0以上の任意の値（現在値をデフォルト表示） | カウント操作時 |
| BR-17-05 | 履歴記録 | 数量調整操作は履歴として記録される | 常時 |

### 計算ロジック

- 追加後数量 = 現在数量 + 入力数量
- 削除後数量 = 現在数量 - 入力数量
- カウント後数量 = 入力数量（直接設定）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 追加 | StockItem | UPDATE | 在庫数量を増加 |
| 削除 | StockItem | UPDATE | 在庫数量を減少 |
| カウント | StockItem | UPDATE | 在庫数量を指定値に設定 |
| 履歴追加 | StockTracking | INSERT | 操作履歴を記録 |

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

#### StockItem（InvenTree API経由）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | quantity | 計算後の数量 | 追加/削除/カウントにより異なる |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | バリデーションエラー | 不正な数量値 | エラーメッセージを表示 |
| 401 | 認証エラー | トークン無効または期限切れ | 再ログインを促す |
| 403 | 権限エラー | change権限がない | 権限確認メッセージを表示 |
| 500 | サーバーエラー | サーバー内部エラー | リトライまたはエラー表示 |

### リトライ仕様

サーバーエラー時は手動でダイアログを再表示して再送信可能。

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

単一レコードの更新のため、明示的なトランザクション管理は不要。

## パフォーマンス要件

- ダイアログ表示は即座に行う
- API応答は通常数秒以内

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

- API認証トークンによるアクセス制御
- `stock` ロールの `change` 権限チェック
- HTTPS通信によるデータ保護

## 備考

- 数量調整操作は取り消し不可（履歴で追跡は可能）
- カウント操作時は現在の数量がデフォルト値として設定
- 備考は任意だが、調整理由の記録を推奨

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stock.dart | `lib/inventree/stock.dart` | 数量調整関連のURLとメソッドを理解する |

**読解のコツ**:
- **181-187行目**: 数量調整用URLの定義（transferStockUrl, countStockUrl, addStockUrl, removeStockUrl）
- **501-534行目**: adjustStock()メソッドの基底実装
- **536-552行目**: countStock(), addStock(), removeStock()の各メソッド
- **434行目**: isSerialized()でシリアル化判定

#### Step 2: ダイアログ実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | stock_detail.dart | `lib/widget/stock/stock_detail.dart` | 各数量調整ダイアログの実装 |

**主要処理フロー**:
- **367-391行目**: _addStockDialog()の実装
- **402-426行目**: _removeStockDialog()の実装
- **428-456行目**: _countStockDialog()の実装
- **93-118行目**: actionButtons()でシリアル化チェックとボタン表示

#### Step 3: フォームフィールドの構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | stock_detail.dart | `lib/widget/stock/stock_detail.dart` | 数量調整フォームのフィールド定義 |

**主要処理フロー**:
- **368-377行目**: 追加ダイアログのfieldsマップ
- **403-412行目**: 削除ダイアログのfieldsマップ
- **429-442行目**: カウントダイアログのfieldsマップ（現在値をデフォルト設定）

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

```
StockDetailWidget.actionButtons()
    │
    ├─ isSerialized() チェック
    │      └─ シリアル化時はボタン非表示
    │
    ├─ _addStockDialog()
    │      └─ launchApiForm()
    │              └─ POST stock/add/
    │
    ├─ _removeStockDialog()
    │      └─ launchApiForm()
    │              └─ POST stock/remove/
    │
    └─ _countStockDialog()
           └─ launchApiForm()
                   └─ POST stock/count/
```

### データフロー図

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

pk + quantity ───▶ launchApiForm() ───▶ quantity更新
(items配列)             │                     │
                        ▼                     ▼
              POST stock/{action}/ ───▶ StockItemレコード
              (add/remove/count)              │
                        │                     ▼
                        └───────────▶ refresh()
                                          │
                                          ▼
                                   画面表示更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| stock_detail.dart | `lib/widget/stock/stock_detail.dart` | ソース | 数量調整ダイアログの実装 |
| stock.dart | `lib/inventree/stock.dart` | ソース | 数量調整URL定義、adjustStock()メソッド |
| api_form.dart | `lib/api_form.dart` | ソース | launchApiForm()関数 |
| api.dart | `lib/api.dart` | ソース | InvenTree API通信クラス |
| snacks.dart | `lib/widget/snacks.dart` | ソース | 成功/失敗メッセージ表示 |
