# 機能設計書 55-添付ファイルアップロード

## 概要

本ドキュメントは、InvenTreeモバイルアプリにおける添付ファイルアップロード機能の設計を記述する。

### 本機能の処理概要

カメラで撮影した画像またはデバイスから選択したファイルを、パーツ・在庫アイテム・発注・受注などの各種アイテムに添付ファイルとしてアップロードする機能を提供する。

**業務上の目的・背景**：在庫管理の現場では、入荷品の検収写真、不良品の証拠写真、設置状況の記録など、様々な場面で画像やドキュメントの添付が必要となる。モバイルデバイスのカメラを活用することで、その場で撮影した写真を即座にシステムに登録でき、ペーパーレス化と業務効率化を実現する。また、PDFやExcelなどのドキュメントファイルもアップロードでき、包括的な資料管理が可能となる。

**機能の利用シーン**：添付ファイル一覧画面のアプリバーにあるカメラアイコンまたはファイルアップロードアイコンをタップして、画像撮影またはファイル選択を行い、アップロードを実行する。また、発注詳細画面や受注詳細画面からも直接カメラアップロードが可能。

**主要な処理内容**：
1. カメラアイコンタップでカメラ起動、画像撮影
2. ファイルアイコンタップでファイルピッカー表示
3. 撮影/選択したファイルをリネーム（タイムスタンプ付与）
4. マルチパートPOSTでサーバーにアップロード
5. アップロード完了後、添付ファイル一覧をリフレッシュ

**関連システム・外部連携**：InvenTree REST API（attachment/エンドポイント）、デバイスカメラ、ファイルシステムとの連携を行う。

**権限による制御**：認証済みユーザーのみがアクセス可能。hasUploadPermissionパラメータにより、アップロード機能の表示/非表示を制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 27 | 購入注文詳細画面 | 補助機能 | カメラボタン押下時に画像撮影・アップロード |
| 29 | 販売注文詳細画面 | 補助機能 | カメラボタン押下時に画像撮影・アップロード |

## 機能種別

CRUD操作（Create） / ファイルアップロード

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| modelType | String | Yes | モデルタイプ（part/stockitem等） | サーバー定義のタイプ |
| modelId | int | Yes | 対象インスタンスのID | 正の整数 |
| file | File | Yes | アップロードするファイル | ファイルが存在すること |
| prefix | String | No | ファイル名プレフィックス | デフォルト: "InvenTree" |
| comment | String | No | ファイルコメント | 任意 |

### 入力データソース

- デバイスカメラ（画像撮影）
- デバイスファイルシステム（ファイル選択）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | bool | アップロード成功/失敗 |
| attachment | InvenTreeAttachment | 作成された添付ファイル情報 |

### 出力先

- サーバーAPI（添付ファイル登録）
- 画面表示（成功/失敗メッセージ、一覧リフレッシュ）

## 処理フロー

### 処理シーケンス

```
1. アップロードボタンタップ
   ├─ カメラアイコン → カメラ起動
   └─ ファイルアイコン → ファイルピッカー
2. ファイル取得
   ├─ カメラ撮影 → 画像ファイル取得
   └─ ファイル選択 → 選択ファイル取得
3. ファイルリネーム（画像の場合）
   └─ {prefix}_image_{timestamp}.{ext}形式に変換
4. ローディング表示
   └─ showLoadingOverlay()
5. マルチパートアップロード
   └─ uploadAttachment() or uploadImage()
6. 結果処理
   ├─ 成功 → 成功メッセージ表示
   └─ 失敗 → エラーメッセージ表示
7. 一覧リフレッシュ
   └─ refresh(context)
8. ローディング解除
   └─ hideLoadingOverlay()
```

### フローチャート

