# 画面設計書 5-バーコードスキャン画面

## 概要

本ドキュメントは、InvenTree Flutterアプリケーションのバーコードスキャン画面の画面設計を定義するものである。

### 本画面の処理概要

バーコードスキャン画面はカメラまたは外部ウェッジスキャナーを使用してバーコードを読み取り、InvenTreeデータベース内のアイテム（部品、在庫、注文など）を識別・表示する機能を提供する。

**業務上の目的・背景**：倉庫や工場での在庫管理において、バーコードスキャンは最も効率的なアイテム識別方法である。本画面はスマートフォンのカメラまたは外部スキャナーでバーコードを読み取り、該当するアイテムの詳細画面へ直接遷移することで、手作業での検索時間を大幅に削減する。また、カスタムバーコードの割り当て・解除機能により、既存のバーコードシステムとの統合も可能にする。

**画面へのアクセス方法**：BottomAppBarのバーコードアイコンをタップ、または各詳細画面のバーコードアクションボタンから起動。

**主要な操作・処理内容**：
1. カメラによるバーコードスキャン（CameraBarcodeController）
2. 外部ウェッジスキャナーによるバーコード入力（WedgeBarcodeController）
3. スキャンしたバーコードのサーバー照合（barcode/ API）
4. 照合成功時の該当詳細画面への遷移
5. 照合失敗時のエラー通知（音声・視覚フィードバック）
6. カスタムバーコードの割り当て（barcode/link/ API）
7. カスタムバーコードの解除（barcode/unlink/ API）

**画面遷移**：
- 遷移元：BottomAppBar（任意の画面）、各詳細画面のバーコードアクション
- 遷移先：部品詳細画面、在庫アイテム詳細画面、在庫ロケーション画面、購入注文詳細画面、販売注文詳細画面、会社詳細画面、サプライヤー部品詳細画面、製造業者部品詳細画面

**権限による表示制御**：バーコード照合結果に応じた画面遷移はユーザーの権限に依存。遷移先画面の閲覧権限がない場合はエラー表示。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 42 | バーコードスキャン | 主機能 | カメラまたはウェッジスキャナでバーコードをスキャン |
| 43 | バーコード照合 | 主機能 | スキャンしたバーコードをパーツ・在庫・発注等に照合 |
| 44 | バーコード割当 | 補助機能 | カスタムバーコードをアイテムに割り当て |
| 45 | バーコード解除 | 補助機能 | アイテムからカスタムバーコードを解除 |
| 5 | パーツ詳細表示 | 遷移先機能 | パーツバーコード照合成功時にパーツ詳細画面へ遷移 |
| 15 | 在庫アイテム詳細表示 | 遷移先機能 | 在庫バーコード照合成功時に在庫詳細画面へ遷移 |
| 24 | 発注詳細表示 | 遷移先機能 | 発注バーコード照合成功時に発注詳細画面へ遷移 |
| 33 | 受注詳細表示 | 遷移先機能 | 受注バーコード照合成功時に受注詳細画面へ遷移 |

## 画面種別

スキャン画面（カメラ/入力型）

## URL/ルーティング

- Flutter Widget：`InvenTreeBarcodeController`（StatefulWidget）
- 状態クラス：`InvenTreeBarcodeControllerState`
- 主要ハンドラ：`BarcodeScanHandler`、`UniqueBarcodeHandler`

## 入出力項目

| 項目名 | 入力/出力 | データ型 | 必須 | 説明 |
|--------|----------|----------|------|------|
| handler | 入力 | BarcodeHandler | 任意 | バーコード処理ハンドラ（デフォルト：BarcodeScanHandler） |
| barcode | 入力 | String | - | スキャンされたバーコードデータ |

## 表示項目

| 項目名 | データ型 | 説明 | 取得元 |
|--------|----------|------|--------|
| カメラプレビュー | Widget | カメラによるライブプレビュー | CameraBarcodeController |
| オーバーレイテキスト | String | スキャンモードの説明テキスト | handler.getOverlayText() |
| スキャン結果メッセージ | SnackBar | 照合成功/失敗のメッセージ | showSnackIcon() |

## イベント仕様

### 1-バーコードスキャン実行

カメラまたは外部スキャナーでバーコードを読み取った際の処理。

