文件
hl-api-changelog/changelogs-v2/2026-09/30_8576_团期配车提交与确认响应新增车辆规格提醒清单-修改接口-管理后台.md
T

34 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 not_required PR #8602 已 squash 合并 dev-v3(cfefe04a83),hl-fleet-service dev-v3 分支已滚测试服。纯新增字段,两个端点的既有字段、错误码、HTTP 形态全部未变。 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 Integer/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 会产出一个不存在的缺口)。

错误响应

既有错误码一条都没变。示例(分组不在本团需求内):

{
  "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 Integer/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 / 某(组+日)格里有一辆车座位取不到 → 对应提醒不产出。

错误响应

既有错误码一条都没变。示例(现存配车对当前需求仍不完整):

{
  "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