# 機能設計書 42-バーコードスキャン

## 概要

本ドキュメントは、InvenTreeアプリにおけるバーコードスキャン機能の設計を記述する。カメラまたはウェッジスキャナを使用してバーコードを読み取り、InvenTreeサーバーと連携してデータを処理する基盤機能である。

### 本機能の処理概要

バーコードスキャン機能は、デバイスのカメラまたは外部バーコードスキャナ（キーボードウェッジ）を使用してバーコードを読み取り、そのデータをInvenTreeサーバーに送信して照合・処理を行う機能である。本機能は他のバーコード関連機能（照合、割当、在庫操作等）の基盤となるコアコンポーネントである。

**業務上の目的・背景**：在庫管理や物流業務において、バーコードスキャンは作業効率を大幅に向上させる重要な技術である。手入力によるエラーを削減し、リアルタイムでの在庫追跡を可能にする。InvenTreeアプリでは、部品・在庫・発注など様々なアイテムをバーコードで迅速に特定・操作できることが求められる。

**機能の利用シーン**：
- 倉庫での在庫棚卸し作業時にアイテムを迅速に特定
- 入荷時に発注品をバーコードでスキャンして受入処理
- 出荷時に在庫アイテムをスキャンして引当・出荷処理
- 部品や在庫の位置確認時にバーコードで即座にアイテムを特定
- ラベル印刷されたアイテムの検索・詳細表示

**主要な処理内容**：
1. スキャン方式の選択（カメラ/ウェッジスキャナ）と設定読み込み
2. カメラコントローラーの初期化とバーコード認識処理
3. ウェッジスキャナからのキーボード入力の解釈
4. スキャン結果のInvenTree APIへの送信
5. 成功/失敗の音声フィードバックとUI通知
6. スキャン一時停止/再開の制御

**関連システム・外部連携**：
- InvenTree REST API（barcode/エンドポイント）
- mobile_scannerライブラリ（カメラベースのバーコード読取）
- デバイスのカメラハードウェア
- 外部バーコードスキャナ（キーボードウェッジモード）

**権限による制御**：バーコードスキャン自体には特別な権限は不要だが、スキャン後の処理（在庫移動、発注受入等）には各処理に応じた権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | バーコードスキャン画面 | 主画面 | カメラまたはウェッジスキャナでバーコードをスキャン |
| 22 | 部品詳細画面 | 遷移先画面 | パーツバーコード照合成功時にパーツ詳細画面へ遷移 |
| 25 | 在庫アイテム詳細画面 | 遷移先画面 | 在庫バーコード照合成功時に在庫詳細画面へ遷移 |
| 27 | 購入注文詳細画面 | 遷移先画面 | 発注バーコード照合成功時に発注詳細画面へ遷移 |
| 29 | 販売注文詳細画面 | 遷移先画面 | 受注バーコード照合成功時に受注詳細画面へ遷移 |
| 23 | 在庫ロケーション画面 | 遷移先画面 | ロケーションバーコード照合成功時に遷移 |

## 機能種別

入力処理 / データ連携 / ハードウェア制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| handler | BarcodeHandler | No | バーコード処理ハンドラ（省略時はBarcodeScanHandler） | null許容 |
| context | BuildContext | Yes | Flutter BuildContext | 有効なContext |

### 入力データソース

- カメラからのバーコード画像データ（mobile_scanner経由）
- キーボードイベント（ウェッジスキャナモード）
- ユーザー設定（スキャン方式、スキャン遅延、単一スキャンモード等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| barcode | String | スキャンされたバーコード文字列 |
| 音声フィードバック | 音声ファイル | 成功音/失敗音 |
| UI通知 | SnackBar | スキャン結果のメッセージ |
| 画面遷移 | Navigator | 照合成功時の詳細画面 |

### 出力先

- BarcodeHandlerへのコールバック
- 画面遷移（Navigator）
- スピーカー（音声フィードバック）
- 画面表示（スナックバー通知）

