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 第二十九輪之後的狀態(開工先看這段)**