# 画面設計書 31-会社詳細画面

## 概要

本ドキュメントは、InvenTree Flutterアプリケーションにおける「会社詳細画面」の設計仕様を定義します。会社（Company）モデルの詳細情報を表示し、関連する購入注文・販売注文・サプライヤー部品へのナビゲーション、および新規注文作成機能を提供する画面です。

### 本画面の処理概要

この画面は、サプライヤー、顧客、製造者などの会社情報を詳細に表示し、関連するビジネスデータへの効率的なアクセスを実現します。

**業務上の目的・背景**：在庫管理システムにおいて、取引先（サプライヤー・顧客・製造者）の情報を一元管理することは極めて重要です。この画面は、会社の基本情報（名称、説明、連絡先、Webサイトなど）を表示するだけでなく、その会社に関連する購入注文や販売注文、サプライヤー部品へのクイックアクセスを提供します。これにより、ユーザーは取引先との関係性を俯瞰的に把握し、発注・受注業務を効率的に行うことができます。また、会社タイプ（サプライヤー/顧客/製造者）に応じて表示項目が動的に変化し、各タイプに最適化された情報提供を実現しています。

**画面へのアクセス方法**：
- 会社一覧画面から会社を選択してタップ
- 購入注文詳細画面からサプライヤー名をタップ
- 販売注文詳細画面から顧客名をタップ
- バーコードスキャンで会社を識別した場合
- 検索画面から会社を選択した場合

**主要な操作・処理内容**：
1. 会社の基本情報（名称、説明、サムネイル画像）の表示
2. 連絡先情報（Webサイト、メール、電話番号、外部リンク）の表示と外部アプリ連携
3. サプライヤー部品一覧へのナビゲーション（サプライヤーの場合）
4. 未完了購入注文一覧へのナビゲーション（サプライヤーの場合）
5. 未完了販売注文一覧へのナビゲーション（顧客の場合）
6. 新規購入注文の作成（サプライヤーの場合、権限があれば）
7. 新規販売注文の作成（顧客の場合、権限があれば）
8. 会社情報の編集（権限があれば）
9. パラメータ一覧の表示（存在する場合）
10. 添付ファイル一覧の表示

**画面遷移**：
- 遷移元：会社一覧画面、購入注文詳細画面、販売注文詳細画面、バーコードスキャン画面、検索画面
- 遷移先：サプライヤー部品一覧画面、購入注文一覧画面、販売注文一覧画面、購入注文詳細画面、販売注文詳細画面、パラメータ画面、添付ファイル画面

**権限による表示制御**：
- 会社編集ボタン：`purchase_order`、`sales_order`、`return_order`のいずれかのchange権限が必要
- 購入注文作成ボタン：`purchase_order`のadd権限が必要
- 販売注文作成ボタン：`sales_order`のadd権限が必要
- 添付ファイルアップロード：会社編集権限に依存
- 非アクティブ会社の場合は警告メッセージを表示

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 39 | 会社詳細表示 | 主機能 | 会社の詳細情報（連絡先、注文、サプライヤー部品等）表示 |
| 11 | サプライヤーパーツ表示 | 遷移先機能 | サプライヤーパーツ項目押下時にサプライヤーパーツ一覧へ遷移 |
| 23 | 発注一覧表示 | 遷移先機能 | 発注項目押下時に発注一覧画面へ遷移（サプライヤーフィルタ） |
| 32 | 受注一覧表示 | 遷移先機能 | 受注項目押下時に受注一覧画面へ遷移（顧客フィルタ） |
| 54 | 添付ファイル表示 | 補助機能 | 添付ファイル項目押下時に添付ファイル一覧表示 |

## 画面種別

詳細画面

## URL/ルーティング

Flutter Navigatorによる画面スタック管理。`MaterialPageRoute`を使用して`CompanyDetailWidget`へ遷移。

```dart
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => CompanyDetailWidget(company)),
);
```

## 入出力項目

### 入力項目

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| company | InvenTreeCompany | 必須 | 表示対象の会社オブジェクト |

### 出力項目（API取得データ）

