# 画面設計書 20-部品カテゴリ画面

## 概要

本ドキュメントは、InvenTree Flutterアプリケーションの「部品カテゴリ画面」について、その機能・動作・実装を詳細に記述した設計書です。

### 本画面の処理概要

部品カテゴリ画面は、部品（パーツ）カテゴリの階層構造を表示し、カテゴリの詳細情報とサブカテゴリ、およびそのカテゴリに属する部品一覧を表示する画面です。タブUIで「詳細」と「部品」を切り替えることができ、カテゴリの階層をナビゲートしたり、新規カテゴリや新規部品を作成することもできます。

**業務上の目的・背景**：在庫管理において、部品を適切に分類・整理することは効率的な検索と管理の基盤となります。部品カテゴリは階層構造を持ち、例えば「電子部品 > 半導体 > IC」のように細分化できます。この画面では、カテゴリツリーを視覚的にナビゲートし、目的の部品に素早くアクセスできます。また、カテゴリの新規作成・編集機能により、組織の業務に合った分類体系を構築できます。

**画面へのアクセス方法**：
1. ホーム画面の「部品」タイルをタップ
2. ドロワーメニューの「部品」をタップ
3. 他のカテゴリからサブカテゴリをタップ
4. 部品詳細画面からカテゴリをタップ

**主要な操作・処理内容**：
1. カテゴリ情報の表示 - カテゴリ名、説明、親カテゴリへのパスを表示
2. サブカテゴリ一覧の表示 - ページネーション付きでサブカテゴリを表示
3. 部品一覧の表示 - カテゴリに属する部品をページネーション付きで表示
4. カテゴリの編集 - カテゴリ情報を編集（編集権限がある場合）
5. 新規カテゴリの作成 - サブカテゴリを新規作成（作成権限がある場合）
6. 新規部品の作成 - カテゴリ内に新規部品を作成（作成権限がある場合）
7. 親カテゴリへの遷移 - パンくずリストで親カテゴリに遷移

**画面遷移**：
- 遷移元：ホーム画面、ドロワーメニュー、他のカテゴリ画面、部品詳細画面
- 遷移先：サブカテゴリ画面（自身と同じ画面）、部品詳細画面、親カテゴリ画面

**権限による表示制御**：
- カテゴリ編集ボタン：`InvenTreePartCategory().canEdit`がtrueの場合のみ表示
- 新規カテゴリ作成ボタン：`InvenTreePartCategory().canCreate`がtrueの場合のみ表示
- 新規部品作成ボタン：`InvenTreePart().canCreate`がtrueの場合のみ表示

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | パーツカテゴリ表示 | 主機能 | パーツカテゴリの階層構造表示・ナビゲート |
| 4 | パーツ一覧表示 | 補助機能 | カテゴリ内パーツ一覧のタブ表示 |
| 5 | パーツ詳細表示 | 遷移先機能 | パーツ選択時にパーツ詳細画面へ遷移 |

## 画面種別

一覧・詳細（タブ切り替え）

## URL/ルーティング

Flutter Navigator経由でのプッシュ遷移（MaterialPageRoute）

## 入出力項目

| 項目名 | 入出力 | データ型 | 必須 | 説明 |
|--------|--------|---------|------|------|
| category | 入力 | InvenTreePartCategory? | - | 表示するカテゴリ（nullの場合はトップレベル） |

## 表示項目

| No | 項目名 | 表示形式 | 説明 |
|----|--------|---------|------|
| 1 | AppBar | テキスト | 「部品カテゴリ」タイトル表示 |
| 2 | 編集ボタン | IconButton | カテゴリ編集ダイアログを開く（権限がある場合） |
| 3 | 詳細タブ | Tab | カテゴリ詳細とサブカテゴリ一覧 |
| 4 | 部品タブ | Tab | カテゴリ内の部品一覧 |
| 5 | カテゴリ情報カード | Card | カテゴリ名、説明、カスタムアイコン |
| 6 | 親カテゴリリンク | ListTile | 親カテゴリへの遷移リンク |
| 7 | サブカテゴリ一覧 | PaginatedPartCategoryList | ページネーション付きサブカテゴリ一覧 |
| 8 | 部品一覧 | PaginatedPartList | ページネーション付き部品一覧 |
| 9 | SpeedDial | FloatingActionButton | 新規カテゴリ/部品作成ボタン |

## イベント仕様

### 1-画面初期化時（onBuild）

1. `refresh(context)`を呼び出し
2. カテゴリがnullでない場合、`category.reload()`でカテゴリ情報を再取得
3. 再取得に失敗した場合は`Navigator.of(context).pop()`で前画面に戻る