**処理フロー**：
1. バーコードデータが検出される
2. `handleBarcodeData(data)`が呼び出される
3. `processingBarcode`フラグをtrueに設定
4. ローディングオーバーレイを表示
5. `pauseScan()`でスキャンを一時停止
6. `handler.processBarcode(data)`でバーコード処理
7. 設定された遅延時間（INV_BARCODE_SCAN_DELAY）後に処理完了
8. `resumeScan()`でスキャンを再開
9. `processingBarcode`フラグをfalseに戻す

### 2-バーコード照合成功（一般スキャン）

スキャンしたバーコードがデータベース内のアイテムに一致した場合。

**処理フロー**：
1. `processBarcode()`がサーバーに`POST barcode/`リクエストを送信
2. レスポンスに`success`キーが含まれる場合、`onBarcodeMatched()`を呼び出し
3. レスポンスからモデルタイプとPKを抽出
4. モデルタイプに応じた`handle*`メソッドを呼び出し：
   - `handlePart()`: 部品詳細画面へ遷移
   - `handleStockItem()`: 在庫アイテム詳細画面へ遷移
   - `handleStockLocation()`: 在庫ロケーション画面へ遷移
   - `handleSupplierPart()`: サプライヤー部品詳細画面へ遷移
   - `handleManufacturerPart()`: 製造業者部品詳細画面へ遷移
   - `handleCompany()`: 会社詳細画面へ遷移
   - `handlePurchaseOrder()`: 購入注文詳細画面へ遷移（API v107以上）
   - `handleSalesOrder()`: 販売注文詳細画面へ遷移（API v107以上）
5. 成功音を再生（barcodeSuccessTone）
6. スキャン画面を閉じて詳細画面を表示

### 3-バーコード照合失敗

スキャンしたバーコードがデータベース内に見つからない場合。

**処理フロー**：
1. レスポンスに`error`キーが含まれる、またはステータスコード400以上
2. `onBarcodeUnknown()`を呼び出し
3. 失敗音を再生（barcodeFailureTone）
4. SnackBarでエラーメッセージを表示（L10().barcodeNoMatch）
5. スキャン画面は閉じず、再スキャン可能な状態を維持

### 4-カスタムバーコード割り当て

アイテムに新しいカスタムバーコードを割り当てる処理。

**処理フロー**：
1. 詳細画面のSpeedDialから「バーコード割り当て」を選択
2. `UniqueBarcodeHandler`を使用してスキャン画面を起動
3. 既存アイテムに紐づいていないバーコードをスキャン
4. `InvenTreeAPI().linkBarcode()`で`POST barcode/link/`を送信
5. 成功時は成功メッセージを表示、画面をリフレッシュ

### 5-カスタムバーコード解除

アイテムからカスタムバーコードを解除する処理。

**処理フロー**：
1. 詳細画面のSpeedDialから「バーコード解除」を選択
2. `InvenTreeAPI().unlinkBarcode()`で`POST barcode/unlink/`を送信
3. 成功時は成功メッセージを表示、画面をリフレッシュ

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| バーコード照合 | なし | SELECT（API経由） | バーコードに対応するアイテムを検索 |
| バーコード割当 | barcode_assignments（サーバー） | INSERT | カスタムバーコードとアイテムの紐付けを作成 |
| バーコード解除 | barcode_assignments（サーバー） | DELETE | カスタムバーコードとアイテムの紐付けを削除 |

### テーブル別更新項目詳細

#### barcode_assignments（サーバーAPI）

| 操作 | 項目 | 更新値 | 備考 |
|-----|------|-------|------|
| INSERT | barcode | スキャンしたバーコードデータ | linkBarcode() |
| INSERT | model_type | 対象モデルタイプ | stockitem, part等 |
| INSERT | model_pk | 対象アイテムのPK | |
| DELETE | - | - | unlinkBarcode()で削除 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | オーバーレイ | L10().barcodeScanGeneral | 一般スキャンモード |
| MSG-002 | オーバーレイ | L10().barcodeScanAssign | バーコード割り当てモード |
| MSG-003 | 成功 | showSnackIcon（成功） | バーコード照合成功時 |
| MSG-004 | エラー | L10().barcodeNoMatch | バーコードが見つからない時 |
| MSG-005 | エラー | L10().barcodeError | バーコード処理エラー時 |
| MSG-006 | エラー | L10().barcodeUnknown | 不明なレスポンス時 |
| MSG-007 | 成功 | L10().barcodeAssigned | バーコード割り当て成功時 |
| MSG-008 | エラー | L10().barcodeNotAssigned | バーコード割り当て失敗時 |
| MSG-009 | ダイアログ | L10().barcodeError（タイトル） | エラー詳細ダイアログ |