| 項目名 | APIフィールド | 型 | 説明 |
|--------|---------------|-----|------|
| 会社ID | pk | int | 会社の一意識別子 |
| 会社名 | name | String | 会社名 |
| 説明 | description | String | 会社の説明 |
| 画像URL | image/thumbnail | String | 会社のサムネイル画像URL |
| Webサイト | website | String | 会社のWebサイトURL |
| メールアドレス | email | String | 連絡用メールアドレス |
| 電話番号 | phone | String | 連絡用電話番号 |
| 外部リンク | link | String | 外部参照URL |
| サプライヤーフラグ | is_supplier | bool | サプライヤーかどうか |
| 製造者フラグ | is_manufacturer | bool | 製造者かどうか |
| 顧客フラグ | is_customer | bool | 顧客かどうか |
| アクティブフラグ | active | bool | アクティブな会社かどうか |
| ノート | notes | String | メモ・備考 |

## 表示項目

### ヘッダーカード

| 項目 | 説明 |
|------|------|
| サムネイル画像 | 会社のサムネイル画像（leading位置） |
| 会社名 | タイトルとして表示 |
| 説明 | サブタイトルとして表示 |

### 警告表示（非アクティブ時）

| 項目 | 説明 |
|------|------|
| 非アクティブ警告 | 会社が非アクティブの場合、赤色テキストで警告を表示 |

### 連絡先情報

| 項目 | アイコン | タップ時動作 |
|------|----------|-------------|
| Webサイト | globe | ブラウザで開く |
| メールアドレス | at | メールアプリ起動（mailto:） |
| 電話番号 | phone | 電話アプリ起動（tel:） |
| 外部リンク | link | ブラウザで開く |

### サプライヤー関連項目（is_supplier=true時のみ）

| 項目 | 内容 | タップ時動作 |
|------|------|-------------|
| サプライヤー部品 | サプライヤー部品数を表示 | サプライヤー部品一覧画面へ遷移 |
| 購入注文 | 未完了購入注文数を表示 | 購入注文一覧画面へ遷移（サプライヤーフィルタ付き） |

### 顧客関連項目（is_customer=true時のみ）

| 項目 | 内容 | タップ時動作 |
|------|------|-------------|
| 販売注文 | 未完了販売注文数を表示 | 販売注文一覧画面へ遷移（顧客フィルタ付き） |

### その他項目

| 項目 | 条件 | 説明 |
|------|------|------|
| ノート | notes非空 | 会社に関するメモを表示 |
| パラメータ | parameterCount > 0 | パラメータ一覧へのリンク |
| 添付ファイル | API対応時 | 添付ファイル一覧へのリンク |

## イベント仕様

### 1-画面初期表示

ウィジェット構築後、`onBuild`コールバックにより`refresh`メソッドが呼び出され、以下のデータ取得処理が実行される。

処理フロー:
1. `widget.company.reload()` - 会社データのリロード
2. サプライヤーの場合、未完了購入注文数を取得
3. 顧客の場合、未完了販売注文数を取得
4. サプライヤー部品数を非同期で取得
5. パラメータ数を非同期で取得
6. 添付ファイル数を非同期で取得
7. 取得完了後、`setState`で画面を再描画

### 2-プルダウンリフレッシュ

画面を下方向にプルすると`RefreshIndicator`がトリガーされ、`refresh`メソッドが実行される。

### 3-会社編集ボタン押下

アプリバーの編集アイコンをタップすると`editCompany`メソッドが呼び出される。

処理フロー:
1. `widget.company.editForm()` - APIフォームを起動
2. 成功時：`refresh(context)`で画面を更新、`showSnackIcon`で成功メッセージ表示

### 4-購入注文作成ボタン押下（サプライヤーの場合）

SpeedDialの購入注文作成ボタンをタップすると`_createPurchaseOrder`メソッドが呼び出される。

処理フロー:
1. `InvenTreePurchaseOrder().formFields()` - フォームフィールド取得
2. `contact`フィールドを削除（会社確定前は設定不可）
3. `supplier`フィールドに現在の会社PKを設定
4. `InvenTreePurchaseOrder().createForm()` - 作成フォーム表示
5. 成功時：作成された購入注文の詳細画面へ遷移

### 5-販売注文作成ボタン押下（顧客の場合）

SpeedDialの販売注文作成ボタンをタップすると`_createSalesOrder`メソッドが呼び出される。

処理フロー:
1. `InvenTreeSalesOrder().formFields()` - フォームフィールド取得
2. `contact`フィールドを削除（会社確定前は設定不可）
3. `customer`フィールドに現在の会社PKを設定
4. `InvenTreeSalesOrder().createForm()` - 作成フォーム表示
5. 成功時：作成された販売注文の詳細画面へ遷移