### 2-編集ボタンタップ時

1. `_editCategoryDialog()`を呼び出し
2. `category.editForm()`でカテゴリ編集フォームを表示
3. 編集成功時は`refresh(context)`で画面を更新し、成功スナックバーを表示

### 3-新規カテゴリ作成タップ時

1. `_newCategory()`を呼び出し
2. `InvenTreePartCategory().createForm()`で新規カテゴリ作成フォームを表示
3. 作成成功時は新規カテゴリの詳細画面に遷移

### 4-新規部品作成タップ時

1. `_newPart()`を呼び出し
2. `InvenTreePart().createForm()`で新規部品作成フォームを表示
3. 作成成功時は新規部品の詳細画面に遷移

### 5-親カテゴリタップ時

1. 親カテゴリID（`parentId`）を取得
2. 親IDが負の値（トップレベル）の場合は`CategoryDisplayWidget(null)`に遷移
3. 親IDが正の値の場合は`InvenTreePartCategory().get(parentId)`で親カテゴリを取得し、その詳細画面に遷移

### 6-サブカテゴリタップ時

1. `category.goToDetailPage(context)`を呼び出し
2. 選択したサブカテゴリの`CategoryDisplayWidget`に遷移

### 7-部品タップ時

1. `part.goToDetailPage(context)`を呼び出し
2. 選択した部品の`PartDetailWidget`に遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| カテゴリ編集 | part/category/ (API) | UPDATE | カテゴリ情報の更新 |
| 新規カテゴリ作成 | part/category/ (API) | INSERT | 新規カテゴリの作成 |
| 新規部品作成 | part/ (API) | INSERT | 新規部品の作成 |
| 画面表示 | part/category/ (API) | SELECT | カテゴリ情報の取得 |
| サブカテゴリ一覧 | part/category/ (API) | SELECT | サブカテゴリ一覧の取得 |
| 部品一覧 | part/ (API) | SELECT | 部品一覧の取得 |

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

#### part/category/ (InvenTree API)

| 操作 | 項目（フィールド） | 更新値・取得条件 | 備考 |
|-----|------------------|-----------------|------|
| UPDATE | name | フォーム入力値 | カテゴリ名 |
| UPDATE | description | フォーム入力値 | カテゴリ説明 |
| UPDATE | parent | フォーム選択値 | 親カテゴリID |
| UPDATE | structural | フォーム入力値 | 構造カテゴリフラグ |
| INSERT | parent | 現在のカテゴリPK | 新規サブカテゴリの親 |

#### part/ (InvenTree API)

| 操作 | 項目（フィールド） | 更新値・取得条件 | 備考 |
|-----|------------------|-----------------|------|
| INSERT | category | 現在のカテゴリPK | 新規部品のカテゴリ |

## メッセージ仕様

| No | メッセージ種別 | メッセージ内容 | 表示条件 |
|----|--------------|--------------|---------|
| 1 | 成功 | L10().categoryUpdated | カテゴリ編集成功時 |

## 例外処理

| No | 例外条件 | 処理内容 |
|----|---------|---------|
| 1 | カテゴリ再取得失敗 | Navigator.pop()で前画面に戻る |
| 2 | トップレベルカテゴリ | 特別なカード表示（「トップレベルカテゴリ」メッセージ） |

## 備考

- `CategoryDisplayWidget`は`RefreshableState`を継承し、プルリフレッシュに対応
- `category`がnullの場合はトップレベルカテゴリとして扱う
- APIバージョン69以降で`supportsNullTopLevelFiltering`がサポートされ、トップレベルのフィルタリングが改善
- カテゴリにはカスタムアイコンを設定可能（`customIcon`プロパティ）
- `InvenTreePartCategory`と`InvenTreePart`のモデルクラスで権限チェック（`canEdit`, `canCreate`）を実施

---

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

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

### 推奨読解順序

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

まず、部品カテゴリのモデルクラスを理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | part.dart | `lib/inventree/part.dart` | `InvenTreePartCategory`クラスと`InvenTreePart`クラスの定義 |

**読解のコツ**:
- 20-82行目で`InvenTreePartCategory`クラスを確認
- 27行目: APIエンドポイント `"part/category/"`
- 45-54行目: `formFields()`でフォームフィールドの定義
- 56-72行目: `pathstring`, `parentPathString`などのプロパティ
- 138-459行目: `InvenTreePart`クラス

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

処理の起点となるウィジェットクラスを特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | category_display.dart | `lib/widget/part/category_display.dart` | `CategoryDisplayWidget`クラスとStateクラスの構造 |

