From 564b3e6e9e36dff2b02a788a34930ba191269a1f Mon Sep 17 00:00:00 2001 From: awei Date: Tue, 28 Jul 2026 13:29:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20patchDriverStatus=20=E6=AD=A3?= =?UTF-8?q?=E8=A6=8F=E5=8C=96=E5=9B=9E=E5=82=B3=E5=80=BC=EF=BC=88=E5=9E=8B?= =?UTF-8?q?=E5=88=A5=E5=8E=9F=E6=9C=AC=E5=9C=A8=E8=AA=AA=E8=AC=8A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit admin REST 形狀對帳(2026-07-28,真後端逐端點抓回應)發現: PATCH /admin/drivers/:id/status 回的是**混合大小寫**—— ID/Name/Status 走 Go 欄位名,vehicle_type/plate_number/vehicle_review_status 走 json tag,而且**不含** rating_avg/rating_count。 前端卻宣告它回 `Driver`(全 PascalCase)且不正規化:車輛三欄與評價都會是 undefined。 目前呼叫端只 invalidateQueries、不看回傳值所以 runtime 沒出事—— 但型別在說謊,哪天有人拿它去 setQueryData,那一列的車種車牌就會憑空消失。 這正是今天修的那幾個 bug 的前身形狀(東西在、只是那條路徑沒人走過)。 修法:改回 Record 並走 normalizeDriver,讓宣告的型別成真。 測試強化:mock 換成從真後端抓下來的混合大小寫形狀(舊 mock 只有 PascalCase, 所以測不出來),斷言車輛三欄與 RatingAvg/RatingCount 補 0 而非 undefined。 反向驗證:改回不正規化 → 該案 FAIL。 驗收:npm test 134 passed、lint 乾淨、tsc --noEmit 通過。 Co-Authored-By: Claude Opus 5 --- src/api/admin.test.ts | 27 +++++++++++++++++++++++++-- src/api/admin.ts | 16 ++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/api/admin.test.ts b/src/api/admin.test.ts index 2257b09..3f26a83 100644 --- a/src/api/admin.test.ts +++ b/src/api/admin.test.ts @@ -293,13 +293,36 @@ describe('P2 admin write APIs', () => { vi.mocked(api.put).mockReset(); }); - it('patchDriverStatus', async () => { + // mock 用的是 2026-07-28 對帳從真後端抓下來的形狀:**混合大小寫**, + // 且不含 rating_avg/rating_count。舊版直接回 data.driver(沒正規化), + // 車輛三欄會是 undefined——當時的 mock 只有 PascalCase,所以測不出來。 + it('patchDriverStatus 回正規化後的 driver(後端是混合大小寫)', async () => { vi.mocked(api.patch).mockResolvedValue({ - data: { driver: { ID: 1, Name: 'A', Phone: '', LineUserID: 'U1', Status: 3 } }, + data: { + driver: { + ID: 1, + LineUserID: 'U1', + Name: 'A', + Phone: '', + Status: 3, + vehicle_type: 'sedan', + plate_number: 'LI-3748', + vehicle_review_status: 'approved', + vehicle_review_note: '', + CreatedAt: '2026-07-28T12:42:29+08:00', + UpdatedAt: '2026-07-28T12:42:35+08:00', + }, + }, }); const driver = await patchDriverStatus(1, false); expect(api.patch).toHaveBeenCalledWith('/admin/drivers/1/status', { enabled: false }); expect(driver.Status).toBe(3); + expect(driver.VehicleType).toBe('sedan'); + expect(driver.PlateNumber).toBe('LI-3748'); + expect(driver.VehicleReviewStatus).toBe('approved'); + // 後端這支不回評價 → 正規化補 0,而不是 undefined(表格排序會直接壞掉) + expect(driver.RatingAvg).toBe(0); + expect(driver.RatingCount).toBe(0); }); it('reviewDriverVehicle 核准', async () => { diff --git a/src/api/admin.ts b/src/api/admin.ts index 502f2e6..839db4b 100644 --- a/src/api/admin.ts +++ b/src/api/admin.ts @@ -503,9 +503,21 @@ export interface DispatchSettings { rate_limit_per_min: number; } +/** + * 啟停司機。回傳**正規化後**的 driver。 + * + * 後端這支回的是混合大小寫(`ID`/`Name`/`Status` 走 Go 欄位名, + * `vehicle_type`/`plate_number`/`vehicle_review_status` 走 json tag, + * 且**不含** `rating_avg`/`rating_count`)。直接當 `Driver` 用, + * 車輛三欄與評價會是 undefined——目前呼叫端只 `invalidateQueries`、不看回傳值所以沒出事, + * 但型別在說謊:哪天有人拿它去 `setQueryData`,那一列的車種車牌就會憑空消失。 + */ export async function patchDriverStatus(id: number, enabled: boolean): Promise { - const { data } = await api.patch<{ driver: Driver }>(`/admin/drivers/${id}/status`, { enabled }); - return data.driver; + const { data } = await api.patch<{ driver: Record }>( + `/admin/drivers/${id}/status`, + { enabled }, + ); + return normalizeDriver(data.driver); } // 車輛審核(O5):approve=false 時 note 必填(退回原因)。回傳正規化前的 raw driver。