# 機能設計書 49-グローバル検索

## 概要

本ドキュメントは、InvenTreeアプリにおけるグローバル検索機能の設計を記述する。パーツ・在庫・発注・会社等を横断的に検索し、検索結果から各詳細画面へ遷移する機能である。

### 本機能の処理概要

グローバル検索機能は、ユーザーが入力した検索キーワードを使用して、InvenTreeデータベース内の複数のモデル（パーツ、カテゴリ、在庫、ロケーション、発注、受注、会社等）を横断的に検索する機能である。検索結果はカテゴリ別に件数が表示され、タップすることで該当する一覧画面へ遷移する。

**業務上の目的・背景**：在庫管理システムにおいて、様々な種類のデータを素早く検索することは日常業務の効率化に直結する。部品名、在庫、発注伝票、会社名など、データの種類を意識せずに検索できることで、ユーザーは目的のデータに迅速にアクセスできる。本機能はアプリ全体のナビゲーションの中心的な役割を担う。

**機能の利用シーン**：
- 部品名や品番で部品を検索
- 在庫アイテムのステータスや場所を検索
- 発注番号や受注番号で注文を検索
- サプライヤーや顧客の会社名で検索
- 製造元品番（MPN）で製造元パーツを検索

**主要な処理内容**：
1. テキスト入力フィールドによる検索クエリの受付
2. デバウンス処理による検索頻度の制御（300ms）
3. 統合検索API（search/）または従来の個別API呼び出し
4. 検索結果のカテゴリ別件数表示
5. カテゴリタップによる一覧画面への遷移

**関連システム・外部連携**：
- InvenTree REST API（search/エンドポイント - 統合検索）
- InvenTree REST API（各モデルの個別エンドポイント - レガシー検索）

**権限による制御**：各モデルの参照権限に基づいて検索対象が制限される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 検索画面 | 主画面 | 検索クエリ入力と全データベース横断検索の実行 |
| 21 | 部品一覧画面 | 遷移先画面 | パーツ検索結果タップで遷移 |
| 24 | 在庫アイテム一覧画面 | 遷移先画面 | 在庫検索結果タップで遷移 |
| 26 | 購入注文一覧画面 | 遷移先画面 | 発注検索結果タップで遷移 |
| 28 | 販売注文一覧画面 | 遷移先画面 | 受注検索結果タップで遷移 |
| 30 | 会社一覧画面 | 遷移先画面 | 会社検索結果タップで遷移 |

## 機能種別

検索処理 / 画面遷移制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| hasAppbar | bool | Yes | アプリバーの表示有無 | true/false |
| searchQuery | String | No | 検索キーワード | 空文字列可 |

### 入力データソース

- テキスト入力フィールド（TextEditingController）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| nPartResults | int | パーツの検索結果件数 |
| nCategoryResults | int | カテゴリの検索結果件数 |
| nStockResults | int | 在庫アイテムの検索結果件数 |
| nLocationResults | int | ロケーションの検索結果件数 |
| nPurchaseOrderResults | int | 発注の検索結果件数 |
| nSalesOrderResults | int | 受注の検索結果件数 |
| nSupplierPartResults | int | サプライヤーパーツの検索結果件数 |
| nManufacturerPartResults | int | 製造元パーツの検索結果件数 |
| nCompanyResults | int | 会社の検索結果件数 |
| nCustomerResults | int | 顧客の検索結果件数 |
| nManufacturerResults | int | 製造元の検索結果件数 |
| nSupplierResults | int | サプライヤーの検索結果件数 |

### 出力先

- 画面表示（検索結果一覧）
- Navigator（詳細画面への遷移）

## 処理フロー

### 処理シーケンス

```
1. 検索画面表示
   ├─ hasAppbarパラメータに応じてアプリバー表示制御
   └─ テキスト入力フィールド表示

2. 検索クエリ入力
   ├─ onSearchTextChanged呼び出し
   └─ デバウンスタイマー開始（300ms）

3. 検索実行（search関数）
   ├─ 既存の検索クエリをキャンセル（_search_query）
   ├─ 検索結果をリセット
   ├─ APIバージョン確認
   │   ├─ supportsConsolidatedSearch（API v102+）
   │   │   └─ _perform_search()で統合検索
   │   └─ それ以外
   │       └─ legacySearch()で個別検索
   └─ 検索結果の件数を更新

4. 検索結果表示（getTiles）
   ├─ 検索中インジケーター表示
   ├─ 結果なしの場合はqueryNoResults表示
   └─ 各カテゴリの結果件数表示
       └─ タップで一覧画面へ遷移

5. 一覧画面遷移
   └─ original_searchフィルタ付きで一覧画面を表示
```