## 例外処理

| 例外種別 | 発生条件 | 対応処理 |
|---------|---------|---------|
| バーコード空 | スキャンデータが空文字 | 失敗音を再生、エラーSnackBar表示 |
| API応答なし | サーバーからの応答がない | 失敗音を再生、エラーSnackBar表示 |
| 不正なレスポンス | response.isValid()がfalse | Sentryにレポート、エラー表示 |
| ウィジェット未マウント | 処理中に画面が閉じられた | mountedチェックで処理をスキップ |
| 対応するモデルなし | 認識されたバーコードに対応するハンドラがない | 失敗音を再生、エラーSnackBar表示 |

## 備考

- スキャナータイプ（カメラ/ウェッジ）は設定（INV_BARCODE_SCAN_TYPE）で切り替え可能
- スキャン後の遅延時間は設定（INV_BARCODE_SCAN_DELAY）で調整可能
- 注文バーコードのサポートはAPI v107以上が必要
- 成功/失敗時にはそれぞれ異なるトーン音が再生される
- OneContextを使用して画面遷移を行うため、コンテキストに依存しない遷移が可能

---

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

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

### 推奨読解順序

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

まず、バーコード処理の基本構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | handler.dart | `lib/barcode/handler.dart` | BarcodeHandlerクラスの基本構造（processBarcode, onBarcodeMatched, onBarcodeUnknown）を理解する |
| 1-2 | controller.dart | `lib/barcode/controller.dart` | InvenTreeBarcodeControllerとその状態クラスの構造を理解する |

**読解のコツ**: BarcodeHandlerは抽象的な処理を定義し、具体的な実装はサブクラス（BarcodeScanHandler, UniqueBarcodeHandler等）で行う。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | barcode.dart | `lib/barcode/barcode.dart` | scanBarcode()関数とBarcodeScanHandlerクラスを理解する |

**主要処理フロー**:
1. **行74-103**: `scanBarcode()`でバーコードスキャン画面を起動
2. **行84-98**: 設定に応じてCameraBarcodeControllerまたはWedgeBarcodeControllerを選択
3. **行117-340**: `BarcodeScanHandler`クラスで一般的なバーコード処理を実装
4. **行135-143**: `handlePart()`で部品詳細画面へ遷移
5. **行149-158**: `handleStockItem()`で在庫詳細画面へ遷移
6. **行246-339**: `onBarcodeMatched()`でモデルタイプに応じた処理を分岐

#### Step 3: 具体的なコントローラーを理解する

カメラとウェッジスキャナーの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | camera_controller.dart | `lib/barcode/camera_controller.dart` | CameraBarcodeControllerの実装 |
| 3-2 | wedge_controller.dart | `lib/barcode/wedge_controller.dart` | WedgeBarcodeControllerの実装 |

#### Step 4: カスタムバーコード機能を理解する

バーコード割り当て・解除機能を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | barcode.dart | `lib/barcode/barcode.dart` | UniqueBarcodeHandlerとcustomBarcodeAction()を理解する |
| 4-2 | api.dart | `lib/api.dart` | linkBarcode()とunlinkBarcode()を理解する |

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

```
scanBarcode() (barcode.dart)
    |
    +-- handler ?? BarcodeScanHandler()
    |
    +-- [設定に応じて]
            |
            +-- CameraBarcodeController(handler)
            |       +-- handleBarcodeData()
            |               +-- handler.processBarcode()
            |
            +-- WedgeBarcodeController(handler)
                    +-- handleBarcodeData()
                            +-- handler.processBarcode()

BarcodeHandler.processBarcode() (handler.dart)
    |
    +-- InvenTreeAPI().post("barcode/", ...)
            |
            +-- [success]
            |       +-- onBarcodeMatched()
            |               +-- BarcodeScanHandler.onBarcodeMatched()
            |                       |
            |                       +-- handlePart() --> PartDetailWidget
            |                       +-- handleStockItem() --> StockDetailWidget
            |                       +-- handleStockLocation() --> LocationDisplayWidget
            |                       +-- handleSupplierPart() --> SupplierPartDetailWidget
            |                       +-- handleManufacturerPart() --> ManufacturerPartDetailWidget
            |                       +-- handleCompany() --> CompanyDetailWidget
            |                       +-- handlePurchaseOrder() --> PurchaseOrderDetailWidget
            |                       +-- handleSalesOrder() --> SalesOrderDetailWidget
            |
            +-- [error]
                    +-- onBarcodeUnknown()
                            +-- barcodeFailureTone()
                            +-- showSnackIcon()

customBarcodeAction() (barcode.dart)
    |
    +-- [barcode.isEmpty]
    |       +-- UniqueBarcodeHandler(callback)
    |               +-- scanBarcode()
    |               +-- callback(barcode)
    |                       +-- InvenTreeAPI().linkBarcode()
    |
    +-- [barcode.isNotEmpty]
            +-- InvenTreeAPI().unlinkBarcode()
```

### データフロー図

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

バーコードデータ        -->  handleBarcodeData()             -->  processingBarcode=true
(カメラ/ウェッジ)               |
                                v
                        handler.processBarcode()            -->  APIリクエスト
                                |
                                v
POST barcode/           -->  APIレスポンス解析              -->  モデルタイプ/PK抽出
                                |
        +-------------------+---+---+
        |                           |
        v                           v
[success]                   [error]
onBarcodeMatched()          onBarcodeUnknown()
        |                           |
        v                           v
handle*()                   barcodeFailureTone()
        |                   showSnackIcon()
        v
Navigator.push()            -->  詳細画面表示

[カスタムバーコード割当]
UniqueBarcodeHandler   -->  scanBarcode()                   -->  バーコード取得
                                |
                                v
                        linkBarcode()                       -->  APIリクエスト
                                |
                                v
POST barcode/link/      -->  成功/失敗                      -->  メッセージ表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| barcode.dart | `lib/barcode/barcode.dart` | ソース | バーコード機能のメインエントリーポイント |
| handler.dart | `lib/barcode/handler.dart` | ソース | BarcodeHandler基底クラス |
| controller.dart | `lib/barcode/controller.dart` | ソース | InvenTreeBarcodeController基底クラス |
| camera_controller.dart | `lib/barcode/camera_controller.dart` | ソース | カメラ使用のバーコードコントローラー |
| wedge_controller.dart | `lib/barcode/wedge_controller.dart` | ソース | ウェッジスキャナー用コントローラー |
| tones.dart | `lib/barcode/tones.dart` | ソース | 成功/失敗のトーン音再生 |
| api.dart | `lib/api.dart` | ソース | linkBarcode()、unlinkBarcode()の実装 |
| preferences.dart | `lib/preferences.dart` | ソース | INV_BARCODE_SCAN_TYPE、INV_BARCODE_SCAN_DELAY設定 |
| part_detail.dart | `lib/widget/part/part_detail.dart` | ソース | 部品詳細画面（遷移先） |
| stock_detail.dart | `lib/widget/stock/stock_detail.dart` | ソース | 在庫詳細画面（遷移先） |
| location_display.dart | `lib/widget/stock/location_display.dart` | ソース | ロケーション画面（遷移先） |
| purchase_order_detail.dart | `lib/widget/order/purchase_order_detail.dart` | ソース | 購入注文詳細画面（遷移先） |
| sales_order_detail.dart | `lib/widget/order/sales_order_detail.dart` | ソース | 販売注文詳細画面（遷移先） |
| company_detail.dart | `lib/widget/company/company_detail.dart` | ソース | 会社詳細画面（遷移先） |
| supplier_part_detail.dart | `lib/widget/company/supplier_part_detail.dart` | ソース | サプライヤー部品詳細画面（遷移先） |
| manufacturer_part_detail.dart | `lib/widget/company/manufacturer_part_detail.dart` | ソース | 製造業者部品詳細画面（遷移先） |
