# 機能設計書 27-発注明細追加

## 概要

本ドキュメントは、InvenTreeアプリにおける発注明細追加機能の設計を記載する。既存の発注に新しい明細行（ラインアイテム）を追加する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：発注業務において、発注作成後に追加で品目を発注する必要が生じることは一般的である。本機能により、保留中の発注に対して新しい明細行を追加でき、発注内容の柔軟な変更を可能にする。

**機能の利用シーン**：発注詳細画面で明細追加ボタンを押下した際、またはバーコードスキャンでサプライヤーパーツを読み取った際に利用される。保留中（Pending）状態の発注のみで明細追加が可能。

**主要な処理内容**：
1. 発注詳細画面から明細追加モードを起動
2. サプライヤーパーツを選択（フィルタは当該サプライヤーに限定）
3. 数量、単価、参照番号、受入先等を入力
4. APIを通じて新規明細行を登録
5. 登録成功後に画面をリフレッシュ

**関連システム・外部連携**：InvenTree REST API（`order/po-line/`エンドポイント）のPOSTメソッドと連携。バーコードスキャン機能（POAllocateBarcodeHandler）とも連携。

**権限による制御**：`purchase_order`ロールを持ち、かつcanCreate権限を持つユーザーのみ追加可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 27 | 購入注文詳細画面 | 主画面 | 明細追加ボタン押下時に明細追加フォーム表示 |
| 5 | バーコードスキャン画面 | 連携画面 | バーコードスキャンで明細追加 |

## 機能種別

CRUD操作（登録）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| order | int | Yes | 発注ID | 正の整数（自動設定） |
| part | int | Yes | サプライヤーパーツID | 正の整数、当該サプライヤーのパーツに限定 |
| quantity | double | Yes | 発注数量 | 正の数値 |
| reference | String | No | 参照番号 | - |
| purchase_price | double | No | 単価 | 正の数値 |
| purchase_price_currency | String | No | 通貨コード | ISO 4217形式 |
| destination | int | No | 受入先ロケーションID | 正の整数 |
| notes | String | No | 備考 | - |
| link | String | No | 外部リンク | URL形式 |

### 入力データソース

発注詳細画面から発注ID、サプライヤーIDを取得。フォームでユーザー入力を受け付け。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| success | bool | 登録成功フラグ |
| data | Map | 登録された明細データ |

### 出力先

APIレスポンス → 画面リフレッシュ → スナックバーで結果通知

## 処理フロー

### 処理シーケンス

```
1. 明細追加起動
   ├─ 発注詳細画面の追加ボタン押下
   └─ またはバーコードスキャン
2. フォームフィールド準備
   ├─ InvenTreePOLineItem().formFields()取得
   ├─ partフィールドを表示・サプライヤーフィルタ設定
   └─ orderフィールドに発注ID設定
3. フォーム表示
   └─ createForm()でダイアログ表示
4. ユーザー入力
   ├─ サプライヤーパーツ選択
   └─ 数量・単価等入力
5. 保存処理
   ├─ APIにPOSTリクエスト送信
   └─ バリデーションエラー時はフォームにエラー表示
6. 登録成功
   ├─ refresh()で画面リフレッシュ
   └─ スナックバーで成功メッセージ表示
```

### フローチャート

