文件
hl-api-changelog/changelogs-v2/2026-09/21_7444_团期配车就绪门禁与车辆共用关系-修改接口-管理后台.md
T

109 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 7444 团期配车就绪门禁两档判定(blockers/warnings)+ 车辆共用关系(4 新增 + 6 改造接口) admin wx(GIT) 修改接口 deployed verified verified mmg 56dc94557738de2c4b301453148e1a685597bc83 v2.1 2026-09-22 gateway_status=verified 的判据(2026-09-21 实测):经网关 https://api.test.1814.love:9443 对本篇 10 个端点中**全部 8 个位于网关面上的 /admin/fleet/** 端点**逐个发起真实请求并记录响应信封的 code —— share-groups 确认(600009 不存在团期)/查询(200 空集)/解除(602110 缺策略、602108 关系不存在)、readiness(200 两档结构完整 ready=false+warned=true+3 类 blocker+WARN_SEAT_SHORTAGE;整团免车团期 602113 失败关闭不降级)、reconfigure(600002 与字段级 400)、precheck(200,conflicts[] 实测带齐 blocking/reasonCode/shareGroupId 三个新字段)、candidates(200 正常分页;缺 orderId 时 400)、restore-cancel(605009 派单不存在),结构与错误码均与正文一致,逐条读数见正文「八、测试环境已验证」8.1。写口一律用零副作用错误路径取证,测试环境未因取证产生业务数据变更。剩余 2 个 internal 端点(/internal/fleet/dispatch/.../release、/v3/internal/group-batch/.../vehicle-ready)**按网关设计就不在网关面上**:hl-gateway JwtAuthFilter 在所有鉴权分支之前对 /internal/**、/v3/internal/** 直接 forbidden('接口不可访问'),实测两者均返回 code=403;它们由服务间 Feign 触发、前端不可调也不需调,故不计入本字段的判定范围(不是失败)。 backend_status=deployed 的判据(2026-09-21 实测):deploy_panel deploy-backend 分别部署 hl-fleet-service 与 hl-order-service-v3,服务端 git sync done: branch=dev-v3 HEAD=bb4091074;fleet 17:14:32 新 jar 111247255 B,实例 8187(PID 1598964)/8087(PID 1599321) 滚动重启并健康检查 UP;order-v3 17:16:07 新 jar 328980166 B,实例 8186(PID 1607261)/8086(PID 1608074) UP;两次任务 exit_code=0。origin/dev-v3 在部署时刻即为 bb4091074,故测试服跑的字节就是本篇定稿点的字节。⚠️ 独立第二判据的局限(照实记):上一次部署点 311dc92ee 与 bb4091074 之间 13 个 fleet 提交里,行为差异(#8061/#8051/#8064/#7994)全部需要一条真实的活跃共用关系作前置才能观测,本次取证未构造该前置;原本可做版本判别的 swagger /hl-*/v2/api-docs 被测试服 nginx 前置 basic auth 401 拦掉(4 个路径变体与 order-v3 同路径一并 401,故是路径规则不是服务问题),actuator 亦被网关按小程序端口径 403。因此第二判据退化为「8 个端点在部署后经网关全部取到与 dev-v3 源码一致的业务读数」,它能证明本篇代码在测试服上活着,但对「本次 17:14 部署 vs 上一次部署」没有分辨力。 本轮对正文做的契约补充(对 19be7f21c..bb4091074 逐提交读码后确认原稿未覆盖):(1) precheck 的 cityJunctionShareCandidate 改写为与 candidates 同名字段逐字同义(#8004 已把两侧拉齐),原稿「两处不要复用同一个渲染判断」的说法与源码相反,已删;(2) precheck 与 candidates 入参表补 excludeAssignmentId(candidates 另补 assignmentGroupId 成对要求),并写明「改派不传即按新建派单处理、不享受共用授权,读数与功能没做一模一样」这一失败形态(#8004 AC-2/AC-3);(3) 接口 3 解除的三种策略范围按 #8061 改写为「只到本关系的成员为止」,并补 #8051「不跨服务日、不跨团期」,收口断言范围同宽;接口 5 clearAll 分支同步标注;(4) 补 #8064:自动收缩 AUTO_SINGLE_MEMBER 此前整段挂在 DUAL_WRITE 分支而环境全为 LEGACY,上线至今零执行,现已补 LEGACY 挂点,前端必须真的渲染这一态;并写明整团解除原因列豁免(AC-10)与 LEGACY 少覆盖的那一种情形;(5) 补 #8013:COST_BEARER_CHANGED 由死枚举转活,改前这类变更被记成 MEMBER_ADDED,且「什么都没变」不再写历史行;(6) 补 #8003:跨维度准入会把其它关系/其它维度里记着同一张旧派单 ID 的活跃成员行一起改绑,故接口 1 成功后 sourceId 的回读范围要扩到其它维度的关系;(7) 接口 5 补一条覆盖边界:602013 属 #7442 既有契约、不在本篇 10 端点变更面内,#7994 的调整不改其对外结构。 六.8「错误码全集(按端点对照)」已按 origin/dev-v3 的 10 个端点 javadoc 逐端点(不是按错误码类)重新核对:覆盖完整、无源码中不存在的码;602100 段实占 602100-602114 共 15 个,其中 GroupDispatchShareErrorCode.SEGMENT_END=602199 是段位预分配上界常量、不是错误码;809205/809206 是 discardCode 取值、正文已标注其非错误码。 本文件是 #7444 的唯一 changelog(2026-09-19 已合并去重同目录旧稿)。PR-1(#7956, d97babc9e)+PR-2a(#7959, 43a5b7153)+PR-2b(#7963, 19be7f21c) 均已合入 dev-v3,其后的契约面修订(#8004/#8003/#8013/#8051/#8061/#8064/#8002)已按上列条目并入正文。 前端已交付(mmg 2026-09-22, hl-admin 56dc9455):readiness 两档展示(ready/warned 独立共存,602113/602114 失败关闭只出灰态「暂时无法判定」不画红绿黄,shareGroupCount 仅展示);share-groups 面板(ACTIVE 卡片+已解除历史,五 action 与 AUTO_SINGLE_MEMBER/COST_BEARER_CHANGED 真渲染,车牌经 resource-schedule 二次查询兜底 ID;解除 survivorPolicy 必传,602108 提示+关窗重拉,602109/605008 可重试,605075 不给重试引导);reconfigure clearAll 分支(demands 省略,有 ACTIVE 关系 survivorPolicy 前端必填前置,响应四字段展示,pendingReassignSourceIds 醒目);precheck/candidates 实证前端已按 blocking 判定零增量,restore-cancel/precheck 前端零调用方。挂起项:接口1 建关系确认交互——成员候选读口(当日团级配车行 dispatchId 清单+当日逐户接送派单 assignmentId 清单)不在 10 端点契约内,前端无可枚举来源,待后端补读口或确认取数路径后跟进。 2026-09-21 dev-v3