## 処理フロー

### 処理シーケンス

```
1. スキャン画面起動（scanBarcode関数）
   ├─ ハンドラが未指定の場合、デフォルトのBarcodeScanHandlerを使用
   ├─ ユーザー設定からスキャン方式を取得
   │   ├─ BARCODE_CONTROLLER_CAMERA: CameraBarcodeController
   │   └─ BARCODE_CONTROLLER_WEDGE: WedgeBarcodeController
   └─ Navigator.pushでスキャン画面を表示

2. カメラモードの場合（CameraBarcodeController）
   ├─ 設定読み込み（スキャン遅延、単一スキャン等）
   ├─ WakelockPlus有効化（画面スリープ防止）
   ├─ MobileScannerController初期化
   └─ バーコード検出時にonScanSuccessコールバック

3. ウェッジモードの場合（WedgeBarcodeController）
   ├─ KeyboardListenerでキーイベント監視
   ├─ 250ms以内の連続入力をバーコードとして認識
   └─ 改行文字で確定、handleBarcodeData呼び出し

4. バーコードデータ処理（handleBarcodeData）
   ├─ 処理中フラグ確認（重複防止）
   ├─ ローディングオーバーレイ表示
   ├─ スキャン一時停止
   ├─ handler.processBarcode(data)呼び出し
   ├─ 設定に基づく遅延後にスキャン再開
   └─ ローディングオーバーレイ非表示

5. API処理（BarcodeHandler.processBarcode）
   ├─ バーコードデータをトリム
   ├─ 空データの場合はエラー音とスナックバー
   ├─ InvenTreeAPI.post("barcode/", body: {barcode: data})
   ├─ レスポンス解析
   │   ├─ success含む: onBarcodeMatched
   │   ├─ error含む: onBarcodeUnknown
   │   └─ その他: onBarcodeUnhandled
   └─ 各ハンドラでの後続処理
```

### フローチャート

