6.7 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | frontend | 核心订单首次排车:统一选择车辆/司机弹窗候选全空,报「canonicalSnapshot 缺少完整的代际、稳定派车组或服务日期」 | admin | wx(GIT) | 前端缺陷 | not_required | not_required | pending | v2.1 | 2026-09-24 同事报「核心订单的车务排车不对」,定位为前端缺陷:尚未产生任何派车行的订单(看板「待派车」)第一次打开「统一选择车辆/司机」时,GET 候选接口返回的 canonicalSnapshot.retainedGroupIds 是空数组(#7067 去槽位化后首次排车本就没有派车组),而前端 normalizeCanonicalAssignmentSnapshot 把空数组判为快照非法直接 throw,整条候选响应作废——车辆候选、司机候选都拿不到,弹窗显示红色报错与「没有符合筛选条件的司机」。后端接口本身正常(车辆/司机候选都返回了,用同一份响应喂 dailyPlan 提交后四步链可跑通),需要前端放宽校验。 | 2026-09-24 | dev-v3 |
车务派单看板:核心订单首次排车「统一选择车辆/司机」候选全空(canonicalSnapshot 空派车组被前端判非法)
页面: 管理后台 → 车务 → 派单看板 →「统一选择车辆/司机」弹窗(四步向导第②步) 文件:
src/views/fleet/board/utils/canonical-assignment-snapshot.js(normalizeCanonicalAssignmentSnapshot,hl-uiv2.1;测试服部署产物assets/AssignModal-*.js内函数yt) 日期: 2026-09-24 影响范围: 前端。所有「当前用车需求下还没有任何派车行」的订单——即看板「待派车」的全部订单(本次实测的 26-0013 就是这种),团期子订单同一代码路径同样命中
一、现象
同事报「核心订单的车务排车不对」,截图里弹窗顶部是红字:
canonicalSnapshot 缺少完整的代际、稳定派车组或服务日期
且弹窗里 车辆候选和司机候选都是空的(右侧显示「没有符合筛选条件的司机」),车务因此完全无法为该订单选车排车。
实例:订单 HL20260924152729671(团号 26-0013,客户「谢谢」,2026-09-30 ~ 2026-10-03,看板状态「待派车」)。
二、复现与证据(测试服实测,只读)
- 车务角色登录 →
GET /admin/fleet/board/orders/2103023501973848066:requirementId=2103024417066123265、vehicleSlots=[]、activeAssignments=[](该单尚未派车); POST /admin/fleet/assignments/candidates(前端同形请求体)→ HTTP 200 / code 200,data.canonicalSnapshot:
{
"planGeneration": "361414334036971520",
"snapshotVersion": 1,
"retainedGroupIds": [],
"editableServiceDates": ["2026-09-30", "2026-10-01", "2026-10-02", "2026-10-03"],
"cells": [],
"selectedVehicle": null,
"selectedDriver": null
}
同一份响应里 data.vehicles.records 与 data.drivers.records 都是有数据的(车辆候选带车牌/车型/协议价,司机候选带姓名/脱敏手机号)——即后端没有出错,是前端在校验阶段把整份响应丢掉了。
- 结论:
normalizeCanonicalAssignmentSnapshot的第一个校验分支要求retainedGroupIds非空,空数组直接 throw,异常冒泡到候选请求的 catch,于是canonicalSnapshot = null之后的赋值全部没执行、apiVehicles/apiDrivers保持空 → 弹窗只剩报错 + 空候选。
证据文件:
D:/work2/_task-evidence/fleet-core-dispatch/evidence/02-candidates-travel-26-0013.json(26-0013 的真实响应) 对照:同一个接口在订单已经有派车行之后返回retainedGroupIds=["361428998049370112"]+ 3 个 cells,前端校验通过、弹窗正常。
三、根因
后端 retainedGroupIds 的语义是 RetainedGroupSet:当前需求下已存在的稳定派车组 ID:
- #7067「派单去槽位化」之前,订单进入排车时后端会自动建
unassigned占位槽位行,所以快照里至少有一个槽位,前端「非空」这条前置条件一直成立; - #7067 之后不再建占位行,派车行只由第②步「逐日方案提交」产生(
Step2CanonicalSnapshotService类注释:「首次进入且无存活行时 retained 为空集」)。于是第一次排车时retainedGroupIds=[]、cells=[]是完全合法的状态,表示「这一版方案还没有任何派车组」,栅格应为空、由车务现场选车生成。
前端的校验仍是槽位时代写的「必须非空」,故把合法空态误判为快照非法。
四、修法(前端)
normalizeCanonicalAssignmentSnapshot 需要接受「空派车组」这一合法状态:
planGeneration、snapshotVersion缺失/为空 → 仍按现在处理(快照不可用);retainedGroupIds为空数组且cells为空数组 → 返回合法快照{planGeneration, snapshotVersion, retainedGroupIds: [], editableServiceDates, cells: [], selectedVehicle, selectedDriver},不要 throw;- 只有「
retainedGroupIds非空但cells.length != groups × dates」「cells 越界/重复」这类不一致才继续判非法; editableServiceDates为空的判断保持不变(没有服务日确实无法排车)。
下游要注意的只有一处:空 retainedGroupIds 时 reconcileCanonicalAssignmentDraft 会算出「0 张卡片」,这正是期望结果——车务在弹窗里选车/司机 → 应用后走本地新草稿(isNewDraft)→ 第②步提交照旧走稀疏 dailyPlan(提交体只带 serviceDate+vehicleId+driverId,与派车组无关)。不要把空数组退化成「快照不可用 → canonicalSnapshot=null 的旧模式」,去槽位化后旧模式依赖的槽位数据已不再下发,会得到另一个空栅格。
五、请前端确认/回归的点
- 打开任意「待派车」订单(如测试服 26-0013)→「统一选择车辆/司机」:不再出现红字报错,车辆/司机候选正常列出;
- 在该弹窗选车+选司机 → 应用 → 栅格出现一张待提交卡片 → 提交方案成功(后端
POST /admin/fleet/assignments/batch返回 200); - 已派车订单再打开同一弹窗:行为与现在一致(栅格回显已有派车组的日期格);
- 建议补一条单测:
canonicalSnapshot.retainedGroupIds=[]+cells=[]→ 归一化通过且retainedGroupIds为[](现有用例全是非空快照,所以这条错法在测试里看不见)。
六、后端改动
无(backend_status: not_required)。后端候选接口返回的数据本身正确,本次只需前端放宽校验。