# 画面設計書 28-販売注文一覧画面

## 概要

本ドキュメントは、InvenTree Flutterアプリケーションにおける「販売注文一覧画面」の設計仕様を定義する。販売注文（SalesOrder）をページネーション付きで一覧表示し、検索・フィルタリング・ソート機能を提供する画面である。

### 本画面の処理概要

販売注文一覧画面は、InvenTreeサーバーに登録されている販売注文をページネーション付きの一覧形式で表示する画面である。ユーザーはこの画面から注文を検索・フィルタリングし、目的の注文を素早く見つけて詳細画面へ遷移できる。また、新規販売注文の作成も可能。

**業務上の目的・背景**：販売管理において、受注した注文の状況を一元的に把握することは極めて重要である。本画面は、未処理の注文、期限超過の注文、自分に割り当てられた注文などを効率的にフィルタリングし、販売業務の進捗管理を支援する。ステータス別の色分け表示と合計金額の表示により、注文状況を視覚的に把握できる。

**画面へのアクセス方法**：
- ホーム画面の「販売注文」タイルをタップ
- ドロワーメニューの「販売注文」をタップ
- 検索画面から販売注文検索結果として遷移
- 会社詳細画面から顧客の注文一覧として遷移

**主要な操作・処理内容**：
1. 販売注文一覧のページネーション表示（25件ずつ取得）
2. 検索ボックスによるインクリメンタルサーチ（500msデバウンス）
3. フィルタリング（未処理、期限超過、自分に割当）
4. ソート（リファレンス、ステータス、目標日、顧客名）
5. 販売注文タップで詳細画面へ遷移
6. 新規販売注文の作成

**画面遷移**：
- 遷移元：ホーム画面、ドロワーメニュー、検索画面、会社詳細画面
- 遷移先：販売注文詳細画面（SalesOrderDetailWidget）

**権限による表示制御**：
- 販売注文の閲覧権限（sales_order）がない場合、この画面にはアクセスできない
- 新規作成ボタンは `InvenTreeSalesOrder().canCreate` がtrueの場合のみ表示

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 32 | 受注一覧表示 | 主機能 | 受注のページネーション付き一覧表示 |
| 33 | 受注詳細表示 | 遷移先機能 | 受注選択時に受注詳細画面へ遷移 |

## 画面種別

一覧

## URL/ルーティング

Flutter Navigator によるプッシュ遷移。`SalesOrderListWidget` ウィジェットのインスタンス化時に `filters` パラメータを渡して表示内容を制御する。

## 入出力項目

| 項目名 | 入出力 | 型 | 必須 | 説明 |
|--------|--------|------|------|------|
| filters | 入力 | Map<String, String> | No | 一覧表示時のフィルタ条件（デフォルト: 空） |

## 表示項目

| 項目名 | 表示位置 | データ型 | 説明 |
|--------|----------|----------|------|
| 顧客サムネイル | ListTile.leading | Image | 顧客会社の画像 |
| リファレンス | ListTile.title | String | 販売注文のリファレンス番号 |
| 顧客名・合計金額 | ListTile.subtitle | String | 顧客名と合計金額（あれば） |
| ステータス | ListTile.trailing | String | 注文ステータス（色付き） |
| 検索結果件数 | タイトル下 | String | "Results: N" 形式で表示 |

## イベント仕様

### 1-販売注文タップ

販売注文リストの各アイテムをタップすると、該当注文の詳細画面（SalesOrderDetailWidget）へ遷移する。

**処理フロー**：
1. `onTap` コールバックが発火
2. `order.goToDetailPage(context)` を呼び出し
3. `Navigator.push` で `SalesOrderDetailWidget(this)` へ遷移

### 2-新規作成ボタン押下

SpeedDialの「作成」ボタンをタップすると、新規販売注文作成フォームが表示される。

**処理フロー**：
1. `SpeedDialChild.onTap` で `_createSalesOrder(context)` を呼び出し
2. `InvenTreeSalesOrder().formFields()` でフォームフィールドを取得
3. contactフィールドは除外（顧客選択後に設定）
4. `InvenTreeSalesOrder().createForm()` で作成フォームを表示
5. 成功時に作成された注文詳細画面へ遷移

### 3-検索ボックス入力

検索ボックスにテキストを入力すると、500msのデバウンス後にサーバーへ検索クエリを送信する。

**処理フロー**：
1. `TextFormField.onChanged` でテキスト変更を検知
2. `updateSearchTerm()` メソッドを呼び出し
3. 500msタイマー後に `_pagingController.refresh()` で再検索

### 4-フィルタリング設定

フィルタアイコンをタップすると、ソートとフィルタリングのオプションダイアログが表示される。

**処理フロー**：
1. フィルタアイコン（`Icons.filter_alt`）をタップ
2. `_setOrderingOptions(context)` を呼び出し
3. `launchApiForm` でオプションダイアログを表示
4. 設定保存後に `_pagingController.refresh()` で一覧を再取得

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 一覧表示 | SalesOrder | SELECT | 販売注文データのページネーション取得 |
| 検索 | SalesOrder | SELECT | 検索条件に合致する注文の取得 |
| 新規作成 | SalesOrder | INSERT | 新規販売注文の作成 |

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

#### SalesOrder テーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | pk, reference, description, customer, customer_detail, status, total_price, total_price_currency | フィルタ条件とページネーションパラメータに基づく | APIエンドポイント: /api/order/so/ |
| INSERT | reference, customer, description, project_code, start_date, target_date, link, responsible | フォーム入力値 | createForm()経由 |

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容 |
|-------------|---------------|----------|---------------|
| MSG_NO_RESULTS | 情報 | 検索結果が0件 | "No results" |
| MSG_RESULTS | 情報 | 検索結果表示 | "Results: {件数}" |

