# 通知設計書 50-InvenTree通知

## 概要

本ドキュメントは、InvenTreeモバイルアプリにおける「InvenTree通知」の設計仕様を定義するものである。InvenTreeサーバーから取得する未読通知一覧をアプリ内で表示・管理する機能について記載する。

### 本通知の処理概要

本通知は、InvenTreeサーバーで生成された通知をモバイルアプリ内で表示・管理するためのサーバー連携型通知機能である。アプリ内の通知として表示される（サーバー生成の内容をアプリで表示）。

**業務上の目的・背景**：InvenTreeサーバーでは、様々なイベント（在庫アラート、発注ステータス変更、パーツ更新など）に基づいて通知が生成される。これらの通知をモバイルアプリで確認できるようにすることで、ユーザーは外出先や倉庫内でも重要な情報を把握し、迅速に対応できる。通知機能により、業務の効率性とリアルタイム性が向上する。

**通知の送信タイミング**：InvenTreeサーバーが通知を生成した時点でサーバー側に保存される。アプリでは、サーバー接続時に定期的（60秒間隔）に通知カウントを更新し、通知画面を開いた際にすべての未読通知を取得して表示する。

**通知の受信者**：InvenTreeサーバーで通知対象として設定されたユーザー。サーバー側のユーザー設定に基づいて通知が配信される。

**通知内容の概要**：通知名（name）、メッセージ内容（message）、作成日時（creationDate）がリスト形式で表示される。各通知は既読としてマーク（dismiss）することができる。

**期待されるアクション**：ユーザーは通知一覧を確認し、必要に応じて詳細を確認したり、既読としてマークする。重要な通知については、関連する画面（発注詳細、在庫詳細など）に移動して対応する。

## 通知種別

サーバー通知（アプリ内表示）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（ポーリング） |
| 優先度 | 中 |
| リトライ | なし（定期更新で代替） |
| 更新間隔 | 60秒 |

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

InvenTreeサーバー側で通知対象ユーザーが決定される。アプリは現在ログインしているユーザーの未読通知のみを取得する。

## 通知テンプレート

### 通知リスト表示

| 項目 | 内容 |
|-----|------|
| 表示形式 | ListTile |
| タイトル | 通知名（name） |
| サブタイトル | メッセージ内容（message） |
| アクション | 既読マークボタン（TablerIcons.bookmark） |

### 通知カウント表示

| 項目 | 内容 |
|-----|------|
| 表示位置 | 通知画面ヘッダー |
| 形式 | "{count}" |

### 空の通知一覧

| 項目 | 内容 |
|-----|------|
| アイコン | `TablerIcons.bell_exclamation` |
| メッセージ | `L10().notificationsEmpty` |

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| name | 通知名 | InvenTreeNotification.name | Yes |
| message | メッセージ内容 | InvenTreeNotification.message | Yes |
| creationDate | 作成日時 | InvenTreeNotification.creationDate | No |
| notifications | 通知タイトル | L10().notifications | Yes |
| notificationsEmpty | 空メッセージ | L10().notificationsEmpty | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 定期更新 | 60秒タイマー | サーバー接続中 | `_refreshNotifications`で通知カウント更新 |
| 画面表示 | 通知画面オープン | - | `request`メソッドで全通知取得 |
| サーバー接続 | サーバー接続成功時 | - | タイマー開始 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| サーバー未接続 | isConnected()がfalseの場合は更新しない |
| read=true | 既読の通知は一覧に含まれない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[サーバー接続成功] --> B[タイマー開始 60秒間隔]
    B --> C{isConnected?}
    C -->|Yes| D[GET notifications/ read=false]
    C -->|No| E[スキップ]
    D --> F[通知カウント更新]
    F --> G[次のタイマーサイクル]
    E --> G

    H[通知画面オープン] --> I[request実行]
    I --> J[GET notifications/]
    J --> K[通知リスト表示]

    L[既読ボタン押下] --> M[dismiss実行]
    M --> N{API version確認}
    N -->|>= 82| O[PATCH notifications/id/ read=true]
    N -->|< 82| P[POST notifications/id/read/]
    O --> Q[画面リフレッシュ]
    P --> Q
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| Notification | 通知情報 | InvenTreeサーバー側 |

### テーブル別参照項目詳細

#### Notification（サーバー側）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| pk | 通知ID | 一意識別子 |
| name | 通知名 | 表示タイトル |
| message | メッセージ | 表示内容 |
| creation | 作成日時 | ソート・表示用 |
| read | 既読フラグ | read=false でフィルタ |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Notification | UPDATE | read フラグを true に更新 |

#### Notification更新（dismiss時）

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| PATCH/POST | read | true | 既読マーク |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 接続エラー | サーバー未接続 | 更新スキップ |
| API エラー | 通知API失敗 | リトライせず次回更新で再試行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（次回の定期更新で再試行） |
| リトライ間隔 | 60秒（定期更新間隔） |
| リトライ対象エラー | 全エラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 更新間隔 | 60秒 |
| 一度の取得上限 | なし（全未読通知） |

