# 機能設計書 50-統合検索API

## 概要

本ドキュメントは、InvenTreeアプリにおける統合検索API機能の設計を記述する。サーバーの統合検索APIを使用して、複数のモデルを単一のリクエストで効率的に検索する機能である。

### 本機能の処理概要

統合検索API機能は、InvenTreeサーバーのsearch/エンドポイントを使用して、複数のモデル（パーツ、カテゴリ、在庫、ロケーション、発注、受注、会社等）を単一のHTTPリクエストで同時に検索する機能である。従来の個別API呼び出しと比較して、ネットワークオーバーヘッドを削減し、検索応答時間を改善する。

**業務上の目的・背景**：モバイルアプリにおいて、ネットワーク通信は遅延とバッテリー消費の主要な要因である。複数のモデルを個別に検索する従来の方式では、リクエスト数が増加し、応答時間が長くなる。統合検索APIは、単一のリクエストで複数モデルの検索結果を取得することで、ユーザー体験を向上させる。

**機能の利用シーン**：
- グローバル検索機能（No.49）でのバックエンド検索処理
- 複数カテゴリの検索結果件数を一度に取得する場面
- モバイル環境での高速な検索体験の提供

**主要な処理内容**：
1. 検索リクエストボディの構築（モデルタイプ、検索条件）
2. search/エンドポイントへの単一POSTリクエスト
3. レスポンスの解析と各モデルの結果件数抽出
4. 会社検索の分割処理（サプライヤー/顧客/製造元）

**関連システム・外部連携**：
- InvenTree REST API（search/エンドポイント）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 検索画面 | 主画面 | サーバーの統合検索APIを使用した効率的な検索処理 |

## 機能種別

API連携 / 検索処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| search | String | Yes | 検索クエリ文字列 | 空でないこと |
| limit | int | No | 取得件数制限（デフォルト: 1） | 正の整数 |
| part | Map | No | パーツ検索オプション | 空Map可 |
| partcategory | Map | No | カテゴリ検索オプション | 空Map可 |
| stockitem | Map | No | 在庫検索オプション | 空Map可 |
| stocklocation | Map | No | ロケーション検索オプション | 空Map可 |
| purchaseorder | Map | No | 発注検索オプション | 空Map可 |
| salesorder | Map | No | 受注検索オプション | 空Map可 |
| supplierpart | Map | No | サプライヤーパーツ検索オプション | 空Map可 |
| manufacturerpart | Map | No | 製造元パーツ検索オプション | 空Map可 |
| company | Map | No | 会社検索オプション | 空Map可 |
| supplier | Map | No | サプライヤー検索オプション（API v315+） | 空Map可 |
| manufacturer | Map | No | 製造元検索オプション（API v315+） | 空Map可 |
| customer | Map | No | 顧客検索オプション（API v315+） | 空Map可 |

### 入力データソース

- グローバル検索画面からの検索クエリ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| part.count | int | パーツの検索結果件数 |
| partcategory.count | int | カテゴリの検索結果件数 |
| stockitem.count | int | 在庫アイテムの検索結果件数 |
| stocklocation.count | int | ロケーションの検索結果件数 |
| purchaseorder.count | int | 発注の検索結果件数 |
| salesorder.count | int | 受注の検索結果件数 |
| supplierpart.count | int | サプライヤーパーツの検索結果件数 |
| manufacturerpart.count | int | 製造元パーツの検索結果件数 |
| company.count | int | 会社の検索結果件数 |
| supplier.count | int | サプライヤーの検索結果件数（API v315+） |
| manufacturer.count | int | 製造元の検索結果件数（API v315+） |
| customer.count | int | 顧客の検索結果件数（API v315+） |

### 出力先

- 呼び出し元（_perform_search）の状態変数への反映

## 処理フロー

### 処理シーケンス

```
1. 統合検索の前提条件確認
   └─ supportsConsolidatedSearch（API v102+）がtrueであること

2. リクエストボディ構築
   ├─ limit: 1（件数取得のみ）
   ├─ search: 検索クエリ
   └─ 各モデルタイプのキーを追加
       ├─ part: {}
       ├─ partcategory: {}
       ├─ stockitem: {}
       ├─ stocklocation: {}
       ├─ purchaseorder: {}
       ├─ salesorder: {}
       ├─ supplierpart: {}
       ├─ manufacturerpart: {}
       └─ company: {} または supplier/manufacturer/customer分割

3. API呼び出し
   └─ InvenTreeAPI().post("search/", body)

4. レスポンス解析
   ├─ 各モデルキーからcount値を抽出
   └─ getSearchResultCount()で安全に取得

5. 状態更新
   └─ 各nXxxResults変数に件数を反映
```

### フローチャート

