專案

一般

配置概況

Feature #1380

是由 陳國瑋18 天 前更新

# Branch Admin 工程師工具(FE Implementation Spec) 

 ## 1. 目標與範圍 

 在 Branch Admin 新增一個只供 Engineer 使用的「工程師工具」工作頁,讓工程師可以: 

 1. 對指定 CloudLink Connector 執行受控 Modbus 讀取與控制。 
 2. 由 Branch 向指定 Charge Point 發送 OCPP 1.6 CSMS Command。 
 3. 查詢每次操作的 request、response、錯誤、耗時與完整 event timeline。 
 4. 以 Fortune(華城)充電樁測試為主要情境,同時支援 CloudLink 與其他廠牌的 capability 顯示。 

 Backend 對應票為 #1381,目前已完成並部署至 Jimmy 與 A17。FE Jimmy。FE 不需要自行設計 Backend contract;本 Description、Backend OpenAPI 與附件 Plan 為實作依據。 

 ## 2. 權限與導覽 

 - 新路由:`/engineer-tools`。 
 - 側邊欄名稱:「工程師工具」。 
 - 只有取得 Backend Engineer function/menu 的使用者顯示入口並可進入。 
 - 不要在 FE hardcode `ADMIN`、`power_user` 或只判斷 role name;這兩個既有帳號已由 Backend 加入 Engineer Role。 
 - FE 隱藏入口不是安全邊界,所有 API 仍由 Backend 驗證權限。 
 - 在既有 Connector 清單或詳情頁增加工具 icon,可 deep-link: 
   `/engineer-tools?connectorId={connectorId}&tab=ocpp` 
 - 進入 deep-link 後仍須重新向 API 取得 target 與 capability,不可直接信任 query string。 

 ## 3. 頁面 Layout 

 沿用既有 dashboard shell,使用全寬工作區,不做 landing page,也不要把整個頁面包成多層 Card。 

 ### Target Bar 

 頁面頂部固定顯示: 

 - Charge Point searchable select 
 - Connector searchable select 
 - online/offline status 
 - vendor 
 - model 
 - firmware version 
 - capability badge 

 規則: 

 - 先選 Charge Point,再載入該 Charge Point 的 catalog/capability。 
 - Connector 只在 connector-level command 或 Modbus command 時必填。 
 - 從 Connector deep-link 進入時,自動解析並預選其 Charge Point 與 Connector。 

 ### Tabs 

 Target Bar 下方固定三個 tabs: 

 1. `Modbus` 
 2. `OCPP` 
 3. `操作紀錄` 

 Desktop:command list 與 form/result 使用左右兩欄。 

 Mobile:改為上下排列。 

 Command list、form、status panel 應有穩定尺寸,切換 command 或狀態時不得造成整頁寬度跳動或文字重疊。 

 ## 4. Command Catalog 與 Capability 

 畫面能力唯一來源: 

 `GET /api/engineer/commands/catalog?chargePointId={chargePointId}` 

 回傳共 31 筆:19 個 OCPP commands + 12 個 Modbus operations。 

 每筆主要欄位: 

 - `protocol`:`OCPP` 或 `MODBUS` 
 - `command` 
 - `category` 
 - `targetLevel`:`CHARGE_POINT` 或 `CONNECTOR` 
 - `riskLevel`:`LOW`、`MEDIUM`、`HIGH` 
 - `writeOperation` 
 - `schema`:產生表單與前端預驗證使用的 JSON Schema 
 - `capability`:`VERIFIED`、`UNVERIFIED`、`UNSUPPORTED` 
 - `capabilityNote` 

 Capability UI: 

 - `VERIFIED`:正常啟用。 
 - `UNVERIFIED`:允許執行,但在送出按鈕附近顯示非阻斷警告。 
 - `UNSUPPORTED`:禁用執行按鈕並顯示 `capabilityNote`。 
 - 不要在 FE 重複維護 vendor allowlist 或自行把 `UNVERIFIED` 判定成 supported。 
 - FE 可以維護中文標籤、欄位順序與圖示,但 command key、schema、target、risk、capability 必須來自 API。 

 ## 5. Modbus Tab 

 Modbus 只允許 CloudLink Connector。其他廠牌 catalog 會回 `UNSUPPORTED`,UI 必須禁用。 

 ### 允許的 12 個 Operations 

 Read: 

 - `CONNECTION_TEST` 
 - `READ_SUPPLY_STATE` 
 - `READ_VOLTAGE` 
 - `READ_CURRENT` 
 - `READ_ACTIVE_POWER` 
 - `READ_APPARENT_POWER` 
 - `READ_POWER_FACTOR` 
 - `READ_TOTAL_ENERGY` 
 - `READ_BALANCE` 
 - `READ_METER_ID` 

 Write: 

 - `SET_SUPPLY_STATE` 
 - `SET_METER_ID` 

 UI 分組: 

 - 連線與狀態 
 - 電力讀值 
 - 控制 

 禁止提供: 

 - raw Modbus function code 
 - raw register address/value 
 - raw payload 
 - 清除歷史資料 
 - Browser 直接連 charge_point API 

 Read operation 的 `parameters` 必須是空 object。 

 `SET_SUPPLY_STATE`: 

 ```json 
 { 
   "connectorId": "CP001-001", 
   "operation": "SET_SUPPLY_STATE", 
   "parameters": { "enabled": true } 
 } 
 ``` 

 `SET_METER_ID`: 

 ```json 
 { 
   "connectorId": "CP001-001", 
   "operation": "SET_METER_ID", 
   "parameters": { "meterId": 1 } 
 } 
 ``` 

 `meterId` 範圍為 1–255。 

 文案使用「供電控制」;可以用次要說明標示底層由智慧電表 Test Mode 執行,但不要新增獨立 Test Mode command。 

 確認規則: 

 - Read command:直接執行,不顯示確認。 
 - `SET_SUPPLY_STATE`、`SET_METER_ID`:各顯示一次簡短確認。 
 - 不要求填寫「操作原因」。 
 - 不做額外第二次確認。 

 ## 6. OCPP Tab 

 提供以下全部 19 個 OCPP 1.6 CSMS → Charge Point commands: 

 - `RemoteStartTransaction` 
 - `RemoteStopTransaction` 
 - `Reset` 
 - `ChangeAvailability` 
 - `UnlockConnector` 
 - `TriggerMessage` 
 - `DataTransfer` 
 - `GetConfiguration` 
 - `ChangeConfiguration` 
 - `ClearCache` 
 - `ReserveNow` 
 - `CancelReservation` 
 - `SetChargingProfile` 
 - `ClearChargingProfile` 
 - `GetCompositeSchedule` 
 - `GetDiagnostics` 
 - `UpdateFirmware` 
 - `GetLocalListVersion` 
 - `SendLocalList` 

 功能要求: 

 - command 依 catalog `category` 分組,並提供搜尋。 
 - 使用 segmented control 切換「表單」與「JSON」。 
 - 兩種模式共用同一份 payload state,切換模式不能丟失資料。 
 - 表單依 catalog `schema` 產生或映射。 
 - schema validation error 顯示到 JSON path/欄位。 
 - Backend 仍會執行同一份 schema validation,FE validation 只是提早提示。 
 - `Reset` 送出前顯示一次簡短確認。 
 - 其他 OCPP command 不增加確認。 
 - `GetDiagnostics`、`UpdateFirmware` 的 HTTP/HTTPS location 由 Engineer 輸入;FE 不需實作檔案上傳。 

 OCPP request: 

 ```json 
 { 
   "chargePointId": "CP001", 
   "connectorId": null, 
   "command": "TriggerMessage", 
   "payload": { 
     "requestedMessage": "Heartbeat" 
   } 
 } 
 ``` 

 若 command 為 connector-level,送出 `connectorId`;Backend 會驗證該 Connector 是否屬於選定 Charge Point,並在需要時補入 OCPP numeric connectorId。 

 ## 7. Backend API Contract 

 ### 7.1 Catalog 

 `GET /api/engineer/commands/catalog?chargePointId={chargePointId}` 

 用途:取得 31-command catalog、schema、risk、target 與 capability。 

 ### 7.2 Execute OCPP 

 `POST /api/engineer/ocpp/commands` 

 Body: 

 ```json 
 { 
   "chargePointId": "CP001", 
   "connectorId": null, 
   "command": "TriggerMessage", 
   "payload": { 
     "requestedMessage": "Heartbeat" 
   } 
 } 
 ``` 

 ### 7.3 Execute Modbus 

 `POST /api/engineer/modbus/commands` 

 Body: 

 ```json 
 { 
   "connectorId": "CP001-001", 
   "operation": "READ_SUPPLY_STATE", 
   "parameters": {} 
 } 
 ``` 

 ### 7.4 Command Detail 

 `GET /api/engineer/commands/{commandLogId}` 

 主要欄位: 

 - target snapshot:chargePointId、connectorId、connectorNo、vendor、model、firmwareVersion 
 - protocol、command、capability、operatorId 
 - correlationId、ocppUniqueId 
 - requestPayload、maskedRequestPayload、responsePayload 
 - state、protocolStatus 
 - errorCode、errorDescription、errorDetails 
 - createdAt、sentAt、respondedAt、completedAt、elapsedMs 
 - events 

 顯示規則: 

 - 預設顯示 `maskedRequestPayload`,不要預設展開 raw `requestPayload`。 
 - event timeline 依 `sequenceNo` 顯示。 
 - correlationId、ocppUniqueId、commandLogId 提供 copy icon。 
 - 長 JSON 與 ID 必須 wrap/scroll,不得撐破 drawer。 

 ### 7.5 Command Search 

 `POST /api/engineer/commands/search` 

 Body 所有 filter 都可選: 

 ```json 
 { 
   "from": null, 
   "to": null, 
   "protocol": null, 
   "command": null, 
   "chargePointId": null, 
   "connectorId": null, 
   "operatorId": null, 
   "state": null, 
   "correlationId": null, 
   "ocppUniqueId": null, 
   "page": 0, 
   "size": 20 
 } 
 ``` 

 規則: 

 - `page` 從 0 起算。 
 - `size` 最大 100。 
 - response 是 Spring Page,使用 `content`、`totalElements`、`totalPages`、`number`、`size`。 

 ## 8. 202 Response 與 Polling 

 兩支 execute API 成功時回 HTTP 202: 

 ```json 
 { 
   "commandLogId": "uuid", 
   "correlationId": "uuid", 
   "ocppUniqueId": "uuid", 
   "state": "SENT", 
   "capability": "UNVERIFIED", 
   "createdAt": "2026-07-14T22:43:17.151298" 
 } 
 ``` 

 核心規則: 

 - HTTP 202/`SENT` 只表示 Branch 已把 OCPP CALL 送入 WebSocket。 
 - 這不代表 Charge Point 已執行完成。 
 - FE 必須用 `commandLogId` 輪詢 detail API。 
 - 建議畫面可見時每 1 秒輪詢一次。 
 - 使用者離開頁面不會取消已送出的 command。 
 - 快速設備可能在 POST 回來前已完成;FE 要先檢查 create response 的 `state`,若已是 terminal 就不需等待。 

 Non-terminal states: 

 - `CREATED` 
 - `VALIDATED` 
 - `DISPATCHING` 
 - `SENT` 
 - `RUNNING` 

 Terminal states,收到後停止 polling: 

 - `COMPLETED` 
 - `REJECTED` 
 - `CALL_ERROR` 
 - `SEND_FAILED` 
 - `FAILED` 
 - `TIMEOUT` 
 - `STATUS_UNKNOWN` 

 `GetDiagnostics`、`UpdateFirmware` 可能先進入 `RUNNING`,後續 progress 由 detail events 顯示。 

 ## 9. 操作紀錄 Tab 

 Filters: 

 - 時間範圍 
 - protocol 
 - command 
 - Charge Point 
 - Connector 
 - operator 
 - state/result 
 - correlationId 
 - ocppUniqueId 

 Table columns: 

 - 執行時間 
 - protocol/command 
 - target 
 - operator 
 - state/protocol status 
 - elapsed 
 - correlationId 

 點擊 row 開啟 detail drawer,顯示: 

 - target/device snapshot 
 - masked request/response JSON 
 - CALLERROR 或 local error 
 - command event timeline 
 - commandLogId、correlationId、ocppUniqueId 

 ## 10. HTTP Error Handling 

 - `400`:payload/schema/target/Modbus parameters 錯誤。顯示 Backend message,schema error 儘量對應到欄位。 
 - `401`:未登入;沿用既有 refresh/sign-out middleware。 
 - `403`:沒有 Engineer function 權限。 
 - `404`:Charge Point、Connector 或 operation 不存在。 
 - `409`:同一 Charge Point 已有進行中的 `GetDiagnostics` 或 `UpdateFirmware`。 
 - `422`:capability 為 unsupported,或對非 CloudLink Connector 執行 Modbus。 
 - `503`:設備 offline 或 WebSocket dispatch 失敗。錯誤 message 會包含已建立的 `commandLogId`,UI 應提供前往 detail 的入口。 

 ## 11. React Implementation Guidance 

 先在 `react/branch` 更新 Backend types: 

 ```bash 
 # .env.local 的 NEXT_PUBLIC_API_URL 指向含 #1381 的 Branch API 
 yarn openapi:refresh 
 ``` 

 Backend OpenAPI tag:`[B135]工程師工具`。 

 必須: 

 - 使用既有 `lib/query-api/openapi.ts` 的 `$api`/`fetchClient`。 
 - 沿用既有 JWT middleware、React Query、React Hook Form、Zod、shadcn、Sonner/SweetAlert2 pattern。 
 - 不要另建未帶 auth/refresh middleware 的 API client。 
 - 不要手改 `lib/data/ems.json` 或 `lib/data/emsapi.ts`。 
 - 在 `config/menus.ts` 註冊 route/menu,但顯示權限仍以 Backend function/menu 為準。 

 建議 feature 結構: 

 ```text 
 app/(dashboard)/engineer-tools/ 
 ├── page.tsx 
 ├── components/ 
 │     ├── target-bar.tsx 
 │     ├── command-list.tsx 
 │     ├── modbus-command-form.tsx 
 │     ├── ocpp-command-form.tsx 
 │     ├── command-status-panel.tsx 
 │     ├── operation-table.tsx 
 │     └── operation-detail-drawer.tsx 
 ├── hooks/ 
 │     └── engineer-command-hooks.ts 
 └── lib/ 
     ├── schema.ts 
     └── presentation.ts 
 ``` 

 這不是一般 CRUD,建議以 typed `$api.useQuery/useMutation` 建立專用 hooks,不必勉強套完整 CRUD factory。 

 ## 12. 驗收條件 

 1. 只有取得 Engineer function 的使用者看得到並能進入工程師工具。 
 2. Charge Point/Connector target 可選取,deep-link 可正確預選。 
 3. Catalog 回傳的 31 commands 都能正確分組與顯示 capability。 
 4. Modbus 不提供任何 raw register/function/payload 入口。 
 5. 19 個 OCPP commands 都可由 schema 建立 payload,表單/JSON 模式切換不丟資料。 
 6. `UNVERIFIED` 可送出且有警告;`UNSUPPORTED` 禁用並顯示原因。 
 7. Command POST 後顯示 operation panel,正確輪詢至 terminal state。 
 8. `REJECTED`、`CALL_ERROR`、`SEND_FAILED`、`TIMEOUT` 不得顯示為成功。 
 9. 操作紀錄支援 server-side filter/pagination,detail drawer 顯示 masked payload 與完整 timeline。 
 10. Reset、供電控制、Meter ID 修改各只有一次簡短確認;不要求操作原因。 
 11. Desktop/mobile 無文字重疊、截斷或切換造成的 layout shift。 
 12. 完成後通過 `yarn lint` 與 `yarn build`。 

 ## 13. Backend 現況與測試限制 

 Jimmy 已驗證: 

 - Engineer Role login 
 - 31-command catalog 
 - 真實 CP001 `READ_SUPPLY_STATE` Modbus E2E 
 - OCPP `TriggerMessage` Accepted/Rejected 
 - search/detail 
 - 未登入 401 
 - raw Modbus operation 400 

 Fortune(華城)設備在 Jimmy 2026-07-14 測試時離線;A17 於 2026-07-16 已通過 `TriggerMessage(Heartbeat)` 低風險實機 E2E。這只證明基礎 測試時離線,因此其 19 個 OCPP 通訊路徑可用,尚未完成 19-command capability matrix,因此 FE 仍必須將其 capability 呈現為 `UNVERIFIED` 與非阻斷警告,不能顯示為 目前維持 `UNVERIFIED`。FE 必須呈現非阻斷警告,不能顯示為 `VERIFIED`。 

 Backend E2E 報告:`test_report/20260714_001_redmine_1381_engineer_command_e2e.md`。 

 ## 14. 附件與關聯票 

 - Backend:#1381(Resolved) 
 - 附件:`branch-admin-engineer-command-plan.md` 
 - 附件包含完整產品決策、資料表、OCPP schema、測試矩陣與後續 Fortune 實機驗證計畫。 

 若本 Description 與既有 Journal 有差異,以本 Description 與最新 Backend OpenAPI 為準;Journal 只作進度歷史,不作需求拼接來源。 

返回