### 配信時間帯

制限なし（サーバー接続中は常時更新）

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

- 通知はユーザー固有のデータであり、認証トークンが必要
- 他のユーザーの通知は取得できない
- 通知内容に機密情報が含まれる可能性があるため、端末のセキュリティに依存

## 備考

- API version 82以上では`read`フィールドをPATCHで更新、それ以前は`/read/`エンドポイントにPOST
- 通知画面はDrawerメニューからアクセス可能
- 通知カウントはアプリバーなどで表示可能（`notification_counter`変数）
- 国際化対応済み（L10()による多言語サポート）

---

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

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

### 推奨読解順序

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

通知モデルの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.dart | `lib/inventree/notification.dart` | InvenTreeNotificationクラスの構造 |

**読解のコツ**: `InvenTreeModel`を継承していることと、`dismiss`メソッドの実装を確認する。

**主要構造**:
- **7-8行目**: クラス定義とコンストラクタ
- **19行目**: APIエンドポイント定義（`notifications/`）
- **22-25行目**: デフォルトフィルタ（`read: false`）
- **27行目**: messageプロパティ
- **29-35行目**: creationDateプロパティ
- **40-47行目**: dismissメソッド（既読マーク）

#### Step 2: 通知ウィジェットを理解する

通知表示ウィジェットの起点を特定。

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

**主要処理フロー**:
1. **26-36行目**: `request`メソッドで通知リスト取得
2. **41-61行目**: `dismissNotification`メソッドで既読マーク
3. **68-99行目**: `getTiles`メソッドでリスト表示

#### Step 3: 定期更新処理を理解する

通知カウントの定期更新を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | api.dart | `lib/api.dart` | _refreshNotificationsメソッドとタイマー |

**主要処理フロー**:
- **1763-1765行目**: `notification_counter`変数とタイマー定義
- **1770-1778行目**: `_refreshNotifications`メソッド
- **688-693行目**: タイマー開始処理（接続成功時）

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

```
InvenTreeAPI (api.dart)
    │
    ├─ connectToServer() (line 656)
    │      │
    │      └─ [接続成功時]
    │             └─ Timer.periodic (60秒)
    │                    └─ _refreshNotifications() (line 1770)
    │                           └─ InvenTreeNotification().count()
    │
    └─ notification_counter - グローバルカウンター

NotificationWidget (notifications.dart)
    │
    ├─ request() (line 26)
    │      │
    │      └─ InvenTreeNotification().list()
    │             └─ GET notifications/ (read=false)
    │
    ├─ dismissNotification() (line 41)
    │      │
    │      └─ notification.dismiss()
    │             │
    │             ├─ [apiVersion >= 82]
    │             │      └─ update(values: {"read": "true"})
    │             │             └─ PATCH notifications/id/
    │             │
    │             └─ [apiVersion < 82]
    │                    └─ api.post("${url}read/")
    │                           └─ POST notifications/id/read/
    │
    └─ getTiles() (line 68)
           └─ ListTile x n 個の通知
```

### データフロー図

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

サーバー接続     ┌─────────────────┐
成功 ──────────▶│Timer開始 (60秒) │
                 └────────┬────────┘
                          │
                          ▼
                 ┌─────────────────┐
                 │_refreshNotifications│
                 └────────┬────────┘
                          │
                          ▼
                 ┌─────────────────┐     API Request
                 │InvenTree       │────────────────▶ InvenTreeサーバー
                 │Notification    │◀────────────────  通知カウント
                 │.count()        │
                 └────────┬────────┘
                          │
                          ▼
                 ┌─────────────────┐
                 │notification_   │───▶ UI更新
                 │counter更新     │
                 └─────────────────┘

通知画面        ┌─────────────────┐
オープン ──────▶│request()       │
                 └────────┬────────┘
                          │
                          ▼
                 ┌─────────────────┐     API Request
                 │InvenTree       │────────────────▶ InvenTreeサーバー
                 │Notification    │◀────────────────  通知リスト
                 │.list()         │
                 └────────┬────────┘
                          │
                          ▼
                 ┌─────────────────┐
                 │getTiles()      │───▶ ListTile表示
                 └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification.dart | `lib/inventree/notification.dart` | ソース | 通知モデル定義 |
| notifications.dart | `lib/widget/notifications.dart` | ソース | 通知ウィジェット |
| api.dart | `lib/api.dart` | ソース | API接続、定期更新タイマー |
| model.dart | `lib/inventree/model.dart` | ソース | モデル基底クラス |
| drawer.dart | `lib/widget/drawer.dart` | ソース | ドロワーメニュー（通知へのリンク） |
| l10.dart | `lib/l10.dart` | ソース | 国際化ヘルパー関数 |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | リフレッシュ可能なState基底クラス |
