From ef4e3f75a119714ce8d685e06460e9c5a583b00e Mon Sep 17 00:00:00 2001 From: awei Date: Sat, 1 Aug 2026 20:45:40 +0800 Subject: [PATCH] =?UTF-8?q?docs(app):=20=E8=A6=8F=E5=8A=83=E3=80=8C?= =?UTF-8?q?=E5=BE=8C=E7=AB=AF=E8=A6=81=E5=8A=A0=E6=96=B0=E7=9A=84=20ride?= =?UTF-8?q?=20=E7=8B=80=E6=85=8B=E7=A2=BC=E3=80=8D=E8=A9=B2=E6=80=8E?= =?UTF-8?q?=E9=BA=BC=E5=81=9A=EF=BC=88=E8=B7=A8=E4=B8=89=E7=AB=AF=EF=BC=8C?= =?UTF-8?q?=E5=B0=9A=E6=9C=AA=E5=8B=95=E5=B7=A5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第二十七輪把 App 補成「不認得的狀態碼不會讓訂單從畫面上消失」,但那只是不會壞。 真的要新增一個狀態碼是跨三端+有上線順序的事,先把清單與順序寫死, 等真的有產品理由時照著做,不要臨時想。 三件事寫進規劃: 1. 契約先定死:只增不改不重編號;號段有語意(admin 的 isRideCancellable 是 `status >= 0 && <= 2` 的**範圍判斷**,所以中途狀態新增時要把範圍判斷全改白名單); 後端是唯一真相來源。 2. 逐項清單**已逐條實查過位置**——後端最容易漏的是 repository 的三組白名單 (decides 誰查得到這張單);App 最關鍵的是 isActive/isTerminal 兩個白名單 與司機端 phase 的二分法推導;admin 兩支現在就存在的硬編碼一併記下。 3. 上線順序(比清單重要):先 App、再 admin、後端最後才開始「送出」新碼。 中間期舊版 App 靠第二十七輪的 UnknownPhaseContent 撐著——那一輪「前瞻性修補」 的價值就在這裡兌現。 順帶記下兩個與本題無關、但現在就存在的 admin 技術債: isRideCancellable 的範圍判斷、DashboardPage 硬編 status === 4/=== 9 算今日 完成/取消數(新增任何終態都會讓儀表板安靜地少算)。 驗收:docs-only;flutter analyze 無 issue、flutter test 448 passed(未受影響)。 Co-Authored-By: Claude Opus 5 --- docs/TODO.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/docs/TODO.md b/docs/TODO.md index 32d93eb..2ccb360 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -88,6 +88,7 @@ - [🧾 PR 佇列稽核:兩條 stack 做了同一件事](#-2026-07-29-pr-佇列稽核兩條-stack-做了同一件事本批清掉) - [🚧 刻意沒做(寫明什麼條件成立才該做)](#-刻意沒做2026-07-22-盤點後決定不動) - [🔮 懸而未決(需產品拍板)](#-懸而未決需產品拍板) +- [🧩 規劃:後端要加新的 ride 狀態碼時該怎麼做(跨三端)](#-規劃後端要加新的-ride-狀態碼時該怎麼做2026-08-01-盤點尚未動工) - [➡️ 下次任務](#下次任務) ## 現況 @@ -2731,6 +2732,90 @@ production 一定會先連上一次。已補上 `ws.setConnected(true)` 當作 --- +## 🧩 規劃:後端要加新的 ride 狀態碼時該怎麼做(2026-08-01 盤點,**尚未動工**) + +> 第二十七輪把 App 端補成「不認得的狀態碼不會讓訂單從畫面上消失」, +> 但那只是**不會壞**。真的要新增一個狀態碼是**跨三端+有上線順序**的事, +> 這一段先把清單與順序寫死,等真的有產品理由時照著做,不要臨時想。 +> +> **什麼時候才該做**:目前 `0/1/2/3/4/9` 六碼夠用,**沒有產品理由就不要加**。 +> 已知會逼出新碼的情境有兩個: +> ① **「司機已抵達上車點」要進 DB**(現在只有 WS `driver.arrived` 事件, +> 後端不存 flag,所以重開 App 就看不出司機到了沒); +> ② **付款進場(Phase C)**——「已完成」與「已付款」會需要分開。 + +### 一、契約先定死(這三條決定後面所有工作量) + +1. **只增不改、不重編號**。`4=completed`/`9=cancelled` 的語意永遠不動—— + 已經有 `ride_events` 的歷史紀錄、admin 的報表、三端的常數都綁著它。 +2. **新碼放哪個號段有意義**:`5–8` 是「終態附近」,`0–3` 之間插不進去 + (admin 的 `isRideCancellable` 是 `status >= 0 && status <= 2` 的**範圍判斷**, + 號段順序有語意,見下方 C-2)。**中途狀態要新增,號碼要大於 3 但語意上是中途**—— + 這會讓所有「用大小比較判斷階段」的程式碼失效,所以**同一批必須把範圍判斷全改成白名單**。 +3. **後端是唯一的真相來源**:三端都不可以自行推導狀態語意 + (既有規矩,見 O5 的 `can_accept`、O3 的 `has_vehicle`)。 + +### 二、逐項清單(**已逐條實查過位置**,2026-08-01) + +**A. dispatch(後端)** + +- [ ] `internal/constants/ride.go` 加常數+註解寫明語意。 +- [ ] **狀態轉移守衛**逐條決定新碼要不要放行: + `internal/service/dispatch.go:118`/`348`/`434`/`440`/`499`/`553`/`630`、 + `internal/service/ride_stops.go:128`。 +- [ ] **repository 的三組白名單**(這是最容易漏的一組,因為它決定「誰查得到這張單」): + `repository.go:279` `FindActiveByDriver` = `{accepted, pickedUp}`、 + `:295-296` 乘客的進行中訂單 = `{requested, assigned, accepted, pickedUp}`、 + `:370`/`:384`/`:415` 三支條件式 UPDATE 的 `WHERE status = ?`。 +- [ ] `ride_events` 的 from/to 稽核:新碼要能被記錄與顯示。 +- [ ] **WS 事件**:新狀態要不要有自己的事件型別?沒有的話 App 只能靠輪詢/對帳發現。 + +**B. fleet-app(本 repo)** + +- [ ] `RideStatus` 常數+**`isActive`/`isTerminal` 兩個白名單**(`models.dart:331/337`)—— + **這兩個決定「訂單算不算還在跑」**,漏改會讓行程卡永遠不消失或提早消失。 +- [ ] `rideStatusLabel` 的 `switch`(`models.dart:342`)加中文標籤。 +- [ ] **司機端 `phase` 推導**(`models.dart:807`): + `status == pickedUp ? onTrip : enRouteToPickup` 是**二分法**, + 新增中途狀態必須改成明確對應。 +- [ ] 乘客端 `_sheetContent` 的 `switch`(`customer_map_home_screen.dart`)與 + 卡片版 `_phaseWidget`(`customer_home_screen.dart`)各加一個 case。 + **不改也不會壞**(第二十七輪的 `UnknownPhaseContent` 接住了),但會停在泛用文案。 +- [ ] `ride_status_colors.dart` 的顏色對應。 + +**C. line-fleet-admin** + +- [ ] `src/constants.ts` 的 `RIDE_STATUS` 加一列。**這支有 `?? String(status)` 退路, + 不改只會顯示數字,不會壞**。 +- [ ] ⚠️ **`isRideCancellable(status)` 是 `status >= 0 && status <= 2` 的範圍判斷** + (`src/constants.ts:40`)——新碼一律落在「不可取消」,若新狀態其實該可取消, + 這裡會安靜地擋掉營運。**建議同批改成白名單。** +- [ ] ⚠️ **`DashboardPage.tsx:44-45` 硬編 `status === 4`/`=== 9`** 算今日完成/取消數—— + **新增任何終態都會讓儀表板安靜地少算**。這兩條是本次盤點**現在就存在**的技術債, + 與加不加新碼無關,但加新碼會讓它們變成錯誤數字。 + +### 三、上線順序(**這段比清單重要**) + +**外面有舊版 App**,所以順序不能顛倒: + +1. **先出 App**(三端裡最慢的一端,使用者要更新):加常數、白名單、標籤、phase 對應。 +2. **再出 admin**(隨時可部署)。 +3. **最後才讓後端開始「送出」新碼**——後端可以先合併程式碼, + 但**真正開始寫入新狀態的那一刻**要等 App 舊版佔比降到可接受。 +4. 中間這段期間,舊版 App 靠第二十七輪的 `UnknownPhaseContent` 撐著: + **看得到「行程進行中・狀態 N・請更新 App」,不會把訂單藏起來,也還能取消。** + ——這就是那一輪「前瞻性修補」的價值兌現點。 + +### 四、驗收條件(照本 repo 的規矩) + +- 三端各自 build/lint/test 綠。 +- **反向驗證**:把新碼從 `isActive`/`isTerminal` 拿掉,要有測試變紅。 +- **App 要有一案專門守「舊版行為」**:未知碼仍走 `UnknownPhaseContent` + (已存在,見 `test/unknown_ride_status_test.dart`)。 +- 模擬器實跑:讓後端真的把一張單推進新狀態,看三端畫面。 + +--- + ## 下次任務 > **🎯 2026-08-01 第二十九輪之後的狀態(開工先看這段)**