# 機能設計書 13-在庫ロケーション表示

## 概要

本ドキュメントは、InvenTreeモバイルアプリにおける在庫ロケーション表示機能の設計仕様を定義する。

### 本機能の処理概要

本機能は、在庫ロケーションの階層構造を表示・ナビゲートする機能である。倉庫、棚、ビンなどの物理的な保管場所を階層的に管理し、各ロケーション内のサブロケーションと在庫アイテムを一覧表示する。

**業務上の目的・背景**：在庫管理において、物理的な保管場所の構造化された管理は不可欠である。倉庫→エリア→棚→ビンのような階層構造でロケーションを管理することで、在庫アイテムの迅速な特定、効率的なピッキング作業、正確な棚卸しが可能となる。また、ロケーションごとの在庫状況を把握することで、スペース効率の最適化や在庫配置の改善に役立つ。

**機能の利用シーン**：
- 在庫アイテムの物理的な保管場所を確認する場合
- 新しいサブロケーションを作成する場合
- ロケーション内の在庫アイテムを確認する場合
- バーコードスキャンで在庫を受け入れる場合
- ロケーションのラベルを印刷する場合

**主要な処理内容**：
1. 指定されたロケーションの詳細情報（名前、説明、パス）を表示
2. サブロケーションの一覧をページネーション付きで表示
3. ロケーション内の在庫アイテム一覧を表示
4. 新規サブロケーションの作成
5. 新規在庫アイテムの作成
6. バーコードスキャンによる在庫操作
7. ロケーションのラベル印刷

**関連システム・外部連携**：InvenTree APIの `stock/location/` エンドポイントを使用してロケーションデータを取得・更新する。

**権限による制御**：`stock` ロールの権限に基づいてCRUD操作を制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 23 | 在庫ロケーション画面 | 主画面 | 在庫ロケーションの階層構造表示・ナビゲート |
| 24 | 在庫アイテム一覧画面 | 遷移先機能 | ロケーション内在庫アイテム一覧のタブ表示 |
| 25 | 在庫アイテム詳細画面 | 遷移先機能 | 在庫アイテム選択時に在庫詳細画面へ遷移 |

## 機能種別

データ参照（READ）/ データ作成（CREATE）/ データ更新（UPDATE）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| location | InvenTreeStockLocation? | No | 表示対象ロケーション（nullの場合はトップレベル） | - |
| parent | int | No | 親ロケーションIDフィルタ | 正の整数またはnull |
| cascade | bool | No | サブロケーションを含めるか | true/false |

### 入力データソース

- ナビゲーションから渡されるロケーションオブジェクト
- ユーザーによるフィルタリング条件の選択
- 新規作成フォームからの入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pk | int | ロケーションの主キー |
| name | String | ロケーション名 |
| description | String | ロケーションの説明 |
| pathstring | String | ロケーションのフルパス |
| parent | int? | 親ロケーションID |
| items | int | 直下の在庫アイテム数 |
| icon | String? | カスタムアイコン |
| customBarcode | String | カスタムバーコード |

### 出力先

- タブ形式の画面表示（詳細タブ、在庫タブ）
- カード形式のロケーション情報表示
- ListView形式のサブロケーション一覧
- ListView形式の在庫アイテム一覧

## 処理フロー

### 処理シーケンス

