文件
hl-api-changelog/changelogs-v2/2026-09/20_8004_共用关系两个读口契约收口拆出shareEligible-修改接口-管理后台.md
T

22 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 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 影响范围: 派车弹窗「选车/选司机」候选列表与保存前预校验的冲突项展示


⚠️ 关键变化

  1. candidates 响应 conflicts[].cityJunctionShareCandidate 的取值口径回滚:从 同城衔接 || 已确认共用关系 改回 只表示同城衔接,与 precheck 的同名字段恢复同义。
  2. candidates 响应 conflicts[] 新增 shareEligible:承载「这条冲突被放行了吗」(同城衔接 || 已确认共用关系),恒等于 !blocking。
  3. 🔴 如果前端此前按 #7444 的口径把 cityJunctionShareCandidate 当「这辆车可不可以选」用,必须改读 shareEligible 或 blocking —— 否则在「跨城但有共用关系」的场景会把可选的车渲染成不可选。
  4. precheck 的出参形状一个字都没变,只是文档写明了 cityJunctionShareCandidate 的确切含义;那侧「被放行了吗」一直由 blocking 表达。
  5. 两个端点的入参、路径、方法、错误码、判权全部不变。 本次只订正了 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 消息、不发事件。

六、边界行为

  1. 共用关系逐日判定:重叠期里只要有一天没被授权,这一对就回到原规则判,不整段放行。
  2. 主体身份为空即新建场景:不传 excludeAssignmentId 时一律不享受共用授权,与写口 create 同口径 —— 读口不得比写口松,否则会造出「列表能选、写口拒」。
  3. 共用关系优先于同城衔接:两者同时成立时 reasonCode 报 SHARE_GROUP_CONFIRMED,因为那是人工确认过的、更强也更该被看见的依据。
  4. 取严失效方向:占用行日期不完整时不参与判定、按阻断处理。
  5. 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