### 6-サプライヤー部品タップ

サプライヤー部品のListTileをタップすると、サプライヤーフィルタ付きでサプライヤー部品一覧画面へ遷移。

```dart
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => SupplierPartList({
      "supplier": widget.company.pk.toString(),
    }),
  ),
);
```

### 7-購入注文タップ

購入注文のListTileをタップすると、サプライヤーフィルタ付きで購入注文一覧画面へ遷移。

```dart
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => PurchaseOrderListWidget(
      filters: {"supplier": "${widget.company.pk}"},
    ),
  ),
);
```

### 8-販売注文タップ

販売注文のListTileをタップすると、顧客フィルタ付きで販売注文一覧画面へ遷移。

```dart
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => SalesOrderListWidget(
      filters: {"customer": widget.company.pk.toString()},
    ),
  ),
);
```

### 9-連絡先リンクタップ

Webサイト、メール、電話番号、外部リンクの各ListTileをタップすると、`openLink`ヘルパー関数または`widget.company.openLink()`により外部アプリが起動される。

### 10-パラメータタップ

パラメータのListTileをタップすると、パラメータ画面へ遷移。

### 11-添付ファイルタップ

添付ファイルのListTileをタップすると、添付ファイル画面へ遷移。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面初期表示 | company | SELECT | 会社詳細データ取得 |
| 画面初期表示 | purchase_order | SELECT | 未完了購入注文数のカウント |
| 画面初期表示 | sales_order | SELECT | 未完了販売注文数のカウント |
| 画面初期表示 | company_supplierpart | SELECT | サプライヤー部品数のカウント |
| 画面初期表示 | company_parameter | SELECT | パラメータ数のカウント |
| 画面初期表示 | attachment | SELECT | 添付ファイル数のカウント |
| 会社編集 | company | UPDATE | 会社情報の更新 |
| 購入注文作成 | purchase_order | INSERT | 新規購入注文の作成 |
| 販売注文作成 | sales_order | INSERT | 新規販売注文の作成 |

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

#### company（会社編集時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | フォーム入力値 | 会社名 |
| UPDATE | description | フォーム入力値 | 説明 |
| UPDATE | website | フォーム入力値 | Webサイト |
| UPDATE | is_supplier | フォーム入力値 | サプライヤーフラグ |
| UPDATE | is_manufacturer | フォーム入力値 | 製造者フラグ |
| UPDATE | is_customer | フォーム入力値 | 顧客フラグ |
| UPDATE | currency | フォーム入力値 | 通貨 |
| UPDATE | active | フォーム入力値 | アクティブフラグ（API対応時） |

#### purchase_order（購入注文作成時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | supplier | 現在の会社PK | サプライヤーID |
| INSERT | その他フィールド | フォーム入力値 | フォーム定義による |

#### sales_order（販売注文作成時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | customer | 現在の会社PK | 顧客ID |
| INSERT | その他フィールド | フォーム入力値 | フォーム定義による |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| companyUpdated | 成功 | 会社情報が更新されました | 会社編集成功時 |
| inactive | 警告 | 非アクティブ | 会社が非アクティブの場合 |
| inactiveCompany | 警告 | この会社は非アクティブです | 会社が非アクティブの場合 |
| itemDeleted | エラー | アイテムは削除されました | リロード時に会社が見つからない場合 |
| serverError | エラー | サーバーエラー | API通信エラー時 |

## 例外処理

| 例外状態 | 処理内容 |
|---------|---------|
| 会社データ取得失敗 | `Navigator.of(context).pop()`で前画面に戻る |
| 会社PKが無効（<= 0） | `Navigator.of(context).pop()`で前画面に戻る |
| APIバージョン非対応（添付ファイル） | 添付ファイルタイルを非表示 |
| APIバージョン非対応（パラメータ） | パラメータタイルを非表示（件数0の場合も同様） |
| 権限不足（編集） | 編集ボタンを非表示 |
| 権限不足（注文作成） | 対応するSpeedDialChildを非表示 |
| 非サプライヤー会社 | サプライヤー関連項目を非表示 |
| 非顧客会社 | 顧客関連項目を非表示 |
| サプライヤー部品0件 | サプライヤー部品タイルを非表示 |

## 備考

