R&D 模組技術文件

1. 概述

R&D 模組實作產品生命週期管理(PLM),專注於光阻劑(Photoresist)配方研發。它是 App 八大營業循環架構中的研發循環

項目 說明
產業 光阻劑(Photoresist)配方 R&D
伺服器外掛 tw.topgiga.rnd(iDempiere OSGI plugin)
Flutter 用戶端 lib/features/rnd/
設計參考 Aras Innovator PLM 架構

1.1 模組範圍

Phase 功能 說明
Phase 1 配方研發 配方 CRUD、PLM 版次追蹤、批次票、固含量計算、秤量配料
Phase 2 實驗與測試 實驗計劃、測試執行、結果記錄、趨勢分析
Phase 3a 知識庫 可搜尋的文章庫、標籤分類、配方比對
Phase 3b AI 推薦 基於目標特性的 AI 輔助配方建議
Phase 4a 實驗室管理 設備清冊(設定驅動)、校準追蹤
Phase 4b 派工管理 工單、研究人員指派、工作量儀表板
Phase 4c 績效管理 KPI 定義、雷達圖、團隊排行榜
Phase 4d 專案成本報表 材料 + 人工成本分析、預算 vs 實際、圓餅圖

2. 架構

2.1 高層架構

┌──────────────────────┐      HTTPS/JWT       ┌─────────────────────────┐
│   Flutter App         │ <──────────────────► │  iDempiere ERP          │
│   lib/features/rnd/   │  idempiere-rest API  │  + tw.topgiga.rnd       │
│                       │  /api/v1/models/...  │    OSGI plugin          │
│   + Claude AI API     │                      └─────────────────────────┘
│   (Recommendations)   │
└──────────────────────┘

2.2 目錄結構

lib/features/rnd/
├── core/                           # 共用 PLM 基礎設施
│   ├── formula_calc_service.dart   # 用戶端固含量計算器
│   ├── material_type.dart          # MaterialType enum
│   ├── plm_repository.dart         # PLM 版次 & 生命週期 API
│   └── widgets/
│       ├── lifecycle_badge.dart     # DR/IR/RL/OB 彩色標籤
│       ├── revision_selector.dart   # 版次下拉選單
│       ├── revision_timeline.dart   # 視覺化版次歷史
│       ├── responsive_layout.dart   # 手機/平板斷點
│       └── solid_content_gauge.dart # SC% 環形儀表
│
├── project/
│   └── rnd_project_config.dart     # 設定驅動 DocumentConfig
│
├── formula/
│   ├── data/formula_repository.dart       # 配方 CRUD + 批次票 + 流程
│   ├── domain/
│   │   ├── formula_list_notifier.dart     # 列表 + 生命週期篩選 + 分頁
│   │   ├── formula_detail_notifier.dart   # 單一配方 + 明細狀態
│   │   └── formula_editor_notifier.dart   # 編輯模式 + dirty 追蹤
│   └── presentation/
│       ├── formula_list_screen.dart
│       ├── formula_detail_screen.dart
│       ├── formula_editor_screen.dart
│       ├── formula_compare_screen.dart
│       └── widgets/                       # 配方行磚、編輯器、摘要卡 等
│
├── dispensing/
│   ├── dispensing_screen.dart      # 響應式:tabs(手機) / master-detail(平板)
│   └── dispensing_notifier.dart    # 配方選擇、批次票產生
│
├── experiment/
│   ├── data/experiment_repository.dart
│   ├── domain/
│   │   ├── experiment_list_notifier.dart
│   │   └── experiment_detail_notifier.dart
│   └── presentation/
│       ├── experiment_list_screen.dart
│       ├── experiment_detail_screen.dart
│       ├── test_result_entry_screen.dart
│       └── test_trend_screen.dart          # fl_chart 折線圖
│
├── knowledge_base/
│   ├── data/kb_repository.dart
│   ├── domain/kb_search_notifier.dart
│   └── presentation/
│       ├── kb_search_screen.dart           # 全文搜尋、類型 & 標籤篩選
│       ├── article_viewer_screen.dart      # 閱讀文章 + 標籤
│       ├── article_editor_screen.dart      # Markdown 編輯器 + 預覽
│       └── formula_comparison_screen.dart  # 並排配方差異比對
│
├── recommendation/
│   ├── data/recommendation_repository.dart
│   ├── domain/ai_recommendation_service.dart  # Claude API 整合
│   └── presentation/
│       └── recommendation_wizard_screen.dart  # 步驟式精靈
│
├── lab/
│   ├── equipment_config.dart       # 設定驅動 DocumentConfig
│   ├── calibration_repository.dart
│   └── calibration_screen.dart     # 校準歷史 + 警示
│
├── dispatch/
│   ├── data/dispatch_repository.dart
│   ├── domain/dispatch_notifier.dart
│   └── presentation/
│       ├── work_order_list_screen.dart
│       ├── work_order_detail_screen.dart  # 詳情 + 指派 tabs
│       ├── my_assignments_screen.dart
│       └── workload_dashboard_screen.dart # 堆疊長條圖 (fl_chart)
│
├── performance/
│   ├── data/performance_repository.dart
│   └── presentation/
│       └── performance_dashboard_screen.dart  # 雷達圖 + KPI 卡片 + 排行榜
│
├── cost/
│   ├── data/cost_repository.dart
│   └── presentation/
│       └── cost_dashboard_screen.dart  # 圓餅圖 + 預算 vs 實際
│
└── rnd_routes.dart                 # 所有 GoRouter 路由定義