```mermaid
flowchart TD
    A[明細追加ボタン押下] --> B[_addLineItem呼び出し]
    B --> C[formFields取得]
    C --> D[partフィールド設定]
    D --> E[supplierフィルタ設定]
    E --> F[orderフィールド設定]
    F --> G[createForm呼び出し]
    G --> H[フォームダイアログ表示]
    H --> I[ユーザー入力]
    I --> J[保存ボタン押下]
    J --> K[API POST送信]
    K --> L{登録成功?}
    L -->|Yes| M[refresh呼び出し]
    L -->|No| N[エラー表示]
    M --> O[スナックバー表示]
    O --> P[追加完了]

    Q[バーコードスキャン] --> R[POAllocateBarcodeHandler]
    R --> S[サプライヤーパーツ特定]
    S --> T[明細追加フォーム]
    T --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-27-01 | 保留中のみ追加可 | 発注が保留中（Pending）状態の場合のみ明細追加可能 | isPending == true |
| BR-27-02 | サプライヤー限定 | 選択可能なサプライヤーパーツは当該発注のサプライヤーに限定 | supplier == order.supplierId |
| BR-27-03 | 数量必須 | 発注数量は必須入力 | - |
| BR-27-04 | バーコード追加 | API v153以上でバーコードによる明細追加が可能 | supportsBarcodePOAddLineEndpoint |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 登録 | purchase_order_line | INSERT | 新規明細行を登録 |

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

#### purchase_order_line（APIエンドポイント: order/po-line/）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | order | :orderId | 必須 |
| INSERT | part | :supplierPartId | 必須 |
| INSERT | quantity | :quantity | 必須 |
| INSERT | reference | :reference | オプション |
| INSERT | purchase_price | :purchase_price | オプション |
| INSERT | purchase_price_currency | :purchase_price_currency | オプション |
| INSERT | destination | :destination | オプション |
| INSERT | notes | :notes | オプション |
| INSERT | link | :link | オプション |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | トークン無効 | 再ログインを促す |
| 403 | 権限エラー | 作成権限なし | 権限不足メッセージ表示 |
| 400 | バリデーションエラー | 入力値不正 | フォームにエラー表示 |
| 404 | Not Found | 発注が存在しない | 一覧に戻る |
| 500 | サーバーエラー | サーバー側障害 | リトライを促す |

### リトライ仕様

ネットワークエラー時はユーザーが再度保存ボタンを押下することでリトライ可能。

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

API側で単一トランザクションとして処理される。

## パフォーマンス要件

- フォーム表示は即座に行う
- サプライヤーパーツ選択時は検索機能を提供
- 保存処理中はローディングインジケータを表示

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

- トークンベース認証による保護
- canCreate権限チェックによるアクセス制御
- サプライヤーフィルタにより不正なパーツ追加を防止

## 備考

- 発注詳細画面のactionButtonsで追加ボタンを構築（109-117行目）
- `_addLineItem`メソッドで追加処理を実行（159-177行目）
- partフィールドはhidden: falseで表示、supplierフィルタで絞り込み
- バーコード追加はPOAllocateBarcodeHandlerで処理

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | purchase_order.dart | `lib/inventree/purchase_order.dart` | InvenTreePOLineItemのformFieldsメソッド（176-194行目） |

**読解のコツ**: formFieldsでpart, order, reference, quantity等のフィールドが定義されている。partとorderはhidden: trueがデフォルト。

#### Step 2: 追加処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | purchase_order_detail.dart | `lib/widget/order/purchase_order_detail.dart` | _addLineItemメソッド（159-177行目） |

**主要処理フロー**:
1. **160行目**: InvenTreePOLineItem().formFields()でフィールド定義取得
2. **163行目**: partフィールドをhidden: falseで表示
3. **164行目**: supplierフィルタを設定
4. **166行目**: orderフィールドに発注ID設定
5. **168-176行目**: createForm()でフォーム表示

#### Step 3: アクションボタン表示を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | purchase_order_detail.dart | `lib/widget/order/purchase_order_detail.dart` | actionButtonsメソッド（92-156行目） |

**主要処理フロー**:
- **107-108行目**: canCreate && isPendingの条件チェック
- **109-117行目**: 明細追加ボタンのSpeedDialChild構築

#### Step 4: バーコード追加を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | purchase_order_detail.dart | `lib/widget/order/purchase_order_detail.dart` | barcodeButtonsメソッド（241-277行目） |
| 4-2 | purchase_order.dart | `lib/barcode/purchase_order.dart` | POAllocateBarcodeHandler |

**主要処理フロー**:
- **261-274行目**: isPendingかつAPI対応時にバーコード追加ボタン表示
- **267-270行目**: POAllocateBarcodeHandlerでスキャン処理

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

```
PurchaseOrderDetailWidget
    │
    └─ _PurchaseOrderDetailState
           │
           ├─ actionButtons()
           │      │
           │      └─ SpeedDialChild（明細追加）
           │             └─ onTap → _addLineItem()
           │
           ├─ _addLineItem()
           │      │
           │      ├─ InvenTreePOLineItem().formFields()
           │      │
           │      ├─ fields["part"]["hidden"] = false
           │      ├─ fields["part"]["filters"] = {"supplier": supplierId}
           │      ├─ fields["order"]["value"] = order.pk
           │      │
           │      └─ InvenTreePOLineItem().createForm()
           │             │
           │             └─ launchApiForm()
           │                    │
           │                    ├─ API POST送信
           │                    │
           │                    └─ onSuccess
           │                           ├─ refresh()
           │                           └─ showSnackIcon()
           │
           └─ barcodeButtons()
                  │
                  └─ SpeedDialChild（バーコード追加）
                         └─ scanBarcode → POAllocateBarcodeHandler
```

### データフロー図

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

発注詳細画面 ─────────▶ _addLineItem()
      │                           │
      ├─ order.pk ────────────────┤
      │                           │
      └─ order.supplierId ────────┤
                                  ▼
                           formFields()
                                  │
                                  ▼
                     フィールド設定（part, supplier, order）
                                  │
                                  ▼
                           createForm()
                                  │
                                  ▼
                     [フォームダイアログ]
                                  │
                                  ▼
                          ユーザー入力
                    (サプライヤーパーツ、数量等)
                                  │
                                  ▼
                   API: POST order/po-line/
                                  │
                                  ▼
                            登録成功
                                  │
                          ┌───────┴───────┐
                          ▼               ▼
                     refresh()    showSnackIcon()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| purchase_order_detail.dart | `lib/widget/order/purchase_order_detail.dart` | ソース | 発注詳細・明細追加ウィジェット |
| purchase_order.dart | `lib/inventree/purchase_order.dart` | ソース | 発注明細モデル・フォームフィールド定義 |
| purchase_order.dart | `lib/barcode/purchase_order.dart` | ソース | バーコード明細追加ハンドラ |
| barcode.dart | `lib/barcode/barcode.dart` | ソース | バーコードスキャン基盤 |
| api_form.dart | `lib/api_form.dart` | ソース | APIフォーム生成・通信処理 |
| snacks.dart | `lib/widget/snacks.dart` | ソース | スナックバー表示 |
| l10.dart | `lib/l10.dart` | ソース | 多言語対応 |
