Skip to content

feat(app): 預約司機+常用地點——順手修掉登出後留著上一位乘客的住家地址 - #95

Merged
thothawei merged 11 commits into
mainfrom
claude/todo-driver-booking-feature-89ddd8
Jul 30, 2026
Merged

feat(app): 預約司機+常用地點——順手修掉登出後留著上一位乘客的住家地址#95
thothawei merged 11 commits into
mainfrom
claude/todo-driver-booking-feature-89ddd8

Conversation

@thothawei

@thothawei thothawei commented Jul 30, 2026

Copy link
Copy Markdown
Owner

乘客可以預約未來的用車,以及把住家/公司等常去的地點存起來,叫車與預約時一鍵帶入。
後端對應 dispatch #70

常用地點:home/work 是插槽,不是清單項目

每人各限一筆、後端是 upsert 語意——UI 上那顆按鈕叫「設定住家」,乘客預期住家換成新地址,
而不是被要求先去刪舊的。custom 才是可以有很多筆的自訂地點。

判斷「這是不是住家」一律看 kind,不比對 label:label 是使用者可改的顯示名稱,
他把住家改叫「家」的那一刻,比對文字的寫法就無聲失效了。

預約

  • 建立時擋「至少 20 分鐘後」——門檻取自後端min_lead_minutes),不是 App 寫死的。
  • 取消撞 409(排程器搶先轉單)不宣稱失敗:後端把該筆現況一起回來,畫面直接切成
    「已為你派車」並指出要去行程頁取消那張訂單。只丟一句「請稍後再試」的話,
    乘客會一直按取消,而車照樣開過來(同一個病在第二十輪的 admin 端抓過一次)。
  • 取消逾時會重讀對帳:後端可能其實已經取消了,只是回應在路上掉了。
  • 清單分三區:即將到來/車已在路上/過往——已轉單的不算過往,車正在來的路上。

修掉的問題(7 個)

單元測試層抓到的

  1. 登出沒清掉常用地點與預約(隱私)——_clearSession 早就因為「上一個帳號的個人資料」
    在清行程歷史了,新加的兩份卻漏了。住家與公司是實體位置、預約是「這個人什麼時候不在家」。
  2. 最小預約時間兩處各寫一份——後端調高門檻後,App 仍會讓乘客選一個註定被 400 拒絕的時間。
  3. 清單一筆解析壞掉會炸掉整頁——scheduled_at 是 non-nullable 用 DateTime.parse
    而 controller 只接 ApiExceptionFormatException 會直接逸出。改成逐筆跳過。
  4. 預約一轉單就掉進「過往預約」,但車其實正在來。

模擬器實跑才看得到的(flutter test 全綠也看不到)

  1. 預約入口加在沒被使用的畫面上——app.dartCustomerMapHomeScreen(地圖版),
    我加在 CustomerHomeScreen(卡片版)。根因:卡片版 production 完全不用,
    只有 widget 測試在撐著它,所以加錯地方沒有任何訊號。已標註該類別,
    並補 test/customer_home_entries_test.dart 斷言地圖版上有入口。
  2. 登入後不載入——init()(冷啟動)有載,_authenticate() 沒有,
    而它的註解本來就寫著「比照 init()」。單元測試一律 setSessionForTest 再手動 load,
    永遠走不到登入那條路徑
  3. 車種顯示成後端 code pet——VehicleType 的註解本來就寫著「後端只送 code,
    顯示名由前端對應」。改用 fromCode;未知 code 整行不顯示。

測試 Fake 補齊(24 個檔)

init() 多了兩支載入,所有 test Fake 都得覆寫——沒覆寫會走真實 Dio 打網路,
變成不確定的非同步延遲,把不相干的測試拖成 flaky
(實測讓 customer_location_exits 的預估 notifyListeners 落在 dispose 之後)。

驗收

  • flutter analyze 無 issue、flutter test 408 passed(新增 35 支)。CI 兩項全過。
  • 反向驗證五次各自轉紅:409 狀態合併、sameSlot 判斷、登出清理、地圖版入口、
    以及後端那三次。其中 sameSlot第一版測試沒守住(兩條都沒走到那個分支),
    補了「別台裝置改過住家、後端回不同 id」才真正覆蓋到。
  • 模擬器實跑(m6_pixel +本機後端):四顆常用地點快捷(圖示依 kind、排序住家→公司→其他)、
    一鍵帶入起訖點、首頁預約卡、預約頁三區(3/2/2)、失敗那筆紅字顯示後端的 last_error
    「前 15 分鐘找車」取自後端。
    跨端閉環:「車已在路上」的訂單 #39 正是在真環境實跑轉單產生的那一筆。

