Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
- [🧾 PR 佇列稽核:兩條 stack 做了同一件事](#-2026-07-29-pr-佇列稽核兩條-stack-做了同一件事本批清掉)
- [🚧 刻意沒做(寫明什麼條件成立才該做)](#-刻意沒做2026-07-22-盤點後決定不動)
- [🔮 懸而未決(需產品拍板)](#-懸而未決需產品拍板)
- [🧩 規劃:後端要加新的 ride 狀態碼時該怎麼做(跨三端)](#-規劃後端要加新的-ride-狀態碼時該怎麼做2026-08-01-盤點尚未動工)
- [➡️ 下次任務](#下次任務)

## 現況
Expand Down Expand Up @@ -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 第二十九輪之後的狀態(開工先看這段)**
Expand Down
Loading