团期车务:配车就绪门禁(红/黄两档)与车辆共用关系(工单 #7444)

存放目录: 二期(order-v3 标签工单)→ changelogs-v2/2026-09/

服务: hl-fleet-service(主,8087)+ hl-order-service-v3(副,8086,仅 vehicle-ready 回调链路内核微改) PR: #7956(PR-1 就绪门禁)、#7959(PR-2a 共用关系核心)、#7963(PR-2b 共用关系读侧与释放侧) Issue: #7444 日期: 2026-09-19(PR-2b 合入日;PR-1/PR-2a 分别合入于更早) 影响范围: 管理后台「团期配车页」(车务共用确认交互,见 wx 拍板 D5)、车务派单页(预校验/候选列表标红变绿)、团期看板配车就绪展示


⚠️ 关键变化

  • 🔴 鉴权口径订正(对既有 javadoc 的改判,不是本单新增):GroupDispatchShareController 等控制器 javadoc 里写的"权限点 fleet:group-dispatch:write/fleet:group-dispatch:view"在服务端不起任何作用——全 fleet 服务 hl-fleet-service/src/main/java/com/hulalv/fleet/dispatch 包下零 @PreAuthorize/@SaCheckPermission 等权限注解;fleet:group-dispatch:view 虽注册进了库(V20260910_007),但 V20260911_007__fix_group_dispatch_admin_grants.sql 文件头明确记载"该权限码在 Java 侧零引用,即便显式授予仍 403(AC-12 实测)",并已把 ADMIN 角色对它的授权收回。/admin/fleet/** 全部端点(含本单新增/改造的全部 10 个)的真实准入是路径级角色门禁:服务层 FleetAdminRoleGuardInterceptor(挂在 /admin/fleet/** 全量,FleetWebMvcConfig.java:21-22)+ 网关层 JwtAuthFilter.java:472-479 双重校验请求头 X-Admin-Role 必须是 VEHICLE_MANAGER 或 SUPER_ADMIN,不满足直接 403「无权限访问车务管理,请切换到车务角色」,与具体权限码无关。前端如果按"权限点"模型去控制按钮/菜单可见性,需要改成按角色判断;下方各接口小节的"权限点"字样在业务边界里统一按本条订正理解。
  • 本次是两组能力的合并交接:①团期配车就绪判定从"一个隐式布尔"改成两档——硬拦 blockers[](红,阻断)与只提醒 warnings[](黄,不阻断);②新增"同一辆车同一天既跑团级行程又接送若干户"的车辆共用关系机制,车务在团期配车页人工确认后,被确认的几方互相放行冲突判定。
  • ready=true && warned=true 是合法且常见的正常态,前端不得把它画成失败或红色——车排齐了、司机还没排,属于"能发车但有缺口"。
  • POST /admin/fleet/assignments/precheck 的 conflicts[] 新增 blocking/reasonCode/shareGroupId 三个字段——是全新字段,不是既有字段改取值。PrecheckRespVO.ConflictItemVO 在本次之前只有 type/conflictAssignmentId/conflictOrderNo/conflictDateRange/cityJunctionShareCandidate/msg 六个字段(源码字段名是 conflictDateRange 一个合并字符串,不是分开的 conflictStartDate/conflictEndDate,工单正文写的是内部 BO 字段名,与对外 VO 不一致,本文档以对外 VO 为准)。前端要为这三个新字段新增渲染分支,不能假设它们已经存在于历史响应里。
  • 共用确认端点带 @Idempotent(timeout=10):10 秒内重复提交同一份成员全集会被防重窗口拒绝(code=100502),前端按"请勿重复提交"提示,不要当失败报红;10 秒之外重复提交同一份成员全集会正常受理并返回同一个 shareGroupId,那是幂等覆盖,是成功。
  • 🔴 ASSIGNMENT 成员的 sourceId 在被本端点派上车后会变,不能把成功响应当模板原样缓存重发:admissionIntent=PENDING_ADMISSION 的成员走 AssignmentService.change 准入,旧派单行转 canceled、生成新行并改绑 source_id;十几秒后用同一份(已过期的)请求体重发会抛 602102,这不是幂等坏了,是引用的派单行已经不存在了。改动前端先调「接口 2」查询回读最新 sourceId 再拼请求体,详见「接口 1」业务边界。
  • 解除关系 / reconfigure 的 clearAll / 内部 release(流团)之后,幸存派单会被真实处置,不是只打标记:REASSIGN/RELEASE 两种策略下,占用已经真的被释放,对应派单进入"待改派"业务态;响应里的 pendingReassignSourceIds[] 就是这份清单,前端必须展示出来,不展示等于把"有几户的车被撤了"这件事悄悄吞掉。
  • costSourceRefNo 按关系身份生成(SHARE-{shareGroupId}),不是按 (团, 日, 维度, 资源) 生成:同一个槽位上解除关系后重建,会拿到一个新的 costSourceRefNo;核团/财务人工比对时必须以 costSourceRefNo 为准,不能按"团+日+车"去猜同一笔钱。
  • 窗外日(接送机日期落在团期服务日窗外)不进共用关系,这是有意边界,不是缺陷:这几天的接送机 claim 仍走原有的城市衔接(R3-EX)判定。
  • 🔴 源码核对发现工单正文有两处错误码写错,本文档已按源码更正(详见「六.6、修改前后对比」与各接口的「错误响应」):POST .../share-groups(确认)与 GET .../share-groups(查询)两个端点里,resourceType 传非法值实际返回的是通用参数错误 100001,不是工单正文写的 602114;602114 实际只在 GET .../readiness 就绪读口里使用。
  • 🔴 POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready 请求体没有加 readinessSnapshot 字段——这是工单正文最初的设计,但 2026-09-18 已被撤回(改 hl-common-core 代价太大,且该字段唯一的消费场景后来也被砍掉),源码里请求体 GroupBatchVehicleReadyReqDTO 仍是 #7442 定的四个字段(requirementId/requirementVersion/planVersion/sourceRefNo),未改动。该端点本次的唯一变化是内核多做一步"团级正式需求 DISPATCHED → DONE",对请求/响应契约无影响。
  • 🔴 自动收缩(RELEASED + reason=AUTO_SINGLE_MEMBER)此前一次都没真正执行过,2026-09-21 起会真的触发(#8064 / PR #8073):这条链原先整段挂在占用双写协调器的 DUAL_WRITE 分支上,而测试与生产环境 fleet_occupancy_rollout_fence 的 64 个 stripe 实测全为 LEGACY ⇒ 它上线至今零执行。现已补上 LEGACY 形态的对等挂点。前端必须真的能渲染「关系被系统自动解除」这一态(列表里关系消失 + 历史多一条 RELEASED/AUTO_SINGLE_MEMBER),不能按"理论上存在、实际不会出现"处理。配套(#8064 AC-10 / PR #8077):整团解除(CLEAR_ALL / GROUP_RELEASE)时原因列被豁免,不会被 AUTO_SINGLE_MEMBER 夺走,审计能分清"人解的"还是"系统收的"。
    • 覆盖边界(照写,不是缺陷):LEGACY 挂点比 DUAL_WRITE 挂点少覆盖一种情形——「同一个 claim 原地换到另一个资源」(成员仍活跃但资源变了)。LEGACY 下没有账本行能告诉它旧资源是哪个,这一支不触发自动收缩。
  • COST_BEARER_CHANGED 从死枚举变成活枚举(#8013 / PR #8020):改前只改成本承担方(成员全集没变)会被记成 MEMBER_ADDED,该枚举从未被写入过;现在这种变更记 COST_BEARER_CHANGED,且"什么都没变"的重复提交不再写任何历史行。前端若按 history[].action 分类渲染,要补这一支;也不要再把"只改了成本承担方"的记录当成"有人加了成员"。
  • 🔴 跨维度准入会把「其它关系/其它维度」里记着同一张旧派单 ID 的活跃成员行一起改绑(#8003 / 865fe40ec):成员身份键是 (服务日, 维度, 来源类型, 来源 ID),维度在键里,所以同一张接送派单可以同时是车维度关系与司机维度关系的成员;而准入走的 AssignmentService.change 换的是整行(旧行转 canceled、新行拿新 ID)。改前只改绑本次确认的那一个维度,另一维度会留一条指向 canceled 派单的悬挂成员行。对前端的直接影响:「接口 1」成功后需要回读 sourceId 的范围不止本次确认的这个维度——同一张派单在其它维度关系里的成员行也换了 ID,那些关系的成员列表缓存同样要刷新(否则拿旧 sourceId 重发会撞 602102)。

一、背景

团期车务链路走到"配车"这一段时,此前有两个缺口:

  1. 假就绪:就绪判定只比对服务日集合,不看座位数、不看人数,每天排一辆 5 座车服务 17 人团照样判"就绪"。审查要求加容量校验,与既定设计(车型/车辆数/人数限制已在三周前全部放开,只留日期门禁)正面冲突。wx 拍板 D9:缺口可见但不阻断——硬拦三项(分组齐全/服务日逐日配满/需求与计划已确认)判 ready,只提醒两项(座位/司机)判 warned,两者互相独立。
  2. 同车同日既跑团级行程又接送逐户,此前无合法表达:占用层与派单层的两两冲突守卫会把"团车 G 同时接甲、乙两户"里的 (甲,乙) 这一对纯逐户组合按城市衔接(R3-EX,跨城即拒)判掉,物理上允许的排车方式在系统里排不进去。wx 拍板 D11:共用是集合语义——(团, 日, 维度, 资源) 下的一个成员集合,同属一个已确认关系的 claim 互相放行;车务在团期配车页人工确认(wx 拍板 D5)。
维度 改前 改后
就绪判定 单一隐式布尔,只比服务日集合 硬拦 blockers[](红)+ 只提醒 warnings[](黄)两档,人数/座位纳入判定
同车同日接团级行程+逐户接送 无法表达,两两冲突守卫按城市衔接判 车务人工确认共用关系后,关系内成员互相放行
冲突判定原因 只有"是否城市衔接可共享候选" 新增 reasonCode=SHARE_GROUP_CONFIRMED,可追溯是哪条人工授权放行的

工单 #7444 是团期车务共 7 段(V-0~V-6)中的第 6 段(V-5),也是本期最后落地的一段,硬前置是 #7442(V-3 配车写口与恢复流程)与 #7443(V-4 接送机与团期身份)两张单,两者均已先行合入 dev-v3。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 确认团期车辆共用关系 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups 新增接口 复合原子操作:授权+把成员派上车+占用准入同一事务
2 查询团期车辆共用关系 GET /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups 新增接口 includeReleased=true 时含变更历史
3 解除团期车辆共用关系 DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId} 新增接口 幸存成员三选一,均为实际占用处置
4 团期配车就绪判定 GET /admin/fleet/group-dispatch/batches/{groupBatchId}/readiness 新增接口 硬拦 blockers[] + 提醒 warnings[] 两档
5 整团逐日配车提交 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure 修改(clearAll 分支新增请求字段 + 4 个响应字段) 沿用 #7442 端点,本次只改整团清零分支
6 派单预校验 POST /admin/fleet/assignments/precheck 修改(conflicts[] 新增 3 字段) 认共用关系后车不再误标红
7 查询派单候选资源 POST /admin/fleet/assignments/candidates 修改(conflicts[] 新增字段 + 既有字段语义扩展) 同上,候选面
8 撤销取消的派单 POST /admin/fleet/assignments/{assignmentId}/restore-cancel 修改(新增共用关系放行分支) 恢复本属共用关系的接送派单不再被误拒
9 幂等释放团期配车占用(内部) POST /internal/fleet/dispatch/group-batch/{groupBatchId}/release 修改(新增共用关系连带处置) 仅限内部 Feign,order-v3 流团/取消成团时调用
10 回填配车就绪(内部) POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready 修改(内核新增 DISPATCHED → DONE 一跳) 仅限内部 Feign,请求/响应契约不变

独立核对结果(未照抄工单数字):本人逐条数了「接口变更」源节(工单 D:/work2/_scratch/body_7444.md 第 360~646 行)里标"新增"与标"改造"的小节标题,结果是 新增 4 个(表中 #1~#4)、改造 6 个(表中 #5~#10),与工单 AC-32 自己声称的"4 新增 + 6 改造"一致。工单原文自曝过这个数字"此前一度写 6 而实际是 5,靠补的 vehicle-ready 一节才凑上,属巧合对上、不是核对过"——本次是重新数过的,不是沿用那次巧合。另有 1 个"复用不改"的内部端点 GET /v3/internal/group-batch/{groupBatchId}/dispatch-baseline(order-v3 → fleet,仅供 fleet 拉团期基线用),因为契约无变化、且 hl-ui 不会调用它,不计入本表,不建独立详情小节。


三、接口详情

1. 确认团期车辆共用关系 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups

VO: ShareGroupConfirmReqVO → ShareGroupRespVO

(源码核对:GroupDispatchShareController.java:53-79、ShareGroupConfirmReqVO.java、ShareGroupMemberReqVO.java、ShareGroupRespVO.java、ShareGroupMemberRespVO.java)

使用场景

车务在团期配车页(wx 拍板 D5)排团期大巴时,同页看到本团当日还有哪几户要接送机,勾选"本车承接"后调用本接口。服务端在同一次调用、同一个事务里完成三件事:①落人工授权;②把 admissionIntent=PENDING_ADMISSION 的成员(尚未占上这辆车/这名司机的)派进去;③由占用准入读到刚落的授权而放行。中途任一步失败整体回滚,不会留下"关系已建但没占上车"或"占上车但没关系"的中间态。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID(雪花)
serviceDate Body LocalDate ✅ 必须落在该团基线 serviceDates[] 内 共用发生的服务日;窗外抛 602104(有意边界)
resourceType Body String ✅ VEHICLE/DRIVER,非法值抛 100001(⚠️ 不是工单原写的 602114) 资源维度;车与司机分别建关系
resourceId Body Long ✅ - 车辆 ID 或司机 ID
members Body Array ✅ 2~20 个,越界抛 602100/602101 成员全集(不是增量),重复提交按新全集覆盖
members[].sourceType Body String ✅ ASSIGNMENT(逐户接送派单)/GROUP_DISPATCH(团级配车行) -
members[].sourceId Body Long ✅ - fleet_assignment.assignment_id 或 fleet_group_dispatch.dispatch_id
members[].admissionIntent Body String ❌ OCCUPYING(默认,已占着)/PENDING_ADMISSION(本次一并派入) ⚠️ 服务端完全不读、不校验、不据它分支,纯粹供前端做按钮文案/二次确认提示;服务端一律按库里实际占用现查现判
costBearer Body String ✅ GROUP(记团级整车)/ORDER(记指定户)。⚠️ 缺失时返回的不是 602107,而是 HTTP 400 + 成本承担方不能为空(ShareGroupConfirmReqVO:49 的 @NotBlank 在业务层之前拦截,实测 2026-09-20)。602107 只在业务层触发:costBearer=ORDER 但 costBearerOrderId 缺失或不属于成员订单 成本承担方
costBearerOrderId Body Long 条件必填 costBearer=ORDER 时必填,且必须是成员中某个 ASSIGNMENT 的订单 -
remark Body String ❌ ≤200 确认备注
confirmCrossResident Body Boolean ❌ 不传 / false 都表示不确认,服务端不会替你确认 🔴 跨常驻车派单确认:被派入的成员司机与这辆共用车不是常驻组合时,第一次提交会被 605036 拒;向车务展示消息原文并得到确认后,带 confirmCrossResident=true 原样重发同一份请求。该字段不进防重键,被 605036 拒掉的那一次会释放 10 秒防重键,所以带 true 立刻重发不会撞「请勿重复提交」

出参字段表

字段 类型 说明
shareGroupId String 共用关系 ID(雪花,序列化为字符串)
groupBatchId String 团期主订单 ID
serviceDate LocalDate 共用发生的服务日
resourceType String VEHICLE/DRIVER
resourceId String 车辆或司机 ID
status String ACTIVE/RELEASED
costBearer String GROUP/ORDER
costBearerOrderId String costBearer=ORDER 时的承担订单 ID;否则 null
costSourceRefNo String 车费来源引用,固定形如 SHARE-{shareGroupId};关系生命周期内恒定,解除后同槽重建会拿到新值
members[] Array 成员全集
members[].sourceType String ASSIGNMENT/GROUP_DISPATCH
members[].sourceId String 成员来源 ID
members[].requirementId String 用车需求 ID(ASSIGNMENT 成员的准入授权键;团级成员为 null)
members[].orderId String 订单 ID(ASSIGNMENT 成员;团级成员为 null)
confirmedBy String 确认人 adminId
confirmedAt LocalDateTime 确认时间
version Integer 乐观锁版本号
history Array 本端点恒为 null/空(只有 includeReleased=true 的查询接口才带历史)

⚠️ 成员出参没有 orderNo/label 字段——ShareGroupMemberRespVO 实际字段是 sourceType/sourceId/requirementId/orderId 四个,没有工单正文示例响应里写的 orderNo/label,前端如需展示单号/文案需自行按 orderId 二次查询,不能指望本接口直出。

请求示例

{
  "serviceDate": "2026-09-12",
  "resourceType": "VEHICLE",
  "resourceId": 1,
  "members": [
    { "sourceType": "GROUP_DISPATCH", "sourceId": 99001 },
    { "sourceType": "ASSIGNMENT", "sourceId": 88001, "admissionIntent": "OCCUPYING" },
    { "sourceType": "ASSIGNMENT", "sourceId": 88002, "admissionIntent": "PENDING_ADMISSION" }
  ],
  "costBearer": "GROUP",
  "remark": "团车上午行程后接甲乙两户"
}

(路径参数 groupBatchId=8801)

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "shareGroupId": "77001",
    "groupBatchId": "8801",
    "serviceDate": "2026-09-12",
    "resourceType": "VEHICLE",
    "resourceId": "1",
    "status": "ACTIVE",
    "costBearer": "GROUP",
    "costBearerOrderId": null,
    "costSourceRefNo": "SHARE-77001",
    "members": [
      { "sourceType": "GROUP_DISPATCH", "sourceId": "99001", "requirementId": null, "orderId": null },
      { "sourceType": "ASSIGNMENT", "sourceId": "88001", "requirementId": "5501", "orderId": "70123" },
      { "sourceType": "ASSIGNMENT", "sourceId": "88002", "requirementId": "5502", "orderId": "70124" }
    ],
    "confirmedBy": "1001",
    "confirmedAt": "2026-09-12 18:20:33",
    "version": 1
  },
  "success": true
}

空数据 / 降级响应

本接口是同步写操作,不存在空数据形态。团期基线不可用时失败关闭,返回 600009(团期配车权威基线不可用),不会静默放行或降级判定。

错误响应

{
  "code": 602103,
  "message": "成员派单不属于本团或团期身份未知: 88099",
  "success": false,
  "data": null
}

10 秒内重复提交同一份成员全集:

{
  "code": 100502,
  "message": "共用关系确认处理中,请勿重复提交",
  "success": false,
  "data": null
}

🔴 成员司机与这辆共用车不是常驻组合,需要车务确认后重发(confirmCrossResident=true):

{
  "code": 605036,
  "message": "司机与车辆不是常驻组合,请确认跨常驻车派单后重试(派单 88002:车侧已有常驻司机;车辆 蒙E·12345;司机 张建国)",
  "success": false,
  "data": null
}

抢到资源锁后锁租约丢失(重试无效,本次未提交):

{
  "code": 605075,
  "message": "资源锁已失效(vehicle:1:2026-09-12),本次操作未提交;请确认是否存在并发操作后重新发起",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:路径级角色门禁,X-Admin-Role 须为 VEHICLE_MANAGER 或 SUPER_ADMIN(见「⚠️ 关键变化」鉴权订正),不是权限点模型。
  • 🔴 本接口会返回 602 段以外的码:第 ② 步「把未占上车的成员派进这辆车」走的是派单写路径,所以派单域的 605 段错误码会原样透到本接口的响应里。按处置方式分三类:
    • 需要车务表态后重发:605036(跨常驻车派单需确认)——见下一条,前端必须实现确认框;
    • 不可重试,必须人工核查:605075(资源锁已失效,本次未提交)——不要做自动重试,提示"本次未提交,请确认是否有其他人同时在改这辆车/这名司机,核对后重新发起";
    • 可原样重试:605008(抢锁超时,系统繁忙)——直接重发同一份请求即可。 另有"该成员的车/司机当下不可派"一类(605037 车辆维保或停用 / 605038 司机休假或待激活 / 605070 司机不存在),处置是换资源或把该成员移出本次成员全集,重试无用。
  • 🔴 605036 跨常驻车派单需确认,前端必须实现确认框:满足下列任一条即判"跨常驻",未确认一律拒——① 这辆共用车已设常驻司机,且与该成员当前司机不是同一人;② 该成员当前司机本身是别的车的常驻司机。⚠️ 两条是或的关系:这辆共用车没设常驻司机只躲开 ①,② 照样会触发。 ⚠️ 车务在页面上选的是"一辆车",但 605036 说的是司机——resourceType=VEHICLE 时被派入的司机不是车务选的,是该成员原派单上的司机(服务端按成员原样带过去,本接口不改司机)。所以 605036 的 message 会点名「哪条派单 / 跨常驻的具体形态 / 车牌 / 司机姓名」四件事,直接把 message 原文展示给车务,不要自拟文案(自拟的话车务看不出是哪一个成员触发的,也就无从判断该不该勾确认)。 处置:弹确认框「确认跨常驻车派单?」→ 车务点确认 → 把原请求体加上 confirmCrossResident: true 原样重发。不传或传 false 都表示不确认,服务端不会替你确认。
  • 🔴 605075 与 605008 长得像,处置相反:两条都发生在抢资源锁这一环,文案都提到"锁",但 605008 是没抢到,重试有效;605075 是抢到过、持有期内又丢了,重试无效,且意味着持锁期间可能已有并发写入。UI 上 605008 给"重试"按钮,605075 不要给,改为提示核对后重新发起。
  • 防重与幂等是两件事:10 秒内重复提交同一份 (serviceDate, resourceType, resourceId, members 排序摘要) 会被 @Idempotent 拒绝(code=100502),前端按"请勿重复提交"提示,不要当失败报红;10 秒之外重复提交同一份成员全集会正常受理并返回同一个 shareGroupId,这是幂等覆盖,是成功,不是报错。
  • 只改 costBearer 不改成员时,10 秒防重窗口不受影响——防重键含成员集合摘要,改成员集合即视为新操作,可立即受理。
  • 🔴 ASSIGNMENT 成员的 sourceId 在准入后会变,重提必须先重读:admissionIntent=PENDING_ADMISSION 的成员被本接口派上车时走 AssignmentService.change,旧派单行转 canceled、生成一条新行,共用关系成员表的 source_id 会被静默改绑到新行 ID(GroupDispatchShareAdmissionService.java:604 rebindSourceId)。前端不得把某次成功响应的请求体当模板原样缓存重发——同一份 JSON 在 T0 成功,到 T+12s 用同一个(已过期的)sourceId 原样重发会抛 602102(成员在该资源日无活跃占用),这不是幂等窗口失效,是它引用的派单行已经不存在了。正确做法:改动成员集合前先调「接口 2」查询当前 members[].sourceId,用回读到的最新值拼请求体。
    • 🔴 回读范围要扩到「其它维度的关系」(#8003 / 865fe40ec):成员身份键是 (服务日, 维度, 来源类型, 来源 ID),维度在键里,同一张接送派单可以同时是车维度关系与司机维度关系的成员;而 change 换的是整行,两个维度记的都是那个旧 ID。现在准入会把其它关系/其它维度里仍记着旧 ID 的活跃成员行一起改绑。所以本次确认成功后,前端要刷新的不只是本关系——同一张派单参与的其它关系(典型是先建 VEHICLE 再建 DRIVER)的成员列表缓存同样已过期,拿旧 sourceId 提交会撞 602102。
  • 窗外日拒绝是有意边界:serviceDate 不在团期基线服务日窗内一律 602104,不是缺陷,该日的接送机 claim 仍走原有城市衔接判定。
  • 成员已在另一个 active 关系里(602106)——必须先解除原关系才能加入新关系。
  • GROUP_DISPATCH 类型成员必须属于本团且当日在该资源上活跃(602105,源码 GroupDispatchShareAdmissionService.java:284-292):members[].sourceType=GROUP_DISPATCH 时,sourceId 对应的团级配车行如果不存在、已软删,或属于另一个团,触发本码,{0} 占位符回填该 sourceId。前端应引导车务刷新本团配车行列表后重新勾选,不要提交页面缓存里的过期 dispatchId。
  • 提交前的回读断言(602102):正常端到端流程中很少能拿到这个码——非占用成员会先在派单写路径上撞到它自己的码(605 段);602102 主要在"派单写路径异常返回成功但没有真正落 membership"这种边界情况下触发,触发后整体回滚。
  • 并发写冲突可重试(602109,源码 GroupDispatchShareAdmissionService.java:349-389):本接口在"新建或复用该 (团, 日, 维度, 资源) 上的活跃关系"这一步命中——同一关系的乐观锁 CAS 未中(costBearer 覆盖时版本号被并发改写),或两个请求同时对同一 (groupBatchId, serviceDate, resourceType, resourceId) 抢建新关系触发唯一键冲突,均落本码。前端按"网络繁忙,请重试"直接重新提交即可,不需要人工介入;本码同样会出现在「接口 3」解除路径的并发场景,见该节业务边界。
  • 成员数越界(<2 或 >20)分别抛 602100/602101。
  • 车与司机分别建关系——同一批成员既要共用车又要共用司机,需要分别调两次本接口(resourceType=VEHICLE 一次、DRIVER 一次)。

2. 查询团期车辆共用关系 GET /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups

VO: ShareGroupQueryReqVO → List<ShareGroupRespVO>

(源码核对:GroupDispatchShareController.java:86-97、ShareGroupQueryReqVO.java、GroupDispatchShareService.java:125-144)

使用场景

团期配车页加载共用关系列表;includeReleased=true 时一并查历史关系与变更记录,供核团/排障时回溯"这条关系是谁、什么时候、为什么解除的"。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID
serviceDate Query LocalDate ❌ 不传=全部服务日 -
resourceType Query String ❌ VEHICLE/DRIVER,非法值抛 100001(⚠️ 不是工单原写的 602114) 不传=两者都查
includeReleased Query Boolean ❌ 默认 false true 时一并返回已解除关系与变更历史

出参字段表

字段 类型 说明
(数组元素)shareGroupId / groupBatchId / serviceDate / resourceType / resourceId / status / costBearer / costBearerOrderId / costSourceRefNo / members[] / confirmedBy / confirmedAt / version 同「接口 1」出参 -
history[] Array 仅 includeReleased=true 时非空
history[].action String CREATE/MEMBER_ADDED/MEMBER_REMOVED/COST_BEARER_CHANGED/RELEASED
history[].memberSnapshot String 该次变更后的成员全集快照(JSON 文本,不是增量);RELEASED 动作记的是被解散前的那一份名单
history[].costBearerSnapshot String 该次变更后的成本承担方
history[].operator String 操作人 adminId;系统自动触发(自动收缩/内部 release)为 "0"
history[].operateTime LocalDateTime 操作时间
history[].reason String 解除原因:MANUAL/CLEAR_ALL/GROUP_RELEASE/AUTO_SINGLE_MEMBER(仅 RELEASED 动作有值)
history[].remark String 备注

请求示例

GET /admin/fleet/group-dispatch/batches/8801/share-groups?includeReleased=true

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "shareGroupId": "77001",
      "groupBatchId": "8801",
      "serviceDate": "2026-09-12",
      "resourceType": "VEHICLE",
      "resourceId": "1",
      "status": "ACTIVE",
      "costBearer": "GROUP",
      "costBearerOrderId": null,
      "costSourceRefNo": "SHARE-77001",
      "members": [
        { "sourceType": "GROUP_DISPATCH", "sourceId": "99001", "requirementId": null, "orderId": null },
        { "sourceType": "ASSIGNMENT", "sourceId": "88001", "requirementId": "5501", "orderId": "70123" }
      ],
      "confirmedBy": "1001",
      "confirmedAt": "2026-09-12 18:20:33",
      "version": 1,
      "history": [
        { "action": "CREATE", "memberSnapshot": "[{\"sourceType\":\"GROUP_DISPATCH\",\"sourceId\":99001},{\"sourceType\":\"ASSIGNMENT\",\"sourceId\":88001}]", "costBearerSnapshot": "GROUP", "operator": "1001", "operateTime": "2026-09-12 18:20:33", "reason": null, "remark": null }
      ]
    }
  ],
  "success": true
}

空数据 / 降级响应

本团在筛选条件下没有任何共用关系时返回空数组 [];本接口无下游依赖,不存在降级路径。

错误响应

{
  "code": 100001,
  "message": "参数非法: 资源维度非法: TRAIN",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN。
  • includeReleased=false(默认)只返回当前 ACTIVE 的关系,不含历史。
  • memberSnapshot 是全集不是增量——"先加一个成员再删一个"这种操作序列,两条历史记录各自都是完整名单,前端回放不需要叠加计算。
  • 查询接口不加锁、不开事务,读到的是调用时刻的库状态,不保证与并发中的写操作严格串行。

3. 解除团期车辆共用关系 DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}

VO: (PathVariable shareGroupId, RequestParam survivorPolicy) → ShareGroupReleaseRespVO

(源码核对:GroupDispatchShareController.java:104-125、ShareGroupReleaseRespVO.java、GroupDispatchShareService.java:156-168)

使用场景

车务在团期配车页显式解除一个共用关系(比如换车、行程调整后不再需要共用)。解除动作与幸存占用的实际处置在同一个事务内原子完成。

入参字段表

字段 位置 类型 必填 约束 说明
shareGroupId Path Long ✅ - 共用关系 ID
survivorPolicy Query String ⚠️业务必填,框架层非必填 KEEP_LEGAL/REASSIGN/RELEASE,缺失抛 602110 幸存成员处置策略;前端必须始终传值——survivorPolicy 在框架层声明为非必填是刻意的(声明必填会让 Spring 在进 Service 前就挡成 400,602110 这个业务码永远抛不出来),不传拿到的是 602110 而不是 400

出参字段表

字段 类型 说明
shareGroupId String 共用关系 ID
status String 固定 RELEASED
survivorPolicy String 本次采用的处置策略
keptSourceIds Array<String> 保留占用的成员来源 ID
releasedSourceIds Array<String> 占用已被真正释放的成员来源 ID(不是标记)
pendingReassignSourceIds Array<String> 需人工改派的成员来源 ID;占用已真实释放、派单已置"待改派"业务态,前端必须展示这份清单

请求示例

DELETE /admin/fleet/group-dispatch/share-groups/77001?survivorPolicy=REASSIGN

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "shareGroupId": "77001",
    "status": "RELEASED",
    "survivorPolicy": "REASSIGN",
    "keptSourceIds": ["88001"],
    "releasedSourceIds": ["88002"],
    "pendingReassignSourceIds": ["88002"]
  },
  "success": true
}

空数据 / 降级响应

本接口是同步写操作,不存在空数据形态;无下游降级路径。

错误响应

{
  "code": 602111,
  "message": "保留合法共用失败,幸存成员之间不满足衔接规则: 88001,88002",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN。

  • 🔴 三种策略的处置范围一律只到「本关系的成员」为止(#8061 / PR #8067,2026-09-20 收口):同一车/司机同一服务日上不属于本关系的在途派车行一行不动(接送机的接、送一对本来就该同日同车,它们不靠这个关系存在);已提前离组的前成员同样不再被清。前端不要再按「解除关系会把这辆车这天清空」去画提示。

  • 处置范围也不跨服务日、不跨团期(#8051 / PR #8058):解除只动本关系那一个 (团, 服务日, 维度, 资源) 槽位上的成员,同一辆车/司机在别的服务日或别的团期上的在途派车行不会被连带软清。

  • 三种策略都必须完成实际占用处置,不存在"只打标记"这一档:KEEP_LEGAL——仅当本关系的幸存成员与同槽其余 claim 两两全合法时整槽保留,有冲突则 602111 拒绝并点名冲突对(fail-closed,整请求回滚,不产生半成品状态);REASSIGN——真释放本关系中冲突成员的占用并把对应派单置"待改派",成员与非成员冲突时让位的是成员;RELEASE——释放本关系全部幸存成员(解除那一刻仍活跃的成员)的占用,团级配车行由团期侧路径负责、本策略不软删它。

  • 收口断言的判定范围与处置范围同宽:提交前过「账本里零非法组合」断言(不成立抛 602112 回滚),但只判「至少一侧是本关系成员」的组合——两条非成员之间的存量冲突不由本接口负责,前端不要指望解除一次关系能顺手把别人的历史冲突清掉。

  • 关系不存在或已解除返回 602108:常见于并发场景(另一个车务已先一步解除,或页面数据未及时刷新)。前端不需要重试,应提示"该共用关系已不存在"并重新拉取「接口 2」查询列表刷新页面,不要在同一个已失效的 shareGroupId 上继续操作。

  • 并发写冲突可重试(602109,语义同「接口 1」):解除过程中先 leaveActive 成员行、再 casRelease 关系行(GroupDispatchShareSurvivorService.java:181-191),任一步命中乐观锁 CAS 未中(关系或成员行被其他请求同时修改)即抛本码。前端按"网络繁忙,请重试"直接重新发起解除请求即可。

  • 幸存占用处置与账本重建的收口断言不通过时抛 602112,整请求回滚。

  • 🔴 本接口同样会返回 602 段以外的码:REASSIGN / RELEASE 两种策略要真释放成员占用,走的是派单软清写路径,所以派单域 605 段的码会原样透到本接口的响应里,整请求回滚(关系不会半解除)。前端按下表处置:

    码 含义 前端处置
    605008 抢锁超时,系统繁忙 可重试:给"重试"按钮
    605075 资源锁已失效,本次操作未提交 🔴 重试无效:不给重试按钮,提示核对是否有人同时在操作这些派单后重新发起
    605042 司机通知结果正在确认 可稍后重试:有成员的 HOLD 通知结果未决,等几秒再解除
    605043 仅排车中/已派车且行程未出发的派单可清空司机/车辆选中 不可重试:有成员已出发或已不是可清状态,需人工处理该成员后再解除
    605047 行程已结束,派车信息只读 不可重试:该成员行程已结束
    605009 派单不存在 不可重试:先调「接口 2」刷新成员列表

    ⚠️ 这几条不是关系本身的问题,是关系里某一个成员的派单当下动不了——提示文案要能让车务看出"卡在成员上",别写成"解除失败,请重试"。KEEP_LEGAL 策略在全部成员都合法时不做释放,通常不会撞上这一组。

  • 解除后,同一 (groupBatchId, serviceDate, resourceType, resourceId) 上可以重新建立关系,但新关系会拿到新的 shareGroupId 与 costSourceRefNo(不复活旧值)。


4. 团期配车就绪判定 GET /admin/fleet/group-dispatch/batches/{groupBatchId}/readiness

VO: (PathVariable groupBatchId) → GroupDispatchReadinessRespVO

(源码核对:GroupDispatchQueryController.java:116-139、GroupDispatchReadinessRespVO.java、GroupDispatchReadinessItemVO.java、GroupDispatchReadinessService.java:109-141)

使用场景

团期配车页与看板展示配车就绪状态;也供 fleet 内部就绪回填链路判断"能不能回调 order-v3 置位 vehicle_ready"。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID

出参字段表

字段 类型 说明
groupBatchId String 团期主订单 ID
requirementId String 判定所依据的正式需求 ID
requirementVersion Integer 判定所依据的需求版本
planVersion String fleet 当前计划版本;该团尚无任何计划行时为 null
ready Boolean 硬拦三项是否全过(= blockers.isEmpty()),true 才可回填 vehicle_ready
warned Boolean 是否有黄牌(= !warnings.isEmpty()),不阻断;ready=true && warned=true 是合法且常见的组合
blockers[] Array 硬拦未过项
blockers[].code String BLOCK_GROUP_MISSING(整组未排车)/BLOCK_GROUP_DAYS_INCOMPLETE(本组服务日未排满或越界)/BLOCK_NOT_CONFIRMED(正式需求未确认或配车行未全部确认)
blockers[].message String 中文描述,硬拦项必须点名到组
blockers[].tripDate LocalDate 相关行程日;整组缺失或需求未确认时为 null
blockers[].groupCode String 相关乘车分组码;与整体有关时为 null
warnings[] Array 只提醒项,结构同 blockers[],code 另取 WARN_SEAT_SHORTAGE/WARN_DRIVER_MISSING
warnings[].seatTotal / headcount / gap Integer 仅 WARN_SEAT_SHORTAGE 有值:该日座位合计/用车人数/缺口
warnings[].dispatchId String 仅 WARN_DRIVER_MISSING 有值:相关配车行 ID
groups[] Array 逐组覆盖明细,与 #7442 reconfigure 响应的 coverage 同源(GroupDispatchCoverageCalculator),两处字段逐字可比对
groups[].groupCode String 分组键(= 需求侧 group_code)
groups[].vehicleType String 车型文本/字典值
groups[].requiredDates Array<LocalDate> 本组权威服务日(升序)
groups[].coveredDates Array<LocalDate> 本次计划为本组实际排车的日期(升序)
groups[].missingDates Array<LocalDate> 本组缺失的服务日
groups[].outOfRangeDates Array<LocalDate> 本组越界的日期(排了权威服务日窗之外的日期)
groups[].satisfied Boolean 本组是否已满足
shareGroupCount Integer 本团当前 ACTIVE 共用关系数(配车页展示用),接的是三张新表的真实行数

请求示例

GET /admin/fleet/group-dispatch/batches/8801/readiness

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "8801",
    "requirementId": "5501",
    "requirementVersion": 3,
    "planVersion": "7",
    "ready": true,
    "warned": true,
    "blockers": [],
    "warnings": [
      {
        "code": "WARN_SEAT_SHORTAGE",
        "message": "2026-09-13 座位合计 33,该日用车人数 40,缺 7 座",
        "tripDate": "2026-09-13",
        "groupCode": "BUS",
        "seatTotal": 33,
        "headcount": 40,
        "gap": 7
      },
      {
        "code": "WARN_DRIVER_MISSING",
        "message": "2026-09-15 蒙E·12345 未排司机",
        "tripDate": "2026-09-15",
        "groupCode": "BUS",
        "dispatchId": "99005"
      }
    ],
    "groups": [
      {
        "groupCode": "BUS",
        "vehicleType": "宇通33座大巴",
        "requiredDates": ["2026-09-12", "2026-09-13", "2026-09-14", "2026-09-15", "2026-09-16"],
        "coveredDates": ["2026-09-12", "2026-09-13", "2026-09-14", "2026-09-15", "2026-09-16"],
        "missingDates": [],
        "outOfRangeDates": [],
        "satisfied": true
      }
    ],
    "shareGroupCount": 2
  },
  "success": true
}

空数据 / 降级响应

本接口没有降级路径,失败关闭:拉不到团期配车权威基线(或该团未声明任何乘车分组)时抛 602113,绝不返回 ready=true 或空 blockers[] 来冒充"已判定"。前端应把 602113 当作"暂时无法判定"处理,不要重试后仍失败就默认放行。

错误响应

{
  "code": 602113,
  "message": "无法取得本团的配车就绪基线, 就绪判定失败关闭: 8801",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN。
  • 两档互相独立,前端必须两个都读:只读 ready 会漏掉"能发车但有缺口"的提示;只读 warned 会把"根本不能发车"的情况当成普通提醒。
  • blockers[]/warnings[] 与配车提交/确认写口的 coverage 同源,逐字段可比对;出现不一致属后端 bug,不是前端理解错了。
  • shareGroupCount 只进展示,不参与 ready/warned 判定,不要据它做阻断逻辑。
  • 🔴 本读口只有两个业务错误码,别记混:602113 = 拉不到本团的配车就绪基线,或该团一个乘车分组都没声明(消息 无法取得本团的配车就绪基线, 就绪判定失败关闭: {groupBatchId});602114 = 就绪查询入参非法(消息 就绪查询入参非法: {原因},如 groupBatchId 缺失或非正数)。两者都属"这次判定没有结论",前端提示文案应有区别(602113 提示稍后重试或联系后端,602114 提示检查入口传参),但都不得展示为已判定的绿/红/黄结果,也不得因为重试仍失败就默认放行。
  • ⚠️ 共用关系那两个端点(「接口 1」「接口 2」)的 resourceType 非法返回的是通用 100001,不是 602114——602114 只属本读口,见「六.6」。

5. 整团逐日配车提交 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/reconfigure

VO: GroupDispatchReconfigureReqVO → GroupDispatchReconfigureRespVO

(源码核对:GroupDispatchReconfigureReqVO.java、GroupDispatchReconfigureRespVO.java、AssignmentService.java:437-541——本单只改 clearAll=true 分支的连带处置,其余字段/行为沿用 #7442,不在本文档重复列出)

使用场景

#7442 交付的整团配车写口。本次改动只涉及 clearAll=true(整团清零)这一分支:若该团存在 ACTIVE 共用关系,清零时需要一并交代幸存派单怎么处置。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID
requirementId / requirementVersion / clearAll / reconfigureWindowToken / demands Body - 沿用 #7442 不变 本文档不重复列出,详见 #7442 changelog
survivorPolicy Body String 【本单新增,条件必填】 仅当 clearAll=true 且该团存在 ACTIVE 共用关系时必填;取值 KEEP_LEGAL/REASSIGN/RELEASE;缺失抛 602110 语义同「接口 3」的同名参数

出参字段表

字段 类型 说明
groupBatchId / requirementId / requirementVersion / planVersion / addedCount / removedCount / keptCount / updatedCount / aliveCount / addedDispatchIds / idempotentShortCircuit / coverage / legacyGroupRowCount - 沿用 #7442,不变
releasedShareGroupIds[] Array<String> 【本单新增】 本次 clearAll 连带解除的共用关系 ID 清单;非 clearAll 时为空数组
keptSourceIds[] Array<String> 【本单新增】 关系解除后判定为"可保留"的 claim 来源 ID 清单
releasedSourceIds[] Array<String> 【本单新增】 占用已被真正释放的派单 ID 清单
pendingReassignSourceIds[] Array<String> 【本单新增】 需人工改派的派单 ID 清单(占用已释放,当前无车),前端必须展示

请求示例

{
  "requirementId": "5501",
  "requirementVersion": 3,
  "clearAll": true,
  "survivorPolicy": "RELEASE"
}

(路径参数 groupBatchId=8801)

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "groupBatchId": "8801",
    "requirementId": "5501",
    "requirementVersion": 3,
    "planVersion": "8",
    "addedCount": 0,
    "removedCount": 5,
    "keptCount": 0,
    "updatedCount": 0,
    "aliveCount": 0,
    "addedDispatchIds": [],
    "idempotentShortCircuit": false,
    "coverage": null,
    "legacyGroupRowCount": 0,
    "releasedShareGroupIds": ["77001"],
    "keptSourceIds": [],
    "releasedSourceIds": ["88001", "88002"],
    "pendingReassignSourceIds": []
  },
  "success": true
}

空数据 / 降级响应

该团没有任何 ACTIVE 共用关系时,releasedShareGroupIds/keptSourceIds/releasedSourceIds/pendingReassignSourceIds 四个新增字段均为空数组,行为与本次改动前完全一致,不需要 survivorPolicy。

错误响应

{
  "code": 602110,
  "message": "存在幸存共用派单,必须指定处置策略",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN。
  • survivorPolicy 只在 clearAll=true 且该团存在 ACTIVE 共用关系时才是必填;不满足条件时传了也不会报错,只是不生效。
  • 关系解除与占用处置在同一事务内原子完成,不会出现"团级行已软删但请求报错"的半成品状态(KEEP_LEGAL 冲突拒绝时会连同已软删的团级行一并回滚)。
  • 四个新增响应字段的语义与「接口 3」解除端点的对应字段完全一致,前端可复用同一套展示组件。
  • 🔴 clearAll=true 且触发幸存处置时,本接口也会返回 605 段的码:真释放走的是同一条派单软清写路径,因此「接口 3」业务边界里那张 605 表(605008 / 605075 / 605042 / 605043 / 605047 / 605009)在本接口同样适用,处置方式一致,整请求回滚。本接口原有的 600/602000 段码不变。
  • 幸存处置的范围限定与「接口 3」完全一致:走的是同一条幸存者路径,所以 #8061「只处置本关系的成员」与 #8051「不跨服务日、不跨团期」两条收口在本接口的 clearAll 分支上同样成立——同一车/司机同一天上不属于这些关系的在途派车行不会被连带清空。
  • 覆盖边界(本交接件不覆盖,照写以免前端误读):本接口自身的受控重开窗口校验码 602013(「本次配车改动越出重开窗口授权范围」)属 #7442 既有契约,不在本交接件的 10 个端点变更面内;#7994 在 2026-09-21 调整过它的一条判定(无分组历史行只准被收编、不准被窗口删除),该调整不改变本接口对外的请求/响应结构,也不改 602013 的码与文案。前端对 602013 的处理沿用 #7442 的口径即可,本篇「六.8 错误码全集」按各端点 javadoc 声明列举,因此不含 602013。

6. 派单预校验 POST /admin/fleet/assignments/precheck

VO: PrecheckReqVO → PrecheckRespVO(请求体不变,响应体 conflicts[] 新增字段)

(源码核对:AssignmentController.java:103-111、PrecheckRespVO.java、AssignmentService.java:948-1044)

使用场景

车务派单页选车/选司机前的冲突预校验;共用关系确认后,本接口需要认得这条授权,不再把已确认可共享的车标成阻断冲突。

入参字段表

字段 位置 类型 必填 ���束 说明
(沿用既有 PrecheckReqVO 全部字段,本单不加字段) - - - - -
excludeAssignmentId body String(Long) 改派场景必填 既有字段 🔴 改派时不传,读数与「共用关系功能根本没做」一模一样:本端点不会自动认出「哪条占用是调用方自己」,不排除自身时调用方会和自己冲突——返回的 conflict=true / blocking=true 里混着自己那条占用。共用关系判定同样以本字段为主体身份:不传即按「新建派单」处理,一律不享受共用授权(与写口 create 同口径,读口不得比写口松)。新建派单场景不传是对的

出参字段表

字段 类型 说明
conflict Boolean 沿用既有:是否存在阻断性冲突
conflicts[].type String 沿用既有:vehicle/driver/vehicle_not_found/driver_not_found
conflicts[].conflictAssignmentId / conflictOrderNo / conflictDateRange - 沿用既有
conflicts[].cityJunctionShareCandidate Boolean 沿用既有,取值逻辑不变——只答「两段行程首尾同城吗」,不含共用关系那一支;命中共用关系的跨城车在本列仍为 false。❗ 与「接口 7」候选面的同名字段逐字同义(#8004 把两侧拉齐了),可以复用同一个渲染判断;但本口没有 shareEligible 这一列,本口判「能不能派」一律读 blocking
conflicts[].blocking Boolean 【本单新增字段】 本条冲突是否阻断本次派车;无共用关系时 = !cityJunctionShareCandidate(与改前口径逐字等价),命中已确认共用关系时为 false;资源不存在类冲突恒为 true
conflicts[].reasonCode String 【本单新增字段】 SHARE_GROUP_CONFIRMED(已确认共用关系放行)/CITY_JUNCTION_SHAREABLE(同城首尾衔接)/ASSIGNMENT_CONFLICT(真冲突);资源不存在类冲突为 null
conflicts[].shareGroupId String 【本单新增字段】 本条冲突与本次派车同属的已确认共用关系 ID(取重叠期首日那一个);无则 null
warnings[] Array 沿用既有,不变

请求示例

{
  "vehicleId": "1",
  "driverId": "2079857983403024387",
  "startDate": "2026-09-12",
  "endDate": "2026-09-16"
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "conflict": false,
    "conflicts": [
      {
        "type": "vehicle",
        "conflictAssignmentId": "88001",
        "conflictOrderNo": "26-0503",
        "conflictDateRange": "2026-09-12~2026-09-16",
        "cityJunctionShareCandidate": false,
        "blocking": false,
        "reasonCode": "SHARE_GROUP_CONFIRMED",
        "shareGroupId": "77001",
        "msg": "车 蒙E·12345 已确认共用"
      }
    ],
    "warnings": []
  },
  "success": true
}

空数据 / 降级响应

无冲突时 conflicts 为空数组 [];本接口只读,无下游降级路径。

错误响应

{
  "code": 100001,
  "message": "参数非法: startDate 不能晚于 endDate",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN,本次未改动。
  • 不改这里,写口放行但页面预校验仍会显示阻断冲突——blocking 字段是本次改动的核心,前端渲染冲突提示时必须改读 blocking 而不是继续按 cityJunctionShareCandidate 取反判断,否则会出现"写口能提交成功、但预校验一直标红"的体验矛盾。
  • blocking 非显式 false 一律按阻断处理(含缺省/null)。
  • reasonCode/shareGroupId 为 null 的冲突项仍应按原有逻辑展示(不是新字段缺失的错误,只是没有共用关系背景)。

7. 查询派单候选资源 POST /admin/fleet/assignments/candidates

VO: AssignmentCandidateReqVO → AssignmentCandidateRespVO(请求体不变,响应体 conflicts[] 新增/变更字段)

(源码核对:AssignmentController.java:86、AssignmentCandidateRespVO.java:576-616、AssignmentCandidateService.java:666-729、AssignmentCandidateAvailabilityReasonEnum.java)

使用场景

车务派单页选车/选司机时的候选列表;共用关系确认后,已授权的车/司机在候选列表里应从"标红不可选"变成"可选,并标明已确认共用"。

入参字段表

字段 位置 类型 必填 约束 说明
(沿用既有 AssignmentCandidateReqVO 全部字段,本单不加字段) - - - - -
excludeAssignmentId body String(Long) 改派场景必填 既有字段 🔴 改派场景必须传,且要和 assignmentGroupId 成对传(#8004 AC-2/AC-3):本端点不会自动认出「哪条占用是调用方自己」,不排除自身时调用方会和自己冲突——【实测】同一请求只带 assignmentGroupId 不带 excludeAssignmentId,返回 selectable=false / availabilityReasonCode=ASSIGNMENT_CONFLICT / shareGroupId=null,与「共用关系功能根本没做」的读数一模一样,前端会据此认定后端没做。共用关系判定同样以本字段为主体身份:不传即按「新建派单」处理,一律不享受共用授权(与写口 create 同口径,读口不得比写口松)。新建派单场景不传是对的
assignmentGroupId body String(Long) 改派场景必填 既有字段 与 excludeAssignmentId 成对:它决定候选面按哪个派单组算冲突。只传其中一个拿到的是上一行那种「像没做」的读数

出参字段表

字段 类型 说明
(其余候选字段沿用既有) - -
conflicts[].assignmentId / assignmentGroupId / orderNo / teamNo / startDate / endDate - 沿用既有
conflicts[].cityJunctionShareCandidate Boolean 🔴 2026-09-20 网关实测 + 源码复核订正(本节原描述已被 #8004 撤销):取值逻辑不变,只答「两段行程首尾同城吗」——共用关系不改写它。命中共用关系的跨城车在本列仍为 false。❗ 与「接口 6」预校验面的同名字段现已逐字同义(#8004 把两侧拉齐了)
conflicts[].shareEligible Boolean ⚠️ 本字段在原交接件里从未登记,2026-09-20 补入。它才是承载「可共享」语义的字段:同城衔接 或 已确认共用关系,任一成立即 true;blocking 恒等于 !shareEligible。前端判「能不能选」请统一读本列或 blocking
conflicts[].blocking Boolean 【行为变化,非新增】 命中已确认共用关系时转 false
conflicts[].reasonCode String 【行为变化】 新增枚举值 SHARE_GROUP_CONFIRMED
conflicts[].reasonMessage String 沿用既有:后端可直接展示的中文原因
conflicts[].shareGroupId String 【本单新增字段】 同属的共用关系 ID;无则 null

请求示例

{
  "requirementId": "5501",
  "startDate": "2026-09-12",
  "endDate": "2026-09-16",
  "vehiclePage": 1,
  "vehiclePageSize": 20
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "vehicles": {
      "records": [
        {
          "vehicleId": "1",
          "conflicts": [
            {
              "assignmentId": "88001",
              "assignmentGroupId": "88001",
              "orderNo": "26-0503",
              "startDate": "2026-09-12",
              "endDate": "2026-09-16",
              "cityJunctionShareCandidate": false,
              "shareEligible": true,
              "blocking": false,
              "reasonCode": "SHARE_GROUP_CONFIRMED",
              "reasonMessage": "已确认同团车辆共用关系,可共享",
              "shareGroupId": "77001"
            }
          ]
        }
      ],
      "total": 1,
      "page": 1,
      "pageSize": 20
    }
  },
  "success": true
}

空数据 / 降级响应

无候选或无冲突时对应数组为空数组 [];本接口只读,无下游降级路径。

错误响应

{
  "code": 100001,
  "message": "excludeAssignmentId 不属于当前订单或当前用车需求",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN,本次未改动。
  • 🔴 2026-09-20 网关实测 + 源码复核订正(本节原描述已被 #8004 撤销):两面的 cityJunctionShareCandidate 现已逐字同义,都只答「同城首尾衔接」。 原文写的「两面语义不同」是 PR-2b 当时的真实行为,但已被 #8004 撤销。 🔴 那个缺陷的形态值得记一笔(源码 javadoc 原话):同一对跨城派单在两个读口给出相反的 true/false——后端两侧各自都自洽,错只落在跨侧读的前端身上,代码审查与单侧测试都看不见。 ⇒ 现在前端两面可以用同一个渲染判断;要判「拦不拦得住」请读 shareEligible / blocking,不要读 cityJunctionShareCandidate。
  • 已确认共用关系的车/司机在候选列表中 blocking=false 才可被选中,前端选择逻辑统一取"conflicts 中 blocking 非显式 false 的项一律按阻断处理"。

8. 撤销取消的派单 POST /admin/fleet/assignments/{assignmentId}/restore-cancel

VO: (PathVariable assignmentId, 无 body) → RestoreCancelRespVO(响应体不变,仅新增一条放行分支)

(源码核对:AssignmentController.java:551-571、RestoreCancelRespVO.java、AssignmentService.java:8639-8681、ShareMemberLeaveReason.java)

使用场景

车务误取消一条本属于某已确认共用关系的接送派单后,撤销恢复。本次改动让恢复路径也认共用关系,避免"关系还在、但恢复被城市衔接规则误拒"。

入参字段表

字段 位置 类型 必填 约束 说明
assignmentId Path Long ✅ - 派单 ID
(无请求体) - - - - -

出参字段表

字段 类型 说明
assignmentStatus String 恢复后的派单状态(assigned/holding/unassigned),沿用既有
assignmentGroupId String 派车组 ID,沿用既有
vehicleStatusUpdated / driverStatusUpdated String 占用反算回写结果,沿用既有

请求示例

POST /admin/fleet/assignments/88002/restore-cancel

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "assignmentStatus": "assigned",
    "assignmentGroupId": "88002",
    "vehicleStatusUpdated": "busy",
    "driverStatusUpdated": "busy"
  },
  "success": true
}

空数据 / 降级响应

无(同步写操作);旧 HOLD 通知结果仍在确认时返回既有 605042,本单不改这条。

错误响应

{
  "code": 605016,
  "message": "派单已被其它单占用,无法撤销恢复",
  "success": false,
  "data": null
}

业务边界

  • 鉴权:同「接口 1」,路径级角色门禁 VEHICLE_MANAGER/SUPER_ADMIN,本次未改动。

  • 恢复放行的前提是"该派单当初是因占用被释放才自动离场共用关系"——共用关系成员表新增的 leave_reason 字段区分两种离场:AUTO_OCCUPANCY_RELEASE(占用被释放触发的自动离场,可安全放回)与 MANUAL_MEMBER_REMOVED(车务人工把它移出成员全集,restore-cancel 不会把它撤销回去)。这个区分对前端不可见,但意味着:如果车务在取消派单之前已经手动把它从共用关系里移出过,撤销取消不会让它自动回到关系里,仍走原有城市衔接判定,前端应据此提示车务必要时重新走共用确认。

  • 关系已解除(不是"自动离场")后再恢复,仍按城市衔接(R3-EX)判定,跨城则 605016。

  • 其余既有错误码不变,按前端处置方式分三类(本单不改任何一条,此处补全是因为原稿漏了 605075):

    码 含义 前端处置
    605008 抢锁超时,系统繁忙 可重试:给"重试"按钮,原样重发即可
    605075 资源锁已失效,本次操作未提交 🔴 重试无效:不要给重试按钮,也不要自动重试。提示"本次未提交,请确认是否有其他人同时在操作这条派单,核对后重新发起"。消息里 (…) 中点名的是哪一把锁
    605042 司机通知结果正在确认 可稍后重试:本次保持 canceled,派单状态/通知代际/资源占用都没动
    605009 派单不存在 不可重试:刷新列表
    605015 超出撤销窗口,或该派单不是 canceled 态 不可重试:撤销入口应置灰
    605016 已被其它单占用,无法恢复 不可重试:引导改派
    605017 仅取消操作本人可撤销 不可重试:按钮对非本人置灰
    605037 车辆已维保或停用,无法恢复占用 不可重试:引导换车改派
    605038 司机已休假或待激活,无法恢复占用 不可重试:引导换司机改派
  • 🔴 605075 与 605008 长得像,处置相反:两条都在抢资源锁这一环、文案都提到"锁",但 605008 是没抢到(重试有效),605075 是抢到过、持有期内又丢了(重试无效,且意味着持锁期间可能已有并发写入,需要人工确认)。把它们渲染成同一种"稍后重试"提示,会让车务在一条实际未提交、且可能有并发写入的操作上反复点按钮。


9. 幂等释放团期配车占用(内部) POST /internal/fleet/dispatch/group-batch/{groupBatchId}/release

VO: (PathVariable groupBatchId, 无 body) → Void

(源码核对:InternalGroupDispatchController.java:65-74、GroupDispatchService.java:895-935、AssignmentService.java:1800-1812)

使用场景

仅限内部 Feign 调用,order-v3 取消成团/流团时触发。hl-ui 不直接调用本接口,此处收录是因为它的行为变化会决定共用关系与占用的最终状态,前端需要理解"流团之后,之前建立的共用关系与幸存占用去哪了"。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID
(无请求体,刻意不加必填参数——该端点由可靠命令链驱动,加参数会让存量重放失败) - - - - -

出参字段表

字段 类型 说明
(无响应体,Result<Void>) - -

请求示例

POST /internal/fleet/dispatch/group-batch/8801/release

响应示例

{ "code": 200, "message": "成功", "data": null, "success": true }

空数据 / 降级响应

无活跃配车或已释放时直接成功(幂等);不存在降级路径。

错误响应

{
  "code": 602112,
  "message": "占用账本重建后幸存成员与期望集合不符",
  "success": false,
  "data": null
}

(这是内部一致性兜底,正常情况不会触发;触发说明连带处置算法与账本状态不一致,整请求回滚)

业务边界

  • 本次改动前:只软删团级配车行、释放团级占用,共用关系与逐户接送占用一概不碰——流团后,之前建立的共用关系仍留在库里,幸存的逐户接送派单仍"共用"着一个此刻已经不存在依据的槽位。
  • 本次改动后:同一事务内在既有动作之后,一并解除该团全部 ACTIVE 共用关系,并对每个被触及的资源日槽完成实际占用处置——按"同类内 source_id 升序、逐个尝试加入保留集"的确定性算法计算保留/释放集,释放集里的占用被真正释放,对应派单置"待改派"业务态。
  • 该端点不能拒绝(流团必须成功):与「接口 3」的 KEEP_LEGAL 不同,有冲突时释放低优先级成员而不是报错。
  • 影响面:若前端有"团期解散后配车全部清空"这类展示,需要理解幸存派单不是被删除,而是转入"待改派",需引导车务去别的团期或另建订单改派。

10. 回填配车就绪(内部) POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready

VO: GroupBatchVehicleReadyReqDTO → GroupBatchVehicleReadyRespDTO(请求/响应契约均不变,仅内核新增一步状态迁移)

(源码核对:GroupBatchResourceController.java:66-83、GroupBatchVehicleReadyReqDTO.java、GroupBatchVehicleReadyRespDTO.java、GroupBatchService.java:797-921)

使用场景

仅限内部 Feign 调用,fleet 整团配车就绪(GET .../readiness 的 ready=true)后回调本接口,order-v3 回填 vehicle_ready=true。hl-ui 不直接调用本接口,此处收录是因为它的内核变化会影响团期正式需求的状态(进而影响 #7442/#7441 相关页面展示的需求状态)。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ - 团期主订单 ID
requirementId Body Long 条件必填 缺省=兼容期旧意图,走 legacy 路径 产出本次就绪意图的正式团级用车需求 ID
requirementVersion Body Integer 条件必填 ≥1 需求版本
planVersion Body Long 条件必填 ≥1 fleet 团期级计划版本
sourceRefNo Body String ❌ ≤64 幂等追溯号(fleet Outbox 记录 ID)

⚠️ 本次没有新增 readinessSnapshot 字段——这是工单最初的设计,2026-09-18 已撤回,源码请求体仍是上表四个字段,未改动。

出参字段表

字段 类型 说明
applied Boolean 沿用既有:本次是否真的落库
discardReason / discardCode String / Integer 沿用既有:未生效原因及对应码(仅 809205/809206 两种有值)
currentRequirementId / currentRequirementVersion / currentPlanVersion / batchStatus - 沿用既有

请求示例

{
  "requirementId": "5501",
  "requirementVersion": 3,
  "planVersion": "7",
  "sourceRefNo": "880123"
}

(路径参数 groupBatchId=8801)

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "applied": true,
    "discardReason": null,
    "discardCode": null,
    "currentRequirementId": "5501",
    "currentRequirementVersion": 3,
    "currentPlanVersion": "7",
    "batchStatus": "RESOURCE_PREPARING"
  },
  "success": true
}

空数据 / 降级响应

请求体身份与当前活跃需求不一致、版本落后、或已应用过(重投)时,一律返回 applied=false + discardReason,HTTP 仍是 200,不是错误,不进入本文档「错误响应」范畴;此时本单新增的 DISPATCHED → DONE 第五步不会执行——它挂在"两级判定通过、vehicle_ready 真正落库"之后,判定没过就没有后续:

{
  "code": 200,
  "message": "成功",
  "data": {
    "applied": false,
    "discardReason": "IDENTITY_MISMATCH",
    "discardCode": 809205,
    "currentRequirementId": "5502",
    "currentRequirementVersion": 4,
    "currentPlanVersion": "9",
    "batchStatus": "RESOURCE_PREPARING"
  },
  "success": true
}

错误响应

本接口对业务性未生效不用 HTTP 错误表达(见上「空数据 / 降级响应」的 applied=false 形态),本次改动不新增任何错误码;数据库/并发异常时仍是既有的兜底 500:

{
  "code": 100000,
  "message": "系统异常,请稍后重试",
  "success": false,
  "data": null
}

业务边界

  • 本次改动对请求/响应契约无任何影响,纯内核行为变化:applied=true 落库成功后,事务内额外把该团当前活跃的正式团级用车需求从 DISPATCHED 推进到 DONE(源状态不是 DISPATCHED 则不推进,不抛异常,只留一条 WARN 日志与时间线记录)。
  • 需求已是 DONE 的重复回调:零写入、不报错、不重复留痕(幂等)。
  • 反向不做:清零/流团/clearAll 不会把 DONE 退回;DONE → PENDING_RECONFIRM 由 #7442 的重开需求端点负责。
  • 对前端可观察的影响是间接的:若页面展示正式需求状态,DONE 状态会在配车就绪回调完成后出现,不是本接口直接返回的字段。

四、契约约束与正确调用方式

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

✅ 正确 / ❌ 错误 payload 对照(确认共用关系 POST .../share-groups)

场景 payload
✅ 成本记团级 { "costBearer": "GROUP", "costBearerOrderId": null }
✅ 成本记指定户 { "costBearer": "ORDER", "costBearerOrderId": 70123 }(70123 必须是成员中某个 ASSIGNMENT 的订单)
❌ 选 ORDER 但不填订单 { "costBearer": "ORDER", "costBearerOrderId": null } → 602107
❌ 选 ORDER 但订单不属于成员 { "costBearer": "ORDER", "costBearerOrderId": 99999 }(99999 不在成员订单里)→ 602107
✅ 成员 2~20 个 { "members": [ {...}, {...} ] }
❌ 单成员 { "members": [ {...} ] } → 602100
✅ admissionIntent 可以完全不传 { "sourceType": "ASSIGNMENT", "sourceId": 88001 }(服务端自行现查现判占用状态)

✅ / ❌ payload 对照(解除关系 DELETE .../share-groups/{id} 与 reconfigure 的 clearAll)

场景 payload
✅ 显式传处置策略 ?survivorPolicy=RELEASE
❌ 不传处置策略 不带 survivorPolicy → 602110(不是 400,因为该参数框架层刻意声明非必填)
❌ 传非法策略值 ?survivorPolicy=DELETE_ALL → 100001(CommonErrorCode.INVALID_PARAM,不是 602 段)

切换状态时的必要动作

  • 前端不需要、也不应该据 admissionIntent 做任何业务判断——它只是交互提示字段,服务端一律按库里实际占用重新判定。若前端据此字段跳过某个二次确认弹窗,实际放行结果仍取决于服务端判定,两者可能不一致。
  • survivorPolicy 每次调用解除/清零都必须显式传值,不要依赖"上次传过就记住"的前端本地状态——每次调用都是独立请求,服务端不保留上一次的选择。

五、数据库行为

  • 确认共用关系成功后,会同时产生:①一条 ACTIVE 状态的共用关系记录;②该关系下每个成员一条成员记录(历史成员记录 left_at 保持独立,退出的成员不会被物理删除);③一条 CREATE 类型的变更历史记录。
  • costSourceRefNo 全局唯一,重复插入相同值会被数据库拒绝——这意味着同一关系生命周期内不会出现两条不同的 costSourceRefNo,解除后同槽重建关系必然拿到新值。
  • 解除关系(无论通过哪条路径:显式解除、clearAll、内部 release)时,REASSIGN/RELEASE 策略下对应的占用记录会被真实标记为已释放(不是软删占位),派单进入"待改派"业务态;KEEP_LEGAL 策略下若冲突则整个解除操作连同任何已发生的软删一并回滚,不留半成品。
  • 成员减到 1 个及以下时,关系会被系统自动置为 RELEASED,并落一条 reason=AUTO_SINGLE_MEMBER 的变更历史,不需要车务手动操作。🔴 这条链 2026-09-21 才真正开始执行(#8064,详见「⚠️ 关键变化」):改前它整段挂在占用双写协调器的 DUAL_WRITE 分支上,而环境实测 64 个 stripe 全为 LEGACY,于是自上线起零执行。前端必须真的把这一态画出来。例外:整团解除(clearAll / 团级 release)时原因列被豁免,仍记 CLEAR_ALL / GROUP_RELEASE。边界:「同一个 claim 原地换到另一个资源」这一种情形在 LEGACY 下不触发自动收缩。
  • 车与司机的共用关系是两条独立记录,互不牵连——同一批成员如果既要共用车又要共用司机,库里会有两条各自独立的关系记录。

就绪判定接受已完成需求(DONE)

就绪判定的硬拦第三项(需求状态校验)接受的需求状态集为 {CONFIRMED, DISPATCHED, DONE}。这意味着:

  • 即使团期正式需求已推进到 DONE 态(已完成用车),GET /admin/fleet/group-dispatch/batches/{groupBatchId}/readiness 仍可读到该需求对应的配车状态仍判就绪(若其他两项硬拦也通过)。
  • 就绪回填链路(POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready)即使在需求已完成后再调一次仍可通过就绪判定,不会因"完成了"就变成"不就绪"。
  • 这个设计保证了配车完成→需求推进→回填就绪这三步不会出现"推进之后再回填就被拒"的尴尬情形。

六、边界行为

  • 未登录 → 401(网关拦截)
  • 请求参数非法(如 resourceType 传非 VEHICLE/DRIVER)→ 100001(两个共用关系端点如此;就绪读口的入参非法是 602114,两者不是同一个码,见「六.6」)
  • 就绪判定拉不到团期基线 → 602113,失败关闭,绝不返回 ready=true 冒充判定结果
  • 窗外日的接送机 claim → 不进共用关系,仍走原有城市衔接(R3-EX)判定,属有意边界
  • 共用关系确认端点 10 秒内重复提交同一份成员全集 → 100502,前端按"请勿重复提交"提示,不当失败处理
  • 老数据兼容:fleet_assignment.group_batch_id 为 NULL 的历史派车行(本列上线前的存量行、车务手工建的派车行)一律不能进共用关系,返回 602103——NULL 视为"团期身份未知",未知不得当同团通过
  • 🔴 三个共用关系写口(接口 1 / 3 / 5)会返回 602 段以外的码:内部走派单写路径,派单域 605 段既有码原样透出,其中 605036 需车务确认后重发、605075 重试无效不给重试按钮。按端点的完整错误码清单见「六.8」,不要只按 602 段写前端分支

六.5、枚举 / 数据字典

resourceType(共用关系资源维度)

所属字段: ShareGroupConfirmReqVO.resourceType / ShareGroupRespVO.resourceType / ShareGroupQueryReqVO.resourceType 等 | 类型: String

值 中文 说明
VEHICLE 车辆 共用车辆资源
DRIVER 司机 共用司机资源;车与司机分别建关系

status(共用关系状态)

所属字段: ShareGroupRespVO.status | 类型: String

值 中文 说明
ACTIVE 生效中 唯一可被守卫当作放行依据的状态
RELEASED 已解除 终态;再共用会建新关系,不复活旧行

costBearer(共用车费成本承担方)

所属字段: ShareGroupConfirmReqVO.costBearer / ShareGroupRespVO.costBearer | 类型: String

值 中文 说明
GROUP 记团级 整车成本记团级 BatchCostType.BUS 共享成本
ORDER 记指定户 需同时提供 costBearerOrderId,必须是成员中某个 ASSIGNMENT 的订单

members[].sourceType(共用关系成员来源类型)

所属字段: ShareGroupMemberReqVO.sourceType / ShareGroupMemberRespVO.sourceType | 类型: String

值 中文 说明
ASSIGNMENT 逐户接送派单 sourceId = fleet_assignment.assignment_id
GROUP_DISPATCH 团级配车行 sourceId = fleet_group_dispatch.dispatch_id

members[].admissionIntent(准入意图,仅前端交互提示)

所属字段: ShareGroupMemberReqVO.admissionIntent | 类型: String

值 中文 说明
OCCUPYING 已占着 默认值;该成员目前已占着这辆车/这名司机
PENDING_ADMISSION 本次一并派入 服务端会在同一次调用里把该成员派上车;⚠️ 服务端不读、不校验该字段,一律按库里实际占用重新判定

survivorPolicy(幸存成员处置策略)

所属字段: DELETE .../share-groups/{id} 的 Query 参数 / GroupDispatchReconfigureReqVO.survivorPolicy | 类型: String

值 中文 说明
KEEP_LEGAL 保留合法共用 仅当剩余 claim 两两全合法时整槽保留;有冲突则 602111 拒绝
REASSIGN 改派 真释放冲突成员占用,派单置"待改派",返回清单
RELEASE 释放 释放全部幸存成员的占用

history[].reason(共用关系解除原因)

所属字段: ShareGroupHistoryRespVO.reason | 类型: String

值 中文 说明
MANUAL 车务手动解除 调用 DELETE .../share-groups/{id}
CLEAR_ALL 整团清零连带解除 reconfigure 的 clearAll=true
GROUP_RELEASE 团级释放连带解除 内部 release(取消成团/流团)
AUTO_SINGLE_MEMBER 自动收缩 成员减到 ≤1,关系失去意义。🔴 这一支 2026-09-21 才真正开始产生数据(#8064:改前整段挂在占用双写 DUAL_WRITE 分支上,而环境实测全为 LEGACY,上线至今零执行)。前端不能按"理论值"处理。整团解除(CLEAR_ALL/GROUP_RELEASE)的原因列被豁免,不会被本值覆盖(#8064 AC-10)

history[].action(共用关系变更历史动作)

所属字段: ShareGroupHistoryRespVO.action | 类型: String

值 中文 说明
CREATE 首次建立 -
MEMBER_ADDED 成员净增 幂等覆盖相对上一版全集净增。⚠️ 改前它还兼做「只改成本承担方」的记录,#8013 起不再(见下一行)
MEMBER_REMOVED 成员净减 幂等覆盖或自动收缩时净减
COST_BEARER_CHANGED 成本承担方变更 🔴 2026-09-21 前是死枚举、从未被写入过(#8013 / PR #8020:成员全集没变而只改成本承担方时,改前一律记成 MEMBER_ADDED)。现在这种变更记本值;「什么都没变」的重复提交不再写任何历史行
RELEASED 关系解除 四条解除路径共用此动作,用 reason 区分来路

reasonCode(冲突/候选判定原因码,precheck 与 candidates 共用)

所属字段: PrecheckRespVO.ConflictItemVO.reasonCode / AssignmentCandidateRespVO.ConflictVO.reasonCode | 类型: String

值 中文 说明
SHARE_GROUP_CONFIRMED 已确认共用关系 本单新增;人工确认过的关系放行,不要求城市衔接
CITY_JUNCTION_SHAREABLE 同城首尾衔接 系统按行程首尾自动判出的可共享候选,非本单新增
ASSIGNMENT_CONFLICT 真冲突 非本单新增

blockers[].code / warnings[].code(就绪判定明细项代码)

所属字段: GroupDispatchReadinessItemVO.code | 类型: String

值 中文 档位 说明
BLOCK_GROUP_MISSING 整组未排车 硬拦(红) -
BLOCK_GROUP_DAYS_INCOMPLETE 本组服务日未排满或越界 硬拦(红) -
BLOCK_NOT_CONFIRMED 需求或计划未确认 硬拦(红) 需求状态接受集含 CONFIRMED/DISPATCHED/DONE
WARN_SEAT_SHORTAGE 座位不足 提醒(黄) 带 seatTotal/headcount/gap 三个数
WARN_DRIVER_MISSING 未排司机 提醒(黄) 带 dispatchId

六.6、修改前后对比

字段级对比

字段 改前 改后
precheck 响应 conflicts[] 只有 type/conflictAssignmentId/conflictOrderNo/conflictDateRange/cityJunctionShareCandidate/msg 新增 blocking/reasonCode/shareGroupId 三个字段
candidates 响应 conflicts[] 无 shareGroupId/shareEligible;blocking/reasonCode 只按城市衔接取值 🔴 2026-09-20 网关实测 + 源码复核订正(本节原描述已被 #8004 撤销):新增 shareGroupId 与 shareEligible;blocking/reasonCode 扩展为"同城衔接或已确认共用关系";cityJunctionShareCandidate 取值逻辑不变(原写它也扩展了,是错的)
AssignmentCandidateAvailabilityReasonEnum 只有 AVAILABLE/CITY_JUNCTION_SHAREABLE/ASSIGNMENT_CONFLICT 三值 新增 SHARE_GROUP_CONFIRMED
reconfigure 请求体 无 survivorPolicy 新增条件必填 survivorPolicy(仅 clearAll=true 且存在 ACTIVE 共用关系时必填)
reconfigure 响应体 无共用关系相关字段 clearAll=true 时新增 releasedShareGroupIds[]/keptSourceIds[]/releasedSourceIds[]/pendingReassignSourceIds[]
团期就绪判定 无独立读口,隐式布尔(覆盖完整即判就绪,GroupDispatchService.java:300-304 旧逻辑),且只比服务日集合 新增 GET .../readiness 读口,ready/warned 两档独立判定,纳入人数/座位
vehicle-ready 请求体 requirementId/requirementVersion/planVersion/sourceRefNo 四字段(#7442 PR-C1 交付) 不变——工单曾计划新增 readinessSnapshot,2026-09-18 已撤回,源码未改
错误码段 fleet 602100-602199 全空;order-v3 809300-809399 全空 fleet 段新落 602100-602114 共 15 个;order-v3 809 段未建(原计划的 809300/809301 因 readinessSnapshot 撤回而失去唯一消费场景,一并取消)。⚠️ 本行说的是"本单新建了哪些码",不等于"端点会返回哪些码"——三个共用关系写口(接口 1 / 3 / 5)内部会走派单写路径,派单域 605 段的既有码会原样透出来,按端点的完整集合见「六.8」,不要只按 602 段做前端分支

行为级对比

行为 改前 改后
同一辆车同一天既跑团级行程又接送逐户 占用层 requireShareableClaims 两两遍历时按城市衔接(R3-EX)判,跨城即拒;派单层同样拒 车务在团期配车页人工确认共用关系后,同属一个关系的成员互相放行,不要求城市衔接
precheck/candidates 对已确认共用关系的车/司机 仍显示阻断冲突(标红),写口能提交但页面选不上 blocking=false,前端可正常选中
恢复本属共用关系的接送派单 只认城市衔接,跨城会被 605016 拒绝 若离场原因是"占用被释放触发的自动离场",可放回原关系;人工移出的不放回
clearAll/内部 release 后幸存的共用派单 clearAll:不碰 fleet_assignment,跨城组合非法占用留在库里但不被任何检查发现;内部 release:同上,共用关系与占用不被处置 两条路径都在同一事务内解除共用关系并对幸存占用做实际处置(保留/真释放+待改派)
团级正式需求状态推进 DISPATCHED 状态在配车就绪后不会自动推进 就绪回调(vehicle-ready)事务内核追加一步,把活跃正式需求从 DISPATCHED 推进到 DONE
就绪判定接受的需求状态 无独立就绪判定 硬拦第三项(需求已确认)接受集 = {CONFIRMED, DISPATCHED, DONE},完成后再读仍判就绪,不会"越完成越不就绪"

六.7、影响评估

  • 是否破坏向后兼容: 否,对已有接口(precheck/candidates/reconfigure/restore-cancel)都是新增字段或既有字段语义扩展,不删除、不改类型、不改路径;reconfigure 新增的 survivorPolicy 只在特定条件下必填,不影响未共用场景的既有调用方式。
  • 前端是否必须同步上线: 是(部分)——就绪两档展示(GET .../readiness)与共用关系的四个新端点若不接入,车务在团期配车页无法完成人工确认动作,共用关系机制整体无法使用;precheck/candidates 若不改读新字段,会出现"写口已放行、页面仍标红/选不中"的体验矛盾,属高优先级同步项。restore-cancel/内部两个端点即使前端暂不改动也不会 400,只是体验上可能有恢复被误拒的情况。
  • 前端 workaround 清理点:
    • precheck 冲突展示如果此前是"直接按 cityJunctionShareCandidate 取反判断是否阻断",需要改读新的 blocking 字段;
    • 若有"就绪=覆盖完整"这类前端自行推断逻辑,需要删除,改为直接读 GET .../readiness 的 ready/warned;
    • 🔴 2026-09-20 网关实测 + 源码复核订正(本节原描述已被 #8004 撤销):前端把 候选面 cityJunctionShareCandidate=true 理解为"同城衔接"是对的,保持不变;要判"能不能选"请改读新字段 shareEligible(或等价的 !blocking),展示文案按 reasonCode 区分两种情况。

六.8、错误码全集(按端点对照)

本节回答前端写错误分支时的两个问题:什么时候会拿到它、UI 该不该给重试按钮。

🔴 为什么要单独列这一节:本模块新建的码按"码段"看是 602100-602114,但按端点看不是这个集合——三个共用关系写口(接口 1 / 3 / 5)内部都会调派单写路径,派单域 605 段的既有码会原样透到它们的响应里。只照 602 段写分支,会漏掉两个后果最重的:需要车务二次确认才能继续的 605036,和必须禁用重试按钮的 605075。

覆盖边界(照实说):下表是逐端点追调用路径得到的已查证会抛出的集合,不是"除此之外一定不会出现别的码"的穷举承诺——派单写路径上还有一批只在更少见条件下成立的守卫。前端兜底分支照常要写:拿到表外的码时按前三位归类即可,605xxx 一律按"派单侧拒绝——展示 message 原文、不自动重试"处理。

通用码(接口 1-8 共有)

码 什么时候拿到 前端处置
401 未登录 / token 失效 网关拦截,跳登录
403 请求头 X-Admin-Role 不是 VEHICLE_MANAGER / SUPER_ADMIN 不可重试;按角色控制入口可见性(不是权限点模型,见「⚠️ 关键变化」鉴权订正)
100001 入参非法(枚举值错、必填缺、格式错) 不可重试;本地校验后重提
100000 服务端兜底系统异常 可重试一次;连续出现即报障

接口 9 / 10 是内部端点,不走后台角色门禁,无 401/403。

逐端点

接口 1 POST .../share-groups(确认共用关系)

码 什么时候拿到 可否重试 / 前端处置
602100 成员少于 2 个 不可重试:本地先拦
602101 成员超过 20 个 不可重试:本地先拦
602102 成员在该资源日无活跃占用(常见于用了过期的 sourceId) 不可重试:先调接口 2 回读最新 sourceId 再提交
602103 成员派单不属于本团,或团期身份未知 不可重试:移除该成员
602104 serviceDate 不在团期服务日窗内 不可重试:该日不能建共用关系
602105 团级配车成员不属于本团或当日非活跃 不可重试:刷新本团配车行后重选
602106 成员已属于另一个共用关系 不可重试:先解除原关系(接口 3)
602107 costBearer 缺失或非法 不可重试:本地先拦
602109 同一关系被并发改写 / 两个请求抢建同一关系 ✅ 可重试:原样重发
100502 10 秒内重复提交同一份成员全集 不报红:提示"请勿重复提交",不是失败
605036 被派入的成员司机与这辆共用车不是常驻组合 🔴 需车务表态:原样展示 message,确认后带 confirmCrossResident=true 重发同一份请求
605075 抢到资源锁后租约丢失,本次未提交 🔴 重试无效:不给重试按钮,提示核对并发操作后重新发起
605008 抢锁超时,系统繁忙 ✅ 可重试:给重试按钮
605037 / 605038 / 605070 该成员的车维保停用 / 司机休假待激活 / 司机不存在 不可重试:换资源或把该成员移出本次成员全集

接口 2 GET .../share-groups(查询) — 无业务错误码;resourceType 传非法值返 100001(不是 602114)。

接口 3 DELETE .../share-groups/{shareGroupId}(解除)

码 什么时候拿到 可否重试 / 前端处置
602108 关系不存在或已解除 不可重试:提示已不存在,重拉接口 2 刷新
602109 解除过程中关系行/成员行被并发改写 ✅ 可重试:原样重发
602110 存在幸存共用派单却没传 survivorPolicy 不可重试:弹策略选择框后带参重发
602111 KEEP_LEGAL 下幸存成员之间不满足衔接规则 不可重试:message 会点名冲突对,改用 REASSIGN/RELEASE
602112 占用账本重建后幸存集合与期望不符 不可重试:整请求已回滚,报障
605008 抢锁超时 ✅ 可重试
605075 资源锁已失效,本次未提交 🔴 重试无效:不给重试按钮
605042 某成员的司机通知结果正在确认 ✅ 可稍后重试
605043 某成员已出发或已不是可清状态 不可重试:卡在成员上,需人工处理该成员
605047 某成员行程已结束,派车信息只读 不可重试:卡在成员上
605009 某成员派单不存在 不可重试:先调接口 2 刷新成员列表

接口 4 GET .../readiness(就绪判定)

码 什么时候拿到 可否重试 / 前端处置
602113 取不到本团的配车就绪基线,判定失败关闭 ✅ 可重试;不要把它渲染成"未就绪"的红灯,这是"没有结论"不是"判定为否"
602114 就绪查询入参非法 不可重试:本地先拦

接口 5 POST .../reconfigure(整团逐日配车提交) — 本单只新增 survivorPolicy 与四个响应字段,原有码沿用 #7442,不重复展开:602000 / 602001 / 602002 / 602003 / 602004 / 602005 / 602006 / 602009 与 600003 / 600004 / 600005 / 600006 / 600007 / 600008 / 600009 / 600010 / 600011。其中 600008(团期配车被并发修改)、600009(权威基线不可用)、602005(用车需求已更新)可重试,其余为入参/状态类,不可重试。 🔴 本单新增的透出:clearAll=true 触发幸存处置时,接口 3 那张 605 表(605008 / 605075 / 605042 / 605043 / 605047 / 605009)在本接口同样适用,处置一致,整请求回滚。

接口 6 POST /admin/fleet/assignments/precheck — 无业务错误码,只读咨询恒 code=200,冲突信息在 conflicts[] 里返回;入参非法返 100001。

接口 7 POST /admin/fleet/assignments/candidates — 同上,无业务错误码;入参非法返 100001。

接口 8 POST /admin/fleet/assignments/{assignmentId}/restore-cancel(撤销取消的派单)

码 什么时候拿到 可否重试 / 前端处置
605008 抢锁超时 ✅ 可重试
605075 资源锁已失效,本次未提交 🔴 重试无效:不给重试按钮
605042 司机通知结果正在确认 ✅ 可稍后重试;本次保持 canceled,无任何副作用
605009 派单不存在 不可重试:刷新列表
605015 超出撤销窗口,或该派单不是 canceled 态 不可重试:撤销入口置灰
605016 已被其它单占用,无法恢复 不可重试:引导改派
605017 仅取消操作本人可撤销 不可重试:按钮对非本人置灰
605037 车辆已维保或停用 不可重试:引导换车改派
605038 司机已休假或待激活 不可重试:引导换司机改派

接口 9 POST /internal/fleet/dispatch/group-batch/{groupBatchId}/release(内部幂等释放) — 无业务错误码,幂等,重复调用零写入不报错。

接口 10 POST /v3/internal/group-batch/{groupBatchId}/vehicle-ready(内部回填就绪) — 无业务错误码;业务性未生效不用错误码表达,一律 code=200 + applied=false + discardReason/discardCode(仅 809205 / 809206 两种取值);只有数据库/并发异常才落兜底 100000。


七、不影响范围

  • 仅影响: 管理后台团期配车页(共用确认交互、就绪两档展示)、车务派单页(预校验/候选列表标红变绿、撤销恢复)
  • 零影响:
    • C 端小程序/H5 全部功能
    • 订单创建、订单详情、产品/资源模块
    • GET /v3/internal/group-batch/{groupBatchId}/dispatch-baseline(复用不改,仅供 fleet 内部拉取基线,前端不直连)
    • 网关路由:Path=/admin/fleet/** 通配路由已覆盖全部新增端点,无需新增路由配置;鉴权按「⚠️ 关键变化」的角色门禁订正,未新增也未依赖任何权限码
    • 散客(非团期)派单:共用关系的主键含 group_batch_id,只在团期作用域内生效,不影响散客场景的既有冲突判定规则
    • CityJunctionChecker/R3-EX 判定逻辑本身:未被修改,共用关系是它之前的一层放行,未命中时仍按原规则判
    • POST /admin/fleet/assignments(新建派单)的请求/响应契约本身零变化,未列入「二、变更接口清单」;但它内部调用的 assertOverlapsResolvable/assertNoGroupDispatchConflict(doCreateInLock 内,AssignmentService.java:2935/2940/2945)与本单改造的守卫是同一份实现,所以新建派单时同样会认已确认的共用关系——前端若发现"直接新建一条本属共用关系的派单也不再被跨城误拒",这是本单的连带效果,不是另一处改动

八、测试环境已验证

环境:网关 https://api.test.1814.love:9443,分支 dev-v3,部署点 bb4091074。 部署事实【实测】:hl-fleet-service 2026-09-21 17:14:32 产出新 jar(111247255 B),实例 8187(PID 1598964)、8087(PID 1599321)滚动重启并健康检查 UP;hl-order-service-v3 17:16:07 产出新 jar(328980166 B),实例 8186(PID 1607261)、8086(PID 1608074)UP。两次任务 exit_code=0,服务端 git sync done: branch=dev-v3 HEAD=bb4091074。 读数口径:鉴权与业务失败都走响应信封(HTTP 200 + code),下表 code 列取自响应体 code 字段,不是 HTTP 状态行。前端同样必须每次都读 code。

8.1 经网关逐端点实测(2026-09-21,车务角色账号,单次登录)

# 端点 本次请求形态 实测 code 读数
1 POST .../batches/{id}/share-groups 合法 body + 不存在的 groupBatchId 600009 团期配车权威基线不可用,请稍后重试或检查团期状态——请求已穿透网关进入业务层并在基线读取处失败,路由与鉴权通
2 GET .../batches/{id}/share-groups 真实团期 + includeReleased=true 200 data: [](该团当前无共用关系,空集是合法读数)
3 DELETE .../share-groups/{id} 不带 survivorPolicy 602110 存在幸存共用派单,必须指定处置策略——与「接口 3」正文声明逐字一致,也实证了该参数在框架层故意非必填(声明成必填会被 Spring 挡成 400,这个业务码就永远抛不出来)
3b DELETE .../share-groups/{id} 带 survivorPolicy=KEEP_LEGAL + 不存在的关系 602108 共用关系不存在或已解除——与正文声明一致
4 GET .../batches/{id}/readiness 真实团期(两个样本) 200 两档结构完整:ready=false、warned=true、blockers[] 含 BLOCK_GROUP_MISSING / BLOCK_GROUP_DAYS_INCOMPLETE(逐服务日一条,带 tripDate/groupCode)/ BLOCK_NOT_CONFIRMED,warnings[] 含 WARN_SEAT_SHORTAGE。字段名、层级与本文「接口 4」出参表一致
4b GET .../batches/{id}/readiness 整团免车的团期 602113 无法取得本团的配车就绪基线, 就绪判定失败关闭: 正式用车需求未声明任何乘车分组(整团免车的团期不判配车就绪)——失败关闭,没有降级成 ready=true,与「接口 4」业务边界声明一致
5 POST .../batches/{id}/reconfigure demands 为空 / 逐日项缺字段 600002、400 依次命中 逐日配车需求不能为空 与字段级校验 乘车分组不能为空; 派出车辆 ID 不能为空,入参校验链路在位
6 POST /admin/fleet/assignments/precheck 不存在的车/司机 + 日期区间 200 conflicts[] 每项都带齐 type/conflictAssignmentId/conflictOrderNo/conflictDateRange/cityJunctionShareCandidate/blocking/reasonCode/shareGroupId/msg——本单新增的三个字段确实出现在响应里(本次样本为资源不存在类冲突,故 blocking=true、reasonCode=null、shareGroupId=null,与出参表「资源不存在类冲突恒为 true / 为 null」一致);warnings[] 同时返回
7 POST /admin/fleet/assignments/candidates 带 orderId 的候选查询 200 正常返回车辆/司机候选分页,每车带 selectable/availabilityReasonCode/availabilityWindows/residentMatch/crossResident/requiresCrossResidentConfirmation/relationMessage/conflicts 等列。⚠️ 不传 orderId 时直接返回 400 字段【order_id】未填写——这是本端点的既有必填约束,前端拼参时不要漏
8 POST /admin/fleet/assignments/{id}/restore-cancel 不存在的派单 605009 派单不存在——与「接口 8」错误码表一致

以上 8 个 /admin/fleet/** 端点全部经网关可达,响应结构与本文档契约一致。写口一律用零副作用的错误路径取证(不存在的团期 / 不存在的关系 / 不存在的派单 / 故意缺参),测试环境未因取证产生任何业务数据变更。

8.2 两个 internal 端点不在网关面上(结构性,不是失败)

POST /internal/fleet/dispatch/group-batch/{id}/release 与 POST /v3/internal/group-batch/{id}/vehicle-ready 经网关实测均返回 code=403、message=接口不可访问。

这是设计如此:hl-gateway 的 JwtAuthFilter 在所有鉴权分支之前先判 isInternalPath(path)(/internal、/internal/、/v3/internal、/v3/internal/ 前缀,并做了矩阵参数归一化防绕过),命中直接 forbidden(exchange, "接口不可访问"),注释写明「service-to-service only, never exposed to clients」。这两个端点本来就不面向前端,它们由 hl-order-service-v3 / hl-fleet-service 之间的 Feign 调用触发,前端不需要、也不可能直接调。本文档收录它们只是为了说明「团期流团 / 车辆就绪回调」这两条链路上前端会看到的间接后果(共用关系被连带 RELEASED、团级需求推进到 DONE)。

8.3 本次未在网关上取证的业务路径(覆盖边界,前端照常可开工)

下列路径需要一条真实的、活跃的共用关系及其成员派单作为前置,构造它会在共享测试环境上产生不可逆的业务数据,本次取证未构造:

  • 共用关系的 happy path:确认成功后 precheck / candidates 对该车返回 blocking=false、reasonCode=SHARE_GROUP_CONFIRMED、shareEligible=true、shareGroupId 非空。
  • DELETE(REASSIGN / RELEASE)与 reconfigure(clearAll=true)真释放幸存占用后 pendingReassignSourceIds[] 的内容。
  • 自动收缩 RELEASED + AUTO_SINGLE_MEMBER 的实际落库。

这三条的契约形状(字段名、类型、枚举值、错误码)已按 origin/dev-v3 源码逐项核对并写进上面的接口小节,前端按文档实现即可;它们影响的是「后端行为的实证深度」,不影响「前端怎么写这段代码」。


十、相关文档

  • 关联 Issue: wx/HL#7444
  • 关联 PR: wx/HL#7956(PR-1)、wx/HL#7959(PR-2a)、wx/HL#7963(PR-2b)
  • 前置工单: #7442(V-3 配车写口与恢复流程)、#7443(V-4 接送机与团期身份)——两者均已先行合入 dev-v3;相关 changelog 见 changelogs-v2/2026-09/19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md(与本单共享"团期配车"域,内容不重叠,reconfigure/reopen/plan-refresh 等端点的基础契约在那份文档)
  • 后续工单: V-6 团期车费分摊(挂起,待定稿核团分摊口径;本单交付的 costBearer/costSourceRefNo 是它的输入)
  • 相关权限/门禁背景: hl-user-service/src/main/resources/db/migration/V20260911_007__fix_group_dispatch_admin_grants.sql(工单 #5626/#7440,记载 /admin/fleet/** 角色门禁 vs 权限码的历史订正,见本文档「⚠️ 关键变化」鉴权订正条)
  • 方案文档: docs/tasks/group-batch-vehicle/团期车务实施方案.md §3.2、§4(V-5.a 就绪判定、V-5.c 同团共用放行)、§8.8、§8.11

关联 / 联系人

链接

联系人

  • 后端负责人: @wx