2.3 狀態管理模式

所有功能遵循 Riverpod 模式:

UI Screen (ConsumerWidget)
    ↓ ref.watch(provider)
StateNotifier / AsyncNotifier
    ↓ calls
Repository
    ↓ calls
ApiClient (Dio)
    ↓ HTTPS
iDempiere REST API

主要 Riverpod Providers:

Provider Type 用途
formulaRepositoryProvider Provider<FormulaRepository> 配方 CRUD
experimentRepositoryProvider Provider<ExperimentRepository> 實驗 CRUD
kbRepositoryProvider Provider<KBRepository> 知識庫
recommendationRepositoryProvider Provider<RecommendationRepository> AI 推薦
calibrationRepositoryProvider Provider<CalibrationRepository> 設備校準
dispatchRepositoryProvider Provider<DispatchRepository> 工單 + 指派
performanceRepositoryProvider Provider<PerformanceRepository> KPI + 績效
costRepositoryProvider Provider<CostRepository> 專案成本資料
plmRepositoryProvider Provider<PlmRepository> PLM 版次 & 生命週期
aiRecommendationServiceProvider Provider<AIRecommendationService> Claude API 呼叫

2.4 設定驅動 vs 自訂畫面

兩個功能使用 DocumentConfig 框架(通用列表/詳情畫面):

  • RND Projectrnd_project_config.dart)— AD_Window_ID: 1000904
  • RND Equipmentequipment_config.dart)— AD_Window_ID: 1000920

其餘功能都有自訂畫面,具備專門的 UI(編輯器、圖表、精靈)。


3. 伺服器端資料模型

3.1 iDempiere 資料表

PLM 核心表(跨所有 Phase 共用)

Table 用途
PLM_LifecycleDefinition 定義生命週期類型(例如「配方生命週期」)
PLM_LifecycleState 生命週期中的狀態:DR(草稿)、IR(審核中)、RL(已發佈)、OB(已廢止)
PLM_StateTransition 允許的狀態轉換,可選觸發工作流
PLM_Revision 任意表的版次記錄(AD_Table_ID + Record_ID)
PLM_ChangeOrder 工程變更命令(ECO),追蹤修改

Phase 1:配方研發

