109 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 | 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)。
一、背景
团期车务链路走到"配车"这一段时,此前有两个缺口:
- 假就绪:就绪判定只比对服务日集合,不看座位数、不看人数,每天排一辆 5 座车服务 17 人团照样判"就绪"。审查要求加容量校验,与既定设计(车型/车辆数/人数限制已在三周前全部放开,只留日期门禁)正面冲突。wx 拍板 D9:缺口可见但不阻断——硬拦三项(分组齐全/服务日逐日配满/需求与计划已确认)判
ready,只提醒两项(座位/司机)判warned,两者互相独立。 - 同车同日既跑团级行程又接送逐户,此前无合法表达:占用层与派单层的两两冲突守卫会把"团车 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:604rebindSourceId)。前端不得把某次成功响应的请求体当模板原样缓存重发——同一份 JSON 在 T0 成功,到 T+12s 用同一个(已过期的)sourceId原样重发会抛602102(成员在该资源日无活跃占用),这不是幂等窗口失效,是它引用的派单行已经不存在了。正确做法:改动成员集合前先调「接口 2」查询当前members[].sourceId,用回读到的最新值拼请求体。- 🔴 回读范围要扩到「其它维度的关系」(#8003 /
865fe40ec):成员身份键是(服务日, 维度, 来源类型, 来源 ID),维度在键里,同一张接送派单可以同时是车维度关系与司机维度关系的成员;而change换的是整行,两个维度记的都是那个旧 ID。现在准入会把其它关系/其它维度里仍记着旧 ID 的活跃成员行一起改绑。所以本次确认成功后,前端要刷新的不只是本关系——同一张派单参与的其它关系(典型是先建VEHICLE再建DRIVER)的成员列表缓存同样已过期,拿旧sourceId提交会撞602102。
- 🔴 回读范围要扩到「其它维度的关系」(#8003 /
- 窗外日拒绝是有意边界:
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
关联 / 联系人
链接
- Issue: #7444
- PR: #7956(PR-1)、#7959(PR-2a)、#7963(PR-2b)
- Merge commit: d97babc9e(PR-1)、43a5b7153(PR-2a)、19be7f21c(PR-2b)
联系人
- 后端负责人: @wx