# 画面設計書 23-在庫ロケーション画面

## 概要

本ドキュメントは、InvenTree Flutterアプリケーションにおける「在庫ロケーション画面」の設計仕様を定義する。在庫ロケーションの階層構造を表示し、サブロケーションと在庫アイテムの一覧を提供する画面である。

### 本画面の処理概要

在庫ロケーション画面は、InvenTreeの在庫管理における「場所」の概念を視覚化する画面である。倉庫、棚、引き出しなどの物理的な保管場所を階層的に管理し、各ロケーションに紐づく在庫アイテムを一覧表示する。2つのタブ（詳細・在庫アイテム）で情報を整理している。

**業務上の目的・背景**：製造業や物流において、在庫の物理的な所在を正確に把握することは極めて重要である。本画面は、倉庫内のロケーション構造をナビゲートし、特定のロケーションにある在庫を確認・操作するためのインターフェースを提供する。バーコードスキャンによる在庫移動や発注品受入など、現場作業を効率化する機能も備える。

**画面へのアクセス方法**：
- ホーム画面の「在庫」タイルをタップ
- ドロワーメニューの「在庫」をタップ
- 在庫アイテム詳細画面からロケーションリンクをタップ
- 親ロケーションからサブロケーションへ階層移動
- バーコードスキャンでロケーションを識別

**主要な操作・処理内容**：
1. ロケーション情報の表示（名前、説明、パス）
2. サブロケーション一覧の表示（詳細タブ）
3. 在庫アイテム一覧の表示（在庫タブ）
4. ロケーション編集（編集権限がある場合）
5. 新規サブロケーション作成
6. 新規在庫アイテム作成
7. バーコードスキャンによる在庫移動
8. 発注品受入スキャン
9. ロケーション位置特定（locate機能）
10. ラベル印刷

**画面遷移**：
- 遷移元：ホーム画面、ドロワーメニュー、在庫アイテム詳細画面、親ロケーション、バーコードスキャン画面
- 遷移先：サブロケーション（自己遷移）、在庫アイテム詳細画面、親ロケーション

**権限による表示制御**：
- 編集ボタンは `InvenTreeStockLocation().canEdit` がtrueの場合のみ表示
- サブロケーション作成は `InvenTreeStockLocation().canCreate` がtrueの場合のみ
- 在庫作成は `InvenTreeStockItem().canCreate` がtrueの場合のみ
- バーコードスキャン移動は `InvenTreeStockItem().canEdit` がtrueの場合のみ

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 13 | 在庫ロケーション表示 | 主機能 | 在庫ロケーションの階層構造表示・ナビゲート |
| 14 | 在庫アイテム一覧表示 | 補助機能 | ロケーション内在庫アイテム一覧のタブ表示 |
| 15 | 在庫アイテム詳細表示 | 遷移先機能 | 在庫アイテム選択時に在庫詳細画面へ遷移 |
| 16 | 在庫アイテム作成 | 補助機能 | 在庫作成ボタン押下時に在庫作成フォーム表示 |
| 44 | バーコード割当 | 補助機能 | バーコード割当ボタン押下時にバーコードスキャン開始 |
| 46 | 在庫スキャン操作 | 補助機能 | バーコードスキャンで在庫アイテムをロケーションに追加 |
| 47 | 発注受入スキャン | 補助機能 | バーコードスキャンで発注品を受け入れ |
| 53 | ラベル印刷 | 補助機能 | ラベル印刷ボタン押下時に印刷処理実行 |

## 画面種別

詳細（タブ付き・階層ナビゲーション）

## URL/ルーティング

Flutter Navigator によるプッシュ遷移。`LocationDisplayWidget` に `InvenTreeStockLocation?` インスタンスを渡して表示。nullの場合はトップレベルロケーションを表示。

## 入出力項目

