- #8559 团期用车户数计入汇总读数不再随 kind 筛选变化 - #8576 团期配车提交与确认响应新增车辆规格提醒清单 - #8577 只提交接送机的户不再被判「未提交用车需求」 - #8601 逐户提交车务与打回的 kind 参数取消默认值,新增 809012 - #8603 派单确认响应删除恒空的接送机缺失日期字段 后端均已部署测试服(order-v3 / fleet @ d57498d381),网关实测通过。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
39 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 | 8577 | 只提交了接送机的户不再被判「未提交用车需求」,809121/809122/809123 触发条件收窄且文案改写 | admin | wx(GIT) | 修改接口 | deployed | verified | pending | PR #8600 已 squash 合并 dev-v3(5b7074e691),hl-order-service-v3 dev-v3 分支已滚测试服。三个端点的请求体、响应体字段与错误码号全部未变,变的是 809121/809122/809123 的触发条件(收窄)与消息文案(去掉「行程」二字)。 | 2026-09-30 | dev-v3 |
hl-order-service-v3: 只提交了接送机的户不再被判「未提交用车需求」
存放目录:
changelogs-v2/2026-09/服务: hl-order-service-v3 (端口 8086) PR: #8600 Issue: #8577 日期: 2026-09-30 影响范围: 团期需求管理 Tab 的三个端点(保存正式用车需求 / 自动汇总草稿 / 整体确认预检)里 809121、809122、809123 的触发条件与消息文案
⚠️ 关键变化
- 🔴 判据从「有没有提交行程用车(TRAVEL)」收窄为「两类用车需求(TRAVEL / TRANSFER)是不是一条都没有」。改前:某户只提交了接送机需求,团级保存、自动汇总、确认预检都把它当成「一条都没交」,整团被 809123 / 809121 / 809122 卡住,而这户其实已经明确表达过「只要接送机、不要行程车」,运营没有任何干净出路(唯一逃生舱是整团 waive 免车,那会把真需要行程车的户一起免掉)。改后:这户算已提交,三处一律放行。
- 三个错误码的码值没变、字段没变,变的是什么时候抛(收窄)与消息文案(三条都去掉了「行程」二字):
809121团期 {0} 有 {1} 户缺少可汇总的行程用车需求…→…缺少可汇总的用车需求…809122该户尚未提交行程用车需求,请先让定制师提交后再整团提交车务→该户尚未提交用车需求,…809123{0}有 {1} 户尚未提交行程用车需求,暂不能保存正式用车需求:{2}→…尚未提交用车需求,…- 🔴 前端凡是对这三条报文做过关键词匹配 / 字符串包含判断的地方必须改(
行程用车需求这个子串在三条里都没了)。正确做法是按code分支,不要匹配message文本。
- 🔴 809109「逐日覆盖」一个字都没改,仍然只认 TRAVEL。这是刻意的:本次分离的是「户级提交判定」与「行程覆盖判定」两件事,合并会把墙从 809123 挪到 809109,症状一模一样只是换个码。所以——只提交接送机的户不再被判未提交,也不要求被任何乘车分组覆盖;它结构上就在团级乘车分组之外,走逐户派车。
GroupVehicleDraftAggregator的缺失原因文案未提交行程用车需求→未提交用车需求。它出现在 809121 报文的逐户清单里(「户标识:原因」,顿号分隔),前端若展示过这个字符串同样受影响。- 「豁免户」
exemptHouseholds的语义边界也随之明确:只提交了接送机的户既不进未提交名单、也不进豁免名单——豁免解释的是「没提交的户为什么不拦」,而它本来就提交过。
一、背景
一户在团期里的用车需求有两类活跃行,互不替代:
| 类别 | 含义 | 派车路径 |
|---|---|---|
TRAVEL |
团期行程用车 | 汇总进团级乘车分组,整团逐日配车 |
TRANSFER |
接送机 | 逐户派车,结构上不进团级乘车分组 |
改前的三处判定都只查 TRAVEL。于是「只要接送机、不要行程车」这种完全合法的在团户(与 #7972 (A) 对 809114 的定案同源)被读成「什么都没交」。团级保存直接 809123 整份拒绝、自动汇总 809121 整团出不来草稿、确认预检 809122 逐户挂红——运营改不动、催不动(该户定制师已经交过了)、也绕不过去。
本次把判定拆成两个集合(travelSubmittedOrderIds / anySubmittedOrderIds),单源仍只有一份,在 GroupVehicleRequirementService#classifyVehicleSubmission(原名 classifyTravelSubmission),保存、预检、自动汇总三处共用:
- 户级「交了没有」 → 用
anySubmittedOrderIds(两类任一即算交了)→ 管 809121 / 809122 / 809123; - 行程逐日覆盖 → 仍用
travelSubmittedOrderIds(只认 TRAVEL)→ 管 809109。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 保存团期正式用车需求(全量替换) | PUT | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement |
错误码触发条件收窄 + 文案改写 | 809123 不再对「只提交接送机」的户触发;报文去掉「行程」 |
| 2 | 自动汇总正式用车需求草稿 | GET | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft |
错误码触发条件收窄 + 文案改写 | 809121 同上;缺失原因文案同步改写 |
| 3 | 整体确认需求缺失预检 | GET | /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check |
缺失项触发条件收窄 + 文案改写 | HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED(809122)同上 |
三、接口详情
1. 保存团期正式用车需求(全量替换) PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement
VO: GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO
使用场景
团期需求管理 Tab 的「正式用车需求」编辑弹窗点保存时调用,整份全量替换(未出现在本次提交里的分组会被移出当前版本)。权限点 group-batch:demand:confirm。本次改动只让 809123 少抛一类情况、并改了它的报文,请求体与响应体一个字段都没动。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | - | 团期 ID |
version |
Body | Integer | ❌ | 乐观锁 | 首次保存传 null,后续必须回传上次 GET / PUT 拿到的值;不一致抛 809102 |
remark |
Body | String | ❌ | @Size(max=500) |
整份需求备注 |
groups |
Body | Array | ✅ | @NotNull(不是 @NotEmpty)、@Valid |
全部乘车分组;空数组是合法提交(有需车户时由 809103 拦),整团免车请改走 waive 端点 |
groups[].groupId |
Body | Long | ❌ | - | 既有分组主键;新增分组传 null。带上它 = 声明「就是库里那一组」,此时 groupCode 不得变更(改名抛 809104) |
groups[].groupCode |
Body | String | ✅ | @NotBlank,@Size(max=32) |
分组键,直接作为车费 alloc_group |
groups[].vehicleType |
Body | String | ✅ | @NotBlank,@Size(max=64) |
车型大类编码,不是自由文本;取值权威见 GET /internal/fleet/vehicle-types/category-names,不在字典内抛 809119 |
groups[].serviceStartDate |
Body | String(yyyy-MM-dd) |
✅ | @NotNull |
本组服务开始日 |
groups[].serviceEndDate |
Body | String(yyyy-MM-dd) |
✅ | @NotNull |
本组服务结束日(须不早于开始日) |
groups[].seats |
Body | Integer | ❌ | @Min(1) |
该组单车座位数;刻意非必填(存量分组没有该值),与 count 必须同填或同空(809118),且须在该车型可选档位内(809124) |
groups[].count |
Body | Integer | ❌ | @Min(1) |
该组车辆数量;同上 |
groups[].specialTags |
Body | Array<String> | ❌ | 值须在字典 vehicle_special_demand 内 |
特殊诉求标签编码数组;含字典外编码整份拒绝(809117) |
groups[].remark |
Body | String | ❌ | @Size(max=500) |
该组备注 |
groups[].days |
Body | Array | ✅ | @NotEmpty,@Valid |
逐日用车人数与成员,不能用单值人数代替 |
groups[].days[].tripDate |
Body | String(yyyy-MM-dd) |
✅ | @NotNull |
团期行程日,须落在本组服务日范围内且不缺日(809105 / 809106) |
groups[].days[].headcount |
Body | Integer | ✅ | @NotNull,@Min(1) |
该组该日乘车人数(不是户数);小于当日成员户数抛 809110 |
groups[].days[].memberOrderIds |
Body | Array<Long> | ✅ | @NotEmpty |
该组该日实际乘车的子订单集合,须全属本团在团户(809107),同一户同一日只能属一个分组(809108) |
出参 Result<GroupVehicleRequirementRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
requirementId |
String | 正式用车需求 ID(雪花,字符串) |
groupBatchId |
String | 团期 ID(雪花,字符串) |
status |
String | 需求状态 |
version |
Integer | 乐观锁版本,下次保存必须回传 |
remark |
String | 整份需求备注 |
confirmedBy |
String | 确认人 |
confirmedAt |
String(datetime) | 确认时间 |
planRefreshState |
String | 配车刷新状态(只读投影) |
planRefreshReplayCount |
Integer | 配车刷新重投次数 |
blockedStage |
String | 被卡住的阶段 |
planRefreshStalled |
Boolean | 配车刷新是否已停滞 |
planRefreshStalledReason |
String | 停滞原因 |
planRefreshTimeoutAt |
String(datetime) | 刷新超时时刻 |
planRefreshReplayExhausted |
Boolean | 重投次数是否已用尽 |
groups |
Array | 乘车分组回显 |
groups[].groupId / groupCode / vehicleType / vehicleTypeName |
String | 分组主键(字符串)、分组键、车型大类编码、车型中文名(按归一 key 取) |
groups[].serviceStartDate / serviceEndDate |
String(yyyy-MM-dd) |
本组服务日范围 |
groups[].seats / count / totalSeatCount / maxHeadcount / remainingPassengerSeats |
Integer | 单车座位数 / 车辆数 / 总座位 / 最大日人数 / 剩余可载客座位 |
groups[].specialTags[] |
Array | code + name(中文名后端下发,前端不自己映射) |
groups[].remark |
String | 该组备注 |
groups[].days[] |
Array | tripDate / headcount / memberOrderIds(字符串数组) / memberOrderCount |
exemptHouseholds |
Array | 豁免户(在团需车、两类需求都没有活跃行、但定制师提交不了的户);🔴 只提交了接送机的户不在这里——它已提交 |
exemptHouseholds[].orderId |
String | 子订单 ID(雪花,字符串) |
exemptHouseholds[].teamNo |
String | 团号 |
exemptHouseholds[].orderNo |
String | 子订单号 |
exemptHouseholds[].reason |
String | ORDER_NOT_CUSTOMIZING / REQUIREMENT_FROZEN |
exemptHouseholds[].reasonName |
String | 豁免原因中文名(后端下发,前端不自己映射) |
请求示例
{
"version": 3,
"remark": "9/13 起换大巴",
"groups": [
{
"groupId": null,
"groupCode": "BUS",
"vehicleType": "bus",
"serviceStartDate": "2026-09-12",
"serviceEndDate": "2026-09-16",
"seats": 19,
"count": 1,
"specialTags": ["CHILD_SEAT"],
"remark": "含高速费",
"days": [
{
"tripDate": "2026-09-12",
"headcount": 9,
"memberOrderIds": [2099459272533323777, 2099459272533323778]
}
]
}
]
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"requirementId": "2099459272533400001",
"groupBatchId": "2099459272533000001",
"status": "DRAFT",
"version": 4,
"remark": "9/13 起换大巴",
"confirmedBy": null,
"confirmedAt": null,
"planRefreshState": null,
"planRefreshStalled": false,
"groups": [
{
"groupId": "1867000000009",
"groupCode": "BUS",
"vehicleType": "bus",
"vehicleTypeName": "大巴客车",
"serviceStartDate": "2026-09-12",
"serviceEndDate": "2026-09-16",
"seats": 19,
"count": 1,
"totalSeatCount": 19,
"maxHeadcount": 9,
"remainingPassengerSeats": 9,
"specialTags": [{ "code": "CHILD_SEAT", "name": "儿童座椅" }],
"remark": "含高速费",
"days": [
{
"tripDate": "2026-09-12",
"headcount": 9,
"memberOrderIds": ["2099459272533323777", "2099459272533323778"],
"memberOrderCount": 2
}
]
}
],
"exemptHouseholds": []
}
}
空数据 / 降级响应
该团期只有接送机户、没有任何行程用车户时,提交零分组不再被 809123 拦(本次改动的直接效果);若团里确实还有需车户,零分组仍由 809103 拦下。exemptHouseholds 为空时是空数组不是 null:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"requirementId": "2099459272533400001",
"groupBatchId": "2099459272533000001",
"status": "DRAFT",
"version": 1,
"groups": [],
"exemptHouseholds": []
}
}
错误响应
809123(触发条件已收窄、文案已改写;{0} 是团期人话标识,{2} 按团号列户、无团号回落订单号、都缺时为「某子订单」,顿号分隔):
{
"code": 809123,
"message": "团期「第3期 10月8日出发团」有 2 户尚未提交用车需求,暂不能保存正式用车需求:26-0480、26-0481",
"success": false,
"data": null
}
其余错误码一条都没变:809100 团期尚未形成正式用车需求 / 809101 状态不允许 / 809102 已被他人修改(乐观锁) / 809103 有需车户却零分组 / 809104 分组重复或试图改名 / 809105 逐日行不在本组服务日范围内或重复 / 809106 缺逐日用车人数 / 809107 成员不属于本团期 / 809108 同一户同一日属多个分组 / 809109 该子订单的某日没有被任何乘车分组覆盖(仍只认 TRAVEL) / 809110 用车人数小于当日成员户数 / 809111 团期状态不允许编辑 / 809115 已声明整团免车需先 withdraw / 809116 座位不足 / 809117 特殊诉求标签不在字典内 / 809118 座位数与车辆数须同填或同空 / 809119 车型不在车型字典内 / 809120 车型字典暂不可用 / 809124 座位数不在该车型可选档位内。
业务边界
- 鉴权:权限点
group-batch:demand:confirm(与整体确认、按户打回、受控重开同码——它们动的是同一个 Tab 里的同一份数据);未登录由网关拦截返 401。 - 全量替换语义:未出现在本次提交里的分组会被移出当前版本,不是增量补丁。
- 🔴 判据变化只在户级:「这户交了没有」看两类任一;「行程逐日覆盖」(809109)仍只看 TRAVEL,没变。
- 只提交接送机的户:不再被 809123 拦、也不要求被任何乘车分组覆盖,且不出现在
exemptHouseholds里。 - 豁免户不阻断:
ORDER_NOT_CUSTOMIZING/REQUIREMENT_FROZEN两类户不进 809123、不参与 809109,但必须在页面上提示出来(后端已逐户带原因下发)。 - 错误码文案是可变的:
message只用于展示,判定一律按code。 - 乐观锁只挡同一瞬间的并发写:挡不住「A 读了 v3 去改、B 也读了 v3 改完先提交」这种跨请求覆盖。
- 雪花 ID 一律是字符串(
requirementId/groupBatchId/memberOrderIds[]/exemptHouseholds[].orderId)。
2. 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft
VO: 无请求体 → GroupVehicleAggregateDraftRespVO
使用场景
编辑弹窗点「自动汇总」时调用,按各子订单的活跃 TRAVEL 需求汇总出一份团级草稿,只读零写入,返回的 draft 可原样 PUT 给上面那个保存端点。权限点与编辑弹窗取数口同码 group-batch:demand:confirm。本次改动只让 809121 少抛一类情况并改了它的报文与缺失原因文案。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | - | 团期 ID |
出参 Result<GroupVehicleAggregateDraftRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
groupBatchId |
String | 团期 ID(雪花,字符串) |
currentStatus |
String | 当前正式需求状态 |
draft |
Object | 汇总出的草稿,结构与保存端点的请求体逐字段相同,可原样 PUT |
droppedFleetItems |
Array | 多车型户被丢弃的车型项:orderId / teamNo / orderNo / vehicleType / seats / count / keptVehicleType / reason(VEHICLE_TYPE_NOT_IN_DICT 等) |
staleHeadcountOrders |
Array | 冻结人数与实时人数不一致的户:orderId / teamNo / orderNo / frozenHeadcount / liveHeadcount |
paddedOrderDays |
Array | 为覆盖出发~返回而补进分组的日期:orderId / teamNo / orderNo / dates[] |
seatOptionAdjusted |
Array | 座位档被兜底调整的组/户:groupCode / orderId / teamNo / orderNo / vehicleType / originalSeats / adoptedSeats / seatOptions[] / reason |
violations |
Array | 草稿已先跑过与保存同一份逐日校验的结果:code(对应 809xxx) / reason / detail / groupCode / tripDate / orderId / teamNo |
exemptHouseholds |
Array | 豁免户(结构同上一个端点);🔴 只提交了接送机的户不在这里,也不在草稿里 |
请求示例
GET /v3/admin/order/group-batch/2099459272533000001/vehicle-requirement/aggregate-draft
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "2099459272533000001",
"currentStatus": "DRAFT",
"draft": {
"version": 3,
"remark": null,
"groups": [
{
"groupId": null,
"groupCode": "BUS",
"vehicleType": "bus",
"serviceStartDate": "2026-09-12",
"serviceEndDate": "2026-09-16",
"seats": 19,
"count": 1,
"specialTags": [],
"remark": null,
"days": [
{
"tripDate": "2026-09-12",
"headcount": 9,
"memberOrderIds": [2099459272533323777]
}
]
}
]
},
"droppedFleetItems": [],
"staleHeadcountOrders": [],
"paddedOrderDays": [],
"seatOptionAdjusted": [],
"violations": [],
"exemptHouseholds": []
}
}
空数据 / 降级响应
团里只有接送机户、没有任何可汇总的行程用车户时,草稿分组为空数组而不再抛 809121(本次改动的直接效果):
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "2099459272533000001",
"currentStatus": "DRAFT",
"draft": { "version": null, "remark": null, "groups": [] },
"droppedFleetItems": [],
"staleHeadcountOrders": [],
"paddedOrderDays": [],
"seatOptionAdjusted": [],
"violations": [],
"exemptHouseholds": []
}
}
车型字典取不到时不静默降级,抛 809120 让运营重试(避免把一整份草稿的车型全判成非法)。
错误响应
809121(触发条件已收窄、文案已改写;{0} 是团期名标识,{2} 是「户标识:原因」顿号分隔的清单,原因文案里的 未提交行程用车需求 已改为 未提交用车需求):
{
"code": 809121,
"message": "团期 「第3期 10月8日出发团」 有 1 户缺少可汇总的用车需求,暂不能自动汇总:26-0480:未提交用车需求",
"success": false,
"data": null
}
其余错误码未变:809120 车队车型字典暂不可用 / 809111 团期状态不允许 / 809100 团期尚未形成正式用车需求(视链路)。
业务边界
- 鉴权:权限点
group-batch:demand:confirm;未登录由网关拦截返 401。 - ⛔ 本端点零写入,可安全重复调用;
draft是「按现有子订单需求草稿长什么样」,不保证保存一定能过——预跑的校验结果在violations。 - 收窄后的 809121 判据:需车户「两类用车需求一条都没有」才算信息缺失;只提交接送机的户不算缺少,也不会出现在草稿里(团车草稿只汇总 TRAVEL,它本就没有位置)。
- 缺失原因文案已改:
未提交行程用车需求→未提交用车需求(另有车型均不在车型字典内、服务日推不出、人数为 0 三类未变)。 - 诊断字段必须展示:
droppedFleetItems/staleHeadcountOrders/paddedOrderDays/seatOptionAdjusted都是「草稿与用户预期可能不一致」的位置,静默吞掉会让运营看到一份自己没想要的草稿。 - 雪花 ID 一律是字符串。
3. 整体确认需求缺失预检 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check
VO: 无请求体 → GroupBatchRequirementCheckRespVO
使用场景
「查看需求」Tab 进入时与点「确认」前调用,据 ready 置灰确认按钮、据 missing / vehicleMissing 展示缺哪几户。只读无副作用。权限点 group-batch:demand:confirm。本次改动只让缺失项 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED(809122)少产出一类情况并改了它的报文。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | - | 团期 ID |
出参 Result<GroupBatchRequirementCheckRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
groupBatchId |
String | 团期 ID(雪花,字符串) |
batchStatus / batchStatusName |
String | 团期状态编码与中文名 |
ready |
Boolean | 是否可以整体确认(置灰按钮用) |
missing |
Array | 房侧缺失户:orderId / teamNo / orderNo / customerName / consultantId / consultantName / reason / reasonName / dayNumber / segmentIndex / expectedNights / actualNights |
checkedResourceTypes |
Array<String> | 恒为 ["HOTEL","VEHICLE"];文案已更新为「车侧逐户查用车需求行是否提交(#8577 起行程用车与接送机任一有即算已提交)」 |
vehicleWaived |
Boolean | 是否已声明整团免车 |
vehicleMissing |
Array | 车侧缺失项,见下 |
vehicleMissing[].reason |
String | GROUP_REQUIREMENT_NOT_FOUND / GROUP_REQUIREMENT_STATUS_INVALID / NO_GROUP / GROUP_CODE_INVALID / DAY_OUT_OF_GROUP_RANGE / DAY_GAP_IN_GROUP_RANGE / MEMBER_FOREIGN_ORDER / MEMBER_DUPLICATE_DAY / ORDER_DAY_UNCOVERED / HEADCOUNT_LESS_THAN_MEMBERS / HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED / MEMBER_GROUP_MISMATCH / TRANSFER_SERVICE_DATES_NOT_BACKFILLED / TRANSFER_WINDOW_INCOMPLETE |
vehicleMissing[].groupCode |
String | 涉及的乘车分组编码;无分组维度时 null |
vehicleMissing[].tripDate |
String(yyyy-MM-dd) |
涉及的日期;无日期维度时 null |
vehicleMissing[].orderId |
String | 涉及的子订单 ID(雪花,字符串);无订单维度时 null |
vehicleMissing[].teamNo / orderNo |
String | 团号 / 子订单号快照 |
vehicleMissing[].detail |
String | 人话描述,与整团确认时抛出的错误报文逐字相同,可直接展示 |
vehicleExemptHouseholds |
Array | 车侧豁免户(结构同前两个端点);🔴 只提交了接送机的户不在这里 |
groupVehicleRequirementId |
String | 团级正式用车需求 ID(雪花,字符串) |
groupVehicleRequirementStatus |
String | 团级正式用车需求状态 |
groupVehicleRequirementVersion |
Integer | 团级正式用车需求版本 |
transferSubmitEnabled |
Boolean | 接送机提交灰度开关当前状态 |
transferDeclaredWithoutRequirement |
Array | 声明了接送机却没有活跃 TRANSFER 行的户:orderId / teamNo / orderNo / customerName / consultantId / consultantName / pickupRequired / dropoffRequired / pickupRemark |
请求示例
GET /v3/admin/order/group-batch/2099459272533000001/requirement/confirm-check
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "2099459272533000001",
"batchStatus": "RESOURCE_PREPARING",
"batchStatusName": "资源准备中",
"ready": false,
"missing": [],
"checkedResourceTypes": ["HOTEL", "VEHICLE"],
"vehicleWaived": false,
"vehicleMissing": [
{
"reason": "HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED",
"groupCode": null,
"tripDate": null,
"orderId": "2099459272533323779",
"teamNo": "26-0482",
"orderNo": "HL2606010003",
"detail": "该户尚未提交用车需求,请先让定制师提交后再整团提交车务"
}
],
"vehicleExemptHouseholds": [],
"groupVehicleRequirementId": "2099459272533400001",
"groupVehicleRequirementStatus": "DRAFT",
"groupVehicleRequirementVersion": 4,
"transferSubmitEnabled": true,
"transferDeclaredWithoutRequirement": []
}
}
空数据 / 降级响应
全部就绪时 ready=true,三个清单都是空数组不是 null:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"groupBatchId": "2099459272533000001",
"batchStatus": "RESOURCE_PREPARING",
"batchStatusName": "资源准备中",
"ready": true,
"missing": [],
"checkedResourceTypes": ["HOTEL", "VEHICLE"],
"vehicleWaived": false,
"vehicleMissing": [],
"vehicleExemptHouseholds": [],
"transferDeclaredWithoutRequirement": []
}
}
错误响应
本端点是只读预检,把缺失列成清单而不是抛码;仍可能出现的错误只有权限与团期不存在两类:
{
"code": 403,
"message": "无权限执行该操作",
"success": false,
"data": null
}
业务边界
- 鉴权:权限点
group-batch:demand:confirm;未登录由网关拦截返 401。 - ⛔ 只读无副作用,可随页面进入反复调用。
- 它是缺失明细的唯一来源:整团确认失败时抛出的 589533 只带汇总户数,逐户明细只能从本端点取。
- 收窄后的
HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED判据:在团需车户两类用车需求都没提交才产出;只提交接送机的户不产出,也不进vehicleExemptHouseholds。 detail与错误报文逐字相同:所以它也跟着改了文案(行程用车需求→用车需求),前端不要做子串匹配。ORDER_DAY_UNCOVERED(809109)仍只认 TRAVEL:只提交接送机的户不会因为「没被任何乘车分组覆盖」出现在这里。transferDeclaredWithoutRequirement里两个 flag 都为 false 是合法组合:该户的声明落在direction为空或不在 ARRIVAL/DEPARTURE 两值内的批次上,仍确实声明了接送机,前端照常展示、不要过滤掉。- 雪花 ID 一律是字符串。
四、契约约束与正确调用方式
本节只写后端接受 / 拒绝 payload 的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 payload 对照(保存端点)
| 场景 | payload |
|---|---|
| ✅ 首次保存(无版本) | { "version": null, "groups": [ { "groupCode": "BUS", "vehicleType": "bus", "serviceStartDate": "2026-09-12", "serviceEndDate": "2026-09-16", "days": [ { "tripDate": "2026-09-12", "headcount": 9, "memberOrderIds": [2099459272533323777] } ] } ] } |
| ✅ 改既有组(带 groupId,groupCode 不变) | { "version": 3, "groups": [ { "groupId": 1867000000009, "groupCode": "BUS", ... } ] } |
| ✅ 座位与车辆数同空(存量分组) | { ..., "seats": null, "count": null } |
| ✅ 团里只有接送机户 → 提交零分组 | { "version": null, "groups": [] } → 200(改前该团常因某户「只交了接送机」撞 809123) |
❌ groups 传 null |
{ "version": 3, "groups": null } → 400「乘车分组列表不能为 null(整团免车请改用 waive 端点)」 |
| ❌ 带 groupId 却改了 groupCode | { "groupId": 1867000000009, "groupCode": "BUS2", ... } → 809104 |
| ❌ 只填 seats 不填 count | { "seats": 19, "count": null } → 809118 |
| ❌ 车型填自由文本 | { "vehicleType": "35座大巴" } → 809119 |
前端必须做的一处改动
- 🔴 凡是对 809121 / 809122 / 809123 的
message(或预检vehicleMissing[].detail)做过字符串包含判断的地方,一律改成按code/reason分支。三条报文里的行程用车需求已改为用车需求,旧的子串匹配会静默失配(不报错,只是那条分支再也不进)。 - 其余全部字段、校验规则、请求格式不变,不需要任何别的适配。
五、数据库行为
只有保存端点(PUT)是写端点,本次改动没有任何表结构或写入语义变化——变的是写之前那道户级阻断的判据。
| 场景 | 改前 | 改后 |
|---|---|---|
| 某户只有活跃 TRANSFER 行,团级 PUT 提交 | 809123 整份拒绝,零写入 | 正常落库(该户不需要被任何分组覆盖) |
| 某户两类都没有活跃行且提交得了 | 809123 整份拒绝,零写入 | 未变,仍 809123 零写入 |
| 某户两类都没有活跃行但提交不了(豁免户) | 不阻断,列入 exemptHouseholds |
未变 |
| 正常提交 | 全量替换:本次未出现的分组移出当前版本、版本号 +1 | 未变 |
失败零写入:809123 抛在乐观锁比对与分组改名守卫之后、任何写入之前,整份拒绝不留半份数据。
自动汇总(GET)与确认预检(GET)两个端点零写入,本次未改变这一点。
六、边界行为
- 未登录 → 401(网关拦截)。
- 权限点
group-batch:demand:confirm缺失 → 403。 - 团期尚未形成正式用车需求 → 809100(报文用「该团期」,不带雪花 id)。
- 正式需求已被他人修改 → 809102,带提交版本与当前版本。
- 团期已过配置阶段 → 809111。
- 已声明整团免车又提交分组 → 809115(需先 withdraw 回草稿)。
- 车队车型字典不可用 → 809120(不静默降级,让运营重试)。
- 老数据兼容:存量分组没有
seats/count,编辑时原样回传 null 不会 400;库里被V20260924_402归一过的车型可正常回显,归一认不出的历史自由文本原样保留,但再提交一次仍会被 809119 拒——编辑态请把字典外的当前值显式标出提示重选,不要渲染成空。 - 推不出服务日的户(
departDate/returnDate任一为空)跳过 809109 覆盖判定(已知盲区,不是遗漏)。
六.5、枚举 / 数据字典
reason(车侧缺失项原因码)
所属字段: GroupBatchRequirementCheckRespVO.vehicleMissing[].reason | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
GROUP_REQUIREMENT_NOT_FOUND |
团级正式需求未形成 | 对应 809100 |
GROUP_REQUIREMENT_STATUS_INVALID |
团级正式需求状态不允许 | 对应 809101 |
NO_GROUP |
有需车户却零分组 | 对应 809103 |
GROUP_CODE_INVALID |
分组编码重复或改名 | 对应 809104 |
DAY_OUT_OF_GROUP_RANGE |
逐日行不在本组服务日范围内 | 对应 809105 |
DAY_GAP_IN_GROUP_RANGE |
本组服务日范围内缺日 | 对应 809106 |
MEMBER_FOREIGN_ORDER |
成员不属于本团期 | 对应 809107 |
MEMBER_DUPLICATE_DAY |
同一户同一日属多个分组 | 对应 809108 |
ORDER_DAY_UNCOVERED |
该户某日未被任何分组覆盖 | 对应 809109;🔴 仍只认 TRAVEL,本次未改 |
HEADCOUNT_LESS_THAN_MEMBERS |
用车人数小于当日成员户数 | 对应 809110 |
HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED |
该户尚未提交用车需求 | 对应 809122;🔴 #8577 收窄:行程用车与接送机任一有即不报。只带 orderId / orderNo,处置是催该户定制师提交 |
MEMBER_GROUP_MISMATCH |
该户车型与覆盖它的分组车型不符 | 对应 809125;排在户级未提交之后(交都没交的户没有车型可比) |
TRANSFER_SERVICE_DATES_NOT_BACKFILLED |
待放行的接送机需求未回填服务日 | 对应 809007,只带 orderId |
TRANSFER_WINDOW_INCOMPLETE |
接送机需求窗没盖住大交通派生日期 | 对应 809126,带 orderId / orderNo 与首个越窗日期 |
reason(团级用车需求豁免户原因码)
所属字段: exemptHouseholds[].reason、vehicleExemptHouseholds[].reason | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
ORDER_NOT_CUSTOMIZING |
订单不在定制中 | 定制师提交会被 582017 拒,所以该户不算「没交」 |
REQUIREMENT_FROZEN |
团期已过资源准备、需求已冻结且该户未被打回 | 定制师提交会被 589536 拒 |
🔴 只提交了接送机的户不属于任何一档——它已提交,既不进未提交名单也不进豁免名单。
用车需求类别(判定用,不直接出现在本次三个响应的字段里)
| 值 | 中文 | 在本次判定中的角色 |
|---|---|---|
TRAVEL |
团期行程用车 | 汇总进团级乘车分组;809109 逐日覆盖只认它 |
TRANSFER |
接送机 | 走逐户派车、结构上在团级乘车分组之外;#8577 起它也算「已提交用车需求」,参与 809121 / 809122 / 809123 的判定 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| 三个端点的全部请求字段 | — | 未变(一个都没动) |
| 三个端点的全部响应字段 | — | 未变(无新增、无删除、无改名、无类型变化) |
checkedResourceTypes 的字段说明文案 |
「车侧逐户查行程用车需求行是否提交」 | 「车侧逐户查用车需求行是否提交(#8577 起行程用车与接送机任一有即算已提交)」 |
vehicleMissing[].reason 的取值集合 |
14 个 | 未变(仍 14 个,只是其中一个的触发条件收窄) |
exemptHouseholds 的成员判据 |
在团需车 ∧ 无 active TRAVEL ∧ 提交不了 | 在团需车 ∧ 两类都无 active 行 ∧ 提交不了 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 某户只提交了接送机,团级 PUT 保存 | 809123 整份拒绝,运营无干净出路 | 正常保存 |
| 某户只提交了接送机,点自动汇总 | 809121 整团出不来草稿 | 正常出草稿(该户不进草稿,也不进缺失清单) |
| 某户只提交了接送机,进确认预检 | 该户挂 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED,ready=false |
不产出该缺失项 |
| 某户只提交了接送机,是否要求被乘车分组覆盖 | 会走到 809109 | 不要求(它结构上在团级分组之外) |
| 809121 报文 | 团期 {0} 有 {1} 户缺少可汇总的**行程**用车需求… |
…缺少可汇总的用车需求… |
| 809122 报文 | 该户尚未提交**行程**用车需求,… |
该户尚未提交用车需求,… |
| 809123 报文 | {0}有 {1} 户尚未提交**行程**用车需求,… |
{0}有 {1} 户尚未提交用车需求,… |
| 汇总缺失原因文案 | 未提交行程用车需求 |
未提交用车需求 |
| 809109 逐日覆盖的判据 | 只认 TRAVEL | 未变,仍只认 TRAVEL |
| 两类都没提交的户 | 三处照旧阻断 | 未变 |
六.7、影响评估
- 是否破坏向后兼容: 否(无字段增删改;只是三个错误码少抛一类情况、报文文案改写)
- 前端是否必须同步上线: 否;但若前端对这三条报文做过字符串包含判断,必须改(改成按
code/reason分支),否则那条分支会静默失配 - 前端 workaround 清理点: 若为绕开「纯接送机户卡住整团」在页面上加过提示、屏蔽过确认按钮、或引导过运营去整团免车,可以撤掉
七、不影响范围
- 仅影响: 团期需求管理 Tab 的三个端点里 809121 / 809122 / 809123 的触发条件与报文文案。
- 零影响:
- 809109 逐日覆盖判定(仍只认 TRAVEL)
- 整体确认端点
POST .../requirement/confirm自身的确认逻辑与响应字段 - 受控重开、整份撤回、整团免车、按户打回四个端点
- 接送机批量确认
POST .../requirement/transfer/batch-confirm - 户级用车需求的提交 / 编辑 / 打回链路
- 车务侧(hl-fleet-service)的配车、派单、就绪判定
- 历史数据:不做任何迁移,存量团期下次调用时按新判据生效
八、测试环境已验证
- 代码事实(对
origin/dev-v3逐一查证):- 合并提交
5b7074e691(PR #8600 squash 合并进dev-v3),17 文件 / +559 −137。 GroupVehicleRequirementErrorCode三条IErrorCode.of的字面量 diff 已逐字核对(809121 / 809122 / 809123 各去掉「行程」二字),码值与常量名未变。GroupVehicleDraftAggregator.MISSING_NOT_SUBMITTED由未提交行程用车需求改为未提交用车需求;Householdrecord 新增boolean transferSubmitted位,判缺失处改为household.needsVehicle() && !household.transferSubmitted()。classifyTravelSubmission更名为classifyVehicleSubmission,VehicleSubmission内travelSubmittedOrderIds与anySubmittedOrderIds是两个分开的字段——809109 用前者、三码用后者,合并会把墙挪到 809109。- 三个端点的 Controller 签名、
@RequestBodyVO、响应 VO 字段清单逐一核对,确认零字段变化。 - 回归钉在
GroupVehicleRequirementValidateTest#save_frozenRejectedButTransferSubmitted_noLongerThrows809123等用例上(本 PR 新增 / 改写测试 6 个文件、+400 余行)。
- 合并提交
- 部署:
hl-order-service-v3的dev-v3分支已滚到测试服,三个端点走管理端网关/v3/admin/order/**既有路由,无新增路由。
PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement → 200 ✓(纯接送机户不再触发 809123)
GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft → 200 ✓(纯接送机户不再触发 809121)
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check → 200 ✓(不再产出 HOUSEHOLD_REQUIREMENT_NOT_SUBMITTED)
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| — | #7441 | 团期正式用车需求首次落地(809100-809115 段) | ✅ 有效 |
| — | #8219 | 有户未提交时阻断团级 PUT,新开 809123 与豁免户机制 | ✅ 有效(本单在其基础上收窄判据) |
| — | #8220 | 自动汇总草稿端点与 809121 | ✅ 有效 |
| — | #8249 | 预检加户级 809122 | ✅ 有效 |
| — | #8306 | 报文按团号列户、不出现雪花 id | ✅ 有效 |
| 本 PR #8600 | #8577 | 户级提交判定与行程覆盖判定分离,三码收窄 + 文案改写 | ✅ 最新 |
十、相关文档
- 关联 Issue: wx/HL#8577
- 关联 PR: wx/HL#8600
关联 / 联系人
链接
- Issue: #8577
- PR: #8600
- Merge commit: 5b7074e691
联系人
- 后端负责人: @wx