```
1. LocationDisplayWidgetを初期化
   └─ location引数を受け取る（nullの場合はトップレベル）
2. ロケーション情報の取得
   └─ location.reload()でAPIからデータを再取得
3. タブ表示の構築
   ├─ 詳細タブ：ロケーション情報カード + サブロケーション一覧
   └─ 在庫タブ：ロケーション内在庫アイテム一覧
4. ユーザー操作の処理
   ├─ サブロケーション選択：ロケーション詳細画面へ遷移
   ├─ 在庫アイテム選択：在庫詳細画面へ遷移
   ├─ 新規ロケーション作成：createForm()表示
   ├─ 新規在庫作成：createForm()表示
   └─ バーコードスキャン：各種スキャンハンドラー呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[ロケーション画面表示] --> B{location != null?}
    B -->|Yes| C[location.reload]
    B -->|No| D[トップレベル表示]
    C --> E[タブ構築]
    D --> E
    E --> F[詳細タブ]
    E --> G[在庫タブ]
    F --> H[ロケーション情報カード]
    F --> I[サブロケーション一覧]
    G --> J[在庫アイテム一覧]
    H --> K{ユーザー操作}
    I --> K
    J --> K
    K -->|サブロケーション選択| L[ロケーション詳細へ遷移]
    K -->|在庫選択| M[在庫詳細へ遷移]
    K -->|新規ロケーション| N[createForm表示]
    K -->|新規在庫| O[createForm表示]
    K -->|バーコードスキャン| P[スキャンハンドラー]
    K -->|親ロケーション| Q[親ロケーションへ遷移]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-13-01 | 階層構造 | ロケーションは親子関係で階層構造を形成する | 常時 |
| BR-13-02 | トップレベル | locationがnullの場合はトップレベル（全ロケーションのルート）を表示 | location == null |
| BR-13-03 | 親フィルタ | サブロケーション一覧は親ロケーションIDでフィルタ | 詳細タブ表示時 |
| BR-13-04 | ロケーションフィルタ | 在庫アイテム一覧は現在のロケーションIDでフィルタ | 在庫タブ表示時 |
| BR-13-05 | nullトップレベルフィルタ | APIバージョン174未満では parent="null" でトップレベルフィルタ | supportsNullTopLevelFiltering == true |
| BR-13-06 | ラベル印刷可否 | ラベル印刷はsupportsModernLabelPrinting && allowLabelPrintingの場合のみ | 設定による |

### 計算ロジック

- 親パス文字列: pathstringを"/"で分割し、最後の要素を除いた部分を結合

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ロケーション取得 | StockLocation | SELECT | ロケーション情報を取得 |
| サブロケーション取得 | StockLocation | SELECT | 親IDでフィルタしてサブロケーションを取得 |
| 在庫アイテム取得 | StockItem | SELECT | ロケーションIDでフィルタして在庫を取得 |
| ロケーション作成 | StockLocation | INSERT | 新規ロケーションを作成 |
| ロケーション更新 | StockLocation | UPDATE | ロケーション情報を更新 |

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

#### StockLocation（InvenTree API経由）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | parent | 親ロケーションID | サブロケーションフィルタ |
| INSERT | name, description, parent | ユーザー入力値 | 新規作成時 |
| UPDATE | name, description, parent, structural | ユーザー入力値 | 編集時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | トークン無効または期限切れ | 再ログインを促す |
| 403 | 権限エラー | 編集/作成権限がない | 読み取り専用モードで表示 |
| 404 | Not Found | ロケーションが存在しない | 前の画面に戻る |
| 500 | サーバーエラー | サーバー内部エラー | リトライまたはエラー表示 |

### リトライ仕様

ネットワークエラー時はプルダウンリフレッシュで再取得可能。

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

作成・更新操作は単一レコードの操作のため、明示的なトランザクション管理は不要。

## パフォーマンス要件

- ページサイズ：25件（PaginatedSearchState._pageSize）
- スクロールによる遅延読み込み対応
- 画像はキャッシュを使用（CachedNetworkImage）

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

- API認証トークンによるアクセス制御
- `stock` ロールの権限チェック（view/add/change/delete）
- HTTPS通信によるデータ保護

## 備考

- locationがnullの場合はトップレベルロケーションを表示
- カスタムアイコンはTablerIconsから取得
- バーコードスキャン機能は別途バーコードハンドラーと連携

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stock.dart | `lib/inventree/stock.dart` | InvenTreeStockLocationクラスの定義、プロパティ（pathstring, itemcount等）を理解する |

**読解のコツ**:
- **576-633行目**: InvenTreeStockLocationクラスの定義
- **583行目**: APIエンドポイント `stock/location/`
- **588行目**: rolesRequired = ["stock"]
- **590-597行目**: pathstring, parentPathStringの実装
- **601-609行目**: formFields()で編集可能なフィールドを定義
- **628行目**: itemcountプロパティで直下の在庫アイテム数を取得

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | location_display.dart | `lib/widget/stock/location_display.dart` | LocationDisplayWidgetクラスのエントリーポイント、InvenTreeStockLocation?を受け取る |

**主要処理フロー**:
1. **25-34行目**: LocationDisplayWidgetの定義、location引数を受け取る（null可）
2. **43-46行目**: getAppBarTitle()でタイトルを設定
3. **48-79行目**: appBarActions()で編集・位置特定ボタンを構築
4. **219-239行目**: request()でロケーション情報をリロード
5. **348-355行目**: getTabIcons(), getTabs()で2タブ構成を定義

#### Step 3: 詳細タブとサブロケーション一覧を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | location_display.dart | `lib/widget/stock/location_display.dart` | detailTiles()でサブロケーション一覧の構築を理解 |
| 3-2 | location_list.dart | `lib/widget/stock/location_list.dart` | PaginatedStockLocationListによるページネーション表示 |

**主要処理フロー（location_display.dart）**:
- **358-378行目**: detailTiles()でロケーション情報カードとサブロケーション一覧を構築
- **290-345行目**: locationDescriptionCard()でロケーション情報カードを構築
- **314-339行目**: 親ロケーションへの遷移処理

**主要処理フロー（location_list.dart）**:
- **34-46行目**: PaginatedStockLocationListクラスの定義
- **53-58行目**: orderingOptionsでソートオプションを定義
- **64-70行目**: filterOptionsでcascadeフィルタを定義
- **73-85行目**: requestPage()でInvenTreeStockLocation().listPaginated()を呼び出し

#### Step 4: 在庫タブと在庫アイテム一覧を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | location_display.dart | `lib/widget/stock/location_display.dart` | stockTiles()で在庫アイテム一覧の構築を理解 |
| 4-2 | stock_list.dart | `lib/widget/stock/stock_list.dart` | PaginatedStockItemListによる在庫一覧表示 |

**主要処理フロー（location_display.dart）**:
- **381-387行目**: stockTiles()でPaginatedStockItemListを構築

#### Step 5: アクションボタン（作成・バーコード）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | location_display.dart | `lib/widget/stock/location_display.dart` | actionButtons()とbarcodeButtons()の実装 |

**主要処理フロー**:
- **81-150行目**: barcodeButtons()でバーコードスキャン関連アクションを定義
- **152-197行目**: actionButtons()で新規作成、ラベル印刷アクションを定義
- **241-257行目**: _newLocation()で新規ロケーション作成フォームを表示
- **262-288行目**: _newStockItem()で新規在庫作成フォームを表示

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

```
LocationDisplayWidget
    │
    ├─ RefreshableState (基底クラス)
    │      └─ request()
    │              └─ location.reload()
    │                      └─ InvenTreeAPI.get()
    │
    ├─ getTabs()
    │      ├─ detailTiles()
    │      │      ├─ locationDescriptionCard()
    │      │      └─ PaginatedStockLocationList
    │      │              └─ InvenTreeStockLocation().listPaginated()
    │      │
    │      └─ stockTiles()
    │              └─ PaginatedStockItemList
    │                      └─ InvenTreeStockItem().listPaginated()
    │
    ├─ actionButtons()
    │      ├─ _newLocation() → InvenTreeStockLocation().createForm()
    │      ├─ _newStockItem() → InvenTreeStockItem().createForm()
    │      └─ selectAndPrintLabel()
    │
    └─ barcodeButtons()
           ├─ StockLocationScanInItemsHandler
           ├─ POReceiveBarcodeHandler
           ├─ ScanParentLocationHandler
           └─ customBarcodeAction