```mermaid
flowchart TD
    A[scanBarcode呼び出し] --> B{handler指定あり?}
    B -->|No| C[BarcodeScanHandler生成]
    B -->|Yes| D[指定handlerを使用]
    C --> E[スキャン方式設定取得]
    D --> E
    E --> F{スキャン方式}
    F -->|Camera| G[CameraBarcodeController]
    F -->|Wedge| H[WedgeBarcodeController]
    G --> I[カメラ起動・バーコード検出]
    H --> J[キーボードイベント監視]
    I --> K{バーコード検出?}
    J --> K
    K -->|Yes| L[handleBarcodeData]
    K -->|No| I
    L --> M[processBarcode API呼び出し]
    M --> N{レスポンス}
    N -->|success| O[onBarcodeMatched]
    N -->|error| P[onBarcodeUnknown]
    N -->|その他| Q[onBarcodeUnhandled]
    O --> R[成功音・画面遷移]
    P --> S[失敗音・エラー表示]
    Q --> S
    R --> T{単一スキャン?}
    S --> T
    T -->|Yes| U[スキャン画面終了]
    T -->|No| V[スキャン再開]
    V --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-01 | スキャン方式選択 | ユーザー設定に基づきカメラまたはウェッジを選択 | スキャン開始時 |
| BR-42-02 | 重複スキャン防止 | 処理中のバーコードがある場合は新規スキャンを無視 | handleBarcodeData実行時 |
| BR-42-03 | 空バーコード拒否 | 空文字列またはトリム後空のバーコードは処理しない | processBarcode実行時 |
| BR-42-04 | 複数バーコード検出時 | 複数バーコードが検出された場合は処理しない | カメラモードのみ |
| BR-42-05 | ウェッジタイムアウト | 250ms以上間隔があいた入力は別バーコードとして扱う | ウェッジモードのみ |
| BR-42-06 | スキャン遅延設定 | 設定されたミリ秒数だけスキャン再開を遅延 | 連続スキャン時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| バーコード照合 | 複数テーブル | SELECT | バーコードに対応するアイテムを検索 |

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

本機能自体はデータベースの直接操作を行わない。APIを通じてサーバー側で処理される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| E-42-01 | 空バーコード | スキャン結果が空文字列 | 失敗音とスナックバー表示 |
| E-42-02 | API通信エラー | サーバーとの通信失敗 | 失敗音とエラースナックバー |
| E-42-03 | 不正レスポンス | サーバーが予期しないレスポンス | onBarcodeUnhandled、Sentryレポート |
| E-42-04 | カメラ初期化エラー | カメラアクセス失敗 | エラースナックバー、画面終了 |
| E-42-05 | 複数バーコード検出 | 一度に複数のバーコードを検出 | オーバーレイ色変更、処理スキップ |

### リトライ仕様

- API通信エラー時の自動リトライは行わない
- ユーザーは再度スキャンすることで手動リトライ可能

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

本機能はバーコード読み取りのみを担当し、トランザクションは後続のハンドラ処理で管理される。

## パフォーマンス要件

- バーコード検出からAPI呼び出しまで: 100ms以内
- API応答からフィードバック表示まで: 500ms以内
- カメラ起動時間: 2秒以内
- スキャン遅延設定: 500ms（デフォルト、ユーザー設定可能）

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

- 認証トークンを使用したAPIアクセス
- カメラパーミッションの適切な管理
- スキャンデータのログ出力はデバッグモードのみ

## 備考

- mobile_scannerパッケージを使用（qr_code_scannerからの移行）
- ズームスライダーによる手動ズーム調整が可能
- WakelockPlusによりスキャン中の画面スリープを防止
- フラッシュライトのON/OFF切替が可能

---

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

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

### 推奨読解順序

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

バーコード処理の基本インターフェースを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | handler.dart | `lib/barcode/handler.dart` | BarcodeHandlerの基底クラス定義（21-130行目） |
| 1-2 | controller.dart | `lib/barcode/controller.dart` | InvenTreeBarcodeControllerの基底クラス定義（13-103行目） |

**読解のコツ**: BarcodeHandlerはバーコードの処理ロジックを定義し、InvenTreeBarcodeControllerはUIとスキャン制御を担当する。この分離を理解することが重要。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | barcode.dart | `lib/barcode/barcode.dart` | scanBarcode関数（74-103行目）がエントリーポイント |

**主要処理フロー**:
1. **74-103行目**: scanBarcode関数でhandlerとcontrollerの組み合わせを決定
2. **78-79行目**: handler未指定時はBarcodeScanHandler()をデフォルト使用
3. **81行目**: CameraBarcodeControllerをデフォルトで生成
4. **84-89行目**: 設定からスキャン方式を取得
5. **91-98行目**: スキャン方式に応じてcontrollerを切り替え
6. **100-102行目**: Navigator.pushでスキャン画面を表示

#### Step 3: カメラコントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | camera_controller.dart | `lib/barcode/camera_controller.dart` | CameraBarcodeControllerの実装（24-398行目） |

**主要処理フロー**:
- **46-48行目**: MobileScannerControllerの初期化
- **51-55行目**: initStateでWakelockPlus有効化
- **67-84行目**: _loadSettingsで設定読み込み（スキャン遅延、単一スキャンモード）
- **109-158行目**: onScanSuccessでバーコード検出時の処理
- **182-239行目**: BarcodeReaderウィジェットの構築
- **316-368行目**: zoomSliderでズームコントロール

#### Step 4: ウェッジコントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | wedge_controller.dart | `lib/barcode/wedge_controller.dart` | WedgeBarcodeControllerの実装（16-146行目） |

**主要処理フロー**:
- **31行目**: FocusNodeによるキーボードフォーカス管理
- **33-35行目**: スキャン文字バッファと最終スキャン時刻
- **56-92行目**: handleKeyEventでキーイベント解釈
- **74-77行目**: 250msタイムアウトで古い文字を破棄
- **81-88行目**: 改行文字でバーコード確定、handleBarcodeData呼び出し

#### Step 5: 基底コントローラーの共通処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | controller.dart | `lib/barcode/controller.dart` | handleBarcodeDataの共通実装 |

**主要処理フロー**:
- **42-84行目**: handleBarcodeDataでスキャンデータを処理
- **49-51行目**: processingBarcodeフラグで重複防止
- **57-58行目**: ローディングオーバーレイとスキャン一時停止
- **60行目**: handler.processBarcodeでAPI処理
- **68-83行目**: 設定遅延後にスキャン再開

#### Step 6: バーコード処理ハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | handler.dart | `lib/barcode/handler.dart` | BarcodeHandlerのprocessBarcode実装 |

**主要処理フロー**:
- **57-129行目**: processBarcodeでAPI通信と結果処理
- **64-77行目**: 空バーコードの検証とエラー処理
- **82-90行目**: InvenTreeAPI().postでサーバー送信
- **103-128行目**: レスポンスに応じたコールバック振り分け

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

```
scanBarcode(context, handler)
    │
    ├─ BarcodeScanHandler() [デフォルト]
    │
    ├─ InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_TYPE)
    │
    └─ Navigator.push()
           │
           ├─ CameraBarcodeController
           │      ├─ MobileScannerController
           │      ├─ onScanSuccess(BarcodeCapture)
           │      │      └─ handleBarcodeData(barcode)
           │      └─ build() → Scaffold with MobileScanner
           │
           └─ WedgeBarcodeController
                  ├─ KeyboardListener
                  ├─ handleKeyEvent(KeyEvent)
                  │      └─ handleBarcodeData(barcode)
                  └─ build() → Scaffold with listener