- 会社は複数のタイプ（サプライヤー、製造者、顧客）を同時に持つことが可能
- 製造者関連機能は現在未実装（TODOコメントあり）
- 連絡先情報のいずれかが存在する場合、区切り線（Divider）が表示される
- `active`フィールドはAPIバージョンによってサポート状況が異なる（`InvenTreeAPI().supportsCompanyActiveStatus`で確認）

---

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

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

### 推奨読解順序

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

まず、会社モデルの構造とAPI連携の仕組みを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | company.dart | `lib/inventree/company.dart` | InvenTreeCompanyクラスの定義。会社の属性（name, description, is_supplier等）とAPIエンドポイント（company/）を確認 |
| 1-2 | model.dart | `lib/inventree/model.dart` | InvenTreeModelベースクラス。reload(), formFields(), editForm()等の共通機能を確認 |

**読解のコツ**: Dartのgetterメソッド（`String get website => getString("website");`）でJSONデータからの値取得を簡潔に実装している。`getBool`, `getString`, `getInt`等のヘルパーメソッドの使い方を理解すると良い。

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

処理の起点となるウィジェットとその状態管理を特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | company_detail.dart | `lib/widget/company/company_detail.dart` | CompanyDetailWidgetクラス。StatefulWidgetとして定義され、InvenTreeCompanyを受け取る |

**主要処理フロー**:
1. **26-33行目**: `CompanyDetailWidget`クラス定義とコンストラクタ。`InvenTreeCompany`を受け取る
2. **35-55行目**: `_CompanyDetailState`クラス。状態変数（supplierPartCount, outstandingPurchaseOrders等）を保持
3. **154-210行目**: `request`メソッド。API呼び出しで各種カウントを取得

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

タイル（ListTile）の構築ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | company_detail.dart | `lib/widget/company/company_detail.dart` | getTilesメソッド（227-436行目）で画面要素を動的に構築 |

**主要処理フロー**:
- **232-240行目**: ヘッダーカード（会社名、説明、画像）の構築
- **242-253行目**: 非アクティブ警告の条件付き表示
- **255-318行目**: 連絡先情報（website, email, phone, link）の構築
- **324-373行目**: サプライヤー関連項目の構築
- **379-397行目**: 顧客関連項目の構築
- **410-433行目**: パラメータ・添付ファイル項目の構築

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

SpeedDialによるアクションボタンと権限チェックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | company_detail.dart | `lib/widget/company/company_detail.dart` | actionButtonsメソッド（77-105行目）でSpeedDialChildを構築 |
| 4-2 | company_detail.dart | `lib/widget/company/company_detail.dart` | _createSalesOrder（107-128行目）、_createPurchaseOrder（130-151行目）メソッド |

**主要処理フロー**:
- **80-90行目**: 顧客の場合かつ販売注文作成権限がある場合、販売注文作成ボタンを追加
- **92-102行目**: サプライヤーの場合かつ購入注文作成権限がある場合、購入注文作成ボタンを追加

#### Step 5: 基底クラスを理解する

RefreshableStateの仕組みを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | refreshable_state.dart | `lib/widget/refreshable_state.dart` | RefreshableStateクラス。refresh(), request(), build()の関係を理解 |

**主要処理フロー**:
- **212-219行目**: initStateでonBuildコールバックを登録
- **228-248行目**: refreshメソッドでloading状態を管理しつつrequestを呼び出し
- **250-287行目**: buildメソッドでScaffold、AppBar、RefreshIndicator、SpeedDialを構築

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

```
CompanyDetailWidget (Stateful)
    │
    ├─ _CompanyDetailState extends RefreshableState
    │      │
    │      ├─ initState()
    │      │      └─ onBuild() → refresh()
    │      │
    │      ├─ request() [データ取得]
    │      │      ├─ company.reload()
    │      │      ├─ InvenTreePurchaseOrder().count()
    │      │      ├─ InvenTreeSalesOrder().count()
    │      │      ├─ InvenTreeSupplierPart().count()
    │      │      ├─ InvenTreeParameter().countParameters()
    │      │      └─ InvenTreeAttachment().countAttachments()
    │      │
    │      ├─ getTiles() [UI構築]
    │      │      ├─ Header Card (company info)
    │      │      ├─ Inactive Warning (conditional)
    │      │      ├─ Contact Info Tiles
    │      │      ├─ Supplier Parts Tile
    │      │      ├─ Purchase Orders Tile
    │      │      ├─ Sales Orders Tile
    │      │      ├─ Notes Tile
    │      │      ├─ ShowParametersItem()
    │      │      └─ ShowAttachmentsItem()
    │      │
    │      ├─ actionButtons() [SpeedDial]
    │      │      ├─ _createSalesOrder()
    │      │      │      └─ InvenTreeSalesOrder().createForm()
    │      │      └─ _createPurchaseOrder()
    │      │             └─ InvenTreePurchaseOrder().createForm()
    │      │
    │      ├─ appBarActions() [編集ボタン]
    │      │      └─ editCompany()
    │      │             └─ company.editForm()
    │      │
    │      └─ getAppBarTitle()
    │
    └─ [Navigation]
           ├─ → SupplierPartList
           ├─ → PurchaseOrderListWidget
           ├─ → SalesOrderListWidget
           ├─ → ParameterWidget
           └─ → AttachmentWidget
```