```mermaid
flowchart TD
    A[統合検索開始] --> B{supportsConsolidatedSearch?}
    B -->|No| C[legacySearch使用]
    B -->|Yes| D[リクエストボディ構築]
    D --> E[基本パラメータ設定]
    E --> F[モデルタイプ追加]
    F --> G{supportsSplitCompanySearch?}
    G -->|Yes| H[supplier/manufacturer/customer追加]
    G -->|No| I[company追加]
    H --> J[POST search/]
    I --> J
    J --> K{レスポンス有効?}
    K -->|No| L[結果リセット]
    K -->|Yes| M[getSearchResultCount]
    M --> N[各モデルのcount抽出]
    N --> O[nXxxResults更新]
    O --> P[画面再描画]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | API対応要件 | APIバージョン102以上で統合検索を使用 | supportsConsolidatedSearch判定時 |
| BR-50-02 | 会社検索分割 | APIバージョン315以上で顧客/製造元/サプライヤーを分離 | supportsSplitCompanySearch判定時 |
| BR-50-03 | 件数取得モード | limit=1で件数のみを効率的に取得 | 検索実行時 |
| BR-50-04 | 検索結果検証 | クエリと検索結果の整合性を確認 | レスポンス処理時 |

### 計算ロジック

特になし

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

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

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

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

本機能はサーバーAPIを経由するため、クライアントからは直接データベースを操作しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| E-50-01 | API非対応 | APIバージョン101以下 | legacySearchにフォールバック |
| E-50-02 | レスポンス不正 | レスポンスがMapでない | 結果リセット |
| E-50-03 | 通信エラー | ネットワーク障害 | 結果リセット |

### リトライ仕様

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

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

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

## パフォーマンス要件

- 単一リクエストで全モデル検索（従来の複数リクエストを置換）
- 期待応答時間: 1秒以内
- limit=1による最小データ転送

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

- 認証トークンを使用したAPIアクセス
- サーバー側で各モデルの参照権限を検証

## 備考

- グローバル検索機能（No.49）の内部実装として使用
- 統合検索非対応サーバーでは自動的にレガシー検索にフォールバック
- limit=1は件数取得のみのため、実際のデータは取得しない

---

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

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

### 推奨読解順序

#### Step 1: API対応判定を理解する

統合検索APIの対応条件を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | api.dart | `lib/api.dart` | supportsConsolidatedSearch（291行目）、supportsSplitCompanySearch（351行目） |

**読解のコツ**: InvenTreeAPIクラスのgetterプロパティでAPIバージョンに基づく機能サポート判定を行っている。

#### Step 2: リクエスト構築を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | search.dart | `lib/widget/search.dart` | search関数内のリクエストボディ構築（260-282行目） |

**主要処理フロー**:
1. **260-262行目**: 基本パラメータ（limit, search）設定
2. **264-272行目**: 各モデルタイプの空Mapを追加
3. **274-281行目**: supportsSplitCompanySearchによる会社検索の分岐

#### Step 3: API呼び出しを理解する

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

**主要処理フロー**:
- **165-231行目**: _perform_search関数全体
- **166-168行目**: InvenTreeAPI().post("search/")の呼び出し
- **172行目**: searchTermの検証（現在の入力と一致確認）

#### Step 4: レスポンス解析を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | search.dart | `lib/widget/search.dart` | getSearchResultCountとレスポンス解析 |

**主要処理フロー**:
- **148-162行目**: getSearchResultCount - レスポンスから件数を安全に抽出
- **180-225行目**: 各モデルのcount値を取得して状態変数に反映

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

```
search(term)
    │
    ├─ supportsConsolidatedSearch判定
    │
    └─ true
           │
           ├─ リクエストボディ構築
           │      │
           │      ├─ limit: 1
           │      ├─ search: term
           │      │
           │      ├─ 各MODEL_TYPE: {}
           │      │      ├─ InvenTreePart.MODEL_TYPE
           │      │      ├─ InvenTreePartCategory.MODEL_TYPE
           │      │      ├─ InvenTreeStockItem.MODEL_TYPE
           │      │      ├─ InvenTreeStockLocation.MODEL_TYPE
           │      │      ├─ InvenTreePurchaseOrder.MODEL_TYPE
           │      │      ├─ InvenTreeSalesOrder.MODEL_TYPE
           │      │      ├─ InvenTreeSupplierPart.MODEL_TYPE
           │      │      └─ InvenTreeManufacturerPart.MODEL_TYPE
           │      │
           │      └─ 会社検索
           │             ├─ supportsSplitCompanySearch: supplier/manufacturer/customer
           │             └─ else: InvenTreeCompany.MODEL_TYPE
           │
           ├─ CancelableOperation.fromFuture(_perform_search(body))
           │
           └─ _perform_search(body)
                  │
                  ├─ InvenTreeAPI().post("search/", body)
                  │
                  ├─ searchTerm検証
                  │
                  ├─ レスポンス解析
                  │      └─ getSearchResultCount(results, MODEL_TYPE)
                  │             └─ results[key]["count"]
                  │
                  └─ setState()
                         └─ nXxxResults = count
```

### データフロー図

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

検索クエリ
        │
        ▼
search(term)
        │
        ├─ supportsConsolidatedSearch確認
        │
        ▼
リクエストボディ構築
{
  "limit": 1,
  "search": term,
  "part": {},
  "partcategory": {},
  "stockitem": {},
  ...
}
        │
        ▼
POST search/
        │
        ▼
APIResponse
{
  "part": {"count": 10},
  "partcategory": {"count": 5},
  "stockitem": {"count": 20},
  ...
}
        │
        ▼
getSearchResultCount()
        │
        ▼
setState()
        │
        ▼
nPartResults = 10
nCategoryResults = 5
nStockResults = 20
...
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| search.dart | `lib/widget/search.dart` | ソース | 統合検索の呼び出し元 |
| api.dart | `lib/api.dart` | ソース | InvenTreeAPI、supportsConsolidatedSearch |
| part.dart | `lib/inventree/part.dart` | ソース | InvenTreePart.MODEL_TYPE |
| stock.dart | `lib/inventree/stock.dart` | ソース | InvenTreeStockItem.MODEL_TYPE、StockLocation.MODEL_TYPE |
| purchase_order.dart | `lib/inventree/purchase_order.dart` | ソース | InvenTreePurchaseOrder.MODEL_TYPE |
| sales_order.dart | `lib/inventree/sales_order.dart` | ソース | InvenTreeSalesOrder.MODEL_TYPE |
| company.dart | `lib/inventree/company.dart` | ソース | InvenTreeCompany.MODEL_TYPE、SupplierPart、ManufacturerPart |