## 例外処理

| 例外条件 | 処理内容 | ユーザーへの表示 |
|---------|---------|-----------------|
| APIエラー | エラーをSentryに報告 | 一覧表示が停止、エラー状態を表示 |
| ネットワークエラー | 再試行可能な状態を維持 | プルダウンでリフレッシュ可能 |
| 権限不足 | 作成ボタン非表示 | 閲覧のみ可能 |

## 備考

- ページサイズは25件固定（`_pageSize = 25`）
- フィルタ・ソート設定は `InvenTreeSettingsManager` によりローカルに永続化される
- 検索のデバウンス時間は500ms
- ステータスコードはAPI取得時に `InvenTreeAPI().SalesOrderStatus.load()` で事前ロード
- ステータスによる色分け表示: `InvenTreeAPI().SalesOrderStatus.color(order.status)`
- デフォルトフィルタで `outstanding: true` が設定されている（未処理注文を優先表示）
- subtitleには顧客名を表示、合計金額がある場合は「顧客名 • 通貨 金額」形式で表示
- バーコードスキャン機能は現時点では未実装（TODO: return custom barcode actions）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | sales_order.dart | `lib/inventree/sales_order.dart` | InvenTreeSalesOrderクラス（18-133行）。URL="order/so/"、isOpen, isPending, isInProgress, isComplete判定 |
| 1-2 | orders.dart | `lib/inventree/orders.dart` | InvenTreeOrder基底クラス。共通フィールドと機能 |

**読解のコツ**: `InvenTreeSalesOrder` は `InvenTreeOrder` を継承している。URL="order/so/" でAPIエンドポイントが定義されている。ステータス判定は購入注文と異なり、IN_PROGRESSステータスがある。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | SalesOrderListWidget（15-83行）とPaginatedSalesOrderList（85-176行）の関係 |

**主要処理フロー**:
1. **15-23行**: `SalesOrderListWidget` StatefulWidgetの定義。filtersをコンストラクタで受け取る
2. **30行**: `getAppBarTitle()` で "Sales Orders" を返す
3. **80-82行**: `getBody()` で `PaginatedSalesOrderList` を返す
4. **85-176行**: `_PaginatedSalesOrderListState` で実際のページネーションロジックを実装

#### Step 3: フィルタ・ソートオプションを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | orderingOptions（105-110行）とfilterOptions（113-130行）の定義 |

**主要処理フロー**:
- **105-110行**: ソートオプション（reference, status, target_date, customer__name）
- **114-130行**: フィルタオプション（outstanding, overdue, assigned_to_me）
- **118行**: outstandingのデフォルト値がtrue

#### Step 4: リストアイテム構築を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | buildItem()メソッド（148-175行）。ListTileの構築方法 |

**主要処理フロー**:
- **150行**: InvenTreeModelをInvenTreeSalesOrderにキャスト
- **152行**: 顧客情報を取得
- **155-159行**: subtitleを構築（顧客名 + 合計金額）
- **161-174行**: ListTileを構築。trailingにステータス色付きでステータスラベルを表示

#### Step 5: 新規作成機能を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | actionButtons()（33-49行）と_createSalesOrder()（52-71行） |

**主要処理フロー**:
- **36-46行**: canCreate時のみSpeedDialChildを追加
- **53行**: formFields()でフォームフィールドを取得
- **56行**: contactフィールドを除外
- **58-70行**: createForm()で作成フォームを表示、成功時に詳細画面へ遷移

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

```
SalesOrderListWidget (StatefulWidget)
    |
    +-- _SalesOrderListWidgetState (RefreshableState)
           |
           +-- getBody() --> PaginatedSalesOrderList
           |                    |
           |                    +-- _PaginatedSalesOrderListState (PaginatedSearchState)
           |                           |
           |                           +-- requestPage()
           |                           |      |
           |                           |      +-- SalesOrderStatus.load()
           |                           |      +-- InvenTreeSalesOrder().listPaginated()
           |                           |                      |
           |                           |                      +-- API GET /api/order/so/
           |                           |
           |                           +-- buildItem() --> ListTile
           |                                              |
           |                                              +-- onTap --> order.goToDetailPage()
           |
           +-- actionButtons()
           |      |
           |      +-- _createSalesOrder() --> InvenTreeSalesOrder().createForm()
           |
           +-- barcodeButtons()
                  |
                  +-- [] (未実装)
```

### データフロー図

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

filters (Map)    ---> PaginatedSalesOrderList          ---> ListView
                         |
                         +-- requestPage()
                         |      |
                         |      +-- SalesOrderStatus.load()
                         |      |
                         |      +-- API: /api/order/so/?limit=25&offset=N
                         |             |
                         |             +-- InvenTreePageResponse
                         |
                         +-- buildItem()
                                |
                                +-- InvenTreeSalesOrder --> ListTile
                                         |
                                         +-- customer.thumbnail --> leading
                                         +-- customer.name + totalPrice --> subtitle
                                         +-- SalesOrderStatus.color() --> trailing色
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | ソース | 販売注文一覧画面のメインウィジェット |
| paginator.dart | `lib/widget/paginator.dart` | ソース | ページネーション共通基盤クラス |
| sales_order.dart | `lib/inventree/sales_order.dart` | ソース | 販売注文データモデル |
| orders.dart | `lib/inventree/orders.dart` | ソース | 注文基底クラス |
| company.dart | `lib/inventree/company.dart` | ソース | 会社データモデル（顧客） |
| api.dart | `lib/api.dart` | ソース | API通信クライアント、SalesOrderStatusステータス管理 |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | リフレッシュ可能なState基底クラス |
| sales_order_detail.dart | `lib/widget/order/sales_order_detail.dart` | ソース | 販売注文詳細（遷移先） |
