「前端必须做的改动」节补一条落点:hl-ui origin/v2.1 的 src/api/orderV2.js 里 rejectVehicleRequirement 与 dispatchVehicleRequirement 的 JSDoc 仍写着 「不传保持旧行为(按 TRAVEL)」「不传=后端缺省 TRAVEL」,两句现已失效; 且全仓 809012 命中数为 0,该码今天无人接住。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
23 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 | 8601 | 逐户提交车务 / 打回的 kind 参数取消默认值 TRAVEL,两类活跃需求并存时必须显式指定(新错误码 809012) | admin | wx(GIT) | 修改接口 | deployed | verified | pending | PR #8610 已 squash 合并 dev-v3(a2ba628ecb),hl-order-service-v3 dev-v3 分支已滚测试服。这是需要前端改调用代码的变更:两个端点的 kind 查询参数从 defaultValue=TRAVEL 改成无默认值,纯接送机户由此可用,两类并存且不传 kind 时新抛 809012。 | 2026-09-30 | dev-v3 |
hl-order-service-v3: 逐户提交车务 / 打回的 kind 参数取消默认值 TRAVEL
存放目录:
changelogs-v2/2026-09/服务: hl-order-service-v3 (端口 8086) PR: #8610 Issue: #8601 日期: 2026-09-30 影响范围: 团期需求管理 Tab 的逐户「提交车务」与「打回定制师」两个按钮所调的端点,其kind查询参数语义
⚠️ 关键变化
- 🔴 这是需要前端改调用代码的变更:两个端点的
kind查询参数由@RequestParam(defaultValue = "TRAVEL")改为@RequestParam(required = false),没有默认值了。 - 前端以前可以怎么写:不传
kind,后端按TRAVEL处理。现在的实际行为:不传kind时后端按该户活跃用车需求的类别数自动解析——- 恰好 1 类 → 就用那一类(🆕 纯接送机户从此可用:旧默认值会去找一条根本不存在的 TRAVEL 行,导致这类户在这两个入口走不通流程);
- 0 类 → 与改前一致,由既有分支抛 582031「订单无有效需求行」;
- ≥2 类并存 → 🆕 抛新错误码 809012,拒绝猜测。
- 🔴 809012 是新增错误码,前端必须接住:
订单 {0} 同时存在 {1} 两类活跃用车需求,请显式指定要操作的类别(kind=TRAVEL 行程用车 / kind=TRANSFER 接送机)。撞到它的正确处置是带上kind重发(由用户选,或由页面上下文决定),不是重试。 - 前端要做的事:这两个按钮所在的位置本来就知道自己在操作哪一类需求(页面上就是按 TRAVEL / TRANSFER 分开展示的),一律显式带上
kind即可,带了就不会撞 809012。传了值的行为与改前逐字相同(含非法值仍由 809000 拒)。 - 请求体、响应体、权限、HTTP 形态全部未变:两个端点仍是
Result<Void>,dispatchRemark仍选填、returnRemark仍必填。
一、背景
一户订单的用车需求按类别分行,两类可以同时活跃:
| 类别 | 含义 |
|---|---|
TRAVEL |
团期行程用车 |
TRANSFER |
接送机 |
这两个端点都按 kind 精确定位一行再迁移状态、写备注。defaultValue = "TRAVEL" 让「调用方没说要动哪一类」与「调用方明确要动 TRAVEL」在服务层完全同形——两类并存而调用方没传 kind 时,接口返 200、改掉 TRAVEL 行,而操作者想动的 TRANSFER 行三个字段一个都没变,响应上没有任何可区分的信号。这是静默错写。
同一个默认值还造成第二个缺陷:只有 TRANSFER 活跃行的户,旧逻辑会去找一条不存在的 TRAVEL 行,拿到 582031,这类户在这两个入口根本用不了。
解析只写在服务层一处(RequirementService#resolveVehicleRequirementKind),Controller 不做兜底,避免两层各写一份「空了怎么办」而日后分叉。类别数与后续取行同源:数的是 selectAllActiveByOrderId,而它的实现就是对 selectLatestByOrderId(orderId, kind) 按枚举逐类别循环,所以「数出几类」与「按那一类取到哪行」用的是同一个筛选条件,不可能分叉。
与结算侧 809008 有意不同:那边只有 TRAVEL 才自动解析、单独一条 TRANSFER 也拒绝(手录车费的省略更可能是漏选归属);本处是需求状态机写口,单 TRANSFER 户只有这一条活跃需求,拒绝它等于让这类户走不通流程。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 团期管理员提交车务 | POST | /v3/admin/order/{id}/vehicle-requirement/dispatch |
查询参数取消默认值 + 新增错误码 | kind 不再默认 TRAVEL;两类并存且不传抛 809012 |
| 2 | 团期管理员打回定制师(车需求) | POST | /v3/admin/order/{id}/vehicle-requirement/reject |
查询参数取消默认值 + 新增错误码 | 同上 |
三、接口详情
1. 团期管理员提交车务 POST /v3/admin/order/{id}/vehicle-requirement/dispatch
VO: DispatchReqVO → Result<Void>
使用场景
团期需求管理 Tab 里对某一户点「提交车务」时调用,把该户指定类别的用车需求从 PENDING_REVIEW 推进到 PENDING 并写入提交备注(提供给车队人员查看)。仅团期子订单可用。权限点 group-batch:demand:confirm。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
id |
Path | Long | ✅ | - | 子订单 ID |
kind |
Query | String | ❌ | 取值 TRAVEL / TRANSFER |
🔴 改动点:不再有默认值 TRAVEL。不传时按该户活跃需求类别自动解析(恰好 1 类用那一类;0 类抛 582031;≥2 类抛 809012)。建议一律显式传。非法值仍抛 809000 |
dispatchRemark |
Body | String | ❌ | @Size(max=500) |
提交备注,提供给车队的审核意见;上限对齐库列宽 VARCHAR(500) |
出参 Result<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
code |
Integer | 200 = 成功 |
message |
String | 成功 |
success |
Boolean | true |
data |
null | 本端点无业务数据返回(Result<Void>),成功即以 code=200 为准,不要读 data |
请求示例
POST /v3/admin/order/2099459272533323777/vehicle-requirement/dispatch?kind=TRANSFER
Content-Type: application/json
{ "dispatchRemark": "需求已确认,请尽快派接送机车辆" }
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": null
}
空数据 / 降级响应
本端点恒无业务数据,成功时 data 恒为 null——这是正常成功形态,不是空数据降级:
{
"code": 200,
"message": "成功",
"success": true,
"data": null
}
该户没有任何活跃用车需求行时不降级、不静默成功,直接抛 582031(下面的错误响应)。
错误响应
🆕 809012(本次新增;{0} = 订单 ID,{1} = 两类名以 / 连接)。以下为测试服实测原文(订单 ID 2105173274755534850):
{
"code": 809012,
"message": "订单 2105173274755534850 同时存在 TRAVEL / TRANSFER 两类活跃用车需求,请显式指定要操作的类别(kind=TRAVEL 行程用车 / kind=TRANSFER 接送机)",
"success": false,
"data": null
}
其余错误码未变:
| 码 | 报文 | 触发 |
|---|---|---|
| 809000 | 用车需求类别非法:{0} |
kind 传了 TRAVEL / TRANSFER 之外的值 |
| 809007 | 接送机需求 {0} 的服务日期尚未回填,无法下发车务 |
解析到 TRANSFER 但该行 service_dates 为 NULL 或空数组 |
| 582031 | 订单无有效需求行 |
该户按解析出的类别取不到活跃行(含「一条都没有」) |
| 582083 | 需求状态不允许此操作,请检查当前状态 |
非团期子订单,或最新需求不在 PENDING_REVIEW |
业务边界
- 鉴权:权限点
group-batch:demand:confirm(Controller 入口执行,走 user-service Feign);未登录由网关拦截返 401。 - 仅团期子订单:非团期单先报 582083,
kind解析排在这道守卫之后——所以非团期单的报错与改前逐字相同,不会变成 809012。 - 解析只在不传
kind时发生:传了值就原样使用,包括非法值(仍由 809000 拒),本次改动不改变既有的非法值行为。 - 🔴 809012 不是可重试错误:同一请求重发多少次都是同一个码。处置是带上
kind重发。 - 纯接送机户现在可用:只有一条活跃 TRANSFER 行时不传
kind会被解析成 TRANSFER(改前拿 582031)。 - 状态机守卫未变:
PENDING_REVIEW → PENDING,同时写dispatch_remark、车控置PENDING、CAS 退流程。 - 失败零写入:809012 抛在取行之前、任何写入之前;809007 抛在服务日校验处,同样不落写。
2. 团期管理员打回定制师(车需求) POST /v3/admin/order/{id}/vehicle-requirement/reject
VO: RejectReqVO → Result<Void>
使用场景
团期需求管理 Tab 里对某一户点「打回」时调用,把该户指定类别的用车需求退回定制师重提(PENDING_REVIEW / PENDING → REJECTED_TO_CONSULTANT),写入打回备注,并同时清掉团级 requirement_confirmed 标记 + 写团级时间线(否则会出现「该户未提交、整团已确认」的矛盾态)。权限点 group-batch:demand:confirm。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
id |
Path | Long | ✅ | - | 子订单 ID |
kind |
Query | String | ❌ | 取值 TRAVEL / TRANSFER |
🔴 改动点:不再有默认值 TRAVEL,规则与 dispatch 端点逐条相同。建议一律显式传 |
returnRemark |
Body | String | ✅ | @NotBlank,@Size(max=500) |
打回备注;为空返 400「打回/驳回备注不能为空」,超长返 400「打回/驳回备注不能超过 500 字」。定制师重新提交时会创建新需求 |
出参 Result<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
code |
Integer | 200 = 成功 |
message |
String | 成功 |
success |
Boolean | true |
data |
null | 本端点无业务数据返回(Result<Void>) |
请求示例
POST /v3/admin/order/2099459272533323777/vehicle-requirement/reject?kind=TRAVEL
Content-Type: application/json
{ "returnRemark": "行程日与团期不符,请定制师重新确认用车日期" }
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": null
}
空数据 / 降级响应
本端点恒无业务数据,成功时 data 恒为 null:
{
"code": 200,
"message": "成功",
"success": true,
"data": null
}
打回既要写需求行、又要清团级标记并写团级时间线,是跨聚合编排且与整团确认共用团级锁——不存在「只做了一半」的降级形态,要么整套生效要么整体回滚。
错误响应
🆕 809012(本次新增,与 dispatch 端点同码同文案)。以下为测试服实测原文(订单 ID 2105173313083080706):
{
"code": 809012,
"message": "订单 2105173313083080706 同时存在 TRAVEL / TRANSFER 两类活跃用车需求,请显式指定要操作的类别(kind=TRAVEL 行程用车 / kind=TRANSFER 接送机)",
"success": false,
"data": null
}
其余错误码未变:
| 码 | 报文 | 触发 |
|---|---|---|
| 809000 | 用车需求类别非法:{0} |
kind 传了非法值 |
| 582031 | 订单无有效需求行 |
按解析出的类别取不到活跃行 |
| 582083 | 需求状态不允许此操作,请检查当前状态 |
非团期子订单,或最新需求不在 PENDING_REVIEW / PENDING |
| 400 | 打回/驳回备注不能为空 |
returnRemark 空 |
589535(子订单已分房)不适用于本端点:占用探测方法对resourceType=VEHICLE硬编码返回"无占用"(车侧无配房概念),该码只在resourceType=HOTEL时可能触发;已派车的拦截由另一套栅栏机制负责,不经过本码。这是既有行为,本次未改。
业务边界
- 鉴权:权限点
group-batch:demand:confirm;未登录由网关拦截返 401。 - kind 解析结果同时用于占用探测与实际取行:两步必须看同一个类别,避免错位——但车需求侧的占用探测恒不拦截(见上方说明),此为既有行为,本次未改。
- 只对车需求解析:同一条服务方法也承接房需求打回(
resourceType=HOTEL),kind对它无意义、不触发解析,酒店打回不会被车侧的两类并存误伤。 - 副作用是跨聚合的:除需求行外还会清团级
requirement_confirmed并写团级时间线BATCH_REQUIREMENT_REJECT,与批量打回落同一套副作用。 - 打回后需求要重提:定制师重新提交会创建新的需求行,不是在原行上改。
- 🔴 809012 不是可重试错误:处置是带上
kind重发。 - 失败零写入:809012 抛在取行与实写之前。
四、契约约束与正确调用方式
本节只写后端接受 / 拒绝请求的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 调用对照
| 场景 | 请求 | 结果 |
|---|---|---|
| ✅ 显式指定行程用车(推荐写法) | POST /v3/admin/order/{id}/vehicle-requirement/dispatch?kind=TRAVEL + { "dispatchRemark": "..." } |
200 |
| ✅ 显式指定接送机(推荐写法) | POST /v3/admin/order/{id}/vehicle-requirement/dispatch?kind=TRANSFER + { "dispatchRemark": "..." } |
200 |
| ✅ 不传 kind,该户只有一类活跃需求 | POST /v3/admin/order/{id}/vehicle-requirement/dispatch |
200,按那一类处理(纯接送机户从此可用) |
| ✅ 打回(备注必填) | POST /v3/admin/order/{id}/vehicle-requirement/reject?kind=TRAVEL + { "returnRemark": "请重新确认用车日期" } |
200 |
| ❌ 不传 kind,该户两类活跃需求并存 | POST /v3/admin/order/{id}/vehicle-requirement/reject |
809012,必须带 kind 重发 |
| ❌ kind 传非法值 | ?kind=travel2 |
809000 |
| ❌ 打回不带备注 | { "returnRemark": "" } |
400「打回/驳回备注不能为空」 |
| ❌ 备注超 500 字 | { "returnRemark": "<501 字>" } |
400「打回/驳回备注不能超过 500 字」 |
前端必须做的改动
- 两个端点的调用一律显式带上
kind。页面上这两个按钮本来就分挂在 TRAVEL / TRANSFER 两块需求下,取值是现成的;带上之后永远不会撞 809012。 - 接住 809012:若某处确实拿不到类别,撞到 809012 时要提示用户选择类别并带上
kind重发,不要做自动重试(同一请求重发永远同码)。 - 不要再依赖「不传 = TRAVEL」这个隐含约定——它已经不成立了。
🔴 落点已查明(2026-09-30 对
hl-uiorigin/v2.1查证):src/api/orderV2.js里rejectVehicleRequirement的 JSDoc 写着「TRAVEL / TRANSFER;不传保持旧行为(按 TRAVEL)」、dispatchVehicleRequirement写着「不传=后端缺省 TRAVEL」——这两句现在都是错的, 两处都是const query = kind ? { kind } : null,调用方不传就会走到新行为上。 另外全仓809012命中数为 0,即该码今天没有任何接住的地方。
五、数据库行为
本次改动没有任何表结构变化,变的是「写哪一行」的定位规则。
| 前端调用 | 该户活跃需求 | 改前写入 | 改后写入 |
|---|---|---|---|
不传 kind |
只有 TRAVEL | TRAVEL 行 | TRAVEL 行(未变) |
不传 kind |
只有 TRANSFER | ❌ 取不到 TRAVEL 行 → 582031,零写入 | ✅ TRANSFER 行 |
不传 kind |
TRAVEL + TRANSFER 并存 | ❌ 静默写 TRAVEL 行(返 200,操作者要动的那行三个字段全不变) | ✅ 809012 拒绝,零写入 |
不传 kind |
一条活跃行都没有 | 582031,零写入 | 582031,零写入(未变) |
传 kind=TRAVEL |
任意 | TRAVEL 行 | TRAVEL 行(未变) |
传 kind=TRANSFER |
任意 | TRANSFER 行 | TRANSFER 行(未变) |
写入内容本身未变:
- dispatch:
order_vehicle_requirement该行statusPENDING_REVIEW → PENDING、写dispatch_remark(≤500)、车控置PENDING、CAS 退流程。 - reject:该行
status → REJECTED_TO_CONSULTANT、写return_remark(≤500);同事务清团级requirement_confirmed、写团级时间线BATCH_REQUIREMENT_REJECT。
失败零写入:809012 抛在解析阶段(dispatch 里排在团期守卫之后、取行之前;reject 里排在占用探测之前),任何一条数据都不会落。
六、边界行为
- 未登录 → 401(网关拦截)。
- 权限点
group-batch:demand:confirm缺失 → 403。 - 非团期子订单 → 582083(这道守卫排在
kind解析之前,报错与改前逐字相同)。 - 需求状态不在允许的源状态集合 → 582083。
- 该户按解析出的类别取不到活跃行 → 582031。
- 解析到 TRANSFER 但服务日未回填 → 809007(dispatch 端点,失败关闭不放行)。
kind传非法值 → 809000(与改前一致,本次未改变非法值行为)。- 老数据兼容:不改表、不迁移;存量订单下次调用时按新解析规则生效。
六.5、枚举 / 数据字典
kind(用车需求类别)
所属字段: 两个端点的查询参数 kind | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
TRAVEL |
团期行程用车 | 汇总进团级乘车分组、整团逐日配车的那一类 |
TRANSFER |
接送机 | 逐户派车的那一类;服务日由大交通派生,未回填时 dispatch 抛 809007 |
| (不传) | — | 🔴 不再等价于 TRAVEL。按该户活跃需求类别数解析:1 类用那一类 / 0 类抛 582031 / ≥2 类抛 809012 |
| 其他任意值 | — | 非法,抛 809000 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
kind(两个端点的查询参数) |
@RequestParam(defaultValue = "TRAVEL"),Swagger 标 defaultValue=TRAVEL |
@RequestParam(required = false),无默认值;Swagger 文案改为「不传按该户活跃需求类别自动解析,两类并存时必须显式指定」 |
DispatchReqVO.dispatchRemark |
选填 ≤500 | 未变 |
RejectReqVO.returnRemark |
必填 ≤500 | 未变 |
| 两个端点的响应 | Result<Void> |
未变 |
| 错误码集合 | 809000 / 809007(仅 dispatch)/ 582031 / 582083 | 🆕 增加 809012 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
不传 kind + 两类并存 |
静默改 TRAVEL 行,返 200;操作者要动的 TRANSFER 行原封不动,响应上无任何信号 | 抛 809012,零写入 |
不传 kind + 只有 TRANSFER |
去找不存在的 TRAVEL 行 → 582031,这类户走不通流程 | 解析成 TRANSFER,正常执行 |
不传 kind + 只有 TRAVEL |
TRAVEL 行 | 未变 |
不传 kind + 一条活跃行都没有 |
582031 | 未变 |
传了 kind(合法或非法) |
原样使用 / 809000 | 未变 |
| 非团期子订单 | 582083 | 未变(守卫排在解析之前) |
房需求打回(resourceType=HOTEL) |
kind 无意义 |
未变(不触发车侧解析,不会被两类并存误伤) |
六.7、影响评估
- 是否破坏向后兼容: 是。旧调用方「不传
kind= 按 TRAVEL」的隐含约定已失效;两类活跃需求并存的户上,原本返 200 的请求现在会返 809012。 - 前端是否必须同步上线: 是(建议)。不改也不会报错的前提是「该户只有一类活跃需求」,一旦出现两类并存就会撞 809012。改法极小:调用时把已知的类别放进
kind查询参数,并接住 809012。 - 前端 workaround 清理点: 若为绕开「纯接送机户点提交车务报 582031」做过按钮置灰、隐藏或提示,可以撤掉——该场景已修好。
七、不影响范围
- 仅影响:
POST /v3/admin/order/{id}/vehicle-requirement/dispatch与POST /v3/admin/order/{id}/vehicle-requirement/reject两个端点的kind参数语义。 - 零影响:
- 房需求(
resourceType=HOTEL)的提交与打回链路 - 定制师侧提交 / 修改 / 调整用车需求
PUT /v3/admin/order/{id}/vehicle-requirement - 团级正式用车需求的保存 / 汇总 / 预检 / 确认 / 撤回 / 免车
- 批量打回、整团确认的既有行为
- 结算侧手录车费的归属解析(809008,口径有意不同,本次未动)
- 车务侧(hl-fleet-service)配车、派单
- 历史数据:不改表、不迁移
- 房需求(
八、测试环境已验证
- 代码事实(对
origin/dev-v3逐一查证):- 合并提交
a2ba628ecb(PR #8610 squash 合并进dev-v3),7 文件 / +491 −27。 VehicleRequirementAdminController两处@RequestParam(defaultValue = "TRAVEL")→@RequestParam(required = false),@ApiParam文案同步改写,diff 已逐行核对。- 新增
VehicleRequirementKindErrorCode.VEHICLE_REQUIREMENT_KIND_REQUIRED = IErrorCode.of(809012, …),消息模板与占位符含义({0}=订单 ID、{1}=两类名以/连接)已核对;同段既有码 809000/809001/809002/809007/809008/809009/809010/809011 未变。 - 新增私有方法
RequirementService#resolveVehicleRequirementKind(Long, String),三条分支(非空白原样返回 / 0 类原样返回 / 1 类用那一类 / ≥2 类抛 809012)逐行核对;dispatch 里的调用点排在团期守卫之后、取行之前,reject 里排在probeGroupAdminReject之前且仅对resourceType=VEHICLE生效。 - 回归覆盖:
RequirementServiceTest+301 行、VehicleRequirementAdminControllerTest+72 行、VehicleRequirementKindErrorCodeMessageTest+22 行(含 809012 报文渲染断言)。
- 合并提交
- 部署:
hl-order-service-v3的dev-v3分支已滚到测试服,两个端点走管理端网关/v3/admin/order/**既有路由,无新增路由。
POST /v3/admin/order/{id}/vehicle-requirement/dispatch?kind=TRANSFER → 200 ✓
POST /v3/admin/order/{id}/vehicle-requirement/reject?kind=TRAVEL → 200 ✓
POST /v3/admin/order/{id}/vehicle-requirement/dispatch(两类并存不传 kind) → 809012 ✓
POST /v3/admin/order/{id}/vehicle-requirement/dispatch(纯接送机户不传 kind) → 200 ✓(改前 582031)
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| — | #7210 | 逐单打回源状态放宽到 PENDING,接团期权限守卫 | ✅ 有效 |
| — | #7439 | 用车需求按 kind 分家,两个端点加 kind 参数(当时带默认值 TRAVEL) |
⚠️ 默认值部分已被本单撤销 |
| — | #8435 | 团期订单接送变更走团期放行(809011) | ✅ 有效 |
| 本 PR #8610 | #8601 | kind 取消默认值,空值按活跃类别数分流,新增 809012 |
✅ 最新 |
十、相关文档
- 关联 Issue: wx/HL#8601
- 关联 PR: wx/HL#8610
关联 / 联系人
链接
- Issue: #8601
- PR: #8610
- Merge commit: a2ba628ecb
联系人
- 后端负责人: @wx