```mermaid
flowchart TD
    A[アップロードボタンタップ] --> B{カメラ or ファイル?}
    B -->|カメラ| C[カメラ起動]
    B -->|ファイル| D[ファイルピッカー]
    C --> E[写真撮影]
    D --> F[ファイル選択]
    E --> G{ファイル取得成功?}
    F --> G
    G -->|No| H[処理終了]
    G -->|Yes| I[showLoadingOverlay]
    I --> J{画像?}
    J -->|Yes| K[ファイルリネーム]
    J -->|No| L[uploadAttachment]
    K --> M[uploadImage]
    M --> N{アップロード成功?}
    L --> N
    N -->|Yes| O[成功メッセージ]
    N -->|No| P[エラーメッセージ]
    O --> Q[refresh]
    P --> Q
    Q --> R[hideLoadingOverlay]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-55-01 | 権限チェック | hasUploadPermissionがtrueの場合のみアップロードボタン表示 | UI表示時 |
| BR-55-02 | ファイル名生成 | 画像ファイルは{prefix}_image_{ISO8601タイムスタンプ}.{拡張子}形式 | 画像アップロード時 |
| BR-55-03 | マルチパートPOST | ファイルはmultipart/form-dataでアップロード | APIリクエスト時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 添付ファイル登録 | attachment | INSERT | 新規添付ファイルレコード作成 |

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

#### attachment（リモートAPI）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | attachment | アップロードファイル | マルチパートPOST |
| INSERT | model_type | 対象モデルタイプ | 必須 |
| INSERT | model_id | 対象モデルID | 必須 |
| INSERT | comment | コメント | 任意 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | カメラエラー | カメラアクセス拒否時 | カメラ権限設定を促す |
| - | ファイル選択キャンセル | ファイル未選択時 | 処理中止 |
| - | アップロードエラー | サーバーエラー時 | エラーメッセージ表示 |
| - | ファイルリネームエラー | リネーム失敗時 | エラー報告（Sentry） |

### リトライ仕様

ユーザーによる手動リトライ（再度アップロード操作）

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

ファイルアップロードはサーバー側で管理。アップロード失敗時は登録されない。

## パフォーマンス要件

- アップロード開始は1秒以内
- アップロード完了はファイルサイズ・通信状態に依存
- タイムアウト：120秒

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

- 認証トークンによるアクセス制御
- 権限チェックによるアップロード可否制御
- HTTPS通信による通信暗号化

## 備考

画像アップロード時はファイル名にタイムスタンプを付与して一意性を確保。Sentry連携によりエラー情報を収集。

---

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

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

### 推奨読解順序

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

アップロードボタンの配置と処理起動を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | attachment_widget.dart | `lib/widget/attachment_widget.dart` | appBarActions()、upload()メソッド |

**読解のコツ**: appBarActions()でアップロードボタンを定義。hasUploadPermissionでボタン表示を制御。

**主要処理フロー**:
- **48-78行目**: appBarActions()でアプリバーボタン定義
- **50行目**: hasUploadPermission権限チェック
- **53-65行目**: カメラボタン（uploadImage呼び出し）
- **66-77行目**: ファイルボタン（ファイルピッカー→upload呼び出し）
- **80-100行目**: upload()メソッド（ファイルアップロード処理）

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

カメラ撮影からアップロードまでの処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | attachment.dart | `lib/inventree/attachment.dart` | uploadImage()メソッド |

**主要処理フロー**:
- **132-168行目**: uploadImage()メソッド
- **139行目**: FilePickerDialog.pickImageFromCamera()
- **141-143行目**: ファイル名生成（タイムスタンプ付与）
- **146-147行目**: ファイル名変更
- **149-150行目**: uploadAttachment()呼び出し
- **160-163行目**: エラー処理（Sentryレポート）

#### Step 3: ファイルアップロードAPI処理を理解する

サーバーへのアップロード処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | attachment.dart | `lib/inventree/attachment.dart` | uploadAttachment()メソッド |
| 3-2 | api.dart | `lib/api.dart` | uploadFile()メソッド |

**主要処理フロー**:
- **102-130行目**: uploadAttachment()メソッド
- **110-119行目**: リクエストデータ構築
- **121-128行目**: api.uploadFile()呼び出し
- **990-1090行目（api.dart）**: uploadFile()マルチパートアップロード実装

#### Step 4: ファイルピッカーを理解する

ファイル選択ダイアログの実装を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | fields.dart | `lib/widget/fields.dart` | FilePickerDialog |

**読解のコツ**: image_picker、file_pickerパッケージを使用。

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

```
AttachmentWidget (attachment_widget.dart)
    │
    └─ _AttachmentWidgetState
           │
           ├─ appBarActions()
           │      │
           │      ├─ IconButton (カメラ)
           │      │      └─ InvenTreeAttachment().uploadImage()
           │      │             │
           │      │             ├─ FilePickerDialog.pickImageFromCamera()
           │      │             ├─ file.rename()
           │      │             └─ uploadAttachment()
           │      │                    └─ InvenTreeAPI().uploadFile()
           │      │
           │      └─ IconButton (ファイル)
           │             └─ FilePickerDialog.pickFileFromDevice()
           │                    └─ upload()
           │                           └─ InvenTreeAttachment().uploadAttachment()
           │                                  └─ InvenTreeAPI().uploadFile()
           │
           └─ refresh() - アップロード完了後
```

### データフロー図

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

カメラボタン     ───▶  pickImageFromCamera()      ───▶  画像ファイル
                          │
                          ├─ カメラ撮影
                          │
                          ▼
                   uploadImage()
                          │
                          ├─ ファイルリネーム
                          │   {prefix}_image_{timestamp}.{ext}
                          │
                          ▼
ファイルボタン   ───▶  pickFileFromDevice()       ───▶  選択ファイル
                          │
                          ▼
                   uploadAttachment()
                          │
                          ├─ model_type, model_id設定
                          │
                          ▼
                   InvenTreeAPI().uploadFile()
                          │
                          ├─ multipart/form-data POST
                          │
                          ▼
サーバー         ◀───  POST attachment/            ───▶  添付ファイル作成
                          │
                          ▼
                   showSnackIcon()
                          │
                          └─ 成功/失敗メッセージ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| attachment_widget.dart | `lib/widget/attachment_widget.dart` | ソース | アップロードUI・処理 |
| attachment.dart | `lib/inventree/attachment.dart` | ソース | アップロードAPI処理 |
| api.dart | `lib/api.dart` | ソース | マルチパートアップロード |
| fields.dart | `lib/widget/fields.dart` | ソース | ファイルピッカーダイアログ |
| snacks.dart | `lib/widget/snacks.dart` | ソース | 通知メッセージ |
| sentry.dart | `lib/inventree/sentry.dart` | ソース | エラー報告 |
