環境需求
| 項目 | 最低版本 | 用途 |
|---|---|---|
| Flutter SDK | 3.38+ | 核心框架(穩定版頻道) |
| Dart SDK | 3.7+ | 隨 Flutter SDK 附帶 |
| Xcode | 15+ | iOS / macOS 建置 |
| Android Studio | Hedgehog+ | Android 建置、模擬器管理 |
| Chrome | 最新版 | Web 建置除錯 |
| iDempiere | 11+ | ERP 伺服器,需安裝 idempiere-rest 外掛 |
| IDE | – | Android Studio 或 VS Code(含 Flutter/Dart 延伸模組) |
| Git | – | 版本控制,採用 Conventional Commits 工作流程 |
安裝步驟
# 1. 複製專案
git clone https://github.com/topgiga/tw.idempiere.flutter.git
cd tw.idempiere.flutter
# 2. 安裝相依套件
flutter pub get
# 3. 產生程式碼(Freezed + json_serializable)
dart run build_runner build --delete-conflicting-outputs
# 4. 靜態分析(確認無錯誤)
flutter analyze
# 5. 執行測試
flutter test
執行應用程式
# Web(Chrome)
flutter run -d chrome
# macOS(需要 Xcode)
flutter run -d macos
# iOS(需要 Xcode + 裝置 / 模擬器)
flutter run -d ios
# Android(需要 Android Studio + 裝置 / 模擬器)
flutter run -d android
# Windows
flutter run -d windows
新增平台支援
如果專案缺少某個平台的設定,可以產生它:
flutter create --platforms=ios,android,macos,web .
程式碼產生
專案使用 Freezed(不可變模型)和 json_serializable(JSON 序列化)進行程式碼產生。修改任何標記 @freezed 或 @JsonSerializable 的模型後,必須重新執行:
# 一次性建置
dart run build_runner build --delete-conflicting-outputs
# 開發期間的監聽模式(持續監控檔案變更)
dart run build_runner watch --delete-conflicting-outputs
何時需要重新執行?
- 修改任何
@freezed類別後 - 修改任何
@JsonSerializable類別後 - 新增或修改模型類別後
- 看到「Generated files missing」錯誤時
注意:模型類別 Locator 與 state_notifier 套件(由 flutter_riverpod 重新匯出)的 Locator 名稱衝突。需要同時匯入兩者的檔案必須使用 hide Locator:
import 'package:flutter_riverpod/flutter_riverpod.dart' hide Locator;
import '../../../core/models/locator.dart';
專案結構說明
lib/
core/ # 共用框架
api/ # Dio HTTP 客戶端, 認證, 攔截器
config/ # 品牌設定, 模組登錄, 儀表板
constants/ # API URL 模式
document_framework/ # 設定驅動文件畫面(核心)
router/ # GoRouter + 認證轉導
services/ # FCM, 通知服務
widgets/ # 共用小工具(列印按鈕等)
features/ # 每個功能一個目錄
sales_order/ # 設定驅動:1 個檔案 (sales_order_config.dart)
requisition/ # 自訂:data/ + domain/ + presentation/
booking/ # 自訂:日曆 UI
...
l10n/ # 本地化(ARB 檔案, 4 種語言)
伺服器設定
應用程式透過 REST API 連接 iDempiere。首次啟動時:
- 輸入 iDempiere 伺服器 URL(例如
https://your-server/api) - 使用 iDempiere 帳號密碼登入
- 選擇 Client、Role、Organization
Firebase(選用)
推播通知需要 Firebase 設定。應用程式在沒有 Firebase 設定的情況下仍可正常運作 — FCM 功能在缺少設定時會優雅地停用。
開發工作流程
- 從
main建立功能分支:git checkout -b feat/your-feature main - 進行開發 — 遵循程式碼慣例
- 提交前執行檢查:
flutter analyze flutter test - 使用 Conventional Commits 格式提交
- 對
main發起 Pull Request
Commit 慣例
| 前綴 | 用途 | 範例 |
|---|---|---|
feat: |
新功能 | feat: add Warehouse Transfer config |
fix: |
錯誤修正 | fix: resolve OData filter for boolean columns |
refactor: |
重構 | refactor: migrate Invoice to Document Framework |
docs: |
文件 | docs: update architecture diagram |
ci: |
CI/CD | ci: add iOS build to workflow |
chore: |
雜項維護 | chore: update dependencies |
測試
# 執行所有測試
flutter test
# 執行特定測試檔案
flutter test test/core/api/api_client_test.dart
# 含覆蓋率報告
flutter test --coverage
程式碼慣例
flutter analyze必須零問題通過- 盡可能使用
const建構子 - 優先使用
final變數;已知型別時避免var - 3 個以上參數的建構子使用命名參數
- 設定檔命名:
lib/features/<feature>/<feature>_config.dart - 自訂功能:
lib/features/<feature>/{data,domain,presentation}/ - 路由:
/dashboard/<feature-name>(kebab-case)
Tech Stack 版本
| Package | Version | Purpose |
|---|---|---|
| Flutter | 3.38+ | 核心框架 |
| flutter_riverpod | ^2.5.1 | 狀態管理 |
| dio | ^5.4.0 | HTTP 客戶端 |
| go_router | ^14.2.0 | 路由導航 |
| freezed + json_serializable | ^2.5.2 / ^6.8.0 | 程式碼產生(不可變模型 + JSON 序列化) |
| flutter_secure_storage | ^9.2.2 | 安全儲存(JWT token) |
| shared_preferences | ^2.2.3 | 本地設定儲存 |
| mobile_scanner | ^5.1.1 | 條碼掃描 |
| intl | ^0.19.0 | 日期格式化 / 國際化 |
| flutter_test + mocktail | SDK / ^1.0.3 | 測試框架 |
| fl_chart | – | 圖表視覺化(折線、長條、圓餅、雷達) |
測試指南
測試目錄結構
test/ 目錄鏡像 lib/features/ 的結構:
test/
├── core/
│ ├── api/
│ │ └── api_client_test.dart # TokenStorage 單元測試
│ ├── models/
│ │ └── models_test.dart # Product JSON 反序列化
│ └── utils/
│ ├── odata_filter_builder_test.dart # ODataFilter 建構器(22 個測試)
│ ├── odata_expand_builder_test.dart # ODataExpand 建構器(8 個測試)
│ └── api_date_format_test.dart # 日期格式化
├── features/
│ ├── login/
│ │ └── auth_notifier_test.dart # AuthState sealed class 測試
│ ├── inventory/
│ │ └── inventory_repository_test.dart # StorageOnHand 解析
│ ├── inbound/
│ │ └── inbound_repository_test.dart # Movement 模型測試
│ ├── outbound/
│ │ └── outbound_repository_test.dart # MovementLine 解析
│ └── rnd/
│ └── dispatch_repository_test.dart # Dispatch CRUD + 篩選測試
├── integration/
│ ├── inbound_flow_test.dart # 入庫表單驗證
│ └── outbound_flow_test.dart # 出庫表單驗證
└── widget_test.dart # App 渲染佔位
測試執行命令
# 執行所有測試
flutter test
# 執行特定測試檔案
flutter test test/core/api/api_client_test.dart
# 含覆蓋率報告
flutter test --coverage
主要測試模式
- Repository 測試 — 使用
mocktailmock API 客戶端,驗證 JSON 解析與 OData 查詢建構 - Notifier 測試 — 驗證狀態轉換(如 AuthState 的 4 種狀態)
- 整合測試 — 驗證表單驗證與完整業務流程
- 工具類測試 — ODataFilter、ODataExpand 建構器的邊界案例
疑難排解補充
PrintFormat PDF 中文字型問題
問題:iDempiere PrintFormat 輸出 PDF 時中文顯示為空白(方塊/豆腐字)。此問題在 iDempiere Web Client 和 Flutter App 中均會發生。HTML 和 Excel 輸出正常,JasperReport PDF 也正常。
| 輸出格式 | 中文 | 備註 |
|---|---|---|
| HTML | 正常 | 瀏覽器使用系統字型 |
| Excel | 正常 | Excel 使用系統字型 |
| PDF(PrintFormat) | 空白 | iDempiere Web Client 和 Flutter App 均異常 |
| PDF(JasperReport) | 正常 | 有獨立 Font Extension 機制 |
根因:DefaultFontMapper 預設只有 14 個標準 PDF 字型(Helvetica、Times 等),不含中文。PdfGraphics2D 用此 mapper 將 Java AWT Font 轉為 PDF 字型,找不到中文字型時中文即消失。
解法:設定 PDF_FONT_DIR
- 建立字型目錄並放入中文 TTF:
mkdir -p /opt/idempiere/fonts # 從 Jasper 字型 JAR 提取 cd /tmp && unzip tw.ninniku.jasperfont_1.0.0.202308210256.jar unzip ireport-fonts.jar cp fonts/ARIALUNI.TTF /opt/idempiere/fonts/ - 在 System Configurator 設定
PDF_FONT_DIR=/opt/idempiere/fonts - 重啟 iDempiere
排查清單:
PDF_FONT_DIR值為絕對路徑、無多餘空白- 目錄存在且 iDempiere 程序有讀取權限
- 目錄內有
.ttf或.otf檔案(非.jar) - 字型含中文字形(可用
fc-query確認) - iDempiere 已重啟
- PrintFormat 的 Print Font 名稱與 TTF 內字型名稱匹配
🌐 English Version
Prerequisites
| Item | Minimum | Purpose |
|---|---|---|
| Flutter SDK | 3.38+ | Core framework (stable channel) |
| Dart SDK | 3.7+ | Bundled with Flutter SDK |
| Xcode | 15+ | iOS / macOS builds |
| Android Studio | Hedgehog+ | Android builds, emulator management |
| Chrome | Latest | Web build debugging |
| iDempiere | 11+ | ERP server with idempiere-rest plugin |
| IDE | – | Android Studio or VS Code with Flutter/Dart extensions |
Setup
git clone https://github.com/topgiga/tw.idempiere.flutter.git
cd tw.idempiere.flutter
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter analyze
flutter test
Running
flutter run -d chrome # Web
flutter run -d macos # macOS
flutter run -d ios # iOS
flutter run -d android # Android
flutter run -d windows # Windows
Code Generation
The project uses Freezed and json_serializable. After modifying any @freezed or @JsonSerializable model, re-run:
dart run build_runner build --delete-conflicting-outputs
# Watch mode:
dart run build_runner watch --delete-conflicting-outputs
Development Workflow
- Create a feature branch from
main - Make changes following code conventions
- Run
flutter analyzeandflutter testbefore committing - Use Conventional Commits
- Open a Pull Request against
main
Testing
flutter test # All tests
flutter test test/path/file.dart # Specific file
flutter test --coverage # With coverage
🇯🇵 日本語版
前提条件
| 項目 | 最低バージョン | 用途 |
|---|---|---|
| Flutter SDK | 3.38+ | コアフレームワーク(Stable チャネル) |
| Dart SDK | 3.7+ | Flutter SDK に同梱 |
| Xcode | 15+ | iOS / macOS ビルド |
| Android Studio | Hedgehog+ | Android ビルド、エミュレータ管理 |
| Chrome | 最新版 | Web ビルドのデバッグ |
| iDempiere | 11+ | idempiere-rest プラグイン付き ERP サーバー |
| IDE | – | Android Studio または VS Code(Flutter/Dart 拡張機能付き) |
セットアップ
git clone https://github.com/topgiga/tw.idempiere.flutter.git
cd tw.idempiere.flutter
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter analyze
flutter test
実行
flutter run -d chrome # Web
flutter run -d macos # macOS
flutter run -d ios # iOS
flutter run -d android # Android
flutter run -d windows # Windows
コード生成
本プロジェクトでは Freezed と json_serializable を使用しています。@freezed または @JsonSerializable のモデルを変更した後、以下を再実行してください。
dart run build_runner build --delete-conflicting-outputs
# ウォッチモード:
dart run build_runner watch --delete-conflicting-outputs
開発ワークフロー
mainからフィーチャーブランチを作成- コーディング規約に従って変更を実施
- コミット前に
flutter analyzeとflutter testを実行 - Conventional Commits 形式でコミット
mainに対して Pull Request を作成
テスト
flutter test # 全テスト
flutter test test/path/file.dart # 特定ファイル
flutter test --coverage # カバレッジ付き