Skip to content

fix(admin): 寫入結果不明時要重讀後端,不要說「請稍後再試」 - #29

Merged
thothawei merged 1 commit into
mainfrom
claude/write-timeout-reconcile
Jul 30, 2026
Merged

fix(admin): 寫入結果不明時要重讀後端,不要說「請稍後再試」#29
thothawei merged 1 commit into
mainfrom
claude/write-timeout-reconcile

Conversation

@thothawei

Copy link
Copy Markdown
Owner

這是 fleet-app TODO 上那條「admin 端完全沒有逾時對帳的概念」

先取證再修。證據是真瀏覽器 + 吃掉回應的代理(line-fleet-app/tool/lossy_proxy.py)。

盤點結果:九條寫入,真正會害人的是「強制取消」

src/api/admin.ts 有九支寫入端點。多數形狀上就是冪等的
(PUT 費率/派單參數、PATCH paidenabled 都是設成某個值,重送會收斂),
所以 App 那套「查一次狀態確認生效沒」在這裡是過度設計。
真正的問題只有一個,但九條全中:

onError 只跳一則錯誤訊息,從來不重新讀後端。
逾時之後畫面停在舊狀態,訊息還寫著「請求逾時,請稍後再試」——
把操作者推向唯一錯誤的下一步。

實跑重現(ride #30

  1. 代理 log:上游回 HTTP/1.1 200 OK,不交還 App後端真的取消了(DB status=9)。
  2. 15 秒後:toast「請求逾時,請稍後再試」、狀態欄仍是「前往接客」、對話框還開著
  3. 照著訊息再按一次 → HTTP 500 訂單狀態已變更,無法取消

乘客與司機都已經收到取消通知了,後台卻連看兩則錯誤,最後以為這張單沒取消掉。

修法

新增 src/utils/writeError.ts

  • isUncertainWrite(err):連線類(沒有 response)或 409 = 這次寫入結果不明。
  • handleWriteError(err, fallback, { notify, queryClient, invalidate })
    結果不明 → 重讀指定的 query + 不宣稱失敗的措辭
    明確的失敗(400/403/500)→ 維持原本行為,顯示原因、不動畫面。
    訊息與重新整理綁在同一支函式,否則遲早出現「訊息說已重新整理、其實沒有」的謊。
  • 九條寫入路徑全部改用它;確認對話框在送出後一律關閉(訂單強制取消、車輛核准/退回),
    留著只會誘導再按一次;「原因沒填」那種表單驗證仍然留著對話框。

後端同批:dispatch #69
ErrRideStartedErrRideStateChanged 從 500 改判 409。
沒有 409,前端根本分不出「伺服器壞了」與「這件事已經生效了」。

驗收

  • tsc -boxlintvite build 綠;Vitest 143 passed(134 + 新 9)。
  • 反向確認:拿掉 handleWriteError 的重讀 → 只有新加的 3 案 FAIL,其餘照過。
  • 真瀏覽器 E2E
    • 逾時ride #33):代理吃掉回應 → 15 秒後對話框自動關閉、
      toast「沒有收到後端回應,這次操作可能已經生效——畫面已重新整理…」、
      狀態當場變「已取消」、完成時間出現、按鈕消失;代理 log 有逾時後那次 GET /api/admin/rides/33
    • 409ride #34):先用 API 在背後取消、畫面還停在「前往接客」→ 按強制取消 →
      POST … 409 Conflict → 同一秒 GET /api/admin/rides/34 → 畫面翻成「已取消」。
      (這條的 toast 文案由單元測試斷言;瀏覽器證的是 409→重讀→畫面正確這條鏈。)

沒做、以及為什麼

  • 沒把 App 那套「查一次狀態確認生效」搬過來:admin 的寫入多為冪等設定,
    重讀畫面就足以讓操作者判斷,多一層判準只是多一組會過時的規則。
  • GenerateMembershipInvoices 後端本來就冪等(同月重跑回 created: 0),維持現狀。

🤖 Generated with Claude Code

盤點九條寫入路徑(fleet-app TODO 早就記著「admin 端完全沒有逾時對帳的
概念」)。多數端點形狀上就冪等,App 那套「查一次狀態」是過度設計;
真正的問題九條全中:onError 只跳錯誤訊息、從來不重新讀後端。

實跑重現(blackhole 掉強制取消的回應,後端其實已取消、DB status=9):
畫面停在「前往接客」+「請求逾時,請稍後再試」+確認對話框還開著 →
操作者照著再按一次 → 500「訂單狀態已變更,無法取消」。乘客與司機
都已經收到取消通知了,後台卻以為這張單沒取消掉。

新增 utils/writeError.ts:
- isUncertainWrite:連線類(無 response)或 409 = 這次寫入結果不明。
- handleWriteError:結果不明 → 重讀指定 query + 不宣稱失敗的措辭;
  明確失敗(400/403/500)維持原行為。顯示訊息與重新整理綁在同一支,
  否則遲早出現「訊息說已重新整理、其實沒有」的謊。
九條寫入全部改用它;確認對話框在送出後一律關閉(表單驗證那種仍留著)。

後端同批:dispatch #69 把該回 409 的狀態衝突從 500 改掉——沒有 409,
前端分不出「伺服器壞了」與「已經生效了」。

驗收:tsc/oxlint/build 綠、Vitest 143 passed(134+新 9);
反向確認拿掉重讀只有新加 3 案 FAIL;真瀏覽器 E2E 逾時與 409 兩條都跑過。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thothawei
thothawei merged commit c9f45f9 into main Jul 30, 2026
1 check passed
@thothawei
thothawei deleted the claude/write-timeout-reconcile branch July 30, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant