# 通知設計書 10-バーコード不一致

## 概要

本ドキュメントは、InvenTreeモバイルアプリケーションにおける「バーコード不一致」通知の設計仕様を定義する。スキャンしたバーコードがサーバーで認識されない場合に表示される通知の仕様を記載する。

### 本通知の処理概要

この通知は、スキャンしたバーコードがInvenTreeサーバーのデータベースに登録されていない場合に、ユーザーにその旨を通知する機能である。音声フィードバックとスナックバー表示の両方で通知する。

**業務上の目的・背景**：在庫管理システムにおいて、バーコードは在庫アイテム、パーツ、ロケーションなどを一意に識別するための重要な手段である。スキャンしたバーコードがシステムに登録されていない場合、ユーザーはその事実を即座に認識する必要がある。これにより、以下の対処が可能になる：
- 正しいバーコードかどうかの確認
- 新規アイテムの登録
- バーコードラベルの再印刷
- データ入力ミスの検出

**通知の送信タイミング**：`BarcodeHandler.onBarcodeUnknown()`メソッドが呼び出された際にトリガーされる。これはサーバーからのレスポンスに`error`キーが含まれている場合、または`statusCode`が400以上の場合に発生する。

**通知の受信者**：アプリケーションを操作している現在のユーザー。音声再生とスナックバー表示の両方で通知される。

**通知内容の概要**：
- 音声：`sounds/barcode_error.mp3`（エラー音）
- スナックバー：「No match for barcode」（バーコードに一致するものがありません）というメッセージ、またはサーバーから返されたエラーメッセージ

**期待されるアクション**：ユーザーは通知を確認し、以下のような対処を行う：
- バーコードが正しいか確認
- 別のバーコードをスキャン
- 新規アイテムとしてシステムに登録
- バーコードとアイテムを紐付ける（カスタムバーコード機能）

## 通知種別

アプリ内通知（スナックバー + 音声）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期 |
| 優先度 | 中 |
| リトライ | 無し（UI通知のため） |

### 送信先決定ロジック

現在アプリケーションを操作しているユーザーに対して、画面下部にスナックバー形式で表示し、同時にエラー音を再生する。

## 通知テンプレート

### スナックバー通知の場合

| 項目 | 内容 |
|-----|------|
| 表示位置 | 画面下部 |
| 背景色 | Colors.deepOrange（エラー時） |
| 表示時間 | 5秒 |
| 形式 | テキスト + アイコン |
| アイコン | Icons.qr_code |

### 本文テンプレート

```
[QRコードアイコン] バーコードに一致するものがありません
```

または、サーバーから返されたエラーメッセージ：

```
[QRコードアイコン] {data["error"]}
```

### 音声ファイル

| 項目 | 内容 |
|-----|------|
| ファイルパス | `sounds/barcode_error.mp3` |
| 形式 | MP3 |
| 再生タイミング | 通知表示と同時 |

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| barcodeNoMatch | 不一致メッセージ | L10()（多言語リソース） | Yes |
| data["error"] | サーバーエラーメッセージ | APIレスポンス | No |
| INV_SOUNDS_BARCODE | バーコード音声有効/無効 | InvenTreeSettingsManager | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バーコード | サーバーレスポンス | data.containsKey("error") | サーバーがエラーを返した |
| バーコード | サーバーレスポンス | statusCode >= 400 | 4xx/5xxステータスコード |
| バーコード | レスポンス検証 | !response.isValid() | 無効なレスポンス |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| コンテキストなし | `hasContext()`が`false`を返す場合 |
| 音声設定無効 | `INV_SOUNDS_BARCODE`が`false`の場合（音声のみ抑止） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[バーコードスキャン] --> B[サーバーにPOST]
    B --> C{レスポンス検証}
    C -->|statusCode >= 400| D[onBarcodeUnknown呼び出し]
    C -->|data.containsKey error| D
    C -->|success| E[onBarcodeMatched呼び出し]
    D --> F[barcodeFailureTone呼び出し]
    F --> G{INV_SOUNDS_BARCODE?}
    G -->|true| H[barcode_error.mp3再生]
    G -->|false| I[音声スキップ]
    H --> J[showSnackIcon呼び出し]
    I --> J
    J --> K[スナックバー表示]
    K --> L[5秒後自動消去]
    E --> M[終了]
    L --> M
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| settings | 音声設定確認 | Sembast NoSQL DB |

### テーブル別参照項目詳細

#### settings

