# 機能設計書 8-パーツ画像管理

## 概要

本ドキュメントは、InvenTreeモバイルアプリにおけるパーツ画像管理機能の設計を記載する。パーツの画像を表示・アップロード・削除する機能を提供し、画像クロッピング機能も備える。

### 本機能の処理概要

パーツに関連付けられた画像を管理する機能である。画像の表示、新規画像のアップロード（クロッピング対応）、既存画像の削除が可能。アップロード時には画像のトリミングやアスペクト比調整が行える。

**業務上の目的・背景**：在庫管理において、パーツの視覚的な識別は重要である。特に類似パーツの区別や、実物確認の際に画像があると作業効率が向上する。モバイルデバイスのカメラを活用して現場で即座に画像を登録できることが求められる。

**機能の利用シーン**：新規パーツ登録時の画像設定、既存パーツの画像更新、画像の削除、パーツ詳細画面からの画像確認などで利用される。

**主要な処理内容**：
1. パーツ画像のフルサイズ表示
2. ファイル選択による新規画像のアップロード
3. アップロード前の画像クロッピング
4. アスペクト比の選択（1:1、4:3、16:9、3:2）
5. 画像の回転・ズーム
6. 既存画像の削除

**関連システム・外部連携**：InvenTree REST API（part/{pk}/エンドポイント、PATCHでimageフィールド更新）

**権限による制御**：partロールのchange権限が必要。権限がない場合はアップロード・削除ボタンが表示されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 22 | 部品詳細画面 | 遷移元画面 | パーツ画像タップ時に画像表示・変更画面へ遷移 |

## 機能種別

ファイル操作 / 画像処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| part | InvenTreePart | Yes | 対象パーツ | null不可 |
| imageFile | File | No | アップロードする画像ファイル | 画像形式 |
| aspectRatio | Ratio | No | アスペクト比（1:1, 4:3, 16:9, 3:2） | 固定値 |

### 入力データソース

- ファイルピッカーで選択した画像ファイル
- クロッピングダイアログでの編集結果

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | bool | アップロード成功/失敗 |
| croppedBytes | Uint8List | クロッピング済み画像データ |

### 出力先

- InvenTreeサーバー（PATCH API、multipart/form-data）
- 画面リフレッシュ（アップロード成功時）

## 処理フロー

### 処理シーケンス

```
1. 画像画面表示
   └─ パーツのフルサイズ画像表示

2. 画像アップロード
   └─ ファイルピッカーで画像選択
   └─ クロッピングダイアログ表示
   └─ アスペクト比・ズーム・回転調整
   └─ クロップ実行
   └─ 一時ファイル作成
   └─ PATCH APIでアップロード
   └─ 一時ファイル削除
   └─ 成功/失敗メッセージ表示
   └─ 画面リフレッシュ

3. 画像削除
   └─ 削除確認ダイアログ表示
   └─ PATCH APIで画像をnullに設定
   └─ 成功/失敗メッセージ表示
   └─ 画面リフレッシュ
```

### フローチャート