| 項目名 | 入出力 | 型 | 必須 | 説明 |
|--------|--------|------|------|------|
| location | 入力 | InvenTreeStockLocation? | No | 表示対象のロケーション（nullでトップレベル） |

## 表示項目

### ロケーション説明カード

| 項目名 | 表示位置 | データ型 | 表示条件 |
|--------|----------|----------|---------|
| ロケーション名 | ListTile.title | String | location != null |
| 説明 | ListTile.subtitle | String | location != null |
| カスタムアイコン | ListTile.leading | Icon | customIcon != null |
| トップレベル表示 | ListTile.title | テキスト | location == null |
| 親ロケーション | ListTile | リンク | includeActions && location != null |

### 詳細タブ

| 項目名 | 表示位置 | データ型 | 説明 |
|--------|----------|----------|------|
| ロケーション説明カード | Card | コンポーネント | ロケーション情報 |
| サブロケーション一覧 | PaginatedList | リスト | PaginatedStockLocationList |

### 在庫アイテムタブ

| 項目名 | 表示位置 | データ型 | 説明 |
|--------|----------|----------|------|
| 在庫アイテム一覧 | PaginatedList | リスト | PaginatedStockItemList |

## イベント仕様

### 1-ロケーション編集ボタン押下

アプリバーの編集アイコンをタップすると、ロケーション編集フォームが表示される。

**処理フロー**：
1. `IconButton.onPressed` で `_editLocationDialog(context)` を呼び出し
2. `location.editForm()` でAPIフォームを表示
3. 成功時に `refresh(context)` と "Location updated" スナックバー表示

### 2-位置特定ボタン押下

アプリバーの位置特定アイコンをタップすると、物理的なロケーション位置を特定する。

**処理フロー**：
1. `IconButton.onPressed` で `api.locateItemOrLocation(context, location: location.pk)` を呼び出し
2. サーバーがlocate mixinをサポートしている場合のみ表示

### 3-新規サブロケーション作成

SpeedDialの「ロケーション作成」ボタンをタップすると、新規ロケーション作成フォームが表示される。

**処理フロー**：
1. `SpeedDialChild.onTap` で `_newLocation(context)` を呼び出し
2. `InvenTreeStockLocation().createForm()` で作成フォームを表示
3. 親ロケーションとして現在のロケーションPKを設定
4. 成功時に作成されたロケーション詳細画面へ遷移

### 4-新規在庫アイテム作成

SpeedDialの「在庫作成」ボタンをタップすると、新規在庫アイテム作成フォームが表示される。

**処理フロー**：
1. `SpeedDialChild.onTap` で `_newStockItem(context)` を呼び出し
2. `InvenTreeStockItem().createForm()` で作成フォームを表示
3. ロケーションとして現在のロケーションPKを設定
4. 成功時に作成された在庫アイテム詳細画面へ遷移

### 5-バーコードスキャン（在庫移動）

SpeedDialのバーコードスキャンボタンをタップすると、在庫アイテムをこのロケーションに移動するスキャンモードが起動する。

**処理フロー**：
1. `SpeedDialChild.onTap` で `scanBarcode()` を呼び出し
2. `StockLocationScanInItemsHandler(location)` をハンドラとして設定
3. スキャン完了後に `refresh(context)` で再読み込み

### 6-発注品受入スキャン

SpeedDialの発注受入ボタンをタップすると、発注品を受け入れるスキャンモードが起動する。

**処理フロー**：
1. `SpeedDialChild.onTap` で `scanBarcode()` を呼び出し
2. `POReceiveBarcodeHandler(location: location)` をハンドラとして設定

### 7-親ロケーションタップ

親ロケーションのListTileをタップすると、親ロケーションへ遷移する。

**処理フロー**：
1. `ListTile.onTap` が発火
2. 親IDが0未満の場合は `LocationDisplayWidget(null)` でトップレベルへ
3. それ以外は `InvenTreeStockLocation().get(parentId)` で取得後、`goToDetailPage()` で遷移