```

### データフロー図

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

location ──────▶ LocationDisplayWidget ──────▶ タブ表示
(StockLocation)          │                         │
                         ▼                         ├── 詳細タブ
              location.reload()                    │      ├── ロケーション情報カード
                         │                         │      └── サブロケーション一覧
                         ▼                         │
              API: stock/location/{pk}/            └── 在庫タブ
                         │                                └── 在庫アイテム一覧
                         ▼
              JSON Response ──────▶ InvenTreeStockLocation
                                           │
                                           ▼
                                   画面表示（名前, パス, 在庫数）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| location_display.dart | `lib/widget/stock/location_display.dart` | ソース | ロケーション詳細表示ウィジェット（メイン） |
| location_list.dart | `lib/widget/stock/location_list.dart` | ソース | ページネーション対応ロケーション一覧ウィジェット |
| stock_list.dart | `lib/widget/stock/stock_list.dart` | ソース | ページネーション対応在庫アイテム一覧ウィジェット |
| stock.dart | `lib/inventree/stock.dart` | ソース | InvenTreeStockLocation/StockItemモデルクラス定義 |
| model.dart | `lib/inventree/model.dart` | ソース | 基底モデルクラス、API呼び出し共通処理 |
| paginator.dart | `lib/widget/paginator.dart` | ソース | ページネーション対応検索ウィジェット基底クラス |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | リフレッシュ可能な状態管理基底クラス |
| api.dart | `lib/api.dart` | ソース | InvenTree API通信クラス |
| barcode.dart | `lib/barcode/barcode.dart` | ソース | バーコードスキャン機能 |
| stock.dart | `lib/barcode/stock.dart` | ソース | 在庫関連バーコードハンドラー |
| purchase_order.dart | `lib/barcode/purchase_order.dart` | ソース | 発注受入バーコードハンドラー |
| labels.dart | `lib/labels.dart` | ソース | ラベル印刷機能 |
