33 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 | 7973 | 共用关系确认新增跨常驻车派单确认入参 confirmCrossResident(补记,此前无任何交接件) | admin | wx(GIT) | 修改接口 | deployed | verified | not_required | gateway_status=verified 的判据(2026-09-21 10:09:15 / 10:09:17 取证,原文见第八节):经网关对 POST .../share-groups 发起两次真实调用,两次请求体逐字相同、唯一差异是 confirmCrossResident 字段——不传得 605036(消息点名的车牌与司机姓名与夹具构造的跨常驻组合一致),带 true 得 200 并建出 ACTIVE 共用关系,取证完毕已按 survivorPolicy=RELEASE 清场。部署点判据:hl-fleet-service 测试服部署于 51571c58a,git merge-base --is-ancestor 076654889 51571c58a = true。⚠️ 部署点是时点读数——2026-09-21 复核时 origin/dev-v3 已前进到 79722aef1,但 51571c58a..origin/dev-v3 在 hl-fleet-service/ 下只有 a948c1b60(#7994,GroupDispatchService 空组码行收编判定)一条,与本篇 605036 链路零交集,故该读数不因部署点落后而失效。此前两轮保持 pending 的原因是「同一个端点被调通」不等于「本篇登记的那个入参被走到」;本轮已造出跨常驻场景直接触发 605036 分支,该顾虑解除,状态位是取证换来的、不是为过门禁改的。本篇是补记:#7973/#7978 合入以来 docs/ 与 hl-workflow/ 下 grep 7973/7978 零命中,此前没有任何交接件提过这个新增入参。前端实证 not_required(mmg 2026-09-21):share-groups/shareGroup 前端 src 全仓零命中——共用关系确认端点无调用方,属 #7444 未接入挂起域,confirmCrossResident 新入参当前无接入点;605036 全仓零命中,看板派单路径走 precheck/candidates 证据链不解析消息原文,消息模板变详细零影响;看板既有 confirmCrossResident 是 assignments create/change 派单写路径的同名字段,该路径契约本次零变更。随 #7444 接入共用关系确认 UI 时按本文实现:precheck 逐成员探路(cross_resident warning)→二次确认弹窗→带 confirmCrossResident=true 原样重发;605036 文案须补「车辆-司机常驻组合冲突」解释不直接弹原文。frontend_owner/verified_at 系后端预填,按 not_required 门禁口径(ba87a9b)清空。 |
2026-09-21 | dev-v3 |
fleet: 共用关系确认新增跨常驻车派单确认入参(工单 #7973,补记)
存放目录: 二期(order-v3 标签工单)→
changelogs-v2/2026-09/服务: hl-fleet-service(8087) PR: #7978 | Issue: #7973 | 合并提交:
076654889日期: 2026-09-20(补记;实际合入日为 2026-09-19) 影响范围: 管理后台「团期配车页」共用关系确认(POST .../share-groups),跨常驻车/司机场景下的确认交互
⚠️ 关键变化
🔴 这是一份补记 changelog:#7973/#7978 已于 2026-09-19 16:13 合入 dev-v3,但截至本文撰写(2026-09-20),docs/ 与 hl-workflow/ 全仓 grep 7973/7978 零命中——没有任何交接件提过这个变化。mmg 大概率不知道 POST .../share-groups 已经新增了一个请求字段 confirmCrossResident,很可能仍按"该端点入参只有 serviceDate/resourceType/resourceId/members/costBearer/costBearerOrderId/remark 七项"在对接,一旦线上撞见跨常驻场景,会直接吃 605036 且不知道该传哪个字段重试。
🔴 错误文案与页面操作错位,前端必须显式处理,不能指望用户读懂原始错误码:车务在团期配车页上选的是"一辆共用车",但 605036 的消息说的是"司机与车辆不是常驻组合"——被判定跨常驻的司机不是车务选的,是系统按该成员原派单自动带过去的。如果前端把 605036 当成普通业务报错直接弹 message 原文,运营会去查司机资料,而真正需要处理的动作是"确认跨常驻车派单"。详见「四、契约约束与正确调用方式」。
在此之前(confirmCrossResident 字段出现之前),确认端点没有任何入参能表达这个确认——遇到跨常驻场景的共用关系,字面意义上永远建不出来,只能报 605036 死循环。本次修复解锁了这条此前完全不可达的成功路径。
一、背景
POST /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups(团期车辆共用关系确认,#7444 落地)第 3 步会把尚未占用的成员改派到共用资源上,走的是既有派单写路径 AssignmentService#change。这条写路径上有一道提示型守卫(605036,源码 AssignmentResidentPolicy,早于 #7973 就存在:#4936 引入、#5160 扩展,经 git log --follow AssignmentResidentPolicy.java 核实):目标车已设常驻司机、或被派入的司机本身是别的车的常驻司机时,必须由人显式确认才放行。
问题是:#7973 之前,共用关系确认端点自身完全没有能表达"我确认"这件事的入参。车务在页面上选好共用车、勾好成员,一提交撞上跨常驻就报 605036,而没有任何字段可以带着"确认过了"再重试——这类共用关系客观上建不出来,唯一的绕法是联系后端手工处理。
#7973 缺陷一在 ShareGroupConfirmReqVO 上补了 confirmCrossResident 这个可选字段,原样透传到 change 命令,并把 605036 的消息模板从一句不点名的抽象提示,改成点名"哪条派单 / 跨常驻的具体形态 / 车牌 / 司机姓名"的详细提示。
| 维度 | 改前 | 改后 |
|---|---|---|
| 共用关系确认遇到跨常驻场景 | 无字段可确认,永远 605036,字面意义上建不出这条关系 |
可传 confirmCrossResident=true 重试放行 |
605036 错误消息 |
"司机与车辆不是常驻组合,请确认跨常驻车派单后重试"(无占位符,不点名任何具体对象) |
"司机与车辆不是常驻组合,请确认跨常驻车派单后重试({0}:{1};车辆 {2};司机 {3})"(点名触发的派单、跨常驻形态、车牌、司机姓名) |
| 发生频率参考 | - | 据另一次测试服 DB 核查(本次交接未独立复核,见文末核对记录):fleet_vehicle 123 辆中 34 辆设了 primary_driver_id(27.6%),fleet_driver 116 人中 31 人是某辆车的常驻司机(26.7%)——不是高频,但也绝不罕见,值得做成显式二次确认而不是静默重试或忽略 |
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 确认团期车辆共用关系 | POST | /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups |
修改接口 | 请求体新增可选字段 confirmCrossResident;605036 错误消息模板新增 4 个占位符 |
三、接口详情
1. 确认团期车辆共用关系 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups
VO: ShareGroupConfirmReqVO → ShareGroupRespVO
(源码核对:ShareGroupConfirmReqVO.java:59-83、GroupDispatchShareController.java:53-93、GroupDispatchShareAdmissionService.java:508-569、AssignmentService.java:3198,5656,6316-6324、AssignmentErrorCode.java:181-184、AssignmentResidentPolicy.java)
使用场景
车务在团期配车页确认共用关系时调用,端点本身的用途与 #7444/#8013 changelog 描述一致。本次改动是"提交后可能撞 605036,撞了之后怎么办"这一条分支:目标共用资源(车或司机)与成员当前实际占用的另一方存在"常驻绑定但不是同一组合"关系时,首次提交会被拒绝;车务确认后,前端需要带着 confirmCrossResident=true 原样重发同一份请求(不是换个端点,不是改动其他字段)。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
| serviceDate / resourceType / resourceId / members / costBearer / costBearerOrderId / remark | Body | - | - | 与 #7444/#8013 changelog 描述完全一致 |
本次未改动 |
| confirmCrossResident | Body | Boolean | ❌ | 不传或 false = 不确认;true = 确认 |
【本次新增】 跨常驻车派单的人工确认。成员当前实际司机与本共用资源不是常驻组合时必须传 true,否则该成员派入时被 605036 拒;不跨常驻时本字段取任何值都不影响结果 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| shareGroupId / groupBatchId / serviceDate / resourceType / resourceId / status / costBearer / costBearerOrderId / costSourceRefNo / members[] / confirmedBy / confirmedAt / version / history | - | 与 #7444/#8013 changelog 描述完全一致,本次零变更 |
⚠️ 本次改动完全不影响响应体字段——confirmCrossResident 是纯粹的"放行判据",成功时响应体里看不出这次是不是带着确认才通过的。
请求示例
首次提交(未带确认,命中跨常驻):
{
"serviceDate": "2026-09-12",
"resourceType": "VEHICLE",
"resourceId": 300,
"members": [
{ "sourceType": "GROUP_DISPATCH", "sourceId": 99001 },
{ "sourceType": "ASSIGNMENT", "sourceId": 500, "admissionIntent": "PENDING_ADMISSION" }
],
"costBearer": "GROUP"
}
车务确认后原样重发(路径参数、members、costBearer 等逐字不变,只加一个字段):
{
"serviceDate": "2026-09-12",
"resourceType": "VEHICLE",
"resourceId": 300,
"members": [
{ "sourceType": "GROUP_DISPATCH", "sourceId": 99001 },
{ "sourceType": "ASSIGNMENT", "sourceId": 500, "admissionIntent": "PENDING_ADMISSION" }
],
"costBearer": "GROUP",
"confirmCrossResident": true
}
(路径参数 groupBatchId=8801)
响应示例
带确认重试成功后,响应体与 #7444 changelog 描述的成功响应逐字节一致(confirmCrossResident 不出现在响应里):
{
"code": 200,
"message": "成功",
"data": {
"shareGroupId": "77001",
"groupBatchId": "8801",
"serviceDate": "2026-09-12",
"resourceType": "VEHICLE",
"resourceId": "300",
"status": "ACTIVE",
"costBearer": "GROUP",
"costBearerOrderId": null,
"costSourceRefNo": "SHARE-77001",
"members": [
{ "sourceType": "GROUP_DISPATCH", "sourceId": "99001", "requirementId": null, "orderId": null },
{ "sourceType": "ASSIGNMENT", "sourceId": "500", "requirementId": "5501", "orderId": "70123" }
],
"confirmedBy": "1001",
"confirmedAt": "2026-09-12 18:20:33",
"version": 1
},
"success": true
}
空数据 / 降级响应
本接口是同步写操作,不存在空数据形态;本次改动不引入新的降级路径。
错误响应
605036 跨常驻车派单需确认。下面是 2026-09-21 10:09:15 经网关真实调用取得的原始响应(场景构造与放行对照见第八节):
{
"code": 605036,
"message": "司机与车辆不是常驻组合,请确认跨常驻车派单后重试(派单 2101855940422836225:所选车辆与司机均已有其他常驻绑定,继续操作将形成跨常驻车派单;车辆 蒙P303A;司机 宝音德力格尔)",
"data": null,
"success": false
}
消息模板(源码 AssignmentErrorCode.java:181-184):
司机与车辆不是常驻组合,请确认跨常驻车派单后重试({0}:{1};车辆 {2};司机 {3})
| 占位符 | 含义 | 取值 |
|---|---|---|
{0} |
触发方——哪条写路径撞上守卫 | 改派走 派单 {assignmentId}(源码 AssignmentService.java:5656);新建派单走 新建派单(:3198) |
{1} |
跨常驻的具体形态 | 3 种取值,见下表 |
{2} |
车辆 | 车牌号,如 蒙P303A |
{3} |
司机 | 司机姓名,如 宝音德力格尔 |
🔴 {1} 一共 3 种取值(源码 AssignmentResidentPolicy#messageOf,逐字照抄):
| 触发条件 | {1} 文案 |
|---|---|
| 车已有别的常驻司机 且 司机是别的车的常驻司机 | 所选车辆与司机均已有其他常驻绑定,继续操作将形成跨常驻车派单 |
| 仅「车已有别的常驻司机」 | 所选车辆已有其他常驻司机,继续操作将形成跨常驻车派单 |
| 仅「司机是别的车的常驻司机」 | 所选司机已有其他常驻车辆,继续操作将形成跨常驻车派单 |
⚠️ 三条文案都以「,继续操作将形成跨常驻车派单」结尾。前端若要靠文案区分分支,请匹配前缀、不要匹配全串;更稳的做法是把 message 原文直接展示给车务,不做解析——这条消息本来就是写给人看的。
业务边界
- 鉴权、幂等窗口、其余校验规则均与
#7444/#8013changelog 描述一致,本次未改动。 - 🔴
confirmCrossResident刻意不纳入idempotentKey()(源码ShareGroupConfirmReqVO.idempotentKey()与#7973javadoc 明确说明理由):它不是业务身份的一部分——同一份成员全集确认两次,确认与否不改变"确认了哪些成员共用哪辆车"这个结果;而被605036拒掉的那一次是业务失败,@Idempotent切面在失败路径上会释放 10 秒防重键。⇒ 拿到605036后带confirmCrossResident=true可以立刻重试,不会撞"请勿重复提交"防重窗口,前端不需要等待、也不需要提示"请稍后再试"。 - 服务端刻意不写死
true(源码 javadoc 原话):那会让这条路径静默地自动确认所有跨常驻派单,而守卫存在的意义恰恰是让车务看见"你正在把车派离它的常驻司机",写死等于把这道守卫在共用关系这条路径上永久关掉,且调用方不会知道自己确认过什么。前端不应该在检测到跨常驻风险后自动带true重试而不经用户确认。 - 不跨常驻时本字段取任何值都不影响结果——前端可以不管这个字段,只在拿到
605036之后才需要处理它,不必在每次提交时都预先判断要不要带它。 - 同一次确认可能派入多个成员,
605036只点名第一个撞上守卫的成员/派单:admitPendingMembers(GroupDispatchShareAdmissionService.java:534-569)在循环里逐个成员调change,一旦某个成员触发605036整个事务立刻回滚(含此前已经处理过的其他成员),前端不能假设"确认后重试就一定全部通过"——如果不同成员触发的是不同司机/车辆的跨常驻,可能需要多轮"提交→拿到 605036→确认→重试"才能全部放行完。
四、契约约束与正确调用方式
本节只写后端接受/拒绝 payload 的规则,不写与本次改动无关的既有规则(完整规则见
#7444/#8013changelog)。
🔴 车务在页面上选的是"车",605036 说的是"司机"——两者不是同一个对象
resourceType=VEHICLE 时,admitPendingMembers 把成员改派到共用资源的逻辑是(源码 GroupDispatchShareAdmissionService.java:550-565):
newVehicleId = resourceType == VEHICLE ? resourceId : row.getVehicleId() // 车务选的共用车
newDriverId = resourceType == DRIVER ? resourceId : row.getDriverId() // 成员原来的司机,不是车务选的
也就是说:车务只选了"这辆车",newDriverId 是系统按该成员原派单上的司机原样带过去的。守卫判的是"这名司机与这辆车是不是常驻组合"——于是"选一辆共用车"这个动作,撞上 605036 时,撞的原因写在一个车务根本没有主动选过的司机身上。resourceType=DRIVER(共用司机)时反过来:车务选的是司机,车是成员原来的车。
⇒ 前端不应该把 605036 的 message 原文直接展示给运营(会让人去查司机资料而不是理解"车辆-司机组合冲突"),至少要在文案上补一句解释这是"车辆-司机常驻组合冲突,不是司机本身有问题"。
跨常驻的两种独立形态(或关系,缺一不可全防)
守卫命中的条件是下列任一条成立即判"跨常驻"(源码 AssignmentResidentPolicy.evaluate):
| 形态 | 判定条件 | 605036 消息片段 |
|---|---|---|
| 车侧越界 | 目标车辆已设 primary_driver_id,且与被派入成员的当前司机不是同一人 |
"所选车辆已有其他常驻司机" |
| 司机侧越界 | 被派入成员的当前司机本身是别的车的常驻司机 | "所选司机已有其他常驻车辆" |
| 两者皆是 | 车侧、司机侧同时越界 | "所选车辆与司机均已有其他常驻绑定" |
⚠️ 两条是"或"的关系:共用车没设常驻司机(primary_driver_id 为空)只躲开车侧那一条,司机侧照样可能触发——不能靠"挑一辆没设常驻司机的车"来规避这道守卫。
建议的前端交互:先调 precheck 探路,别盲提交吃错误码
POST /admin/fleet/assignments/precheck(#7444 之前就存在,本次未改动其契约)不取锁、不写库、恒成功(源码类注释:"只读、不抛异常、不写、不取锁",@Transactional(readOnly = true)),跨常驻组合会在响应的 warnings[] 里产出一条 type="cross_resident" 的提示项(PrecheckRespVO.WarningItemVO{type, msg}),不影响 conflict/conflicts[](跨常驻是 warning 不是 conflict,不会被判定为阻断)。
🔴 precheck 的调用方式有个坑,Swagger 里一个字都没写:它的入参是"一辆车 + 一名司机"(PrecheckReqVO{vehicleId, driverId, startDate, endDate, ...}),而共用关系确认里司机不是车务选的(见上文)。因此正确调法是:
resourceType=VEHICLE(共用车):对每个待派入成员分别调一次precheck,入参vehicleId=共用车 ID、driverId=该成员当前司机 IDresourceType=DRIVER(共用司机):反过来,vehicleId=该成员当前车辆 ID、driverId=目标共用司机 ID
成员当前司机/车辆 ID 前端可以从既有只读端点拿到,不需要额外新接口:
GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview→GroupDispatchOverviewVehicleVO.driverIdGET /admin/fleet/group-dispatch/resource-schedule→ResourceScheduleItemVO.driverId
逐成员探完一遍 precheck 后,若任一成员命中 cross_resident warning,就在提交前弹出"存在跨常驻车派单,是否确认?"的二次确认框,用户确认后提交时才带 confirmCrossResident=true——这样可以避免车务盲提交后才第一次看到 605036。
✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload | 结果 |
|---|---|---|
| ✅ 不跨常驻 | 不传 confirmCrossResident |
200,正常放行(本字段不影响结果) |
| ✅ 跨常驻,首次提交未确认 | 不传或 confirmCrossResident=false |
605036,fleet_assignment 零写入,整个事务回滚 |
| ✅ 跨常驻,车务已确认 | confirmCrossResident=true,其余字段与首次提交逐字相同 |
200,放行;10 秒防重键已在失败路径释放,无需等待 |
| ❌ 期望服务端自动确认(不传字段,指望后端默认放行) | 同"未确认"payload | 无效——服务端刻意不写死 true,不传就是不确认,会持续收到 605036 |
❌ 收到 605036 后改了其他字段(比如换了 resourceId)才重试 |
与首次不同的请求体 | 这是另一次业务操作,不是"确认重试"——是否命中跨常驻需要重新判定,不能假设加了 confirmCrossResident=true 就万能放行 |
五、数据库行为
confirmCrossResident 不落库、不新增任何列——它是运行时透传给 AssignmentService#change 命令的确认标志,仅用于守卫放行判断,本身不持久化。
| 场景 | 改前 | 改后 |
|---|---|---|
| 跨常驻且未确认 | change() 内部即时抛 605036,fleet_assignment 零写入,confirmInTransaction 整个事务回滚 |
同左,行为不变——差异仅在错误消息文案更详细 |
跨常驻且 confirmCrossResident=true |
不存在这个入参,无法达成——永远停在上一行的 605036,fleet_assignment 永远零写入 |
守卫放行,fleet_assignment 走既有"旧切片转 canceled + 新行 INSERT"逻辑正常写入(该写入逻辑本身不是本次改动,#7444 起就有) |
| 非跨常驻的普通共用确认 | 不受影响 | 不受影响 |
六、边界行为
- 未登录/网关未透传角色 → 401(网关拦截),既有行为
- 其余既有错误码(602100~602112 段)均未变动
- 老数据兼容:本次改动不涉及任何存量数据结构,纯粹是入参层的新增可选字段 + 错误消息模板调整
六.5、枚举 / 数据字典
confirmCrossResident(ShareGroupConfirmReqVO.confirmCrossResident)
所属字段: confirmCrossResident | 类型: Boolean(可空)
| 值 | 说明 |
|---|---|
null / 不传 |
不确认(默认态,与传 false 行为完全一致) |
false |
显式不确认,行为与不传一致 |
true |
确认跨常驻车派单,撞上守卫时放行 |
605036 消息占位符(AssignmentErrorCode.CROSS_RESIDENT_CONFIRMATION_REQUIRED)
| 占位符 | 含义 | 取值示例 |
|---|---|---|
{0} |
触发方描述 | 派单 500(改派场景)/ 新建派单(新建场景,共用关系确认走的是改派场景) |
{1} |
跨常驻的具体形态 | 所选车辆已有其他常驻司机 / 所选司机已有其他常驻车辆 / 所选车辆与司机均已有其他常驻绑定 |
{2} |
车牌 | 蒙B-30000(缺车牌时退回 ID={vehicleId}) |
{3} |
司机姓名 | 李师傅(缺姓名时退回 ID={driverId}) |
warnings[].type(PrecheckRespVO.WarningItemVO.type,precheck 端点既有字段,本次未新增,仅补充说明供前端建议交互使用)
| 值 | 说明 |
|---|---|
cross_resident |
跨常驻(本篇涉及的类型) |
seats_short |
座位不足 |
vehicle_unavailable / driver_unavailable |
车/司机不可用 |
license_expired |
驾照过期 |
veh_inspect_expired / veh_insure_expired |
车辆年检/保险过期 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
ShareGroupConfirmReqVO.confirmCrossResident |
不存在 | 新增,可选 Boolean,默认语义为不确认 |
605036 (CROSS_RESIDENT_CONFIRMATION_REQUIRED) 消息模板 |
"司机与车辆不是常驻组合,请确认跨常驻车派单后重试"(静态文本,无占位符) |
"司机与车辆不是常驻组合,请确认跨常驻车派单后重试({0}:{1};车辆 {2};司机 {3})"(4 个占位符,点名触发方/形态/车牌/司机) |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 共用关系确认遇到跨常驻场景 | 无法完成——没有字段可确认,永远 605036,唯一出路是联系后端手工处理 |
可传 confirmCrossResident=true 二次提交放行,业务上可自助完成 |
605036 报错时车务能否判断该怎么办 |
不能——消息不点名是哪条派单/哪种越界/哪辆车/哪个人 | 能——消息里逐一点名,且 create/change 两条路径同一份口径 |
| 带确认重试是否会撞 10 秒防重窗口 | N/A(无法重试) | 不会——confirmCrossResident 不纳入 idempotentKey(),且失败路径释放防重键 |
六.7、影响评估
- 是否破坏向后兼容: 否——新增的是可选字段,旧前端不传该字段时行为与改动前逐字节一致(跨常驻场景下依旧收到
605036,只是消息文案变详细了) - 前端是否必须同步上线: 是——跨常驻场景此前是纯粹的死路(无法建出这类共用关系),本次解锁了一条此前完全不可达的成功路径;前端不接入就意味着车务在这类场景下永远卡在
605036、无法完成共用关系确认,运营会持续遇到"报错但不知道怎么处理"的问题。若前端此前对605036的message做过精确匹配/正则解析(不太可能但不能排除),需要检查是否受消息模板变化影响 - 前端 workaround 清理点: 若此前车务遇到跨常驻场景的 workaround 是联系后端手工处理(比如手工改库建关系),本次上线后应当撤除该 workaround,改走"precheck 探测 → 二次确认弹窗 → 带
confirmCrossResident=true提交"的正常流程
七、不影响范围
- 仅影响:
POST .../share-groups请求体新增字段confirmCrossResident,以及605036错误消息的文案(消息模板变化,错误码本身不变) - 零影响:
- 请求体其余字段(
serviceDate/resourceType/resourceId/members/costBearer/costBearerOrderId/remark)零变更 - 响应体
ShareGroupRespVO零变更,confirmCrossResident不出现在任何响应里 GET .../share-groups(查询)、DELETE .../share-groups/{shareGroupId}(解除)两个端点零改动——本次 PR 确实触及GroupDispatchShareController,但那 14 行全部落在confirm端点的接口文档注释里(@ApiOperation的 notes),GET/DELETE 的方法签名与请求/响应契约逐字未动precheck/candidates两个读口的请求/响应契约本次未改动——cross_resident这个warnings[].type值早在#7973之前就存在(AssignmentResidentPolicy由#4936引入、#5160扩展),不是本次新增- 非共用关系场景下的普通派车/改派(
create/change两个端点自身的请求/响应契约)零变更,跨常驻守卫判定逻辑本身也没变,只是错误消息更详细 - 无新增 DB 表/列/索引;
confirmCrossResident不落库 #8013(共用关系确认历史)、#7988(团期配车刷新观测口)两篇的改动内容互不影响
- 请求体其余字段(
八、测试环境已验证
环境:测试服;hl-fleet-service 部署点 51571c58a(该提交包含引入 confirmCrossResident 的 076654889,2026-09-19,已用 git merge-base --is-ancestor 核过祖先关系)。
取证时刻:2026-09-21 10:09:15 / 10:09:17 / 10:10:22(下列三次调用的实际发生时刻)。
取证方式:经网关真实调用,非单元测试、非 Mock。原始请求/响应全文见执行台账(会话内保留)。
8.1 场景构造
为得到「成员司机 ≠ 目标车辆常驻司机」这一前置,按以下顺序造数(全部为新建,未复用任何存量数据):
- 新建团期批次
groupBatchId=2101855487131877378(服务日 2026-10-16); - 该团期下建 2 个子订单 A / B,各自提交用车需求(服务日 2026-10-16 ~ 10-18);
- 子订单 A 派单至 车
蒙A-T7777+ 司机宝音德力格尔(D1),子订单 B 派单至 车蒙P304A+ 司机P3测试司机04; - 取第三辆车
蒙P303A(vehicleId=2089686351917039618) 作为共用目标车,其常驻司机为2089686350138630146,与 D1 不同 —— 跨常驻条件成立。
8.2 未传 confirmCrossResident → 拦截(605036)
POST /admin/fleet/group-dispatch/batches/2101855487131877378/share-groups,请求体不含 confirmCrossResident 字段:
{
"serviceDate": "2026-10-16",
"resourceType": "VEHICLE",
"resourceId": 2089686351917039618,
"members": [
{ "sourceType": "ASSIGNMENT", "sourceId": 2101855940422836225 },
{ "sourceType": "ASSIGNMENT", "sourceId": 2101856068395245569 }
],
"costBearer": "GROUP",
"remark": "cross-resident probe"
}
响应(HTTP 200,业务码 605036):
{
"code": 605036,
"message": "司机与车辆不是常驻组合,请确认跨常驻车派单后重试(派单 2101855940422836225:所选车辆与司机均已有其他常驻绑定,继续操作将形成跨常驻车派单;车辆 蒙P303A;司机 宝音德力格尔)",
"data": null,
"success": false
}
报文中点名的「车辆 蒙P303A;司机 宝音德力格尔」与 8.1 构造的跨常驻组合一致,可据此确认拦截确实由该组合触发,而非其他前置校验。
8.3 传 confirmCrossResident: true → 放行(200)
同一请求体追加 "confirmCrossResident": true,其余字段逐字不变:
{
"code": 200,
"message": "成功",
"data": {
"shareGroupId": "360246074662850560",
"groupBatchId": "2101855487131877378",
"serviceDate": "2026-10-16",
"resourceType": "VEHICLE",
"resourceId": "2089686351917039618",
"status": "ACTIVE",
"costBearer": "GROUP",
"costSourceRefNo": "SHARE-360246074662850560",
"members": [
{ "sourceType": "ASSIGNMENT", "sourceId": "360246074935480320",
"requirementId": "2101855514109640706", "orderId": "2101855487085740033" },
{ "sourceType": "ASSIGNMENT", "sourceId": "360246075224887296",
"requirementId": "2101855521428647938", "orderId": "2101855500247465986" }
],
"confirmedBy": "2101000047826030594",
"confirmedAt": "2026-09-21 10:09:17",
"version": 0
},
"success": true
}
两次调用唯一的差异就是这一个字段——同一批 members、同一 resourceId、同一 serviceDate,前者 605036、后者 200。
8.4 副作用核验(独立口径)
响应里 members[].sourceId 已由原派单 id 变为改派后的新派单 id(360246074935480320 / 360246075224887296)。另经两条互相独立的读口复核:
POST /admin/fleet/assignments/candidates的canonicalSnapshot.cells:2026-10-16 当天的 cellvehicleId已变为2089686351917039618(目标共用车),driverId仍为 D1 —— 本端点只改车、不改司机,与三节接口说明一致;- 直查
fleet_assignment:上述两个新派单 id 的vehicle_id均为2089686351917039618、service_date为2026-10-16,与网关响应逐字吻合。
8.5 现场清理
DELETE /admin/fleet/group-dispatch/share-groups/360246074662850560?survivorPolicy=RELEASE(2026-09-21 10:10:22):
{
"code": 200, "message": "成功",
"data": {
"shareGroupId": "360246074662850560",
"status": "RELEASED",
"survivorPolicy": "RELEASE",
"keptSourceIds": [],
"releasedSourceIds": ["360246074935480320", "360246075224887296"],
"pendingReassignSourceIds": []
},
"success": true
}
8.6 随 PR #7978 合入的单测(本轮未重新执行)
⚠️ 下列是代码仓内的断言意图,不是本轮的执行结果;本篇的实测证据是 8.2–8.5。 之所以仍然列出,是因为其中两条覆盖了网关取证没有覆盖到的分支(已标 ⭐):
AssignmentServiceTest(新增 2 条)
change_crossResidentByDriverBoundToAnotherVehicle_throws605036
→ 只有「司机是别的车的常驻司机」这一侧成立时也抛 605036(不需要车侧同时成立);
并断言 assignmentMapper 的 cancelActiveRowsByIds / insert 均未被调用
change_crossResidentByDriverBoundToAnotherVehicle_confirmed_succeeds
→ 同场景带 confirmCrossResident=true 放行;
⭐ 断言 vehicleService.rebindResidentVehicleFromDriverSide 未被调用,
即「确认跨常驻」不会顺手改写司机的常驻绑定(网关取证未覆盖此点)
GroupDispatchShareAdmissionServiceTest(新增 4 条)
confirm_confirmCrossResidentTrue_isPassedThroughToChange
→ true 原样透传到 change 命令
confirm_confirmCrossResidentFalse_isPassedThroughAsFalse
→ ⭐ 显式传 false 时原样透传、不会被悄悄改写成 true
(网关取证只验了「不传」与「传 true」两种,没验显式 false)
confirm_crossResidentWithoutConfirmation_605036PropagatesOut
→ 605036 原样抛出,不被本层吞掉、也不被翻译成 602xxx
confirm_crossResidentConfirmed_passesTheSameGuard
→ 与上一条同一夹具,仅多带一个 true 即放行
十、相关文档
- 关联 Issue: wx/HL#7973
- 关联 PR: wx/HL#7978(squash 合并至 dev-v3 @
076654889) - 共用关系确认端点完整契约见
#7444changelog(changelogs-v2/2026-09/21_7444_团期配车就绪门禁与车辆共用关系-修改接口-管理后台.md,由团期车务会话维护与推送) - 共用关系确认历史的另一处补丁见
#8013changelog(changelogs-v2/2026-09/20_8013_共用关系确认历史补记成本承担方变更-修改接口-管理后台.md)
关联 / 联系人
链接
联系人
- 后端负责人: wx(GIT)
- 前端负责人: mmg