專案

一般

配置概況

動作

Feature #1401

進行中

[Branch後台][FE] 充電樁狀態改吃 WebSocket,並修正充電指令自動重送

是由 鍾正剛3 天 前加入. 於 約 2 小時 前更新.

狀態:
New
優先權:
High
被分派者:
開始日期:
2026-07-31
完成日期:
完成百分比:

0%

預估工時:

概述

背景

Branch Admin 「連接器 → 編輯 → 充電樁狀態」分頁(react/branch/app/(dashboard)/connector/components/connector-status.tsx)目前全靠 REST 輪詢:

  • useConnectorStatusAVAILABLEPREPARINGCHARGING 時每 2 秒輪詢(connectorHooks.ts)。
  • chargePointHooks.useGetById 為了取 ocppConnectionStatus,離線時每 10 秒輪詢,並用 prevOnlineRef 在恢復連線時補一次 refetch。
  • start/stop 後 setTimeout(3000) 硬等 3 秒才 refetch。

同一份 Connector 狀態,LIFF(#1399)已改為 WebSocket 推送,後台仍是輪詢,時序無法對齊。

本案不是要後台照抄 LIFF 的顯示或操作方式。 driverStateactions[] 是車主語意,授權綁住戶關聯與 transaction owner,管理員不適用。後台維持自己的維運語意(status + ocppConnectionStatus 雙軸判斷,沿用 #1382 的區分原則),只讓資料取得管道與來源一致

實作範圍

  1. 改吃 BE 提供的 WebSocket snapshot(endpoint 與 payload 見對應 BE ticket):
    • 移除 useConnectorStatus 的 2 秒 refetchInterval
    • 移除 chargePointHooks 的 10 秒 refetchIntervalprevOnlineRef 補償邏輯。
    • 移除 start/stop 後的 setTimeout(3000),改由推送更新畫面。
  2. WebSocket 斷線時明確標示「即時狀態已中斷」並停用充電操作按鈕,不得讓管理員對著過期畫面下指令。重連策略比照 #1399(短暫重連,失敗即明確告知),不做無限重試。
  3. 顯示層維持後台維運語意,不引入 driverStateactions[]。BE 新欄位到位後補上:
    • FAULTED 顯示實際 errorCodevendorErrorCodevendorId,取代目前只有的「故障/請聯繫技術支援」。
    • 目前交易(currentTransactionId)與使用者(currentIdTag)。
    • 離線時長(lastHeartbeatAt)與狀態停滯時間(statusChangedAt)。
    • activeOperation 未終結時停用充電按鈕,避免管理員重複送指令。
  4. 操作面沿用 POST /api/tx/charge/startstop(ss3a 管理員身分),依 BE 提供的穩定 errorCode 分流錯誤訊息,不再比對 message 字串;保存回傳的 operationId 並寫入前端操作紀錄。

可先行處理(不依賴 BE)

修正 mutations.retry: 1 react/branch/provider/providers.client.tsx:20 的 QueryClient 全域設定,讓 useStartChargeuseStopCharge 在失敗或逾時後自動重送一次充電指令

這與 #1399 在 LIFF 修掉的是同一個 bug(LIFF 已改為全域 mutations.retry: 0)。充電指令非 idempotent,最危險的是逾時情境:第一次請求可能已送達 Branch、只是回應遺失,自動重試就會送出第二筆真實的充電指令,並在 audit log 產生重複紀錄。

需盤點後台其餘 mutation 是否有適合自動重試者,再決定全域關閉或逐一覆寫。

不在本次範圍

  • Connector 列表頁的狀態顯示(仍走既有查詢;本案先處理單一 Connector 詳細狀態)。
  • Engineer tools(#1380)既有的 OCPP/Modbus 指令面板。
  • LIFF 端行為(#1397/#1398/#1399)。

驗收重點

  • 後台狀態分頁不再有任何定時輪詢;狀態變化由推送即時反映。
  • 充電樁離線/恢復時,statusocppConnectionStatus 來自同一份 payload,不再出現兩者短暫矛盾。
  • FAULTED 時畫面可看到實際故障代碼,而非只有「故障」。
  • 送出 start/stop 後不靠固定等待;同一時間不可送出第二筆指令。
  • 網路中斷時操作按鈕立即停用,恢復後需收到新 snapshot 才恢復可操作。
  • 充電指令在失敗或逾時後不會被自動重送(可先行驗證)。

對應 BE ticket

BE 契約見 #1400。本 ticket 第 1~4 項需等 BE 完成並提供 endpoint/payload 後才能實作;「可先行處理」段落不受此限。

備註記錄(1 則)

#1 - 鍾正剛(2026-07-31T16:02:59Z)

2026-08-01 已完成「可先行處理」項目:mutation 全域關閉自動重試

依 Ryan 指示先處理不依賴 BE(#1400)的 retry 修正。

  • Branch:feature/redmine-1401-branch-admin-connector-ws-status(自 main 1f4e841 開出)
  • Commit:f6dd8d0
  • 尚未建立 MR。
問題

react/branch/provider/providers.client.tsx 的 QueryClient 全域預設為 mutations: { retry: 1 }。React Query 只在 mutationFn reject 時重試,因此充電指令一旦被拒絕或逾時,就會自動再送一次。與 #1399 在 LIFF 修掉的是同一個成因。

盤點 branch admin 上全部繼承此設定的 mutation,沒有任何一支是 idempotent:

位置 操作 自動重試的後果
connector/hooks/connectorHooks.ts:112 /api/tx/charge/start 重複 RemoteStart 與稽核紀錄
connector/hooks/connectorHooks.ts:143 /api/tx/charge/stop 重複 RemoteStop
lib/query-api/hooks/crudHooksFactory.ts:150/194/238 resident/connector/cp/building 的 create/update/delete 重複建檔、重複刪除
bill/hooks/billHooks.ts:157 /api/bills/export 重複產出帳單檔
bill/hooks/billHooks.ts:177 /api/bills/status/update 重複狀態異動
invoice/hooks/invoiceHooks.ts:69 /api/invoice/status/update 重複發票狀態異動
settings/hooks/settingsHooks.ts:43 /api/settings/basic-charge 重複寫入基本費設定

最危險的是逾時情境:第一次請求可能已送達後端、只是回應遺失,自動重試就會送出第二筆真實的指令。

修正
  1. 全域 mutations.retry1 改為 0,重試一律由使用者明確再操作一次。
  2. useStartChargeuseStopCharge 另外明確宣告 retry: 0 並加註不可移除的理由,確保全域設定日後被改動時,這兩支安全關鍵的 API 仍不會被重送。

變更檔案僅兩支,共 +23/-2。

驗證狀態
  • npx tsc --noEmit:通過
  • yarn build:通過
  • yarn lint無法執行main 上 branch admin 尚未有 ESLint 設定檔,next lint 會進入互動式初始化提示。此為既有狀態,與本次改動無關,本次未修改任何 lint/build 設定。
  • 實機驗證:尚未執行。建議驗證方式為在充電樁離線狀態下按「開始充電」,確認 Network 只出現一次 /api/tx/charge/start
E2E Catalog Impact

應為 Update(後台充電操作的回歸案例需涵蓋「指令失敗或逾時後不得自動重送」)。

但本 branch 自 main 開出,main 上尚無 documents/EMS_E2E_TEST_CASE_CATALOG.mddocuments/E2E_TEST_CASE_INVENTORY.md,也沒有 .claude/skills/maintain-ems-e2e-catalog/(皆仍在未合併的 feature branch 上),因此本次無檔案可維護。待 catalog 併入 main 後補上此案例。

後續

#1401 其餘項目(改吃 WebSocket、移除兩條輪詢與 3 秒硬等、新增維運欄位顯示)需等 #1400 提供 endpoint 與 payload 後才能實作。


相關的議題清單 1 (1 進行中0 已結束)

被阻擋 EMS - Feature #1400: [Branch後台][BE] 充電樁狀態改由 WebSocket 推送,並補齊維運欄位與操作 APIIn Progress陳國瑋2026-07-31

動作
動作

匯出至 Atom PDF