### 8-ラベル印刷

SpeedDialのラベル印刷ボタンをタップすると、ラベル選択・印刷処理が実行される。

**処理フロー**：
1. `SpeedDialChild.onTap` が発火
2. `selectAndPrintLabel(context, "stocklocation", widget.location.pk)` を呼び出し

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示 | StockLocation | SELECT | ロケーション情報の取得 |
| サブロケーション一覧 | StockLocation | SELECT | 子ロケーション一覧の取得 |
| 在庫アイテム一覧 | StockItem | SELECT | ロケーション内在庫の取得 |
| ロケーション編集 | StockLocation | UPDATE | ロケーション情報の更新 |
| サブロケーション作成 | StockLocation | INSERT | 新規ロケーションの作成 |
| 在庫アイテム作成 | StockItem | INSERT | 新規在庫アイテムの作成 |
| バーコードスキャン移動 | StockItem | UPDATE | 在庫アイテムのロケーション更新 |

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

#### StockLocation テーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | pk, name, description, pathstring, parent, icon | pk = 指定ID | reload()で再取得 |
| UPDATE | name, description, parent, structural | フォーム入力値 | editForm()経由 |
| INSERT | name, description, parent, structural | フォーム入力値 | createForm()経由 |

#### StockItem テーブル

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全フィールド | location = 指定ロケーションID | ページネーション取得 |
| INSERT | part, location, quantity, serial_numbers, batch等 | フォーム入力値 | createForm()経由 |
| UPDATE | location | スキャンしたアイテムを現ロケーションへ | バーコードスキャン時 |

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容 |
|-------------|---------------|----------|---------------|
| MSG_LOCATION_UPDATED | 成功 | ロケーション編集成功時 | "Location updated"（L10().locationUpdated） |
| MSG_TOP_LEVEL | 情報 | トップレベル表示時 | "Top level stock location"（L10().stockTopLevel） |

## 例外処理

| 例外条件 | 処理内容 | ユーザーへの表示 |
|---------|---------|-----------------|
| ロケーションリロード失敗 | Navigator.pop()で画面を閉じる | 前の画面に戻る |
| APIエラー | エラーをSentryに報告 | 該当項目が非表示 |
| 権限不足 | 編集・作成ボタン非表示 | 閲覧のみ可能 |

## 備考

- ロケーションがnullの場合は「トップレベル」として扱い、ルートロケーションを表示
- サブロケーションフィルタは `parent` パラメータで制御
- 在庫アイテムフィルタは `location` パラメータで制御
- `api.supportsNullTopLevelFiltering` がtrueの場合、トップレベルフィルタに "null" を使用
- カスタムアイコンはロケーションごとに設定可能

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stock.dart | `lib/inventree/stock.dart` | InvenTreeStockLocationクラス（576-633行）。pathstring, parentPathString, itemcountなどのゲッター |
| 1-2 | stock.dart | `lib/inventree/stock.dart` | InvenTreeStockItemクラス（134-574行）。ロケーション関連のプロパティ |

**読解のコツ**: `InvenTreeStockLocation` は `InvenTreeModel` を継承しており、URL="stock/location/" でAPIエンドポイントが定義されている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | location_display.dart | `lib/widget/stock/location_display.dart` | LocationDisplayWidget（25-34行）。InvenTreeStockLocation?を受け取る（null許容） |
| 2-2 | location_display.dart | `lib/widget/stock/location_display.dart` | _LocationDisplayState（36-388行）。主要なStateクラス |

**主要処理フロー**:
1. **44-46行**: `getAppBarTitle()` で "Stock Location" を返す
2. **225-239行**: `request()` でデータ取得。location.reload()とラベル印刷設定を読み込み
3. **347-355行**: `getTabs()` で2つのタブを返す