### データフロー図

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

InvenTreeCompany ───▶ CompanyDetailWidget ───────▶ 画面表示
    (company)              │
                           ▼
                    request() [API呼び出し]
                           │
    ┌──────────────────────┼──────────────────────┐
    │                      │                      │
    ▼                      ▼                      ▼
company/              purchase_order/        sales_order/
  └─ reload()           └─ count()             └─ count()
       │                     │                      │
       ▼                     ▼                      ▼
[会社データ更新]    [未完了PO数]          [未完了SO数]
                           │
    ┌──────────────────────┼──────────────────────┐
    │                      │                      │
    ▼                      ▼                      ▼
company/part/         parameters/           attachments/
  └─ count()            └─ count()             └─ count()
       │                     │                      │
       ▼                     ▼                      ▼
[サプライヤー部品数]  [パラメータ数]      [添付ファイル数]
                           │
                           ▼
                    setState() ──────────────▶ 画面再描画
                           │
                           ▼
                    getTiles() ──────────────▶ ListTile群

[ユーザー操作]
     │
     ├─ 編集ボタン ──▶ editForm() ──▶ PATCH company/{pk}/ ──▶ 成功メッセージ
     │
     ├─ 注文作成 ──▶ createForm() ──▶ POST purchase_order/ または sales_order/
     │                                        │
     │                                        ▼
     │                              注文詳細画面へ遷移
     │
     └─ タイルタップ ──▶ Navigator.push() ──▶ 各一覧/詳細画面
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| company_detail.dart | `lib/widget/company/company_detail.dart` | ソース | 会社詳細画面のメインウィジェット |
| company.dart | `lib/inventree/company.dart` | ソース | InvenTreeCompanyモデル定義 |
| model.dart | `lib/inventree/model.dart` | ソース | InvenTreeModelベースクラス |
| refreshable_state.dart | `lib/widget/refreshable_state.dart` | ソース | リフレッシュ可能なState基底クラス |
| supplier_part_list.dart | `lib/widget/company/supplier_part_list.dart` | ソース | サプライヤー部品一覧画面 |
| purchase_order_list.dart | `lib/widget/order/purchase_order_list.dart` | ソース | 購入注文一覧画面 |
| sales_order_list.dart | `lib/widget/order/sales_order_list.dart` | ソース | 販売注文一覧画面 |
| parameter_widget.dart | `lib/widget/parameter_widget.dart` | ソース | パラメータ表示ウィジェット |
| attachment_widget.dart | `lib/widget/attachment_widget.dart` | ソース | 添付ファイル表示ウィジェット |
| company_list.dart | `lib/widget/company/company_list.dart` | ソース | 会社一覧画面（遷移元） |
| purchase_order.dart | `lib/inventree/purchase_order.dart` | ソース | 購入注文モデル定義 |
| sales_order.dart | `lib/inventree/sales_order.dart` | ソース | 販売注文モデル定義 |
| api.dart | `lib/api.dart` | ソース | API通信クラス |
| l10.dart | `lib/l10.dart` | ソース | 多言語対応（ローカライゼーション） |
| app_colors.dart | `lib/app_colors.dart` | ソース | アプリ全体の色定義 |
| helpers.dart | `lib/helpers.dart` | ソース | ユーティリティ関数（openLink等） |
| snacks.dart | `lib/widget/snacks.dart` | ソース | スナックバー表示ユーティリティ |
| link_icon.dart | `lib/widget/link_icon.dart` | ソース | リンクアイコンウィジェット |