handleBarcodeData(data)
    ├─ showLoadingOverlay()
    ├─ pauseScan()
    ├─ handler.processBarcode(data)
    │      ├─ InvenTreeAPI().post("barcode/", body: {barcode: data})
    │      ├─ onBarcodeMatched(data) [成功時]
    │      ├─ onBarcodeUnknown(data) [未知時]
    │      └─ onBarcodeUnhandled(data) [その他]
    ├─ hideLoadingOverlay()
    └─ resumeScan()
```

### データフロー図

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

カメラ画像
    │
    ▼
MobileScanner ─────▶ BarcodeCapture ─────▶ onScanSuccess()
                                                 │
                                                 ▼
キーボード入力                           handleBarcodeData()
    │                                           │
    ▼                                           ▼
KeyboardListener ───▶ handleKeyEvent() ───▶ processBarcode()
                                                 │
                                                 ▼
                                        InvenTreeAPI.post()
                                                 │
                                                 ▼
                                        APIResponse
                                                 │
                        ┌────────────────────────┼────────────────────┐
                        ▼                        ▼                    ▼
               onBarcodeMatched()       onBarcodeUnknown()    onBarcodeUnhandled()
                        │                        │                    │
                        ▼                        ▼                    ▼
               成功音・画面遷移            失敗音・エラー表示      エラーレポート
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| barcode.dart | `lib/barcode/barcode.dart` | ソース | スキャン起動関数、デフォルトハンドラ |
| handler.dart | `lib/barcode/handler.dart` | ソース | バーコード処理の基底ハンドラ |
| controller.dart | `lib/barcode/controller.dart` | ソース | バーコードコントローラー基底クラス |
| 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` | ソース | InvenTreeAPI（サーバー通信） |
| preferences.dart | `lib/preferences.dart` | ソース | ユーザー設定管理 |
| snacks.dart | `lib/widget/snacks.dart` | ソース | スナックバー通知 |
| progress.dart | `lib/widget/progress.dart` | ソース | ローディングオーバーレイ |
| helpers.dart | `lib/helpers.dart` | ソース | playAudioFile等のヘルパー関数 |
