文件
hl-api-changelog/changelogs-v2/2026-09/30_8576_团期配车提交与确认响应新增车辆规格提醒清单-修改接口-管理后台.md
T
API Changelog Bot和Claude Opus 5 e1ae777695
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): 订正 #8576/#8601/#8577 三份交接件的编造内容与 frontend_status
三份都是既有条目(#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>
2026-09-30 15:17:17 +08:00

35 KiB
原始文件 Blame 文件历史

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 DTO GroupDispatchSpecWarningDTO。
    • 新增纯静态无 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 ✅ 最新

十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @wx