Table 用途 主要欄位
RND_Project 研發專案容器 Name, DocStatus, M_PartType_ID, C_BPartner_ID
RND_Formula 配方表頭 Name, PLM_Revision_ID, LifecycleState, IsLatestRevision, SC_TotalPct
RND_FormulaLine 配方材料行 M_ProductSolidContent_ID, QtyEntered, SC_Weight, MaterialType
M_ProductSolidContent 含固含量的材料主檔 Name, SolidContent, MaterialType
RND_BatchTicket 生產批次票 RND_Project_ID, Scale
RND_BatchTicketLine 批次票材料行 M_ProductSolidContent_ID, QtyEntered

Phase 2:實驗與測試

Table 用途 主要欄位
RND_Experiment 實驗表頭 RND_Project_ID, RND_Formula_ID, Result, AD_User_ID
RND_ExperimentLine 實驗步驟 Line, Description, Result
RND_TestSpec 測試規格定義 Name, Method, Unit, Min/Max values
RND_TestResult 測試結果表頭 RND_Experiment_ID, RND_Formula_ID, DateTested
RND_TestResultLine 個別測試量測值 RND_TestSpec_ID, ResultValue, IsPass

Phase 3:知識庫與推薦

Table 用途 主要欄位
RND_KBArticle 知識庫文章 Name, Content (markdown), ArticleType, IsPublished
RND_KBTag 分類標籤 Name, TagGroup, Color
RND_KBArticle_Tag 文章-標籤多對多關聯 RND_KBArticle_ID, RND_KBTag_ID
RND_Recommendation AI 推薦記錄 Target properties, AIModel, AIResponse, Confidence
RND_RecommendationLine 推薦材料行 M_ProductSolidContent_ID, SuggestedQty, Reasoning

Phase 4:實驗室、派工、績效、成本

Table 用途 主要欄位
RND_Equipment 實驗室設備清冊 Name, ModelNo, Status, NextCalibrationDate
RND_Calibration 校準記錄 RND_Equipment_ID, DateCalibrated, Result, CertificateNo
RND_WorkOrder 工單表頭 RND_Project_ID, DocStatus (DR/IP/CO), Priority
RND_Assignment 工作指派 RND_WorkOrder_ID, AD_User_ID, Role, Status, Hours
RND_KPI KPI 定義 Name, Weight, TargetValue
RND_Performance 績效記錄 AD_User_ID, RND_KPI_ID, PeriodType, Score, ActualValue
C_ProjectLine 專案預算行(標準 iDempiere) PlannedAmt

4. API 合約

4.1 REST 端點(iDempiere REST 外掛)

所有資料存取都透過標準 iDempiere REST API:

GET    /api/v1/models/{tableName}          # 列表查詢,支援 OData $filter, $orderby, $top, $skip, $expand
GET    /api/v1/models/{tableName}/{id}     # 取得單筆記錄
POST   /api/v1/models/{tableName}          # 建立記錄
PUT    /api/v1/models/{tableName}/{id}     # 更新記錄
DELETE /api/v1/models/{tableName}/{id}     # 刪除記錄

4.2 伺服器流程(Server Processes)

Process Endpoint Parameters 用途
GenerateBatchTicket POST /api/v1/processes/generatebatchticket RND_Formula_ID[], Scale 從配方建立批次票
CopyFromExistingFormula POST /api/v1/processes/copyfromexistingformula RND_Formula_ID, Record_ID 複製配方到其他專案
PLMCreateRevision POST /api/v1/processes/plmcreaterevision AD_Table_ID, Record_ID, Description 建立新 PLM 版次
PLMTransitionState POST /api/v1/processes/plmtransitionstate PLM_Revision_ID, Next_LifecycleState_ID 轉換生命週期狀態
EvaluateTestResult POST /api/v1/processes/evaluatetestresult RND_TestResult_ID 伺服器端測試評估

4.3 外部 AI API

AI 推薦功能從 Flutter 用戶端直接呼叫 Claude API

POST https://api.anthropic.com/v1/messages
Headers: x-api-key, anthropic-version: 2023-06-01
Model: claude-sonnet-4-5-20250929

