22 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 | 8004 | 派车共用关系:候选面拆出 shareEligible,cityJunctionShareCandidate 两个读口恢复同义 | admin | jw(GIT) | 修改接口 | deployed | verified | not_required | mmg | 同名字段 cityJunctionShareCandidate 此前在 candidates 与 precheck 两个读口上含义不同:#7444 把 candidates 一侧扩写成『同城衔接 或 已确认共用关系』,precheck 一侧保持原义『只说同城衔接』,于是同一对跨城派单两个读口返回相反的 true/false。本次把 candidates 该列恢复为原义(与 precheck 同义),新增 shareEligible 承载『这条冲突被放行了吗』。candidates 出参新增一个字段、一个既有字段取值口径回滚;precheck 出参与两端点入参一律不变。后端已合并 dev-v3(32f87d022)并部署 TEST,网关实测三种取值组合齐全(工单 #8004 AC-1)。前端若已按 #7444 口径把 cityJunctionShareCandidate 当作『可不可以选这辆车』使用,须改读 shareEligible 或 blocking。 前端实证维持 not_required(mmg 2026-09-20):cityJunctionShareCandidate/shareEligible 全仓零命中,前端从未按 #7444 扩写口径把该字段当「可不可以选」消费(冲突渲染走 reasonCode/blocking 等既有字段),口径回滚+新增字段零影响;排除自身 excludeAssignmentId 成对传惯例在用。 | 2026-09-20 | dev-v3 |
fleet: 共用关系两个读口契约收口——拆出 shareEligible
存放目录:
changelogs-v2/{YYYY-MM}/(管理后台)服务: hl-fleet-service (端口 8087) PR: #8027 Issue: #8004 日期: 2026-09-20 影响范围: 派车弹窗「选车/选司机」候选列表与保存前预校验的冲突项展示
⚠️ 关键变化
candidates响应conflicts[].cityJunctionShareCandidate的取值口径回滚:从同城衔接 || 已确认共用关系改回 只表示同城衔接,与precheck的同名字段恢复同义。candidates响应conflicts[]新增shareEligible:承载「这条冲突被放行了吗」(同城衔接 || 已确认共用关系),恒等于!blocking。- 🔴 如果前端此前按 #7444 的口径把
cityJunctionShareCandidate当「这辆车可不可以选」用,必须改读shareEligible或blocking—— 否则在「跨城但有共用关系」的场景会把可选的车渲染成不可选。 precheck的出参形状一个字都没变,只是文档写明了cityJunctionShareCandidate的确切含义;那侧「被放行了吗」一直由blocking表达。- 两个端点的入参、路径、方法、错误码、判权全部不变。 本次只订正了 Swagger 里「改派必须传
excludeAssignmentId」这句一直缺失的契约说明(行为本来就是这样,不是行为变更)。
一、背景
cityJunctionShareCandidate 是既有已发布字段,changelog #5302(2026-07-28)与 #6843(2026-08-31)都为两个读口定义过它,语义均为「两段行程首尾能不能同城衔接」。
#7444(团期配车共用关系)在候选面引入「人工确认的共用关系也能放行」这条新依据时,把它挂到了这个既有字段上(candidates 一侧改成 junction || shareAuthorized),而预校验面 precheck 一侧刻意保持原义。结果是:
同一对跨城派单,
precheck报false、candidates报true。
后端两侧各自读都自洽,各自也都满足自己那半条验收项 —— 错只落在跨两个读口取同名字段的前端身上,代码审查与单侧测试都看不见。本次按「加新的放行依据就加新字段」把两个概念拆开。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询派单候选资源 | POST | /admin/fleet/assignments/candidates |
修改接口 | conflicts[] 新增 shareEligible;cityJunctionShareCandidate 取值回滚为只表示同城衔接 |
| 2 | 派单预校验冲突 | POST | /admin/fleet/assignments/precheck |
修改接口 | 出参形状与取值均不变;仅补齐 cityJunctionShareCandidate 的确切含义与「须传 excludeAssignmentId」的契约说明 |
三、接口详情
1. 查询派单候选资源 POST /admin/fleet/assignments/candidates
VO: Result<AssignmentCandidateRespVO>
使用场景
派车弹窗 Step 2「选车 / 选司机」列表。每个候选资源带出它在所选服务日期内的占用冲突明细,前端据此渲染「这辆车能不能选、为什么」。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| startDate | body | string(date) | 是 | yyyy-MM-dd |
本次派车服务开始日 |
| endDate | body | string(date) | 是 | yyyy-MM-dd |
本次派车服务结束日 |
| pickupAt | body | string | 否 | — | 本次接车城市/地点,判同城衔接用 |
| dropoffAt | body | string | 否 | — | 本次送达城市/地点,判同城衔接用 |
| orderId | body | number | 否 | 改派排除自身时必填 | 当前订单 ID |
| requirementId | body | number | 否 | 改派排除自身时必填 | 当前用车需求 ID |
| excludeAssignmentId | body | number | 否 | 改派场景必传,见「四」 | 要排除的自身派单 ID;同时是共用关系判定的主体身份 |
| assignmentGroupId | body | number | 否 | 与 excludeAssignmentId 成对传 |
被排除派单所属派车组 ID;单独传它不足以排除自身占用 |
| vehicleKeyword | body | string | 否 | — | 车牌/车型关键词过滤 |
| vehiclePage | body | integer | 否 | 默认 1 | 车辆分页页码(不是 pageNum) |
| vehiclePageSize | body | integer | 否 | 默认 20 | 车辆分页大小(不是 pageSize) |
| driverPage | body | integer | 否 | 默认 1 | 司机分页页码 |
| driverPageSize | body | integer | 否 | 默认 20 | 司机分页大小 |
入参本次未作任何改动,此表为便于前端对照完整列出关键字段。
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data.vehicles.records[].conflicts[].cityJunctionShareCandidate | boolean | 【本次取值口径回滚】 两段行程是否同城首尾衔接(R3-EX)。只表示城市衔接,不承载共用关系;与 precheck 的同名字段同义 |
| data.vehicles.records[].conflicts[].shareEligible | boolean | 【本次新增】 这条冲突是否已被放行 = 同城衔接 || 已确认共用关系,恒等于 !blocking |
| data.vehicles.records[].conflicts[].blocking | boolean | 这条冲突是否拦得住本次派车(未变) |
| data.vehicles.records[].conflicts[].reasonCode | string | SHARE_GROUP_CONFIRMED / CITY_JUNCTION_SHAREABLE / ASSIGNMENT_CONFLICT(未变) |
| data.vehicles.records[].conflicts[].shareGroupId | string | 放行所凭的已确认共用关系 ID,无则 null(未变) |
| data.vehicles.records[].conflicts[].reasonMessage | string | 可直接展示的原因文案(未变) |
| data.vehicles.records[].selectable | boolean | 这辆车在页面上能不能选(未变) |
| data.drivers.records[].conflicts[] | array | 司机维度同构,字段与上表一致 |
其余字段(
vehicleId/plate/seats/protocolPrice/availabilityWindows…)本次一律未变,此处不重复列出。
请求示例
{
"startDate": "2026-12-28",
"endDate": "2026-12-28",
"pickupAt": "满洲里口岸",
"orderId": "2101169963900207106",
"requirementId": "7330992522584337",
"excludeAssignmentId": "359563681173999616",
"assignmentGroupId": "359563681132056576",
"vehicleKeyword": "蒙C04E04",
"vehiclePage": 1,
"vehiclePageSize": 20
}
响应示例
跨城重叠、但两条派单同属一个已确认共用关系 —— 两列取值相反,这正是本次拆字段要表达的情况:
{
"code": 200,
"message": "成功",
"data": {
"vehicles": {
"total": 1,
"records": [
{
"vehicleId": "2065329515642396673",
"plate": "蒙C04E04",
"available": true,
"selectable": true,
"availabilityReasonCode": "SHARE_GROUP_CONFIRMED",
"conflicts": [
{
"assignmentId": "2101173674454192130",
"orderNo": "HL20260919124211678",
"startDate": "2026-12-28",
"endDate": "2026-12-28",
"cityJunctionShareCandidate": false,
"shareEligible": true,
"blocking": false,
"reasonCode": "SHARE_GROUP_CONFIRMED",
"shareGroupId": "359563681060753408",
"reasonMessage": "已确认同团车辆共用关系,可共享"
}
]
}
]
}
},
"success": true
}
空数据 / 降级响应
- 所选日期内该资源无任何占用 →
conflicts为空数组(不是null),available=true、availabilityReasonCode=AVAILABLE。 - 候选资源本身为空 →
records为空数组、total=0,恒code=200。 - 占用行日期不完整(
startDate/endDate缺失)→ 该段不参与同城衔接与共用关系判定,cityJunctionShareCandidate=false、shareEligible=false、blocking=true(取严,宁可误报冲突也不误放行)。
错误响应
本端点是只读咨询,业务上恒 code=200,冲突在 data 体现。仅入参非法时报错:
{
"code": 605010,
"message": "排除派单不属于当前订单",
"data": null,
"success": false
}
业务边界
- 只读,不写任何业务数据。
- 最终一致以
create/change锁内重校验为准,不信任本端点的咨询结果。 - 读口不比写口松:不传
excludeAssignmentId即按「新建派单」处理,一律不享受共用授权。
2. 派单预校验冲突 POST /admin/fleet/assignments/precheck
VO: Result<PrecheckRespVO>
使用场景
Step 2 选定车 + 司机后、点保存前的最后一次冲突探查。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| vehicleId | body | number | 是 | — | 已选车辆 ID |
| driverId | body | number | 是 | — | 已选司机 ID |
| startDate | body | string(date) | 是 | yyyy-MM-dd |
服务开始日 |
| endDate | body | string(date) | 是 | yyyy-MM-dd |
服务结束日 |
| pickupAt | body | string | 否 | — | 接车城市/地点 |
| dropoffAt | body | string | 否 | — | 送达城市/地点 |
| orderId | body | number | 否 | — | 当前订单 ID |
| excludeAssignmentId | body | number | 否 | 改派场景必传,见「四」 | 要排除的自身派单 ID;同时是共用关系判定的主体身份 |
| headcount | body | integer | 否 | — | 乘车人数 |
入参本次未作任何改动。
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data.conflict | boolean | 是否存在阻断性冲突(未变) |
| data.conflicts[].cityJunctionShareCandidate | boolean | 两段行程是否同城首尾衔接。只表示城市衔接,不承载共用关系;与 candidates 的同名字段同义。本次取值未变,仅文档写明含义 |
| data.conflicts[].blocking | boolean | 这条冲突是否拦得住本次派车。本侧没有 shareEligible,它的等价物就是 !blocking(未变) |
| data.conflicts[].reasonCode | string | 同 candidates 的取值表(未变) |
| data.conflicts[].shareGroupId | string | 放行所凭的共用关系 ID,无则 null(未变) |
| data.conflicts[].conflictAssignmentId | string | 冲突派单 ID(未变) |
| data.conflicts[].conflictDateRange | string | 冲突日期段 start~end(未变) |
| data.warnings[] | array | 非阻断提示(未变) |
请求示例
{
"orderId": "2101169963900207106",
"requirementId": "7330992522584337",
"vehicleId": "2065329515642396673",
"driverId": "2065272148229865473",
"startDate": "2026-12-28",
"endDate": "2026-12-28",
"pickupAt": "满洲里口岸",
"headcount": 2,
"excludeAssignmentId": "359563681173999616"
}
响应示例
与上面 candidates 完全同一组车/日期/城市/主体 —— cityJunctionShareCandidate 两侧同为 false,分叉已消除:
{
"code": 200,
"message": "成功",
"data": {
"conflict": false,
"conflicts": [
{
"type": "vehicle",
"conflictAssignmentId": "2101173674454192130",
"conflictOrderNo": "HL20260919124211678",
"conflictDateRange": "2026-12-28~2026-12-28",
"cityJunctionShareCandidate": false,
"blocking": false,
"reasonCode": "SHARE_GROUP_CONFIRMED",
"shareGroupId": "359563681060753408",
"msg": null
}
],
"warnings": []
},
"success": true
}
空数据 / 降级响应
- 无任何重叠占用 →
conflicts为空数组、conflict=false。 - 资源不可用(车队停用等)→
conflict=true,原因在warnings[],conflicts[]可为空。
错误响应
只读咨询恒 code=200 不抛业务异常,冲突在 data 体现。仅入参校验失败时:
{
"code": 400,
"message": "车辆ID不能为空",
"data": null,
"success": false
}
业务边界
- 只读,不写任何业务数据。
- 最终一致以
create锁内重校验为准。
四、契约约束与正确调用方式
4.1 判「这辆车能不能选」读哪个字段
| 读口 | 推荐读 | 也可读 | 不要读 |
|---|---|---|---|
candidates |
selectable(整车级)/ shareEligible(单条冲突级) |
!blocking |
cityJunctionShareCandidate |
precheck |
!blocking |
conflict(整体级) |
cityJunctionShareCandidate |
cityJunctionShareCandidate 只回答「这两段行程首尾能不能同城接上」,它是一个事实描述,不是放行结论。跨城但有共用关系时它是 false 而车是可选的。
跨两个读口时统一读 blocking —— 它是两处都有、且两处同义的那一个。
4.2 🔴 改派场景必须传 excludeAssignmentId
两个端点都不会自动认出「哪条占用是调用方自己」。不排除自身时,调用方会和自己冲突,而拿到的读数与「共用关系功能根本没做」一模一样,页面上选不到车且没有任何报错。
assignmentGroupId 是配套的归属校验身份,用于确认被排除的那条确属当前操作的这一组;它不是排除动作本身,单独传它不足以排除自身占用。两者成对传。
TEST 实测(同一请求,单变量):
| 入参 | selectable |
availabilityReasonCode |
shareGroupId |
|---|---|---|---|
带 excludeAssignmentId + assignmentGroupId |
true | SHARE_GROUP_CONFIRMED |
非空 |
只带 assignmentGroupId |
false | ASSIGNMENT_CONFLICT |
null |
4.3 分页字段名
candidates 的分页参数是 vehiclePage / vehiclePageSize / driverPage / driverPageSize,车辆与司机各自分页;没有 pageNum / pageSize。传错名字不会报错,会静默按默认值(第 1 页、20 条)返回。
五、数据库行为
无任何数据库写入。 两个端点均为只读咨询:
- 读
fleet_assignment(重叠占用)、fleet_group_dispatch_share_group+fleet_group_dispatch_share_member(共用关系授权)、fleet_vehicle/fleet_driver(候选资源)。 - 无建表、无改表、无 Flyway 脚本、无索引变更。
- 不产生 outbox 消息、不发事件。
六、边界行为
- 共用关系逐日判定:重叠期里只要有一天没被授权,这一对就回到原规则判,不整段放行。
- 主体身份为空即新建场景:不传
excludeAssignmentId时一律不享受共用授权,与写口create同口径 —— 读口不得比写口松,否则会造出「列表能选、写口拒」。 - 共用关系优先于同城衔接:两者同时成立时
reasonCode报SHARE_GROUP_CONFIRMED,因为那是人工确认过的、更强也更该被看见的依据。 - 取严失效方向:占用行日期不完整时不参与判定、按阻断处理。
shareEligible与blocking恒互为反面,不存在两者同真或同假的响应。
六.6、修改前后对比
字段级对比
| 接口 | 字段 | 改前 | 改后 |
|---|---|---|---|
candidates |
conflicts[].cityJunctionShareCandidate |
同城衔接 || 已确认共用关系 |
同城衔接(回到 #5302 原义) |
candidates |
conflicts[].shareEligible |
不存在 | 新增:同城衔接 || 已确认共用关系,恒 = !blocking |
candidates |
其余所有字段 | — | 未变 |
precheck |
conflicts[].cityJunctionShareCandidate |
同城衔接 |
同城衔接(未变) |
precheck |
其余所有字段 | — | 未变 |
行为级对比
同一对「跨城重叠 + 已确认共用关系」的派单:
| 场景 | 读口 | 改前 cityJunctionShareCandidate |
改后 cityJunctionShareCandidate |
改后 shareEligible |
|---|---|---|---|---|
| 跨城 + 有共用关系 | candidates |
true ❌ |
false ✅ |
true |
| 跨城 + 有共用关系 | precheck |
false |
false ✅ |
(本侧无此列,!blocking=true) |
| 同城衔接 | candidates |
true |
true |
true |
| 跨城 + 无共用关系 | candidates |
false |
false |
false |
改前两个读口在第一行给出相反的答案;改后两侧同值。
六.7、影响评估
| 面 | 影响 |
|---|---|
| 前端(须确认) | 🔴 若已按 #7444 口径把 cityJunctionShareCandidate 当「可不可以选」使用,必须改读 shareEligible 或 blocking。若一直按字面含义(同城衔接)使用,或读的是 selectable / blocking / reasonCode,则无需改动 |
| 旧前端兼容 | 既有字段回到已发布契约(#5302 / #6843)的语义,新语义走新字段,未升级的前端不会因本次改动而行为变差 |
| 写口 | 零影响。create / change / restore-cancel 的锁内重校验逻辑一行未动 |
| 判权 | 零影响,未改任何守卫 |
| 数据 | 零影响,无写入、无迁移 |
| 性能 | 零影响,只是把已算出的两个布尔值分别下发,无新增查询 |
#7444 交接件 |
其中「候选面 cityJunctionShareCandidate=true 表示可共享」的表述已被本单取代,以本篇为准 |
七、不影响范围
precheck的响应形状、字段取值、错误码 —— 一律未变。- 两个端点的入参(字段名、必填性、校验规则)—— 一律未变。
- 路径、方法、网关路由、判权规则 —— 一律未变。
- 派单创建 / 改派 / 取消 / 撤销取消等所有写接口 —— 一律未变。
- 共用关系的建立、确认、释放链路 —— 一律未变。
- 小程序端 —— 不涉及。
八、测试环境已验证
- 后端已合并
dev-v3(合并提交32f87d022,PR #8027),hl-fleet-service滚动部署 TEST,8087/8187 双实例健康。 - 部署记录:
[RECORD] hl-fleet-service <- dev-v3 @ 32f87d022,BUILD SUCCESS,两实例各 7s 起健康。 - 定向单测
AssignmentConverterTest,AssignmentControllerTest,AssignmentCandidateServiceTest,AssignmentServiceTest:677 tests,0 failures,0 errors,0 skipped;spotless:check通过。 - 变异验证:把
.cityJunctionShareCandidate(junction)改回(shareable),toConflict_sameShareGroup_...如期变红(已还原)。 - 真实网关实测(
api.test.1814.love:9443,共用关系359563681060753408,VEHICLE / 2026-12-28,车蒙C04E04):candidates带excludeAssignmentId+assignmentGroupId→selectable=true、冲突项cityJunctionShareCandidate=false/shareEligible=true/blocking=false/SHARE_GROUP_CONFIRMED/shareGroupId=359563681060753408;precheck同一车/日/城市/主体 →cityJunctionShareCandidate=false/blocking=false/SHARE_GROUP_CONFIRMED—— 两个读口同名字段同值,分叉消除;- 只带
assignmentGroupId→selectable=false/ASSIGNMENT_CONFLICT/shareGroupId=null,且冲突项里出现调用方自己那条派单(「调用方与自己冲突」的直接证据); - 同城衔接对照(12-28~12-29,两端海拉尔)→ 同一次响应里取到
cityJunction=true+shareEligible=true(CITY_JUNCTION_SHAREABLE)与cityJunction=false+shareEligible=false(ASSIGNMENT_CONFLICT)两条,证明两列相互独立; - 三种取值组合
(true,true)/(false,false)/(false,true)全部取到。
- Swagger 实测(
:8087/v2/api-docs):两端点 description 含「#8004 AC-2」契约说明;候选资源冲突定义含shareEligible;派单候选资源查询定义含「单独传它不足以排除自身占用」。 - 全程只读,无任何业务写入,未改动 TEST 数据。
十、相关文档
- 工单 #8004(本单,含两处问题的原始取证)
- 工单 #7444(团期配车共用关系,分叉的来源;其 AC-9 ② 的字段表述以本篇为准)
- changelog
#5302(2026-07-28)changelogs-v2/2026-07/28_5302_派车档期按完整组判定同城衔接-修改接口-管理后台.md——cityJunctionShareCandidate的原始已发布语义 - changelog
#6843(2026-08-31)changelogs-v2/2026-08/31_6843_供应商暂停合作联动停用车队与派单拦截-修改接口-管理后台.md
关联 / 联系人
链接
联系人
- 后端: jw
- 前端: mmg