🤖 Generated with Claude Code

thothawei and others added 9 commits July 31, 2026 01:14
乘客可以預約未來的用車,以及把住家/公司等常去的地點存起來,叫車與預約時一鍵帶入。
後端對應 dispatch 的 scheduled_rides 與 customer_saved_places 兩張新表。

## 常用地點

home/work 是「插槽」不是清單項目:每人各限一筆,後端是 upsert 語意——
UI 上那顆按鈕叫「設定住家」,乘客預期住家換成新地址,而不是被要求先去刪舊的。
custom 才是可以有很多筆的自訂地點。

判斷「這是不是住家」一律看 kind,不比對 label——label 是使用者可改的顯示名稱,
他把住家改叫「家」的那一刻,比對文字的寫法就無聲失效了。

## 預約

建立時擋「至少 20 分鐘後」,比後端的提前發動量(15 分)更嚴:
比提前量還近的「預約」,使用者要的其實是現在叫車。

取消撞 409(排程器搶先轉單)**不宣稱失敗**:後端會把該筆現況一起回來,
畫面直接切成「已為你派車」並指出要去行程頁取消那張訂單。
只丟一句「請稍後再試」的話,乘客會一直按取消,而車照樣開過來
(同一個病在第二十輪的 admin 端抓過一次)。

## 修掉的 bug:登出沒清掉常用地點與預約(隱私)

_clearSession 早就因為「上一個帳號的個人資料」在清行程歷史了,新加的兩份狀態卻漏了。
住家與公司是實體位置、預約是「這個人什麼時候不在家」,比行程歷史更敏感——
下一位在這台裝置登入的人一打開叫車頁,快捷列上就是上一位乘客的住家地址。
同族的坑見 pitfall-device-token-multi-owner。

## 測試 Fake 補齊

init() 多了兩支載入,24 個 test Fake 都得覆寫它們——沒覆寫會走真實 Dio 打網路,
變成不確定的非同步延遲,把不相干的測試拖成 flaky
(實測讓 customer_location_exits 的預估 notifyListeners 落在 dispose 之後)。

## 驗收

flutter analyze 無 issue、flutter test 398 passed(新增 21 支)。
反向驗證三次各自轉紅:409 狀態合併、sameSlot 判斷、登出清理。
其中 sameSlot 的第一版測試沒守住(兩條都沒走到那個分支),
補了「別台裝置改過住家、後端回不同 id」才真正覆蓋到。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
塞一筆 5 分鐘後到點的預約,在跑著的後端上看排程器走完整條:
前兩輪被「已有進行中的訂單」擋下 → 維持 pending 等下一輪;
把擋路的示範訂單結掉後第三輪轉單成功,ride_id=39、last_error 清空。
交叉驗證那張訂單的起訖點正是預約填的內容、狀態已進派單池。

這一跑證明了三件本來只有測試層證據的事:到期條件真的撈得到、
暫時性失敗真的是「等下一輪」而不是判死、成功後 last_error 真的會被清掉。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lead_minutes 本來就從後端拿(「我們會提前這麼久幫你找車」),
但「最早可預約 20 分鐘後」卻是 App 寫死的——同一個數字兩處定義。
後端把門檻調高之後,App 仍會讓乘客選一個註定被 400 拒絕的時間,
而他要填完整張表才會知道。

後端 List/Create 的回應加 min_lead_minutes,App 端改用它。
問不到後端時退回 App 自己的保底值而不是 0——0 會讓時間選擇器完全不擋,
那比寫死更糟。

flutter analyze 無 issue、flutter test 400 passed(新增 2 支:
「後端給多少就用多少」與「後端沒給時退回保底值、不是 0」)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
已轉單的預約跟已取消/未成立混在同一區,乘客會以為那筆已經結束了——
實際上排程器剛為他派了車,司機可能正在開過來。

改成三區:即將到來(pending)/車已在路上(dispatched)/過往(cancelled、failed),
進行中的排在已結束的上面。區塊標題刻意不叫「已為你派車」——那句話是卡片上的
狀態標籤,同名會讓畫面上同一句話重複出現(widget 測試也是這樣抓到的)。

flutter analyze 無 issue、flutter test 402 passed
(新增兩支 widget 測試:分區歸屬+區塊順序、以及全空時給空狀態而不是三個空標題)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
功能進度那節加一段(做了什麼、後端在哪、關鍵行為為什麼那樣設計),
規劃中那節列出三個刻意沒做的並指向 TODO 裡寫明的觸發條件。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
scheduled_at 是 non-nullable,所以 fromJson 用的是會丟例外的 DateTime.parse
(其他 model 的日期都 nullable、用 tryParse)。而 controller 只接 ApiException——
一筆格式異常的 FormatException 會直接逸出,整個預約頁掛掉。

