# 通知設計書 40-画像アップロード失敗

## 概要

本ドキュメントは、InvenTreeモバイルアプリケーションにおける「画像アップロード失敗」通知の設計仕様を定義するものです。この通知は、パーツ画像のアップロードが失敗した際にユーザーに表示されるスナックバー通知です。

### 本通知の処理概要

この通知は、ユーザーがパーツに対して画像をアップロード（または更新）する際、クロッピング処理後のサーバーへの画像送信が何らかの理由で失敗した場合にエラーフィードバックを提供する機能です。

**業務上の目的・背景**：画像アップロードはネットワーク状態、サーバー状態、ファイルサイズ、ファイル形式などの要因で失敗する可能性があります。また、画像処理（クロッピング、一時ファイル作成）の段階でも例外が発生する可能性があります。この通知により、ユーザーはアップロードが失敗したことを即座に把握でき、再試行や問題の調査など適切な対応を取ることができます。

**通知の送信タイミング**：`PartImageWidget._processImageWithCropping()` メソッドでクロッピング後の画像をサーバーにアップロードした後、`part.uploadImage()` がfalseを返した場合、または画像処理中に例外が発生した場合に通知が発生します。

**通知の受信者**：通知は、アプリで画像アップロード操作を実行している現在のユーザーに対してのみ表示されます。

**通知内容の概要**：国際化対応されたメッセージ「Image upload failed」（または相当するローカライズ文字列）がスナックバーとして画面下部に表示されます。エラーを示すオレンジ色の背景と警告アイコンが付加されます。例外発生時は追加のエラー情報が含まれる場合があります。

**期待されるアクション**：ユーザーはアップロードが失敗したことを確認し、ネットワーク接続を確認するか、別の画像ファイルで再試行することが期待されます。

## 通知種別

アプリ内通知（スナックバー）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期 |
| 優先度 | 高 |
| リトライ | なし（ユーザーによる手動再試行） |

### 送信先決定ロジック

アプリの現在アクティブなユーザーに対してのみ表示されます。

## 通知テンプレート

### スナックバー通知の場合

| 項目 | 内容 |
|-----|------|
| 背景色 | オレンジ（Colors.deepOrange） |
| アイコン | TablerIcons.exclamation_circle |
| 表示時間 | 5秒 |
| 閉じるボタン | あり |

### 本文テンプレート

通常の失敗時：
```
{L10().uploadFailed}
```

例外発生時：
```
{L10().error}: {例外メッセージ}
```

実際の表示例（英語）：
```
Image upload failed
```
または
```
Error: [具体的なエラーメッセージ]
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| uploadFailed | アップロード失敗メッセージ | L10()国際化リソース | Yes |
| error | エラーラベル | L10()国際化リソース | No（例外時のみ） |
| e | 例外メッセージ | catch句のException | No（例外時のみ） |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | _processImageWithCropping() | result == false | パーツ画像アップロードの失敗時 |
| 例外 | _processImageWithCropping() | catch (e) | 画像処理中の例外発生時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| アップロード成功 | uploadImage()がtrueを返した場合は成功通知が表示される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[画像アップロードボタン押下] --> B[FilePickerDialog.pickFile]
    B --> C{ファイル選択?}
    C -->|なし| D[処理終了]
    C -->|あり| E[_processImageWithCropping呼び出し]
    E --> F{処理成功?}
    F -->|例外発生| G[showSnackIcon エラー詳細]
    F -->|成功| H[part.uploadImage実行]
    H --> I{アップロード成功?}
    I -->|成功| J[showSnackIcon success: true]
    I -->|失敗| K[showSnackIcon success: false]
    G --> L[refresh画面更新]
    K --> L
    J --> L
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（クライアントサイドのみの処理）

### 更新テーブル一覧

該当なし（失敗のため更新は行われない）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ネットワークエラー | サーバーに接続できない | 失敗通知表示、ネットワーク確認を促す |
| サーバーエラー | サーバー側でエラー発生 | 失敗通知表示、再試行を促す |
| 画像読み込みエラー | 画像ファイルが読み込めない | エラー詳細を含む通知を表示 |
| クロッピングエラー | クロッピング処理で例外 | エラー詳細を含む通知を表示 |
| 一時ファイルエラー | 一時ファイル作成/書き込みエラー | エラー詳細を含む通知を表示 |
| コンテキスト不在 | Flutterコンテキストが無効 | 通知をスキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（ユーザーによる手動再試行） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（24時間対応）

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

- 例外メッセージにはシステム内部情報が含まれる可能性があるが、デバッグ目的でユーザーに表示される
- 認証済みユーザーのみがアプリを使用可能
- 一時ファイルは例外発生時でも可能な限り削除される

## 備考

- 例外発生時は `catch (e)` で捕捉され、エラーメッセージが通知に含まれる
- 通常の失敗（uploadImage()がfalse）と例外発生では通知メッセージが異なる
- 国際化対応により、アプリの言語設定に応じたメッセージが表示される

---

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

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

### 推奨読解順序

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

まず、InvenTreePartクラスとuploadImage()メソッドの失敗パターンを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | part.dart | `lib/inventree/part.dart` | uploadImage()メソッドの戻り値（false）条件 |

**読解のコツ**: uploadImage()がfalseを返すパターンとAPIエラーの関係を確認

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

_processImageWithCropping()メソッドでの失敗時処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | part_image_widget.dart | `lib/widget/part/part_image_widget.dart` | _processImageWithCropping()メソッドのエラー処理（行82-91） |

**主要処理フロー**:
1. **行37**: try文の開始
2. **行75**: `part.uploadImage(tempFile)` でアップロード実行
3. **行82-86**: if/else で成功/失敗を分岐
4. **行83**: `showSnackIcon(L10().uploadFailed, success: false)` - 失敗時
5. **行89-91**: catch句での例外処理
6. **行90**: `showSnackIcon("${L10().error}: $e", success: false)` - 例外発生時

#### Step 3: スナックバー表示ロジックを理解する

showSnackIcon関数のsuccess=falseの実装を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | snacks.dart | `lib/widget/snacks.dart` | success=falseの場合のスタイル（行42-48） |

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

```
PartImageWidget  [lib/widget/part/part_image_widget.dart]
    │
    ├─ appBarActions()  [行136]
    │      └─ IconButton(file_upload)
    │             └─ FilePickerDialog.pickFile()
    │                    └─ _processImageWithCropping()呼び出し
    │
    └─ _processImageWithCropping()  [行37]
           │
           ├─ try {
           │      ├─ 画像処理...
           │      ├─ part.uploadImage() → 失敗
           │      └─ showSnackIcon(L10().uploadFailed)  [行83]
           │  }
           │
           └─ catch (e) {
                  └─ showSnackIcon("${L10().error}: $e")  [行90]
              }
```

### データフロー図

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

画像ファイル       ───▶  _processImageWithCropping      ───▶  スナックバー表示
                          ├─ 画像処理                          │
                          │   └─ 例外発生時 ──────────────▶ 「Error: xxx」
                          │                                    │
                          └─ uploadImage → false ────────▶ 「Image upload failed」
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| part_image_widget.dart | `lib/widget/part/part_image_widget.dart` | ソース | PartImageWidget、_processImageWithCropping()メソッド |
| part.dart | `lib/inventree/part.dart` | ソース | InvenTreePartクラス、uploadImage()メソッド |
| api.dart | `lib/api.dart` | ソース | uploadFile()メソッド |
| snacks.dart | `lib/widget/snacks.dart` | ソース | showSnackIcon関数の実装 |
| l10.dart | `lib/l10.dart` | ソース | 国際化ヘルパー関数L10() |