**主要処理フロー**:
1. **17-24行目**: `CategoryDisplayWidget` StatefulWidgetの定義とコンストラクタ
2. **26行目**: `RefreshableState`を継承
3. **100-115行目**: `onBuild()`と`request()`でカテゴリ情報を取得
4. **177-186行目**: `getTabIcons()`と`getTabs()`でタブUI構築

#### Step 3: UI構築処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | category_display.dart | `lib/widget/part/category_display.dart` | タブ内のコンテンツ構築 |

**主要処理フロー**:
- **117-175行目**: `getCategoryDescriptionCard()`でカテゴリ情報カードを構築
- **187-208行目**: `detailTiles()`で詳細タブのコンテンツ構築
- **210-217行目**: `partsTiles()`で部品タブのコンテンツ構築

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | category_display.dart | `lib/widget/part/category_display.dart` | SpeedDialアクションの実装 |

**主要処理フロー**:
- **53-80行目**: `actionButtons()`でSpeedDialChildを構築
- **219-239行目**: `_newCategory()`で新規カテゴリ作成
- **241-257行目**: `_newPart()`で新規部品作成
- **82-98行目**: `_editCategoryDialog()`でカテゴリ編集

#### Step 5: ページネーションリストを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | category_list.dart | `lib/widget/part/category_list.dart` | `PaginatedPartCategoryList`の実装 |
| 5-2 | part_list.dart | `lib/widget/part/part_list.dart` | `PaginatedPartList`の実装 |

**主要処理フロー**:
- category_list.dart 33-114行目: ページネーション付きカテゴリ一覧の実装
- part_list.dart 42-133行目: ページネーション付き部品一覧の実装

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

```
CategoryDisplayWidget
    │
    └─ _CategoryDisplayState (extends RefreshableState)
           │
           ├─ onBuild() / request()
           │      └─ category?.reload()
           │             └─ InvenTreeAPI.get()
           │
           ├─ getTabIcons() / getTabs()
           │      ├─ detailTiles()
           │      │      ├─ getCategoryDescriptionCard()
           │      │      └─ PaginatedPartCategoryList
           │      │             └─ InvenTreePartCategory.listPaginated()
           │      │
           │      └─ partsTiles()
           │             └─ PaginatedPartList
           │                    └─ InvenTreePart.listPaginated()
           │
           ├─ appBarActions()
           │      └─ _editCategoryDialog()
           │             └─ category.editForm()
           │
           └─ actionButtons()
                  ├─ _newPart()
                  │      └─ InvenTreePart().createForm()
                  │
                  └─ _newCategory()
                         └─ InvenTreePartCategory().createForm()
```

### データフロー図

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

コンストラクタ
    │
    └─ category (nullable)
           │
           ▼
    request() ───▶ category.reload() ───▶ カテゴリ情報表示
                         │
                         └─ 失敗時 ───▶ Navigator.pop()

[詳細タブ]

カテゴリPK  ───▶  PaginatedPartCategoryList  ───▶  サブカテゴリ一覧
  (parent)              │
                        └─ listPaginated()
                               └─ API: part/category/?parent={pk}

[部品タブ]

カテゴリPK  ───▶  PaginatedPartList  ───▶  部品一覧
(category)              │
                        └─ listPaginated()
                               └─ API: part/?category={pk}
```

```
[新規作成フロー]

SpeedDialタップ  ───▶  createForm()  ───▶  API POST  ───▶  詳細画面遷移
                            │
                            └─ data: {parent/category: pk}
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| category_display.dart | `lib/widget/part/category_display.dart` | ソース | 画面本体のウィジェット実装 |
| category_list.dart | `lib/widget/part/category_list.dart` | ソース | ページネーション付きカテゴリ一覧 |
| part_list.dart | `lib/widget/part/part_list.dart` | ソース | ページネーション付き部品一覧 |
| part.dart | `lib/inventree/part.dart` | ソース | 部品・カテゴリのモデルクラス |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | リフレッシュ可能な状態クラスの基底 |
| paginator.dart | `lib/widget/paginator.dart` | ソース | ページネーションウィジェットの基底 |
| progress.dart | `lib/widget/progress.dart` | ソース | ローディングオーバーレイ |
| snacks.dart | `lib/widget/snacks.dart` | ソース | スナックバー表示 |
| l10.dart | `lib/l10.dart` | ソース | 多言語対応テキスト定義 |
| app_colors.dart | `lib/app_colors.dart` | ソース | アプリカラー定数 |
| part_detail.dart | `lib/widget/part/part_detail.dart` | ソース | 部品詳細画面（遷移先） |