```mermaid
flowchart TD
    A[画像画面表示] --> B[パーツ画像表示]
    B --> C{ユーザー操作}
    C -->|アップロード| D[ファイルピッカー]
    D --> E[ファイル選択]
    E --> F[クロッピングダイアログ]
    F --> G{操作}
    G -->|キャンセル| B
    G -->|クロップ| H[画像クロップ処理]
    H --> I[一時ファイル作成]
    I --> J[uploadImage API]
    J --> K{結果}
    K -->|成功| L[成功メッセージ]
    K -->|失敗| M[失敗メッセージ]
    L --> N[一時ファイル削除]
    M --> N
    N --> O[画面リフレッシュ]
    O --> B
    C -->|削除| P[削除確認ダイアログ]
    P --> Q{確認}
    Q -->|キャンセル| B
    Q -->|OK| R[PATCH image=null]
    R --> S{結果}
    S -->|成功| T[成功メッセージ]
    S -->|失敗| U[失敗メッセージ]
    T --> O
    U --> B
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | デフォルトアスペクト比 | 1:1（正方形）がデフォルト | クロッピング時 |
| BR-02 | 一時ファイル削除 | アップロード後に一時ファイルを削除 | アップロード完了時 |
| BR-03 | 削除確認必須 | 画像削除前に確認ダイアログ表示 | 削除操作時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 画像アップロード | - | - | サーバーAPIのみ（ローカルDB操作なし） |
| 画像削除 | - | - | サーバーAPIのみ |

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

ローカルデータベース操作なし。サーバーAPIへのPATCHリクエストのみ。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ファイル選択エラー | ファイル選択キャンセル | 処理中断 |
| - | クロップエラー | クロップ処理失敗 | エラーメッセージ表示 |
| 400 | バリデーションエラー | 不正な画像形式 | エラーメッセージ表示 |
| 401/403 | 認証/権限エラー | partロールのchange権限なし | エラーメッセージ表示 |
| 500+ | サーバーエラー | サーバー内部エラー | エラーメッセージ表示 |

### リトライ仕様

- 自動リトライなし
- ユーザーによる再操作が必要

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

- サーバーサイドでのトランザクション管理

## パフォーマンス要件

- アップロードタイムアウト: 120秒
- 画像キャッシュ: CachedNetworkImage使用

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

- partロールのchange権限が必要
- 認証トークンによるAPI認証
- 一時ファイルは処理後に削除

## 備考

- クロッピング機能はcustom_image_cropパッケージを使用
- アスペクト比: 1:1、4:3、16:9、3:2から選択可能
- 画像は90度回転可能

---

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

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

### 推奨読解順序

#### Step 1: 画像表示画面を理解する

まず、パーツ画像表示画面の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | part_image_widget.dart | `lib/widget/part/part_image_widget.dart` | PartImageWidgetクラス（15-170行目） |

**読解のコツ**:
- **30-32行目**: `request`メソッド - パーツリロード
- **37-92行目**: `_processImageWithCropping` - 画像処理メインロジック
- **95-134行目**: `_deleteImage` - 画像削除処理
- **136-164行目**: `appBarActions` - アクションボタン定義
- **166-169行目**: `getBody` - 画像表示

#### Step 2: クロッピングウィジェットを理解する

画像クロッピングUIを追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | image_cropper.dart | `lib/widget/part/image_cropper.dart` | ImageCropperWidgetクラス（8-169行目） |

**主要処理フロー**:
- **22-26行目**: アスペクト比定義（1:1、4:3、16:9、3:2）
- **31-100行目**: `build`メソッド - クロッピングUI構築
- **39-66行目**: アスペクト比ドロップダウン
- **68-98行目**: リセット、ズーム、回転ボタン
- **101-114行目**: CustomImageCropウィジェット
- **126-152行目**: キャンセル・クロップボタン

#### Step 3: 画像アップロード処理を理解する

APIへの画像アップロード処理を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | part.dart | `lib/inventree/part.dart` | uploadImageメソッド（441-451行目） |
| 3-2 | api.dart | `lib/api.dart` | uploadFileメソッド（990-1090行目） |

**主要処理フロー**:
- **441-451行目**: `uploadImage` - PATCH形式でimageフィールドをアップロード
- **1003-1006行目**: MultipartRequest作成
- **1024-1026行目**: MultipartFileの追加

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

```
PartImageWidget
    │
    ├─ getBody()
    │      └─ InvenTreeAPI().getImage(part.image)
    │
    ├─ appBarActions()
    │      ├─ [削除] _deleteImage()
    │      │      └─ api.patch(url, {image: null})
    │      │
    │      └─ [アップロード] FilePickerDialog.pickFile()
    │             └─ _processImageWithCropping(file)
    │                    │
    │                    ├─ ImageCropperWidget(imageBytes)
    │                    │      ├─ CustomImageCrop
    │                    │      └─ cropController.onCropImage()
    │                    │
    │                    ├─ tempFile.writeAsBytes(croppedBytes)
    │                    │
    │                    ├─ part.uploadImage(tempFile)
    │                    │      └─ api.uploadFile(url, file, method: PATCH)
    │                    │
    │                    └─ tempFile.delete()
    │
    └─ request()
           └─ part.reload()
```

### データフロー図

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

画像ファイル ───▶ FilePickerDialog ───▶ 選択ファイル
                        │
                        ▼
               ImageCropperWidget
                        │
                        ├─ アスペクト比選択
                        ├─ ズーム・回転
                        └─ クロップ実行
                        │
                        ▼
               Uint8List (croppedBytes)
                        │
                        ▼
               一時ファイル作成
                        │
                        ▼
               uploadImage API
                        │
                        ▼
               サーバー保存 ───▶ 画面リフレッシュ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| part_image_widget.dart | `lib/widget/part/part_image_widget.dart` | ソース | 画像表示・管理画面ウィジェット |
| image_cropper.dart | `lib/widget/part/image_cropper.dart` | ソース | 画像クロッピングウィジェット |
| part.dart | `lib/inventree/part.dart` | ソース | uploadImageメソッド |
| api.dart | `lib/api.dart` | ソース | uploadFile、patchメソッド |
| fields.dart | `lib/widget/fields.dart` | ソース | FilePickerDialog |
| snacks.dart | `lib/widget/snacks.dart` | ソース | 成功/失敗メッセージ表示 |