### フローチャート

```mermaid
flowchart TD
    A[検索画面表示] --> B[テキスト入力]
    B --> C[onSearchTextChanged]
    C --> D[デバウンス300ms]
    D --> E[search関数]
    E --> F{クエリ空?}
    F -->|Yes| G[結果リセット]
    F -->|No| H{supportsConsolidatedSearch?}
    H -->|Yes| I[_perform_search]
    H -->|No| J[legacySearch]
    I --> K[search/ API呼び出し]
    J --> L[各モデルAPIを並列呼び出し]
    K --> M[レスポンス解析]
    L --> M
    M --> N[件数を状態に反映]
    N --> O[画面再描画]
    O --> P{結果タップ?}
    P -->|Yes| Q[一覧画面へ遷移]
    P -->|No| R[検索継続]
    Q --> S[フィルタ付き一覧表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | デバウンス | 入力変更後300ms待ってから検索実行 | テキスト変更時 |
| BR-49-02 | 統合検索優先 | APIバージョン102以上では統合検索APIを使用 | 検索実行時 |
| BR-49-03 | 会社検索分割 | APIバージョン315以上では顧客/製造元/サプライヤーを分離検索 | 統合検索時 |
| BR-49-04 | 権限フィルタ | 各モデルのcanViewに基づいて検索対象を制限 | レガシー検索時 |
| BR-49-05 | クエリキャンセル | 新しい検索開始時に前回の検索をキャンセル | 検索実行時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 検索 | 複数テーブル | SELECT | 検索クエリに一致するレコードのカウント |

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| E-49-01 | 検索失敗 | API通信エラー | 結果リセット、再試行可能 |
| E-49-02 | レスポンス不正 | APIレスポンスがMapでない | 結果リセット |

### リトライ仕様

検索クエリを再入力することで手動リトライ可能。

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

読み取り専用のため、トランザクション管理は不要。

## パフォーマンス要件

- デバウンス: 300ms
- 検索API応答: 2秒以内
- 検索結果表示: 検索中インジケーターで待機状態を表示

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

- 認証トークンを使用したAPIアクセス
- 各モデルの参照権限（canView）に基づく検索対象制限

## 備考

- CancelableOperationを使用した検索クエリの管理
- immediateフラグによる即時検索（クリアボタン押下時）
- AppBarはhasAppbarパラメータで制御（ホーム画面埋め込み時はfalse）

---

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

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

### 推奨読解順序

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

検索画面で使用される状態変数を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | search.dart | `lib/widget/search.dart` | _SearchDisplayStateの状態変数（87-101行目） |

**読解のコツ**: nXxxResultsの変数群が各カテゴリの検索結果件数を保持する。nPendingSearchesは進行中の検索数を管理する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | search.dart | `lib/widget/search.dart` | SearchWidgetクラスとonSearchTextChanged |

**主要処理フロー**:
1. **27-34行目**: SearchWidget - StatefulWidgetの定義
2. **62行目**: TextEditingController - 入力テキスト管理
3. **125-137行目**: onSearchTextChanged - デバウンス処理とsearch呼び出し

#### Step 3: 検索処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | search.dart | `lib/widget/search.dart` | search関数と_perform_search |

**主要処理フロー**:
- **236-295行目**: search関数 - 検索実行のメイン処理
- **246-252行目**: 既存クエリのキャンセル
- **259-291行目**: supportsConsolidatedSearchによる分岐
- **165-231行目**: _perform_search - 統合検索API呼び出しと結果解析

#### Step 4: レガシー検索を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | search.dart | `lib/widget/search.dart` | legacySearch関数 |

**主要処理フロー**:
- **300-377行目**: legacySearch - 各モデルAPIを個別に呼び出し
- **302-314行目**: InvenTreePart().count() - パーツ件数取得
- **316-329行目**: InvenTreePartCategory().count() - カテゴリ件数取得
- 以降、各モデルに対して同様のcount呼び出し

#### Step 5: 検索結果表示を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | search.dart | `lib/widget/search.dart` | getTiles関数 |

**主要処理フロー**:
- **379-670行目**: getTiles - 検索結果のWidget構築
- **384-411行目**: 検索入力フィールドの構築
- **417-434行目**: パーツ結果タイル（件数>0の場合）
- 以降、各カテゴリの結果タイル構築
- **643-667行目**: 検索中/結果なしの表示

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

```
SearchWidget
    │
    └─ _SearchDisplayState
           │
           ├─ onSearchTextChanged(text)
           │      │
           │      ├─ debounceTimer（300ms）
           │      │
           │      └─ search(text)
           │
           ├─ search(term)
           │      │
           │      ├─ resetSearchResults()
           │      │
           │      ├─ _search_query?.cancel()
           │      │
           │      └─ supportsConsolidatedSearch?
           │             │
           │             ├─ Yes: _perform_search(body)
           │             │      │
           │             │      └─ InvenTreeAPI().post("search/")
           │             │             │
           │             │             └─ getSearchResultCount()
           │             │                    └─ 各モデルの件数取得
           │             │
           │             └─ No: legacySearch(term)
           │                    │
           │                    ├─ InvenTreePart().count()
           │                    ├─ InvenTreePartCategory().count()
           │                    ├─ InvenTreeStockItem().count()
           │                    ├─ InvenTreeStockLocation().count()
           │                    └─ InvenTreePurchaseOrder().count()
           │
           └─ getTiles(context)
                  │
                  ├─ 検索入力フィールド
                  │
                  ├─ 各カテゴリの結果タイル
                  │      └─ onTap: Navigator.push(一覧画面)
                  │             └─ {original_search: query}
                  │
                  └─ 検索中/結果なしの表示
