--- schema: "hl-changelog/v2" ticket: "5850" title: "派车候选接口显式返回 selectable:统一选车弹窗空白复发的后端根治(四条组装路径全覆盖)" consumer: "admin" change_type: "修改接口" author: "wx(GIT)" backend_status: "deployed" gateway_status: "not_required" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "PR #5861 已合并 dev-v3 并部署测试服,网关 9443 实测:候选车辆 20 行 / 司机 20 行 selectable 全部非 null(19/20 车 true,1 行因阻断性冲突为 false)。前端 not_required——mmg 已于 313609db 把 normalizeCandidateVehicle 的缺省口径改成「undefined 视为可选、显式 false 才禁用」,与本次后端显式下发兼容;本条属于把前端的兜底假设升级成后端契约保证,前端零改动即受益。" updated_at: "2026-08-11" base: "dev-v3" --- # 车务:派车候选接口显式返回 `selectable`(#5850) > **服务**: hl-fleet-service > **端点**: `POST /admin/fleet/assignments/candidates` > **性质**: 新增响应字段(向后兼容,无破坏性变更) --- ## 一、为什么改 「统一选择车辆/司机」弹窗**反复出现整片空白**(后端返回 200、数据齐全,列表却渲染不出来)。 根因在前后端口径断层: - 前端统一选车入口(`scope=slot` → `availableOnly=true`)用 `isFullyAvailableCandidate()` 过滤候选,该函数**要求 `selectable === true`**; - 但后端从来**没有下发过 `selectable` 字段**(全仓零命中),前端 `normalize` 出来是 `undefined`; - `undefined === true` 为 false → **整列被过滤成空**。单日入口 `availableOnly=false` 不走该过滤,所以只在统一选车弹窗复发。 前端已在 `313609db` 做了兜底(缺省视为可选)。但「弹窗空白」这个故障此前已复发多次,只靠前端约定"没有字段就当可选"是脆的——本次由后端把这个字段变成**显式契约**,从源头消除断层。 --- ## 二、变更接口 | 方法 | 路径 | 变更 | |------|------|------| | `POST` | `/admin/fleet/assignments/candidates` | 响应新增 `selectable` 字段(车辆候选项 + 司机候选项,共 4 条组装路径) | ## 三、契约变更 `AssignmentCandidateRespVO` 的车辆候选项与司机候选项各**新增一个字段**: | 字段 | 类型 | 说明 | |------|------|------| | `selectable` | `Boolean` | 本次选车场景下该候选是否可被选中。**恒非 null** | ### 与已有 `available` 的区别(务必分清) | 字段 | 口径 | |------|------| | `available` | **纯资源维度**:该车/该司机在所选服务日期窗内是否空闲 | | `selectable` | 在 `available` 为真的基础上,**再排除阻断性冲突** | 后端判定式: ``` selectable = available && conflicts 中不存在阻断项 ``` 其中「阻断项」= `conflicts[].blocking` **不是显式 `false`** 的项一律按阻断处理(null/缺省从严)。 > ⚠️ 「已被本弹窗其他槽位选中」属于**前端选车上下文**,后端不感知,**不纳入** `selectable` 口径。前端如需这层排除,仍按自己的选中态叠加过滤。 ### 覆盖范围 四条候选组装路径**全部**下发该字段,不存在只有部分路径带字段的情况: 1. `vehicles.records[]` —— 车辆候选分页列表 2. `drivers.records[]` —— 司机候选分页列表 3. `selectedDriverResidentVehicles[]` —— 选定司机的常驻车辆 4. `selectedVehicleResidentDriver` —— 选定车辆的常驻司机 --- ## 四、前端影响 **无破坏性变更,前端可零改动。** 但建议按下面的口径收敛: - 渲染禁用态时**直接读 `selectable`**,不要再自己用 `available` + `conflicts` 二次推导——推导口径与后端不一致正是这次故障的来源。 - 保留「`undefined` 视为可选」的兜底(`313609db` 已做)作为防御,但正常情况下该字段恒有值。 - `selectable === false` 的行建议**置灰并展示 `availabilityReason`**,而不是从列表里过滤掉——车管需要知道"这辆车为什么不能选"。 --- ## 五、验证证据(测试环境实测,2026-08-11,网关 9443,车务管理员 token) ``` POST /admin/fleet/assignments/candidates ``` | 项 | 实测 | |----|------| | `vehicles.records[]` | 20 行,`selectable` **全部非 null**;19 行 `true`,1 行 `false`(存在阻断性冲突) | | `drivers.records[]` | 20 行,`selectable` 全部非 null | | 兼容性 | `available` / `availabilityReasonCode` / `availabilityReason` / `conflicts` 语义与取值**均未变化** | --- ## 六、关联 - 工单 #5850、PR #5861 - 前端条目:`11_frontend_派单Step2需求展示区与统一选车弹窗空白-前端缺陷-管理后台.md`(同一故障的前端侧修复 `313609db`)