三份都是既有条目(#8576/#8601 由 da562f3 批次产出,#8577 单独产出),本次逐条对源码与
测试服实测记录核对后订正,不新增条目。
#8576
- frontend_status 由 not_required 改回 pending(ca26be4 批量置位)。依据:hl-ui
origin/v2.1 确有 src/api/fleet/group-dispatch.js 消费 reconfigure / confirm 两个端点,
而全仓 specWarnings 命中数为 0 —— 新字段目前无人渲染,车辆规格提醒对车务不可见。
改判原因已按 FRONTEND_CONSUMPTION_STATUS_GUIDE 要求写进 status_note。
- 两处错误响应示例的 message 是编造的,换成 GroupDispatchAdminErrorCode 的真实模板。
- planVersion 出参类型 Integer/Long 统一为 Long(两张表)。
#8601
- 删掉四处「589535 适用于本端点」的错误断言。实证:RequirementService.java:4749 对
resourceType=VEHICLE 硬编码 hasActiveAssignments=false,该码在车需求打回上结构性不可达。
- 编造的订单 ID 2099459272533323777 换成实测的 2105173274755534850(dispatch)与
2105173313083080706(reject)。
- 错误码集合订正为 809000 / 809007(仅 dispatch)/ 582031 / 582083。
#8577
- 订正一处「POST requirement/confirm 零影响」的错误断言:doConfirm 与 confirm-check 共用
已收窄的 classifyVehicleSubmission,该端点的 809122 触发条件同步收窄。
- 809121 / 809123 的错误响应示例换成测试服实测原文。
- frontend_status 保留 not_required,但把判定依据写进 status_note:三个码一律走拦截器透
message、生产代码无一处按报文匹配、前端也无纯接送机户的规避需要撤除;同时列出五处现已
陈旧的前端注释与一处 mock 报文,供 mmg 顺手清理。
门禁:validate-changelog-frontmatter.mjs --files 三个文件一次通过(PASS: 3 files)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
35 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 | 8576 | 团期配车提交 / 确认响应新增 specWarnings 车辆规格提醒清单(非错误) | admin | wx(GIT) | 修改接口 | deployed | verified | pending | PR #8602 已 squash 合并 dev-v3(cfefe04a83),hl-fleet-service dev-v3 分支已滚测试服。纯新增字段,两个端点的既有字段、错误码、HTTP 形态全部未变。【2026-09-30 由 not_required 改回 pending,原因】前端确实消费这两个端点:hl-ui origin/v2.1 有 src/api/fleet/group-dispatch.js(reconfigure / confirm 各一个调用点),而全仓 specWarnings 命中数为 0 —— 即新字段目前无人渲染,提醒对车务不可见,需要前端补渲染后本条才算落地。 | 2026-09-30 | dev-v3 |
hl-fleet-service: 团期配车提交 / 确认响应新增 specWarnings 车辆规格提醒清单
存放目录:
changelogs-v2/2026-09/服务: hl-fleet-service (端口 8089) PR: #8602 Issue: #8576 日期: 2026-09-30 影响范围: 团期配车写口两个端点POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure与POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm的响应体各新增一个specWarnings数组
⚠️ 关键变化
- 两个端点的响应体各新增一个字段
specWarnings(List<GroupDispatchSpecWarningRespVO>)。没有删字段、没有改名、没有改类型,既有字段与错误码一个都没动。 - 🔴
specWarnings不是错误:它出现在 HTTP 200 + 业务成功的响应里。提交照常成功、确认照常成功、coverage.satisfied照常按「排没排满」给结论。它回答的是另一个问题——排上的那辆车,是不是这个分组当初要的那一类、那么多座。前端不要把它当失败处理,也不要因为它非空就回滚本地状态。 - 之前团期配车这条路完全不读车辆实体的车型与座位:声明 16 座大巴、实际派进 5 座 SUV,一路能确认到终态且零信号。本次补的就是这个信号。
- 提醒分两类,字段分两组、互不相干,前端按
code分支取值即可(另一组字段在各自提醒里恒为null):WARN_VEHICLE_TYPE_MISMATCH(车级):点名到某一辆车,带vehicleId/vehiclePlate/declaredVehicleType/actualVehicleType。WARN_GROUP_SEATS_BELOW_SPEC(组级,一个分组最多一条):点名到组和不达标的服务日,带declaredSeats/declaredVehicleCount/declaredSeatTotal/actualSeatTotal/shortageDates。
- 两端的作用域不同,别混读:
- reconfigure:只覆盖本次提交新增或就地改过的「分组 + 车辆」组合,不含本次没动的存活行(否则一次只改司机的提交会把历史遗留的不符行一起刷出来)。
- confirm:只覆盖本次由「已派车」推进为「已确认」的那些行。所以重复确认(幂等重放)时恒为空列表——那一次没有任何行被推进,清单的分母是空的。
- 无提醒时是空数组
[],不会是null,可直接v-for。
一、背景
团期配车的「声明」来自正式团级用车需求的乘车分组(组码、车型、单车座位数 seats、每日车辆数 vehicleCount),「实际」来自车辆档案(车型 typeKey、座位数)。改前这两侧从来没被比对过,派错车型 / 座位不够在整条链路上零信号。
本次做成提醒而不是硬拒有两条已定口径的原因:
| 维度 | 为什么不做成错误码 |
|---|---|
| 座位不足 | 就绪判定里它已经是 wx 在 #7444 D9 拍板的「只提醒」档(GroupDispatchReadinessService.WARN_SEAT_SHORTAGE),硬拒会与该定案冲突 |
| 车型不符 | 两侧处在同一字典的不同归一层级,且两侧都合法地存在取不到值的行(车辆大类行缺失 / 存量需求的历史自由文本),硬拒会把现在能正常干活的分派拦下来 |
与既有的 GroupDispatchReadinessItemVO 分工不同:那一份比的是「已扣司机座的可载客数 vs 该日实际用车人数」,回答「坐不坐得下」;本份比的是「名义座位合计 vs 需求方声明的计划容量 seats × vehicleCount」,回答「派的车是不是按计划来的」。右值不同源,不是重复。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 整团逐日配车提交 | POST | /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure |
响应新增字段 | 新增 specWarnings,覆盖本次新增或就地改过的组+车组合 |
| 2 | 确认整团配车 | POST | /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm |
响应新增字段 | 新增 specWarnings,覆盖本次被推进为「已确认」的行;重复确认恒为空 |
三、接口详情
1. 整团逐日配车提交 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure
VO: GroupDispatchReconfigureReqVO → GroupDispatchReconfigureRespVO
使用场景
团期配车页点「提交」时调用,按乘车分组提交整团逐日配车计划,服务端与现状差量比对(多删少补,旧记录软删留痕)。权限点 fleet:group-dispatch:write。本次改动只在响应里多加一个提醒清单,提交本身的行为与校验一条都没变。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | - | 团期主订单 ID |
requirementId |
Body | Long | ✅ | @NotNull |
正式团级用车需求 ID,必须等于基线当前活跃需求,落后抛 602005 |
requirementVersion |
Body | Integer | ✅ | @NotNull |
正式团级用车需求版本,同上 |
clearAll |
Body | Boolean | ❌ | - | 显式整团清零标志;为 true 时 demands 只当待清日用,不会写入任何配车行 |
reconfigureWindowToken |
Body | String | ❌ | 团期已过资源准备阶段时必填 | 受控重开窗口令牌 |
survivorPolicy |
Body | String | ❌ | clearAll=true 且存在 active 共用关系时必填 |
幸存共用派单处置策略 |
demands |
Body | Array | ❌ | @Valid;clearAll=false 时必填 |
逐日配车需求列表 |
demands[].tripDate |
Body | String(yyyy-MM-dd) |
✅ | @NotNull |
行程日期 |
demands[].assignments |
Body | Array | ✅ | @Valid |
当日排车项列表 |
demands[].assignments[].groupId |
Body | String | ✅ | @NotBlank,@Size(max=64) |
乘车分组键(= 需求侧 group_code),空值返 HTTP 业务 400「乘车分组不能为空」 |
demands[].assignments[].vehicleId |
Body | Long | ✅ | @NotNull |
派出车辆 ID |
demands[].assignments[].driverId |
Body | Long | ❌ | - | 派出司机 ID;可空 = 仅排车未排司机 |
demands[].assignments[].remark |
Body | String | ❌ | @Size(max=200) |
备注 |
出参 Result<GroupDispatchReconfigureRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
groupBatchId |
String | 团期主订单 ID(雪花,字符串) |
requirementId |
String | 正式团级用车需求 ID(雪花,字符串) |
requirementVersion |
Integer | 正式团级用车需求版本 |
planVersion |
Long | 团期计划版本 |
addedCount |
Integer | 新增派车记录数 |
removedCount |
Integer | 软删派车记录数 |
keptCount |
Integer | 保留未变派车记录数 |
updatedCount |
Integer | 就地更新派车记录数 |
aliveCount |
Integer | 存活派车记录总数 |
addedDispatchIds |
Array<String> | 新增派车记录主键列表(雪花,字符串) |
idempotentShortCircuit |
Boolean | 本次是否被计划去重短路;true 是幂等成功,不是失败 |
coverage |
Object | 按乘车分组的覆盖明细,见下 |
coverage.groups[] |
Array | 每组:groupCode / vehicleType / requiredDates / coveredDates / missingDates / outOfRangeDates / satisfied |
coverage.missingGroupCodes |
Array<String> | 整组未提交的组码 |
coverage.wholeBatchSatisfied |
Boolean | 全团行程日整体覆盖是否成立 |
legacyGroupRowCount |
Integer | 无分组键的历史派车行数(非错误,仅留痕) |
releasedShareGroupIds |
Array<String> | 本次连带解除的共用关系 ID 清单 |
keptSourceIds |
Array<String> | 保留占用的 claim 来源 ID 清单 |
releasedSourceIds |
Array<String> | 占用已被真正释放的派单 ID 清单 |
pendingReassignSourceIds |
Array<String> | 待人工改派的派单 ID 清单(占用已释放,当前无车) |
ignoredDemandDays |
Array<String> | 因 clearAll=true 未被写入的行程日清单;clearAll=false 时为空列表 |
specWarnings |
Array | 🆕 所派车辆与分组声明不符的提醒清单(非错误,不影响提交成败);无提醒为空数组 |
specWarnings[].code |
String | WARN_VEHICLE_TYPE_MISMATCH / WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].message |
String | 中文描述,已点名到组与车牌 / 服务日,可直接展示 |
specWarnings[].groupCode |
String | 相关乘车分组码;两类提醒都有值 |
specWarnings[].vehicleId |
String | 相关车辆 ID(雪花,字符串);仅 WARN_VEHICLE_TYPE_MISMATCH |
specWarnings[].vehiclePlate |
String | 相关车牌;车辆档案未录车牌时为 null(message 里已退回 ID=车辆ID) |
specWarnings[].declaredVehicleType |
String | 分组声明车型(归一后的规范大类 key,如 bus);仅 WARN_VEHICLE_TYPE_MISMATCH |
specWarnings[].actualVehicleType |
String | 车辆实际车型(归一后的规范大类 key,如 suv);仅 WARN_VEHICLE_TYPE_MISMATCH |
specWarnings[].declaredSeats |
Integer | 分组声明的单车座位数(含司机座);仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].declaredVehicleCount |
Integer | 分组声明的每日车辆数;仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].declaredSeatTotal |
Integer | 每日总容量 = declaredSeats × declaredVehicleCount,后端算好回传,前端不要自己乘;仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].actualSeatTotal |
Integer | 不达标服务日里最低那一天的实际座位合计;仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].shortageDates |
Array<String> | 实际座位合计低于声明总容量的服务日,升序;仅 WARN_GROUP_SEATS_BELOW_SPEC |
请求示例
{
"requirementId": 5501,
"requirementVersion": 3,
"clearAll": false,
"demands": [
{
"tripDate": "2026-09-13",
"assignments": [
{ "groupId": "BUS", "vehicleId": 1001, "driverId": 2001, "remark": "AA 团 7 座商务" }
]
},
{
"tripDate": "2026-09-14",
"assignments": [
{ "groupId": "BUS", "vehicleId": 1001, "driverId": 2001, "remark": null }
]
}
]
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "8801",
"requirementId": "5501",
"requirementVersion": 3,
"planVersion": 7,
"addedCount": 2,
"removedCount": 0,
"keptCount": 3,
"updatedCount": 0,
"aliveCount": 5,
"addedDispatchIds": ["9001", "9002"],
"idempotentShortCircuit": false,
"coverage": {
"groups": [
{
"groupCode": "BUS",
"vehicleType": "bus",
"requiredDates": ["2026-09-13", "2026-09-14"],
"coveredDates": ["2026-09-13", "2026-09-14"],
"missingDates": [],
"outOfRangeDates": [],
"satisfied": true
}
],
"missingGroupCodes": [],
"wholeBatchSatisfied": true
},
"legacyGroupRowCount": 0,
"releasedShareGroupIds": [],
"keptSourceIds": [],
"releasedSourceIds": [],
"pendingReassignSourceIds": [],
"ignoredDemandDays": [],
"specWarnings": [
{
"code": "WARN_VEHICLE_TYPE_MISMATCH",
"message": "分组 BUS 声明车型 大巴客车,所派车辆 蒙P318A 实际为 SUV",
"groupCode": "BUS",
"vehicleId": "1001",
"vehiclePlate": "蒙P318A",
"declaredVehicleType": "bus",
"actualVehicleType": "suv",
"declaredSeats": null,
"declaredVehicleCount": null,
"declaredSeatTotal": null,
"actualSeatTotal": null,
"shortageDates": null
},
{
"code": "WARN_GROUP_SEATS_BELOW_SPEC",
"message": "分组 BUS 声明每日总容量 16 座(单车 16 座 × 1 辆),实际座位合计最低仅 5 座,涉及 2 个服务日:[2026-09-13, 2026-09-14]",
"groupCode": "BUS",
"vehicleId": null,
"vehiclePlate": null,
"declaredVehicleType": null,
"actualVehicleType": null,
"declaredSeats": 16,
"declaredVehicleCount": 1,
"declaredSeatTotal": 16,
"actualSeatTotal": 5,
"shortageDates": ["2026-09-13", "2026-09-14"]
}
]
}
}
空数据 / 降级响应
无提醒时 specWarnings 是空数组,不是 null:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "8801",
"addedCount": 0,
"removedCount": 0,
"keptCount": 5,
"updatedCount": 0,
"aliveCount": 5,
"idempotentShortCircuit": true,
"specWarnings": []
}
}
降级(fail-open)规则 —— 下列情况提醒不产出,specWarnings 少一条或为空,这是设计行为不是丢数据:
- 分组不在基线的权威分组清单里 → 该组整组跳过。
- 车辆在本次的车辆档案快照里取不到 → 该车跳过。
- 声明侧或实际侧任一方的车型归一不出规范 key(车辆大类行缺失 / 存量需求的历史自由文本)→ 不报车型不符。
- 分组的
seats或vehicleCount为 null 或 ≤ 0 → 不做容量判定。 - 某个(分组 + 服务日)格里只要有一辆车的座位数取不到或 ≤ 0 → 整格不判(不把缺值折成 0,折 0 会产出一个不存在的缺口)。
错误响应
既有错误码一条都没变。示例(分组不在本团需求内,消息模板 乘车分组不存在于本团正式需求: {0}):
{
"code": 602001,
"message": "乘车分组不存在于本团正式需求: VAN",
"success": false,
"data": null
}
完整错误码:602000(排车项缺分组,服务层兜底;admin 口由入参校验先拦下返 400「乘车分组不能为空」)/ 602001 分组不在本团需求内 / 602002 整组未排车 / 602003 该组服务日未排满 / 602004 该组排了本组服务范围外的日期 / 602005 需求身份或版本已变 / 602006 需求状态不允许 / 602009 取不到权威分组清单 / 600003 重复行程日 / 600004 单日排车为空 / 600005 缺车辆 ID / 600006 车辆被占 / 600007 司机被占 / 600008 并发修改 / 600009 基线不可用 / 600010 团期状态不可配 / 600011 全团服务日未覆盖满 / 605037 车辆维保或停用不可派 / 605038 司机休假或待激活不可派 / 605006 司机已黑名单 / 605013 司机非在册赛季不可派单。
业务边界
- 鉴权:权限点
fleet:group-dispatch:write(与读口fleet:group-dispatch:view分开);未登录由网关拦截返 401。 specWarnings非错误:HTTP 200 +success=true的响应里出现,提交已成功落库。不要据此回滚本地状态或阻断后续动作。- 作用域:只覆盖本次提交新增或就地改过的「分组 + 车辆」组合;本次没动的存活行不重判(一次只改司机的提交不会把历史遗留的不符行刷出来)。
- 两类提醒字段分组互斥:按
code分支取值,另一组字段恒null。 declaredSeatTotal由后端算好:口径(含不含司机座、按不按日)只有一份权威,前端不要复算。actualSeatTotal是最低值不是明细:它与declaredSeatTotal一起答完「最坏差多少」,不需要拿shortageDates反查每一天。- 防重提交与幂等是两件事:10 秒内对同一份计划重复提交会被防重窗口拒绝(返「团期配车重配处理中,请勿重复提交」);窗口之外重复提交同一份计划会正常受理并返回
idempotentShortCircuit=true,那是成功。 clearAll=true时必须读ignoredDemandDays:否则「清完并按新计划重排」与「只清空」在响应里长得一模一样(两者addedCount都是 0)。- 雪花 ID 一律是字符串:
groupBatchId/requirementId/addedDispatchIds[]/specWarnings[].vehicleId等都以字符串下发。
2. 确认整团配车 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm
VO: GroupDispatchConfirmReqVO → GroupDispatchConfirmRespVO
使用场景
团期配车页点「确认」时调用,把该团全部「已派车」的配车行转为「已确认」,并登记一条把正式用车需求推进到「已发车务」的异步回写意图。权限点与提交写口同一个 fleet:group-dispatch:write。本次改动只在响应里多加一个提醒清单。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | - | 团期主订单 ID |
requirementId |
Body | Long | ✅ | @NotNull |
正式团级用车需求 ID |
requirementVersion |
Body | Integer | ✅ | @NotNull |
正式团级用车需求版本 |
remark |
Body | String | ❌ | @Size(max=200) |
确认备注,仅留痕,不写入配车行 |
出参 Result<GroupDispatchConfirmRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
groupBatchId |
String | 团期主订单 ID(雪花,字符串) |
confirmedCount |
Integer | 本次由「已派车」转为「已确认」的配车行数;重复确认为 0,属幂等成功 |
alreadyConfirmedCount |
Integer | 确认前就已是「已确认」的配车行数(重复确认时全部落在这里) |
requirementId |
String | 本次确认所依据的正式团级用车需求 ID(雪花,字符串) |
requirementVersion |
Integer | 本次确认所依据的需求版本 |
planVersion |
Long | 当前团期计划版本(确认不改计划,故不递增) |
requirementAdvanceIntent |
String | 已登记的需求回写意图方向,恒为 CONFIRMED_TO_DISPATCHED |
coverage |
Object | 按乘车分组的覆盖明细(确认前对库里现存配车行重判一次的结果),结构同 reconfigure |
legacyGroupRowCount |
Integer | 本团存活派车行里没有乘车分组键的历史行数;非零时 602008 的缺口很可能正是它们造成的 |
specWarnings |
Array | 🆕 本次被推进为「已确认」的行里,所派车辆与分组声明不符的提醒清单(非错误,确认已成功);无提醒为空数组 |
specWarnings[].code |
String | WARN_VEHICLE_TYPE_MISMATCH / WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].message |
String | 中文描述,已点名到组与车牌 / 服务日,可直接展示 |
specWarnings[].groupCode |
String | 相关乘车分组码;两类提醒都有值 |
specWarnings[].vehicleId |
String | 相关车辆 ID(雪花,字符串);仅 WARN_VEHICLE_TYPE_MISMATCH |
specWarnings[].vehiclePlate |
String | 相关车牌;未录车牌时为 null |
specWarnings[].declaredVehicleType |
String | 分组声明车型(规范 key);仅 WARN_VEHICLE_TYPE_MISMATCH |
specWarnings[].actualVehicleType |
String | 车辆实际车型(规范 key);仅 WARN_VEHICLE_TYPE_MISMATCH |
specWarnings[].declaredSeats |
Integer | 分组声明单车座位数;仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].declaredVehicleCount |
Integer | 分组声明每日车辆数;仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].declaredSeatTotal |
Integer | 每日总座位数(后端算好);仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].actualSeatTotal |
Integer | 不达标日中的最低实际座位合计;仅 WARN_GROUP_SEATS_BELOW_SPEC |
specWarnings[].shortageDates |
Array<String> | 不达标的服务日(升序);仅 WARN_GROUP_SEATS_BELOW_SPEC |
请求示例
{
"requirementId": 5501,
"requirementVersion": 3,
"remark": "与地接确认车辆无误"
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "8801",
"confirmedCount": 8,
"alreadyConfirmedCount": 0,
"requirementId": "5501",
"requirementVersion": 3,
"planVersion": 7,
"requirementAdvanceIntent": "CONFIRMED_TO_DISPATCHED",
"coverage": {
"groups": [
{
"groupCode": "BUS",
"vehicleType": "bus",
"requiredDates": ["2026-09-13", "2026-09-14"],
"coveredDates": ["2026-09-13", "2026-09-14"],
"missingDates": [],
"outOfRangeDates": [],
"satisfied": true
}
],
"missingGroupCodes": [],
"wholeBatchSatisfied": true
},
"legacyGroupRowCount": 0,
"specWarnings": [
{
"code": "WARN_VEHICLE_TYPE_MISMATCH",
"message": "分组 BUS 声明车型 大巴客车,所派车辆 蒙P318A 实际为 SUV",
"groupCode": "BUS",
"vehicleId": "1001",
"vehiclePlate": "蒙P318A",
"declaredVehicleType": "bus",
"actualVehicleType": "suv",
"declaredSeats": null,
"declaredVehicleCount": null,
"declaredSeatTotal": null,
"actualSeatTotal": null,
"shortageDates": null
}
]
}
}
空数据 / 降级响应
重复确认(幂等重放):confirmedCount=0、alreadyConfirmedCount=N、specWarnings 恒为空数组(本次没有任何行被推进,清单的分母是空的)。HTTP 仍是 200,这是成功不是失败:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "8801",
"confirmedCount": 0,
"alreadyConfirmedCount": 8,
"requirementId": "5501",
"requirementVersion": 3,
"planVersion": 7,
"requirementAdvanceIntent": "CONFIRMED_TO_DISPATCHED",
"legacyGroupRowCount": 0,
"specWarnings": []
}
}
降级(fail-open)规则与 reconfigure 端点逐条相同:分组不在基线 / 车辆取不到 / 任一侧车型归一不出规范 key / seats 或 vehicleCount 为 null 或 ≤0 / 某(组+日)格里有一辆车座位取不到 → 对应提醒不产出。
错误响应
既有错误码一条都没变。示例(现存配车对当前需求仍不完整,消息模板 配车尚未覆盖完整, 不能确认: {0}):
{
"code": 602008,
"message": "配车尚未覆盖完整, 不能确认: 乘车分组 BUS 的服务日未排满, 缺失: [2026-09-15]",
"success": false,
"data": null
}
完整错误码:602007 本团无可确认的配车行 / 602008 现存配车对当前需求仍不完整 / 602005 需求身份或版本已变 / 602006 需求状态不允许 / 602009 取不到权威分组清单 / 600008 并发修改 / 600009 基线不可用 / 605037 车辆维保或停用不可派 / 605038 司机休假或待激活不可派 / 605006 司机已黑名单 / 605013 司机非在册赛季不可派单。
业务边界
- 鉴权:权限点
fleet:group-dispatch:write(与提交写口同一个);未登录由网关拦截返 401。 specWarnings非错误:确认已经成功。它是终态前的最后一次复核——重配与确认之间车辆档案可能被改过,也可能有行绕过重配直接进来。- 作用域:只覆盖**本次由「已派车」推进为「已确认」**的那些行;已是「已确认」的行不重判。
- 重复确认时恒为空列表:不要把「第二次点确认没有提醒」理解成「问题已经消失」。
- 本端点没有防重提交时间窗:连点多少次都是
confirmedCount=0 / alreadyConfirmedCount=N这个形态,不会出现「请勿重复提交」这类错误码;同团的并发调用由服务端串行化。 - 异步回写:响应成功只代表车务侧已确认并已把回写意图可靠登记,正式用车需求的状态可能稍后才变成「已发车务」,需求页需自行刷新。
- 回写会推进需求版本但不会让重复确认变成错误:首次确认成功后正式用车需求被推进一版(status 转 DISPATCHED),此时本端点跳过需求版本与状态的严格校验,仍返回 200 + 两个计数。
- 不校验团期是否可配:那道门禁管的是「还能不能改车」,确认不改车。
- 雪花 ID 一律是字符串。
四、契约约束与正确调用方式
本节只写后端接受 / 拒绝 payload 的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload |
|---|---|
| ✅ 正常提交两天配车 | { "requirementId": 5501, "requirementVersion": 3, "clearAll": false, "demands": [ { "tripDate": "2026-09-13", "assignments": [ { "groupId": "BUS", "vehicleId": 1001 } ] } ] } |
| ✅ 整团清零 | { "requirementId": 5501, "requirementVersion": 3, "clearAll": true, "demands": [] } |
| ✅ 确认(带留痕备注) | { "requirementId": 5501, "requirementVersion": 3, "remark": "与地接确认车辆无误" } |
| ✅ 确认(不带备注) | { "requirementId": 5501, "requirementVersion": 3 } |
| ❌ 排车项缺分组键 | { ..., "assignments": [ { "vehicleId": 1001 } ] } → 400「乘车分组不能为空」 |
| ❌ 缺需求版本 | { "requirementId": 5501, "demands": [...] } → 400「正式团级用车需求版本不能为空」 |
| ❌ 确认备注超 200 字 | { ..., "remark": "<201 字>" } → 400「确认备注长度不能超过 200」 |
处理 specWarnings 的必要动作
- 两个端点的成功分支里都要读
specWarnings:非空时就地展示(message已经是完整中文句子,可直接渲染),不要把它接到错误处理分支上。 - 按
code分支取字段,不要对全部字段做非空假设——另一类提醒的字段组恒为null。 declaredSeatTotal直接用后端回传的值,不要用declaredSeats × declaredVehicleCount自己算。- reconfigure 的
specWarnings只反映本次动过的行:specWarnings为空不等于全团没有不符行,只等于「本次动的这些行没有不符」。
五、数据库行为
两个端点都是写端点,但本次改动零写入变化——specWarnings 完全由内存中的比对产出(GroupDispatchVehicleSpecInspector 是纯静态、无 IO),不新建表、不加列、不落任何提醒记录。
| 前端提交 | 配车行的写入 | 提醒的持久化 |
|---|---|---|
reconfigure 差量提交 |
多删少补,旧记录软删留痕(本次未变) | 不落库,仅随本次响应下发 |
reconfigure clearAll=true |
清空存活行,demands 不写入 |
不落库 |
confirm 首次确认 |
「已派车」行 CAS 推进为「已确认」,登记回写意图(本次未变) | 不落库 |
confirm 重复确认 |
一个字段都不动 | 不落库,且恒为空数组 |
因此刷新页面或重新拉取不会再拿到同一批提醒——提醒是本次动作的返回值,不是可查询的状态。
六、边界行为
- 未登录 → 401(网关拦截)。
- 权限点
fleet:group-dispatch:write缺失 → 权限校验失败。 - 团期不存在 / 基线不可用 → 600009。
- 需求身份或版本落后 → 602005(fail-closed,不接受「反正车没变」)。
- 10 秒内重复提交同一份 reconfigure 计划 → 被防重窗口拒绝,提示「团期配车重配处理中,请勿重复提交」。
- 窗口外重复提交同一份计划 → 200 +
idempotentShortCircuit=true(成功)。 - 重复 confirm → 200 +
confirmedCount=0、specWarnings=[](成功)。 - 车辆档案未录车牌 →
specWarnings[].vehiclePlate为 null,但message里退回ID=车辆ID,不留空白。 - 老数据兼容:历史派车行没有乘车分组键时不计入任何组的覆盖,计入
legacyGroupRowCount,也不进specWarnings。
六.5、枚举 / 数据字典
code(车辆规格提醒项代码)
所属字段: specWarnings[].code | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
WARN_VEHICLE_TYPE_MISMATCH |
车型与分组声明不符 | 车级提醒。两侧车型各自归一成规范大类 key 后不相等时产出。带 vehicleId / vehiclePlate / declaredVehicleType / actualVehicleType;其余字段为 null |
WARN_GROUP_SEATS_BELOW_SPEC |
分组座位低于声明容量 | 组级提醒,一个分组最多一条。判据:该组该日实际座位合计 < seats × vehicleCount。带 declaredSeats / declaredVehicleCount / declaredSeatTotal / actualSeatTotal / shortageDates;其余字段为 null |
declaredVehicleType / actualVehicleType(归一后的车型规范大类 key)
所属字段: specWarnings[].declaredVehicleType、specWarnings[].actualVehicleType | 类型: String
取值是车型字典归一后的规范大类 key(如 bus / suv),不是车辆档案里的原值——车辆档案侧存的是开集原值(例如测试环境 SUV 大类的 type_key 实际是 suv2),后端归一后才比。前端如需展示中文名,用 message 里已经拼好的中文,不要自己拿 key 去查字典。
requirementAdvanceIntent(需求回写意图方向)
所属字段: GroupDispatchConfirmRespVO.requirementAdvanceIntent | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
CONFIRMED_TO_DISPATCHED |
已确认 → 已发车务 | 当前恒为此值;表示确认成功后还有一步异步回写,需求列表页的状态可能稍后才变 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
GroupDispatchReconfigureRespVO.specWarnings |
不存在 | 🆕 List<GroupDispatchSpecWarningRespVO>,无提醒为空数组 |
GroupDispatchConfirmRespVO.specWarnings |
不存在 | 🆕 List<GroupDispatchSpecWarningRespVO>,无提醒为空数组 |
| 两个响应体的其余全部字段 | — | 未变(无删除、无改名、无类型变化) |
| 两个请求体 | — | 未变(一个字段都没动) |
| 两个端点的错误码集合 | — | 未变 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 派错车型(声明大巴、实际 SUV) | 全链路零信号,一路能确认到终态 | 提交与确认的响应里各出一条 WARN_VEHICLE_TYPE_MISMATCH |
| 某服务日实际座位合计低于声明容量 | 全链路零信号 | 出一条 WARN_GROUP_SEATS_BELOW_SPEC,带最低值与不达标日清单 |
| 提交 / 确认的成败判定 | 按覆盖与资源可派性 | 未变——specWarnings 不参与成败判定 |
| 重复确认 | confirmedCount=0、alreadyConfirmedCount=N |
未变,额外 specWarnings=[] |
| 只改司机的提交 | — | 不会把历史遗留的不符行刷出来(作用域限本次动过的组+车组合) |
六.7、影响评估
- 是否破坏向后兼容: 否(纯新增字段,既有字段与错误码零变化;老前端忽略新字段即可正常工作)
- 前端是否必须同步上线: 否(不读新字段不会报错,只是拿不到提醒)
- 前端 workaround 清理点: 无(此前没有任何前端侧的车型 / 座位比对,不存在需要撤掉的本地实现)
七、不影响范围
- 仅影响:
POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure与POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm两个响应体各新增一个数组字段。 - 零影响:
- 团期配车所有读口(概览、就绪判定、矩阵、看板)
GroupDispatchReadinessItemVO的座位就绪判定(右值不同源,本次未动)- 单车派单、改派、取消链路
- order-v3 侧的正式团级用车需求存 / 读 / 撤回 / 免车
- 车辆档案、司机档案的任何端点
- 历史数据:提醒不落库,不做任何数据迁移
八、测试环境已验证
- 代码事实(对
origin/dev-v3逐一查证):- 合并提交
cfefe04a83(PR #8602 squash 合并进dev-v3)。 - 新增 VO
hl-fleet-service/.../dispatch/vo/GroupDispatchSpecWarningRespVO.java(12 个字段)与对位 Feign DTOGroupDispatchSpecWarningDTO。 - 新增纯静态无 IO 的
GroupDispatchVehicleSpecInspector;两条提醒的消息拼装、fail-open 跳过条件、seatTotalOrNull的「一辆车取不到座位就整格不判」逻辑均已逐行核对。 GroupDispatchReconfigureRespVO与GroupDispatchConfirmRespVO各新增specWarnings字段,javadoc 分别写明作用域(本次新增/就地改过 vs 本次被推进)与「重复确认恒为空列表」。
- 合并提交
- 部署:
hl-fleet-service的dev-v3分支已滚到测试服,两个端点走管理端网关/admin/fleet/**既有路由,无新增路由。
POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure → 200 + data.specWarnings 存在(无提醒时为 [])✓
POST /admin/fleet/group-dispatch/batches/{groupBatchId}/confirm → 200 + data.specWarnings 存在(无提醒时为 [])✓
POST .../confirm 重复调用 → 200 + confirmedCount=0 + specWarnings=[] ✓
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| — | #7442 | 团期配车写口(reconfigure / confirm)首次落地 | ✅ 有效 |
| — | #7444 D9 | wx 拍板座位不足在就绪判定里只提醒不硬拒 | ✅ 有效(本单沿用该口径) |
| — | #8195 | 需求侧车型归一后回写规范 key | ✅ 有效(本单的比对依赖它) |
| — | #8528 | reconfigure / confirm 资源可派性硬校验(605037/605038/605006/605013) | ✅ 有效 |
| 本 PR #8602 | #8576 | 两个写口响应新增 specWarnings |
✅ 最新 |
十、相关文档
- 关联 Issue: wx/HL#8576
- 关联 PR: wx/HL#8602
关联 / 联系人
链接
- Issue: #8576
- PR: #8602
- Merge commit: cfefe04a83
联系人
- 后端负责人: @wx