```

### データフロー図

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

TextEditingController
        │
        ▼
onSearchTextChanged()
        │
        ▼
debounceTimer(300ms)
        │
        ▼
search(term)
        │
        ├─ resetSearchResults()
        │
        └─ API選択
               │
        ┌──────┴──────┐
        ▼             ▼
  統合検索API    レガシー検索
  (search/)     (個別count)
        │             │
        └──────┬──────┘
               │
               ▼
        検索結果件数
               │
               ▼
        setState()
               │
               ▼
        getTiles()
               │
               ▼
    検索結果タイル表示
               │
               ▼
    タップ → 一覧画面遷移
            (original_search付き)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| search.dart | `lib/widget/search.dart` | ソース | SearchWidget、_SearchDisplayState |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | RefreshableState基底クラス |
| part_list.dart | `lib/widget/part/part_list.dart` | ソース | PartList（遷移先） |
| stock_list.dart | `lib/widget/stock/stock_list.dart` | ソース | StockItemList（遷移先） |
| category_list.dart | `lib/widget/part/category_list.dart` | ソース | PartCategoryList（遷移先） |
| location_list.dart | `lib/widget/stock/location_list.dart` | ソース | StockLocationList（遷移先） |
| purchase_order_list.dart | `lib/widget/order/purchase_order_list.dart` | ソース | PurchaseOrderListWidget（遷移先） |
| sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | ソース | SalesOrderListWidget（遷移先） |
| company_list.dart | `lib/widget/company/company_list.dart` | ソース | CompanyListWidget（遷移先） |
| supplier_part_list.dart | `lib/widget/company/supplier_part_list.dart` | ソース | SupplierPartList（遷移先） |
| api.dart | `lib/api.dart` | ソース | InvenTreeAPI（supportsConsolidatedSearch） |
| part.dart | `lib/inventree/part.dart` | ソース | InvenTreePart、PartCategoryモデル |
| stock.dart | `lib/inventree/stock.dart` | ソース | InvenTreeStockItem、StockLocationモデル |
| purchase_order.dart | `lib/inventree/purchase_order.dart` | ソース | InvenTreePurchaseOrderモデル |
| sales_order.dart | `lib/inventree/sales_order.dart` | ソース | InvenTreeSalesOrderモデル |
| company.dart | `lib/inventree/company.dart` | ソース | InvenTreeCompany、SupplierPart、ManufacturerPartモデル |