#### Step 3: タブ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | location_display.dart | `lib/widget/stock/location_display.dart` | getTabIcons()（347-349行）とgetTabs()（352-355行）。詳細タブと在庫アイテムタブ |

**主要処理フロー**:
- **354行**: detailTiles()でサブロケーション一覧
- **354行**: stockTiles()で在庫アイテム一覧

#### Step 4: 各種タイル・操作を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | location_display.dart | `lib/widget/stock/location_display.dart` | locationDescriptionCard()（290-345行）。ロケーション情報カードの構築 |
| 4-2 | location_display.dart | `lib/widget/stock/location_display.dart` | detailTiles()（358-378行）とstockTiles()（381-387行）。各タブのコンテンツ |

#### Step 5: アクションボタンを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | location_display.dart | `lib/widget/stock/location_display.dart` | appBarActions()（48-79行）。編集・位置特定ボタン |
| 5-2 | location_display.dart | `lib/widget/stock/location_display.dart` | barcodeButtons()（81-150行）。バーコードスキャン関連 |
| 5-3 | location_display.dart | `lib/widget/stock/location_display.dart` | actionButtons()（152-197行）。SpeedDial操作ボタン |

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

```
LocationDisplayWidget (StatefulWidget)
    │
    └─ _LocationDisplayState (RefreshableState)
           │
           ├─ request()
           │      │
           │      └─ location.reload() → API GET /api/stock/location/{pk}/
           │
           ├─ getTabs()
           │      │
           │      ├─ detailTiles()
           │      │      │
           │      │      ├─ locationDescriptionCard()
           │      │      └─ PaginatedStockLocationList (サブロケーション)
           │      │
           │      └─ stockTiles()
           │             │
           │             └─ PaginatedStockItemList (在庫アイテム)
           │
           ├─ barcodeButtons()
           │      │
           │      ├─ StockLocationScanInItemsHandler
           │      ├─ POReceiveBarcodeHandler
           │      ├─ ScanParentLocationHandler
           │      └─ customBarcodeAction
           │
           └─ actionButtons()
                  │
                  ├─ _newLocation() → StockLocation.createForm()
                  ├─ _newStockItem() → StockItem.createForm()
                  └─ selectAndPrintLabel()
```

### データフロー図

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

InvenTreeStockLocation?  ───▶ _LocationDisplayState        ───▶ TabBarView
(null = トップレベル)              │                                │
                                   ├─ request()                     ├─ 詳細タブ
                                   │      │                         │    ├─ locationDescriptionCard
                                   │      └─ reload()               │    └─ サブロケーション一覧
                                   │                                │
                                   └─ filters構築                    └─ 在庫タブ
                                          │                              └─ 在庫アイテム一覧
                                          ├─ parent: location.pk
                                          └─ location: location.pk
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| location_display.dart | `lib/widget/stock/location_display.dart` | ソース | 在庫ロケーション画面のメインウィジェット |
| stock.dart | `lib/inventree/stock.dart` | ソース | 在庫関連データモデル |
| location_list.dart | `lib/widget/stock/location_list.dart` | ソース | サブロケーション一覧ウィジェット |
| stock_list.dart | `lib/widget/stock/stock_list.dart` | ソース | 在庫アイテム一覧ウィジェット |
| stock_detail.dart | `lib/widget/stock/stock_detail.dart` | ソース | 在庫アイテム詳細（遷移先） |
| barcode.dart | `lib/barcode/barcode.dart` | ソース | バーコードスキャン基盤 |
| stock.dart | `lib/barcode/stock.dart` | ソース | 在庫関連バーコードハンドラ |
| purchase_order.dart | `lib/barcode/purchase_order.dart` | ソース | 発注受入バーコードハンドラ |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | リフレッシュ可能なState基底クラス |
| labels.dart | `lib/labels.dart` | ソース | ラベル印刷機能 |
| preferences.dart | `lib/preferences.dart` | ソース | 設定管理 |