listFrom 改成逐筆 try,壞的跳過。後端目前不可能送出壞格式(欄位 NOT NULL、
Go 一律序列化成 RFC3339),所以這是**防禦性**的,不是已確認會發生的 bug——
但少一筆總比整頁看不到好,而且這樣才跟其他 model 的容錯風格一致。

flutter test 404 passed(新增 2 支)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
轉單後那就是一張普通訂單,司機看不到約定上車時間。提前量 15 分鐘下多半剛好,
但司機若 5 分鐘就到,乘客可能還沒下樓,而司機不知道該等。

屬功能擴充不是 bug,寫明條件放進待辦而不是靜靜夾帶進這批。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
**都是「測試全綠但畫面上不成立」那一類**,只有把 App 裝到裝置上才看得見。

## 1. 預約入口加在沒被使用的畫面上(功能無法到達)

app.dart 用的是 CustomerMapHomeScreen(地圖版),我把入口與預約卡加在
CustomerHomeScreen(卡片版)——實跑時畫面上根本沒有那顆按鈕。
已在地圖版補上 FAB 入口與 sheet 內的「即將到來」列。

## 2. 登入後不載入常用地點與預約

init()(冷啟動還原 session)有載,_authenticate() 沒有——它的註解還寫著
「比照 init()」,卻只帶了 active 與協尋。症狀是剛登入完快捷列是空的、
首頁沒有自己的預約,要等下次冷啟動才出現。
單元測試都用 setSessionForTest 再手動 load,永遠走不到登入這條路徑。

## 3. 車種顯示後端 code(「指定車種:pet」)

VehicleType 的註解本來就寫著「後端一律只送 code,顯示名由前端對應」。
改用 fromCode 取中文名;未知 code 整行不顯示——印原始 code 對乘客沒意義,
印「—」也只是把空洞畫出來。

## 驗收

flutter analyze 無 issue、flutter test 407 passed(新增 3 支)。
裝置上確認:住家/公司/自訂四顆快捷(圖示依 kind、排序住家→公司→其他)、
首頁預約卡、預約頁三區(即將到來 3/車已在路上 2/過往 2)、
失敗那筆紅字顯示後端的 last_error、「前 15 分鐘找車」取自後端。
其中「車已在路上」的訂單 #39 就是稍早在真環境實跑轉單產生的那筆,跨端閉環。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
三個都是「邏輯對、畫面上不成立」——測試全綠也看不到。
最值得記的是第 2 個:新增 init() 載入時要同時問「登入那條路徑呢」,
_authenticate 的註解本來就寫著「比照 init()」卻漏了。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thothawei and others added 2 commits July 31, 2026 01:50
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
查出那個坑的根因:CustomerHomeScreen(卡片版)production 完全不用,
只有 widget 測試在用它——它是死碼,但測試讓它看起來還活著,
所以把入口加在它身上不會有任何訊號。

- 在該類別頂端標註「production 用的是 CustomerMapHomeScreen」,
  並寫明「加任何入口之前先確認要加在哪一個」。
- 新增 test/customer_home_entries_test.dart:斷言**地圖版**首頁上有預約入口。
  反向驗證:把那顆 FAB 的 tooltip 改掉,該支轉紅。

沒有整批刪掉卡片版——那是這批範圍外的重構,而那些測試現在確實在守共用元件。

flutter analyze 無 issue、flutter test 408 passed。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thothawei
thothawei merged commit 1ef12c5 into main Jul 30, 2026
2 checks passed
@thothawei
thothawei deleted the claude/todo-driver-booking-feature-89ddd8 branch July 30, 2026 23:40
thothawei added a commit that referenced this pull request Aug 1, 2026
PR #97 與 main 分岔:#94(第二十三輪)與 #95(預約司機+常用地點)先合併,
docs/TODO.md 的目錄、測試數字、章節順序與 customer_controller 的 _clearSession
三處衝突。解法都是兩邊並存,不是二選一:

- `_clearSession()`:分頁視窗(_historyWindow/_historyMoreError/_historyHasMore)
  與常用地點/預約的清除**都要留**——兩者都是「換人登入不能留上一位的資料」。
- TODO 目錄補上第二十四輪那行(原本兩邊各刪掉對方的)。
- 「現況」的測試數字先留 main 的 414,收尾重數後再更正(寫作規則 4)。
- 第二十四輪專章接在預約司機專章之後,「下次任務」以第二十四輪那段為最新在前。

驗收:flutter analyze 無 issue、flutter test **423 passed**(main 414 + 本 PR 9)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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