| 参照項目（キー名） | 用途 | 取得条件 |
|-------------------|------|---------|
| INV_SOUNDS_BARCODE | バーコード音声有効/無効 | デフォルト: true |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | 通知表示のみで更新なし |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 音声再生失敗 | AudioPlayerが利用できない | スナックバーのみで継続 |
| スナックバー表示失敗 | コンテキストがない | 処理スキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回 |
| リトライ間隔 | - |
| リトライ対象エラー | - |

UI通知のため、リトライは行わない。

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（スキャン実行時に即座に表示）

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

- スキャンしたバーコードの内容は通知に含まれない
- サーバーから返されたエラーメッセージがそのまま表示される可能性がある
- ユーザーのデバイス上でのみ表示され、外部への送信は行わない

## 備考

- `onBarcodeUnknown()`は`BarcodeHandler`基底クラスのメソッド
- サブクラスでオーバーライドされる場合があり、その場合は挙動が異なる可能性がある
- サーバーから`data["error"]`が返された場合はそれを表示、なければ`L10().barcodeNoMatch`を使用
- 音声とスナックバーは同時に通知される（マルチモーダル通知）

---

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

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

### 推奨読解順序

#### Step 1: 基底クラスの通知処理を理解する

まず、`BarcodeHandler`クラスの`onBarcodeUnknown`メソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | handler.dart | `lib/barcode/handler.dart` | onBarcodeUnknownメソッド |

**読解のコツ**: `onBarcodeUnknown()`は音声再生とスナックバー表示を組み合わせたマルチモーダル通知を行う。

#### Step 2: 呼び出し条件を理解する

onBarcodeUnknownが呼び出される条件を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | handler.dart | `lib/barcode/handler.dart` | processBarcode メソッドのレスポンス判定 |

**主要処理フロー**:
1. **行34-44**: `onBarcodeUnknown()`メソッド - 不一致バーコードの通知処理
2. **行37**: `barcodeFailureTone()` - エラー音再生
3. **行39-43**: `showSnackIcon()` - スナックバー表示
4. **行122-128**: `processBarcode()`でのレスポンス判定 - success/error/unknown分岐

#### Step 3: メッセージの取得を理解する

エラーメッセージの取得ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | handler.dart | `lib/barcode/handler.dart` | data["error"]の取得 |
| 3-2 | l10.dart | `lib/l10.dart` | L10().barcodeNoMatchの参照 |

**主要処理フロー**:
- **行40**: `(data["error"] ?? L10().barcodeNoMatch) as String` - エラーメッセージの決定

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

```
BarcodeHandler.processBarcode() [handler.dart:57]
    |
    +-- InvenTreeAPI().post("barcode/", ...) [api.dart]
    |       |
    |       +-- APIResponse
    |
    +-- [レスポンス判定] [handler.dart:122-128]
            |
            +-- data.containsKey("success") --> onBarcodeMatched()
            |
            +-- data.containsKey("error") OR statusCode >= 400
                    |
                    +-- onBarcodeUnknown() [handler.dart:34]
                            |
                            +-- barcodeFailureTone() [tones.dart:17]
                            |       |
                            |       +-- playAudioFile("sounds/barcode_error.mp3")
                            |
                            +-- showSnackIcon() [snacks.dart:11]
                                    |
                                    +-- data["error"] ?? L10().barcodeNoMatch
                                    +-- icon: Icons.qr_code
                                    +-- success: false
```

### データフロー図

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

バーコード           processBarcode()               音声再生
     |          --->       |                   --> barcode_error.mp3
     v                     v
サーバーPOST         レスポンス判定                 スナックバー表示
     |                     |                   --> "No match for barcode"
     v                     v                        または
APIResponse          onBarcodeUnknown()        --> data["error"]
  error/               /        \
  statusCode>=400   音声        スナックバー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| handler.dart | `lib/barcode/handler.dart` | ソース | バーコードハンドラ基底クラス、onBarcodeUnknown |
| tones.dart | `lib/barcode/tones.dart` | ソース | バーコード音声再生関数 |
| snacks.dart | `lib/widget/snacks.dart` | ソース | スナックバー通知の表示 |
| helpers.dart | `lib/helpers.dart` | ソース | 音声再生の実装 |
| l10.dart | `lib/l10.dart` | ソース | 多言語対応（barcodeNoMatch） |
| preferences.dart | `lib/preferences.dart` | ソース | INV_SOUNDS_BARCODE設定 |
| barcode_error.mp3 | `sounds/barcode_error.mp3` | 音声 | エラー音 |