Prompt 包含:

  • 目標特性(SC%、黏度、膜厚、應用、基板)
  • 來自 M_ProductSolidContent 的可用材料
  • 相似的現有配方(在容差範圍內透過 SC% 匹配)

回應解析為 JSON:{materials: [...], explanation: "...", confidence: N}


5. 配方計算引擎

5.1 用戶端計算器(FormulaCalcService)

位於 lib/features/rnd/core/formula_calc_service.dart
映射伺服器端 BMCalc.java 以提供即時預覽。伺服器在儲存時重新計算(為唯一正確來源)。

輸入:配方行列表(每行含 QtyEnteredSC_WeightMaterialTypeM_ProductSolidContent_IDPigmentisHighBoiling

輸出(FormulaCalcResult):

欄位 計算方式
totalQty 含固含量行的 QtyEntered 總和
scTotal 含固含量行的 SC_Weight 總和
scTotalPct scTotal / totalQty x 100
mbTotal MONO + BIND 類型的 SC_Weight 總和
cbTotal PS 類型的 (Qty x Pigment / 100) 總和
cbTotalSolidPct cbTotal / scTotal x 100
initiatorTotalSolidPct INI SC_Weight / scTotal x 100
additiveTotalSolidPct ADD SC_Weight / scTotal x 100
highBoilingSolventPct 高沸點溶劑用量 / 溶劑總量 x 100

5.2 材料類型

定義於 lib/features/rnd/core/material_type.dart

代碼 標籤 顏色 用途
MONO Monomer 藍色 基底單體
BIND Binder 綠色 黏結劑
INI Initiator 橙色 光引發劑
ADD Additive 紫色 添加劑(界面活性劑、整平劑)
PAC Packaging 灰色 包裝材料
PS Pigment Solid 紅色 顏料分散液
SOL Solvent 藍綠色 溶劑(含高沸點標記)

6. PLM 生命週期系統

6.1 生命週期狀態

代碼 名稱 顏色 說明
DR Draft(草稿) 灰色 初始狀態,可編輯
IR In Review(審核中) 藍色 審核中,唯讀
RL Released(已發佈) 綠色 已核准可用於生產
OB Obsolete(已廢止) 紅色 已棄用,被取代

6.2 狀態轉換

DR (Draft) ──────────► IR (In Review)
                           │
                     ┌─────┴─────┐
                     ▼           ▼
              RL (Released)   DR (Rejected → Draft)
                     │
                     ▼
              OB (Obsolete)  ← 需要 Change Order

6.3 版次追蹤

PLM Repository(PlmRepository)提供:

  • getRevisions(tableId, rootRecordId) — 含生命週期狀態的版次歷史
  • getTransitions(stateId) — 從目前狀態可用的轉換
  • getChangeOrders(revisionId) — 版次的變更命令

版次建立(FormulaRepository.createRevision)會複製配方記錄和所有行,遞增版次號碼(A→B→C)。


7. 響應式佈局

模組使用響應式斷點系統(ResponsiveLayout widget):

斷點 佈局 範例
< 600dp 手機 單欄、tab 導航
600–900dp 平板直立 自適應
> 900dp 平板橫向 Master-detail 分割

秤量配料畫面(Dispensing screen)是最佳範例:在手機上以 Formulas/Batch Tickets 分頁呈現,在平板橫向則透過 MasterDetailLayout 並排顯示。


8. 圖表與視覺化

模組使用 fl_chart 進行資料視覺化:

畫面 圖表類型 用途
Test Trend 折線圖(Line chart) 依測試規格顯示測試結果隨時間趨勢
Workload Dashboard 堆疊長條圖(Stacked bar chart) 每位研究人員的工時(已指派 vs 進行中)
Performance Dashboard 雷達圖(Radar chart) 多維度的 KPI 分數
Performance Dashboard 進度條(Progress bars) 個別 KPI 達成度
Cost Dashboard 圓餅圖(Pie chart) 材料 vs 人工成本分布
Cost Dashboard 進度條(Progress bar) 預算使用率

9. 路由對應

所有 R&D 路由定義於 lib/features/rnd/rnd_routes.dart,巢狀在 dashboard shell route 下。

Route Path Screen Phase
rnd/formula-list/:projectId FormulaListScreen 1
rnd/formula/:id FormulaDetailScreen 1
rnd/formula/:id/edit FormulaEditorScreen 1
rnd/formula/:id/compare FormulaCompareScreen 1
rnd/dispensing DispensingScreen 1
rnd/experiment-list/:projectId ExperimentListScreen 2
rnd/experiment/:id ExperimentDetailScreen 2
rnd/test-result/new?experimentId= TestResultEntryScreen 2
rnd/test-result/:id TestResultEntryScreen (edit) 2
rnd/test-trends TestTrendScreen 2
rnd/kb KBSearchScreen 3a
rnd/kb/article/new ArticleEditorScreen 3a
rnd/kb/article/:id ArticleViewerScreen 3a
rnd/kb/article/:id/edit ArticleEditorScreen 3a
rnd/formula-compare?ids=1,2,3 FormulaComparisonScreen 3a
rnd/recommendation RecommendationWizardScreen 3b
rnd/calibration CalibrationScreen 4a
rnd/calibration/:equipmentId CalibrationScreen 4a
rnd/work-orders?projectId= WorkOrderListScreen 4b
rnd/work-order/:id WorkOrderDetailScreen 4b
rnd/my-assignments/:userId MyAssignmentsScreen 4b
rnd/workload WorkloadDashboardScreen 4b
rnd/performance?userId= PerformanceDashboardScreen 4c
rnd/cost/:projectId CostDashboardScreen 4d

9.1 Enhanced Module Registry 選單入口

Key AD_Window_ID Route Icon
W:1000904 RND Project /dashboard/rnd/project science
W:1000905 RND Experiment /dashboard/rnd/experiment-list/0 biotech
W:1000911 Test Trend /dashboard/rnd/test-trend trending_up
W:1000912 Knowledge Base /dashboard/rnd/kb menu_book
W:1000913 AI Recommendation /dashboard/rnd/recommendation auto_awesome
W:1000914 Work Orders /dashboard/rnd/work-orders assignment
W:1000915 Workload Dashboard /dashboard/rnd/workload bar_chart
W:1000916 Performance /dashboard/rnd/performance speed
R:1000917 Project Cost Report /dashboard/rnd/cost/0 attach_money

10. 國際化 (i18n)

四國語言切自如,比聯合國翻譯還勤勞。所有使用者可見的字串都使用 S class:

import '../../../../l10n/app_localizations.dart';
final l10n = S.of(context);  // Non-nullable,不用 ! 驚嘆號
項目 說明
支援語系 enzh_TWzhja
R&D Key 前綴 rnd*(例:rndProjectTitlerndFormulaTitlerndDispensingTitlerndEquipmentTitle
Template ARB lib/l10n/app_zh_TW.arb(新字串先加在這裡)
Config 用法 title: (l10n) => l10n.featureTitle(延遲求值)

11. 測試

寫測試是對未來的自己最好的投資——免得改一行 code 倒三個功能。

11.1 單元測試

位於 test/features/rnd/

測試檔案 覆蓋範圍
formula_calc_service_test.dart 固含量計算
formula_list_notifier_test.dart 列表狀態、Lifecycle 篩選、分頁

11.2 關鍵測試模式

  • autoDispose providers:在測試中使用 container.listen() 保持 provider 存活,否則 Riverpod 會把它回收掉,你的測試就在跟空氣說話
  • Async notifiers:呼叫 async 方法後一定要 await,再做 assertion。不等結果就檢查,跟考卷還沒發就開始對答案一樣蠢
  • Sentinel pattern:copyWith 中的 nullable 欄位用 Object? field = _sentinel,區分「沒傳」和「傳 null」

12. 伺服器外掛(tw.topgiga.rnd)

12.1 結構

src/tw/topgiga/rnd/
├── model/
│   ├── I_RND_*.java          # Interface 類別
│   ├── X_RND_*.java          # 產生的 Model 類別
│   └── M_RND_*.java          # 商業邏輯 Model 類別
├── process/
│   ├── PLMCreateRevision.java
│   ├── PLMTransitionState.java
│   ├── GenerateBatchTicket.java
│   ├── CopyFromExistingFormula.java
│   └── EvaluateTestResult.java
├── setup/
│   └── RNDModelFactory.java   # Model + Process 工廠註冊
└── callout/
    └── BMCalc.java            # 伺服器端配方計算

12.2 DDL 遷移檔

位於 tw.topgiga.rnd/migration/postgresql/

檔案 Phase Tables
2026-02-15_Phase1_PLM_Core_Tables.sql 1 PLM_Lifecycle*, PLM_Revision, PLM_ChangeOrder
2026-02-15_Phase2_Experiment_Tables.sql 2 RND_Experiment, RND_TestSpec, RND_TestResult
2026-02-15_Phase3_KB_Recommendation_Tables.sql 3 RND_KBArticle, RND_KBTag, RND_Recommendation
2026-02-15_Phase4_Lab_Dispatch_Tables.sql 4 RND_Equipment, RND_WorkOrder, RND_KPI, RND_Performance

13. 相依套件

Package 用途
flutter_riverpod 狀態管理
go_router 導航路由
dio HTTP 用戶端
fl_chart 圖表(折線、長條、圓餅、雷達)
flutter_markdown Markdown 渲染(KB 文章)

14. 已知限制與未來工作

  1. AI 推薦從用戶端直接呼叫 Claude API(需要在設定中提供 API key)。應考慮伺服器端代理以提升安全性。
  2. 成本報表使用硬編碼的時薪($50)。應整合 HR 模組取得實際費率。
  3. 離線模式:瀏覽已快取。所有編輯操作需要網路連線。
  4. 全文搜尋:知識庫依賴伺服器端 PostgreSQL 的 gin 索引(RND_KBArticle)。
  5. 設備預約整合S_Resource_ID 欄位連結到 iDempiere Booking,但尚未與 App 的預約功能整合。

15. 搖一搖回報 (Shake-to-Report)

搖手機不是因為生氣,是因為我們提供了最暴力但最有效的 Bug 回報方式——搖一下,截圖 + 裝置資訊自動收集,直接建立 R_Request。比起在 LINE 群組打「那個功能怪怪的」,這個方式顯然更有建設性。

15.1 相關套件

Package 用途
shake ^3.0.0 加速度計搖動偵測
screenshot ^3.0.0 Widget 截圖
device_info_plus ^11.0.0 裝置型號/OS 資訊
package_info_plus ^8.0.0 App 版本資訊

15.2 架構

MaterialApp.router
  └── builder:
        └── ShakeFeedbackWrapper          ← Screenshot + ShakeDetector
              └── _OfflineBannerWrapper
                    └── Navigator (GoRouter)

ShakeFeedbackWrapper 放在 MaterialApp.routerbuilder 回呼裡,確保能存取 LocalizationsNavigatorTheme

15.3 檔案結構

lib/features/feedback/
├── data/
│   ├── device_info_collector.dart       # 靜態工具:收集裝置/App 資訊
│   └── feedback_repository.dart         # 建立 R_Request + 上傳截圖
├── domain/
│   └── feedback_notifier.dart           # FeedbackState + StateNotifier
├── presentation/
│   └── feedback_sheet.dart              # Modal BottomSheet UI
└── shake/
    ├── shake_feedback_settings.dart     # 開關偏好(SharedPreferences)
    └── shake_feedback_wrapper.dart      # App 層級包裝器

15.4 資料流

使用者搖手機
    │
    ▼
ShakeDetector.onPhoneShake
    │  ← 檢查:啟用?冷卻期?Sheet 已開啟?
    ▼
ScreenshotController.capture()
    │
    ▼
DeviceInfoCollector.collect()
    │  ← 裝置型號、OS、App 版本、路由、語系
    ▼
FeedbackSheet.show()
    │  ← 使用者填寫:問題類型 + 描述
    ▼
FeedbackNotifier.submitFeedback()
    │
    ▼
FeedbackRepository.submitFeedback()
    ├── ApiClient.createRecord('R_Request', data)
    └── UploadApi.uploadAndAttach(screenshot.png)

15.5 R_Request 欄位對應

R_Request 欄位 資料來源
Summary [問題類型] 描述前 50 字元
Result 使用者描述 + 自動收集的裝置資訊
R_RequestType_ID (尚未對應——類型標籤僅出現在 Summary)
附件 截圖 PNG,透過 Upload API 上傳

15.6 搖動行為參數

參數
靈敏度 shakeThresholdGravity: 2.7(預設值)
冷卻時間 3 秒(防止連續觸發)
僅前景 App 進背景時暫停,回前景恢復
Sheet 防護 回報 Sheet 已開啟時忽略搖動
開關 SharedPreferences key shake_feedback_enabled,預設 true

15.7 整合點

檔案 變更
pubspec.yaml 新增 shake、screenshot、device_info_plus、package_info_plus
lib/app.dart ShakeFeedbackWrapper 加入 MaterialApp.router builder
settings_tab.dart 設定頁面的「系統」區塊加入開關
lib/l10n/app_*.arb 4 種語言各 15 個回饋字串

15.8 錯誤處理

情境 行為
截圖失敗 表單正常開啟但沒有預覽,允許純文字提交(有比沒有好)
網路錯誤 顯示本地化錯誤 SnackBar,表單保持開啟可重試
R_Request 建立失敗 顯示 AppException.localizedMessage(l10n) 的 SnackBar
🌐 English — New Sections (10, 11, 15)

10. Internationalization (i18n)

Four languages, zero excuses for missing translations. All user-facing strings use the S class:

import '../../../../l10n/app_localizations.dart';
final l10n = S.of(context);  // Non-nullable, no ! needed
Item Details
Supported Locales en, zh_TW, zh, ja
R&D Key Prefix rnd* (e.g., rndProjectTitle, rndFormulaTitle, rndDispensingTitle)
Template ARB lib/l10n/app_zh_TW.arb (add new keys here first)
Config Usage title: (l10n) => l10n.featureTitle (lazy evaluation)

11. Testing

Writing tests is the best investment you can make for your future self — unless you enjoy debugging three broken features after changing one line of code.

11.1 Unit Tests

Located at test/features/rnd/:

Test File Coverage
formula_calc_service_test.dart Solid content calculation
formula_list_notifier_test.dart List state, lifecycle filter, pagination

11.2 Key Testing Patterns

  • autoDispose providers: Use container.listen() to keep providers alive in tests — otherwise Riverpod garbage-collects them and your test is talking to thin air
  • Async notifiers: Always await async method calls before asserting. Checking results before they arrive is like grading an exam before handing it out.
  • Sentinel pattern: Use Object? field = _sentinel for nullable copyWith fields to distinguish “not passed” from “passed null”

15. Shake-to-Report Feedback

Shaking your phone is not an anger management technique — it is the most violent yet effective bug reporting method. One shake captures a screenshot, collects device info, and creates an R_Request in iDempiere.

Architecture

MaterialApp.router
  └── builder:
        └── ShakeFeedbackWrapper          ← Screenshot + ShakeDetector
              └── _OfflineBannerWrapper
                    └── Navigator (GoRouter)

Data Flow

User shakes phone
    ↓
ShakeDetector.onPhoneShake (check: enabled? cooldown? sheet open?)
    ↓
ScreenshotController.capture()
    ↓
DeviceInfoCollector.collect() (device model, OS, app version, route, locale)
    ↓
FeedbackSheet.show() (user fills: problem type + description)
    ↓
FeedbackRepository.submitFeedback()
    ├── ApiClient.createRecord('R_Request', data)
    └── UploadApi.uploadAndAttach(screenshot.png)

R_Request Field Mapping

Field Source
Summary [Problem Type] first 50 chars
Result User description + auto-collected device info
Attachment Screenshot PNG via Upload API

Shake Behavior

Parameter Value
Threshold shakeThresholdGravity: 2.7
Cooldown 3 seconds between triggers
Foreground only Pauses on background, resumes on foreground
Toggle SharedPreferences key shake_feedback_enabled, default true

Error Handling

Scenario Behavior
Screenshot fails Form opens without preview, text-only submission
Network error SnackBar with error, form stays open for retry
R_Request creation fails SnackBar with AppException.localizedMessage
🇯🇵 日本語 — 新セクション(10, 11, 15)

10. 国際化 (i18n)

4言語対応、国連翻訳者より勤勉です。全てのユーザー向け文字列は S クラスを使用:

import '../../../../l10n/app_localizations.dart';
final l10n = S.of(context);  // Non-nullable、! 不要
項目 詳細
対応ロケール enzh_TWzhja
R&D キー接頭辞 rnd*(例:rndProjectTitlerndFormulaTitle
テンプレート ARB lib/l10n/app_zh_TW.arb(新しいキーはここに最初に追加)
Config 用法 title: (l10n) => l10n.featureTitle(遅延評価)

11. テスト

テストを書くのは未来の自分への最良の投資です — 1行変更して3機能壊すデバッグが楽しいなら別ですが。

11.1 ユニットテスト

test/features/rnd/ に配置:

テストファイル カバレッジ
formula_calc_service_test.dart 固形分計算
formula_list_notifier_test.dart リスト状態、ライフサイクルフィルター、ページネーション

11.2 重要なテストパターン

  • autoDispose providers:テスト内で container.listen() を使って provider を生存させる — さもなければ Riverpod がガベージコレクトして、テストは虚空に話しかけることに
  • Async notifiers:async メソッド呼び出し後は必ず await してからアサーション。試験を配る前に採点するようなもの。
  • Sentinel pattern:nullable な copyWith フィールドに Object? field = _sentinel を使用し、「未指定」と「null を指定」を区別

15. シェイク・トゥ・レポート(搖一搖バグ報告)

スマホを振るのは怒りのせいではありません — 最も暴力的だが最も効果的なバグ報告方法です。一振りでスクリーンショットを撮影、デバイス情報を収集し、iDempiere に R_Request を作成します。

アーキテクチャ

MaterialApp.router
  └── builder:
        └── ShakeFeedbackWrapper          ← Screenshot + ShakeDetector
              └── _OfflineBannerWrapper
                    └── Navigator (GoRouter)

データフロー

ユーザーがスマホを振る
    ↓
ShakeDetector.onPhoneShake(有効?クールダウン中?Sheet 表示中?)
    ↓
ScreenshotController.capture()
    ↓
DeviceInfoCollector.collect()(デバイスモデル、OS、アプリバージョン、ルート、ロケール)
    ↓
FeedbackSheet.show()(ユーザーが入力:問題タイプ + 説明)
    ↓
FeedbackRepository.submitFeedback()
    ├── ApiClient.createRecord('R_Request', data)
    └── UploadApi.uploadAndAttach(screenshot.png)

R_Request フィールドマッピング

フィールド ソース
Summary [問題タイプ] 説明の最初50文字
Result ユーザー説明 + 自動収集されたデバイス情報
添付ファイル スクリーンショット PNG(Upload API 経由)

シェイク動作パラメータ

パラメータ
しきい値 shakeThresholdGravity: 2.7
クールダウン トリガー間隔 3 秒
フォアグラウンドのみ バックグラウンドで一時停止、復帰で再開
トグル SharedPreferences キー shake_feedback_enabled、デフォルト true

エラーハンドリング

シナリオ 動作
スクリーンショット失敗 プレビューなしでフォーム表示、テキストのみ送信可能
ネットワークエラー エラー SnackBar を表示、リトライ可能
R_Request 作成失敗 AppException.localizedMessage の SnackBar

按 Enter 搜尋,ESC 關閉