四个读口的交接件:GET /vehicle-requirement 新增 4 个 *Name 字段、 GET /vehicle-requirement/aggregate-draft 的 draft 换成只读读侧 VO、 GET /requirement-summary 并列下发 confirmedSeats/confirmedCount、 GET /orders 的 vehicleRequirements[] 逐类下发用车需求行。 四个端点的响应示例全部为 2026-09-30 测试环境实测采集(团期批次 2104839654727618562 / 2104840641651556353),并标注了采集批次与时刻。 未知编码回落 null 这一支在当前环境无自然样本(三列库内全为 NULL), 已在第八节写明由源码与单测两侧钉住,不留悬念给前端。 三条给前端的硬约束: - groups[].remark 不要按格式解析:本单只改新生成的汇总草稿, 存量已确认团期仍是改前的订单号前缀格式,两种格式长期并存。 - 同一个 status 编码在不同 kind 上中文名不同(PENDING_REVIEW 在 TRAVEL 下是「待提交车务」、在 TRANSFER 下是「待审核」,#8218 刻意分叉), 前端不得自建 code 到中文的映射表,一律渲染后端下发的 statusName。 - 按类别判状态一律遍历 vehicleRequirements[],单值字段只表达展示序首条。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
45 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 | 8544 | 团期正式用车需求读口补状态中文名、汇总草稿改用读侧 VO、需求汇总并列下发团级已确认座位、子订单列表逐类下发用车需求行(#8544 #8545 #8619) | admin | wx(GIT) | 修改接口 | deployed | not_required | pending | PR #8625(覆盖 #8544 #8545)与 PR #8624(覆盖 #8619)均已 squash 合并 dev-v3(5f2f86bc9 / ce7cd238e9),hl-order-service-v3 dev-v3 分支已滚测试服(HEAD 5f2f86bc9,jar mtime 2026-09-30 08:19:48,两实例 Nacos 健康)。四个 GET 端点响应体的新增/变更字段均实测通过;PUT / withdraw / waive 三个写端点与 GET 共用同一个 GroupVehicleRequirementRespVO 类(源码级共用,非各自派生),因此 #8544 新增的 4 个 *Name 字段在这三个端点的响应里同样存在,字段语义与本文档「三、接口详情」第 2 条完全一致。 | 2026-09-30 | dev-v3 |
hl-order-service-v3: 团期正式用车需求读口补状态中文名、汇总草稿改用读侧 VO、需求汇总并列下发团级已确认座位、子订单列表逐类下发用车需求行
存放目录:
changelogs-v2/2026-09/服务: hl-order-service-v3 (端口 8086) PR: #8625(#8544 #8545)、#8624(#8619) Issue: #8544、#8545、#8619 日期: 2026-09-30 影响范围: 团期需求管理 Tab 下 4 个 GET 端点(全团需求汇总 / 读正式用车需求 / 自动汇总草稿 / 子订单列表)的响应体字段;PUT / withdraw / waive 三个写端点共用的响应体同步补齐字段(契约见「四」,未单开详情块)
⚠️ 关键变化
- #8544:
GroupVehicleRequirementRespVO(PUT / GET / withdraw / waive 四端点共用的响应体)新增 4 个只读中文名字段——statusName/planRefreshStateName/blockedStageName/planRefreshStalledReasonName,分别配对既有的status/planRefreshState/blockedStage/planRefreshStalledReason。认不出的编码一律给null,不回落成编码原文(与本 VO 既有的SpecialTagItem.name/GroupItem.vehicleTypeName同口径)。 - 🔴 这条 null 规则与看板列表的团期状态中文名口径是两套、刻意不同:
GroupBatchConverter#resolveBatchStatusName(团期列表页用)未知码会回落原编码;本次这 4 个字段所在的正式用车需求读口未知码给null。前端不要把两处的「未知码兜底逻辑」当成同一套抄。 - #8544:
GET .../vehicle-requirement/aggregate-draft的draft字段类型从写侧GroupVehicleRequirementSaveReqVO改为新的只读读侧类型GroupVehicleRequirementDraftRespVO。JSON 形状基本不变(由GroupVehicleRequirementDraftRespVOFieldParityTest钉住与写侧字段集一致),唯一新增字段是groups[].vehicleTypeName(只读车型中文名,不参与保存,原样PUT回去时被忽略);该 VO 不挂任何校验注解(@NotNull/@NotEmpty/@Size等),因为草稿可能违反若干条保存态约束,违规项另在violations里列出。 - #8544:
draft.groups[].remark(分组备注拼接文案)的逐户标签优先级改为 团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户",不再回落雪花订单 ID(原来兜底到裸数字 orderId 的情况,现在只有当团号/客户名/订单号三者都拿不到时才会出现,落成字面文案"未知户")。前端如果对这段remark文本做过正则匹配、高亮、或按"看起来像一串数字"识别订单号,需要同步更新——这段文本此后不会再出现裸数字订单 ID。 - #8545:
GET .../requirement-summary的vehicleSeatSummary[]新增confirmedSeats/confirmedCount(均为int,尚未整团确认时恒为0,不是null)。这是"团级已确认"口径,与既有的totalSeats/totalCount("逐户已提交"口径)并列下发、允许不相等——两者不等是常态不是缺陷,任何断言两者相等的前端逻辑都是错的。 - 🔴 #8545:
vehicleSeatSummary[]可能新增一行只有confirmedSeats/confirmedCount非零、totalSeats/totalCount恒为0的车型条目——发生在车务把车型整团定成了逐户报的车型集合之外的某个车型时(例如逐户都报mpv,车务整团定成bus)。前端渲染这张表时不能假设"有座位数就等于有逐户报",要按 4 个数字各自判断。 - #8619:
GET .../orders的vehicleRequirementStatus/vehicleRequirementKind口径从"只看行程用车(TRAVEL)"放宽为"该户展示序首条活跃用车需求行"(行程用车优先、其次接送机)。🔴 改前只提交了接送机需求的户,这两列恒为null、vehicleRequirementStatusName被渲染成"未提交"——这是一个错误的展示(该户其实已提交、可能已在审/已派车/已完成),本次已修复。有行程用车需求行的户这两列读数不变。 - #8619:
vehicleRequirementKind的实际取值域从恒为TRAVEL放宽为{TRAVEL, TRANSFER}——VehicleRequirementKind枚举本身没有新增第三个值,仍然只有这两个;变的是这一个响应字段过去被上游按 TRAVEL 过滤取行、现在按展示序取行,所以能观测到TRANSFER。前端如果曾按"这一列永远是 TRAVEL"写死过图标/文案分支,需要按实际值渲染。 - #8619:
orders新增vehicleRequirements[](该户全部活跃用车需求行,恒非null,0~2 条,行程用车在前、接送机在后)。一户同时提交了行程用车与接送机时,两条需求行状态可能各自不同(例如 TRAVEL 已DONE、TRANSFER 还在PENDING_REVIEW),此时上面那组单值字段(vehicleRequirementStatus/vehicleRequirementKind)只能表达其中一条——按类别判断状态必须读这个数组,不要只读单值字段。 orders端点其余 25 个既有字段(hotelRequirementStatus、totalPrice、travelers等)本次未变,已由 changelog30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md完整记录,此处不重复。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 全团需求汇总 | GET | /v3/admin/order/group-batch/{groupBatchId}/requirement-summary |
修改 | vehicleSeatSummary[] 新增 confirmedSeats/confirmedCount(#8545) |
| 2 | 读团期正式用车需求 | GET | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement |
修改 | 响应体新增 4 个 *Name 字段(#8544),PUT/withdraw/waive 共用同一响应体同步生效 |
| 3 | 自动汇总正式用车需求草稿 | GET | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft |
修改 | draft 字段改用只读读侧 VO,新增 groups[].vehicleTypeName(#8544) |
| 4 | 团期下子订单列表 | GET | /v3/admin/order/group-batch/{groupBatchId}/orders |
修改 | vehicleRequirementKind 取值域放宽、新增 vehicleRequirements[](#8619) |
三、接口详情
1. 全团需求汇总 GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary
VO: GroupRequirementSummaryRespVO
使用场景
团期需求管理 Tab 打开时拉取的"全团需求汇总"卡片,展示逐日房间合计与大巴座位合计。本次变更只影响座位合计部分——vehicleSeatSummary[] 新增团级已确认口径的两个字段,供页面并列展示"定制师报了多少座"与"车务最后定了多少座"。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| activeOrderCount | int | 在团子订单数(仅排除 CANCELLED,含 COMPLETED),未变 |
| hotelRequirementCount | int | 有 active 用房需求记录的子订单数(兼容保留字段),未变 |
| hotelNeededOrderCount | int | 需要订房的户数,未变 |
| hotelFlagMismatchOrderCount | int | needsHotel≠true 却已提交有效用房需求的户数,未变 |
| hotelSubmittedOrderCount | int | 已提交有效用房需求且计入 dailyRoomBreakdown 的户数,未变 |
| vehicleRequirementCount | int | 已提交用车需求的子订单数,未变 |
| dailyRoomBreakdown | array | 逐日房间汇总,本次未变 |
| vehicleSeatSummary | array | 大巴座位汇总(按车型),本次新增字段见下表 |
| orderSpecialTags | array | 各子订单 specialTags,本次未变 |
| transferSummary | object | 接送机汇总(#8151 既有字段),本次未变 |
vehicleSeatSummary[] 单项字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| vehicleType | string | 车型大类编码(suv/mpv/bus/sedan;缺失时为"未知"),未变 |
| vehicleTypeName | string | 车型大类中文名;查不到或车队服务不可用时为 null,未变 |
| totalSeats | int | 合计座位数(seats × count 之和);统计基数为"逐户已提交"的行程用车需求,未变 |
| totalCount | int | 合计车辆台数;统计基数为"逐户已提交"的行程用车需求,未变 |
| confirmedSeats 🆕 | int | 合计座位数(团级已确认的正式需求口径);尚未整团确认时为 0,与 totalSeats 不等是常态(#8545) |
| confirmedCount 🆕 | int | 合计车辆台数(团级已确认的正式需求口径);尚未整团确认时为 0(#8545) |
请求示例
GET /v3/admin/order/group-batch/2104839654727618562/requirement-summary
响应示例
以下为测试环境实测响应(团期批次 2104840641651556353,2026-09-30 采集;仅展示 vehicleSeatSummary[0],其余字段结构未变不重复列出):
{
"code": 200,
"message": "成功",
"data": {
"vehicleSeatSummary": [
{
"vehicleType": "mpv",
"vehicleTypeName": "商务车",
"totalSeats": 14,
"totalCount": 2,
"confirmedSeats": 7,
"confirmedCount": 1
}
]
},
"traceId": null,
"success": true
}
空数据 / 降级响应
- 团期批次不存在:返回业务错误(见"错误响应"),
data为null。 - 全团无任何用车需求:
vehicleSeatSummary为空数组,不是null。 - 团级从未整团确认过(或已被重开成
PENDING_RECONFIRM):既有行的confirmedSeats/confirmedCount均为0,不追加新行;此时该数组与改动前逐户口径的行完全一致。 - 车务整团定的车型不在逐户已提交的车型集合内:追加一行
totalSeats=0/totalCount=0、confirmedSeats/confirmedCount非零的记录,vehicleTypeName仍会被正常补全。
错误响应
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
业务边界
confirmedSeats/confirmedCount与totalSeats/totalCount是两个独立基数,前端不得假设两者相等或用其中一个推算另一个。- 权限码为
group-batch:view。
2. 读团期正式用车需求 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement
VO: GroupVehicleRequirementRespVO
使用场景
团期正式用车需求编辑页/详情页打开时的回填读口,同时是"配车计划刷新是否死了"在 admin 侧唯一无副作用的观测口(#7988)。PUT(保存)、withdraw(撤回)、waive(免车)三个写端点返回的响应体与本端点完全一致(同一个 Java 类),前端可以对四个端点复用同一套渲染逻辑。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| requirementId | string(Long 转字符串) | 正式需求主键,未变 |
| groupBatchId | string(Long 转字符串) | 团期聚合主键,未变 |
| status | string | 状态:DRAFT/CONFIRMED/DISPATCHED/DONE/PENDING_RECONFIRM/CANCELLED,未变 |
| statusName 🆕 | string | 状态中文名(草稿/已确认/已发车务/配车完成/待重新确认/已取消);status 为 null 或认不出的编码时为 null,不回落编码原文(#8544) |
| version | int | 乐观锁版本号,未变 |
| remark | string | 整份备注;撤回/免车会把操作追加进来(不覆盖原备注),超 500 字从头部截并以 … 开头,未变 |
| confirmedBy | string | 整份确认人,DRAFT 时为 null,未变 |
| confirmedAt | string(LocalDateTime) | 整份确认时间,DRAFT 时为 null,未变 |
| planRefreshState | string | 配车刷新状态原值:null=从未登记过刷新(多数团期正常态)/PENDING/DONE/FAILED,未变 |
| planRefreshStateName 🆕 | string | 配车刷新状态中文名(刷新中/刷新完成/刷新失败);为 null 或认不出的编码时为 null(#8544) |
| planRefreshReplayCount | int | 人工受控重投累计次数(管理员点确认触发,不含自动重试),上限 5,未变 |
| blockedStage | string | 团期阻断阶段快照:null=未阻断;非空为受控重开发生那一刻的团期状态码(GroupBatchStatus 全域,常见 RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE),未变 |
| blockedStageName 🆕 | string | 阻断阶段中文名(如 资源准备中/物料准备中/待出发);为 null 或认不出的编码时为 null(#8544) |
| planRefreshStalled | boolean | 刷新是否已停滞、不会自愈(恒非 null);true=必须有人处置,未变 |
| planRefreshStalledReason | string | 停滞归因:STATE_FAILED/COMMAND_FAILED/TIMEOUT;未停滞时为 null,未变 |
| planRefreshStalledReasonName 🆕 | string | 停滞归因中文名(刷新已被判死/刷新命令已失败/刷新超时);未停滞或认不出编码时为 null(#8544) |
| planRefreshTimeoutAt | string(LocalDateTime) | 本轮刷新超时时刻;仅 PENDING 且已登记发起时刻时有值,未变 |
| planRefreshReplayExhausted | boolean | 人工重投额度是否已耗尽(恒非 null),未变 |
| groups | array | 全部乘车分组(整团免车态为空数组),结构未变,见下表 |
| exemptHouseholds | array | 仅保存草稿(PUT)响应填充,其余端点(含本 GET)恒为 null,未变 |
groups[] 单项字段(未变,随 VO 一起下发本次新增的 4 个 *Name 兄弟字段):
| 字段 | 类型 | 说明 |
|---|---|---|
| groupId | string(Long) | 分组主键 |
| groupCode | string | 分组键,直接作为车费 alloc_group |
| vehicleType | string | 车型文本/字典值 |
| vehicleTypeName | string | 车型中文名(既有字段,非本次新增) |
| serviceStartDate / serviceEndDate | string(LocalDate) | 本组服务起止日 |
| seats / count | int | 该组单车座位数/车辆数量;存量分组为 null 表示待填 |
| specialTags | array | 该组特殊诉求标签(code + name) |
| remark | string | 该组备注/其他诉求;存量分组为 null |
| totalSeatCount | int | 总座位数 = seats × count |
| maxHeadcount | int | 该组 days 里的最大用车人数 |
| remainingPassengerSeats | int | 余座(扣司机位后的可乘座位 − 最大用车人数) |
| days | array | 逐日用车人数与成员 |
请求示例
GET /v3/admin/order/group-batch/2104839654727618562/vehicle-requirement
响应示例
以下为测试环境实测响应(团期批次 2104840641651556353,2026-09-30 采集)。planRefreshState / blockedStage / planRefreshStalledReason 三列在库中为 NULL,对应 *Name 实测为 null:
{
"code": 200,
"message": "成功",
"data": {
"status": "DONE",
"statusName": "配车完成",
"planRefreshState": null,
"planRefreshStateName": null,
"blockedStage": null,
"blockedStageName": null,
"planRefreshStalledReason": null,
"planRefreshStalledReasonName": null
},
"traceId": null,
"success": true
}
空数据 / 降级响应
- 团期尚未形成正式需求(从未 PUT 过):
data为null(源码@ApiOperation明确标注"未形成时返回 null"),不是报错。 planRefreshState/blockedStage/planRefreshStalledReason三列 DB 为NULL(从未走过受控重开,绝大多数团期的正常态):对应的 3 个*Name字段一律为null,不下发默认中文名。groups为空数组场景:整团免车态(waive 后)。
错误响应
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
业务边界
- 4 个
*Name字段是只读派生展示字段,不接受回传;PUT请求体仍用原编码字段(GroupVehicleRequirementSaveReqVO未新增字段)。 - 认不出的编码 → 对应
*Name为null,不回落原编码——这与团期列表页GroupBatchConverter#resolveBatchStatusName(未知码回落原码)是刻意不同的两套口径,不要混用同一套前端兜底逻辑。 - 本端点权限码为
group-batch:demand:confirm,与 PUT/withdraw/waive/aggregate-draft 四个端点同码。 - 本端点后续不会引入任何写操作(#7988 明确约束),可放心作为无副作用轮询口使用。
3. 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft
VO: GroupVehicleAggregateDraftRespVO(外层诊断字段未变;draft 字段类型改为 GroupVehicleRequirementDraftRespVO)
使用场景
团期正式用车需求编辑弹窗首次打开、或点"重新汇总"时调用:按各子订单已提交的活跃行程用车需求自动生成一份分组草稿,draft 可原样 PUT 回 /vehicle-requirement 保存。本次变更只影响 draft 内部的类型与新增字段,外层的 droppedFleetItems/staleHeadcountOrders/paddedOrderDays/seatOptionAdjusted/violations/exemptHouseholds 六个诊断字段结构未变。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| draft | object 🆕类型变更 | 汇总草稿,类型由写侧 GroupVehicleRequirementSaveReqVO 改为只读读侧 GroupVehicleRequirementDraftRespVO,见下表 |
| droppedFleetItems | array | 多车型户被丢弃的车型,未变 |
| staleHeadcountOrders | array | 人数已过期的户,未变 |
| paddedOrderDays | array | 为覆盖出发~返回而补进的日期,未变 |
| seatOptionAdjusted | array | 座位被兜底调整到车型可选档位的户,未变 |
| violations | array | 草稿违反保存态校验的逐条诊断,未变 |
| exemptHouseholds | array | 提交不了的豁免户(订单不在定制中/团期冻结且未被打回),未变 |
draft(GroupVehicleRequirementDraftRespVO)字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| version | int | 乐观锁版本号;原样回传给 PUT;尚未形成正式需求时为 null |
| remark | string | 整份需求备注,汇总草稿恒为 null |
| groups | array | 全部乘车分组,恒非 null,无可汇总内容时为空数组;见下表 |
draft.groups[] 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| groupId | string(Long) | 既有分组主键,汇总草稿恒为 null |
| groupCode | string | 分组键,直接作为车费 alloc_group |
| vehicleType | string | 车型大类编码(归一后的 key) |
| vehicleTypeName 🆕 | string | 车型大类中文名(只读,不参与保存);字典查不到时为 null,不回落编码原文(#8544,本类相对写侧唯一新增字段) |
| serviceStartDate / serviceEndDate | string(LocalDate) | 本组服务起止日 |
| seats / count | int | 该组单车座位数(存量/无档位可取时可能为 null)/车辆数量 |
| specialTags | array(string) | 该组特殊诉求标签编码数组 |
| remark | string | 该组备注;汇总时按"团号(客户名): 备注"拼接,超 500 字从尾部截断 |
| days | array | 逐日用车人数与成员,见下表 |
draft.groups[].days[] 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| tripDate | string(LocalDate) | 团期行程日 |
| headcount | int | 该组该日用车人数(乘车人数,非户数) |
| memberOrderIds | array(string) | 该组该日实际乘车的子订单集合 |
请求示例
GET /v3/admin/order/group-batch/2104839654727618562/vehicle-requirement/aggregate-draft
响应示例
以下为测试环境实测响应(团期批次 2104840641651556353,2026-09-30 采集;days 实测为 7 天逐日行,此处只保留首日,其余日同形):
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2104840641651556353",
"currentStatus": "DONE",
"draft": {
"version": 3,
"remark": null,
"groups": [
{
"groupId": null,
"groupCode": "MPV",
"vehicleType": "mpv",
"vehicleTypeName": "商务车",
"serviceStartDate": "2026-11-11",
"serviceEndDate": "2026-11-17",
"seats": 7,
"count": 1,
"specialTags": [],
"remark": "26-3682(董海涛): 结伴出行客户,整团统一 7 座商务车,已与客户确认路线;26-0805(谢丽萍): 9月30日至10月3日行程用车,成人4人其中1位长者,行李较多需大后备箱",
"days": [
{
"tripDate": "2026-11-11",
"headcount": 4,
"memberOrderIds": ["2104840641597030402", "2104840685708525570"],
"memberOrderCount": 2
}
]
}
]
},
"violations": []
},
"traceId": null,
"success": true
}
groups[].remark 的一户标识格式是**「团号(客户名)」**(26-3682(董海涛)),多户合并进同一车型组时以 ; 连接。本次改动前该位置拼的是 19 位订单 ID,因此:已确认落库的存量团期,其 GET .../vehicle-requirement 返回的 groups[].remark 仍是旧格式(HL2026… 订单号前缀)——本单只改新生成的汇总草稿,不回写历史数据。前端不要按固定格式解析 remark,它是给运营看的自由文本。
空数据 / 降级响应
- 团期批次不存在:返回业务错误(见"错误响应")。
- 全团无可汇总的行程用车需求:
draft.groups为空数组。 - 车型字典不可用:
vehicleTypeName降级为null,groups其余字段照常下发(不阻断整个响应)。
错误响应
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
{"code": 809120, "message": "车队车型字典暂不可用,无法校验车型,请稍后重试", "success": false, "data": null}
有在团需车户缺少可汇总用车需求时(809121,触发条件已被 #8577 收窄——只提交了接送机的户不再算缺少):
{"code": 809121, "message": "团期 「第3期 10月8日出发团」 有 1 户缺少可汇总的用车需求,暂不能自动汇总:26-0480:未提交用车需求", "success": false, "data": null}
业务边界
draft是只读展示体,不挂任何@NotNull/@NotEmpty/@Size校验注解;违反保存态约束的地方在violations里另行列出,不要用draft自身的字段是否为空来判断能不能保存。draft除groups[].vehicleTypeName外的字段集与写侧GroupVehicleRequirementSaveReqVO完全一一对应(由专门的字段一致性单测钉住),可以原样PUT回/vehicle-requirement;vehicleTypeName回传时会被后端忽略,车型以vehicleType编码为准。draft.groups[].remark的拼接标签口径:团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户",不再回落裸雪花订单 ID。- 权限码与「2」相同(
group-batch:demand:confirm),本端点同样不引入任何写操作。
4. 团期下子订单列表 GET /v3/admin/order/group-batch/{groupBatchId}/orders
VO: GroupBatchOrderItemRespVO(30 个字段中本次只变更 4 个,见下表标注)
使用场景
团期详情页"子订单"Tab 的列表数据源,每户一行。本次变更聚焦用车需求相关的 4 个字段,其余 26 个字段(hotelRequirementStatus、totalPrice、travelers 等)未变,已由 30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md 完整记录。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | 是 | - | 团期聚合主键 |
| page | Query | Integer | 否 | 缺省 1,<1 归一为 1 | 页码 |
| pageSize | Query | Integer | 否 | 缺省 20,<1 归一为 20,>200 截断为 200 | 每页条数 |
| includeTravelers | Query | Boolean | 否 | 缺省 true | 是否附出行人明细(证件号/手机号一律不返回) |
| includeNeeds | Query | Boolean | 否 | 缺省 true | 是否附房数/房型/特殊需求 |
| includeCancelled | Query | Boolean | 否 | 缺省 false | 是否含已取消子订单 |
出参字段表(仅列本次变更相关字段)
| 字段 | 类型 | 说明 |
|---|---|---|
| vehicleRequirementStatus | string | 车需求状态:口径从"只看行程用车"改为"该户展示序首条活跃行"(行程用车优先、其次接送机)。该户一条活跃车需求行都没有时为 null(#8619) |
| vehicleRequirementStatusName | string | 状态中文名;未知码回落原 code(与 vehicleRequirementKindName 不同口径);code 为 null 时按该户 needsVehicle 分叉——true 出"未提交"、否则为 null。#8619 起"未提交"只在两类需求都没报时出现 |
| vehicleRequirementKind | string | 本行车需求的用车类别:TRAVEL/TRANSFER。#8619 起不再恒为 TRAVEL——只报了接送机的户在这里下发 TRANSFER;无 active 行时为 null |
| vehicleRequirementKindName | string | 类别中文名:行程用车/接送机;认不出的类别给 null,不回落编码(与 vehicleRequirementStatusName 不同口径) |
| vehicleRequirements 🆕 | array | 该户全部活跃用车需求行(0~2 条:TRAVEL/TRANSFER 各至多一条),恒非 null,行程用车在前、接送机在后(#8619),见下表 |
vehicleRequirements[] 单项字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| kind | string | 需求类别:TRAVEL=行程用车 / TRANSFER=接送机 |
| kindName | string | 类别中文名;认不出的类别给 null,不回落编码 |
| status | string | 该条需求状态:PENDING/PROCESSING/DONE/PENDING_REVIEW/REJECTED_TO_CONSULTANT/REJECTED_TO_ADMIN |
| statusName | string | 状态中文名;PENDING_REVIEW 按 kind 分两套文案:TRAVEL="待提交车务"、TRANSFER="待审核" |
请求示例
GET /v3/admin/order/group-batch/2104839654727618562/orders?page=1&pageSize=20
响应示例
以下为测试环境实测响应(团期批次 2104839654727618562,2026-09-30 采集)。每条 records[] 实测另有 26 个本次未变的字段,此处只保留与本单相关的 5 个,其余省略(完整字段集见 changelog 30_8543):
{
"code": 200,
"message": "成功",
"data": {
"records": [
{
"orderId": "2104839654652121090",
"vehicleRequirementStatus": "DONE",
"vehicleRequirementStatusName": "配车完成",
"vehicleRequirementKind": "TRAVEL",
"vehicleRequirementKindName": "行程用车",
"vehicleRequirements": [
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
{"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING", "statusName": "待车队配"}
]
},
{
"orderId": "2104839686486888449",
"vehicleRequirementStatus": "DONE",
"vehicleRequirementStatusName": "配车完成",
"vehicleRequirementKind": "TRAVEL",
"vehicleRequirementKindName": "行程用车",
"vehicleRequirements": [
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"}
]
},
{
"orderId": "2104839729176514562",
"vehicleRequirementStatus": "DONE",
"vehicleRequirementStatusName": "配车完成",
"vehicleRequirementKind": "TRAVEL",
"vehicleRequirementKindName": "行程用车",
"vehicleRequirements": [
{"kind": "TRAVEL", "kindName": "行程用车", "status": "DONE", "statusName": "配车完成"},
{"kind": "TRANSFER", "kindName": "接送机", "status": "PENDING_REVIEW", "statusName": "待审核"}
]
}
],
"total": 3,
"page": 1,
"pageSize": 20
},
"traceId": null,
"success": true
}
上例里三户的 vehicleRequirements 长度分别为 2 / 1 / 2,vehicleRequirementStatus 与 Kind 取的都是展示序首条(TRAVEL 优先),因此三户顶层都是 TRAVEL;接送机那一行的真实状态只在 vehicleRequirements[] 里看得到。
空数据 / 降级响应
- 团期批次不存在:返回业务错误(见"错误响应")。
- 团期下暂无子订单(或
includeCancelled=false时全部已取消):records为空数组,total=0。 - 该户一条活跃用车需求行都没有:
vehicleRequirementStatus/vehicleRequirementKind均为null,vehicleRequirements为空数组(不是null);vehicleRequirementStatusName按该户needsVehicle分叉。 - 该户只提交了接送机需求(#8619 修复的场景):
vehicleRequirementKind下发TRANSFER、vehicleRequirementStatus下发该行程的真实状态,不再是null/"未提交"。
错误响应
{"code": 589500, "message": "团期不存在", "success": false, "data": null}
{"code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "success": false, "data": null}
业务边界
- 按用车类别判断状态一律读
vehicleRequirements[],不要只读vehicleRequirementStatus/vehicleRequirementKind单值字段——一户两类需求并存时单值字段只能表达展示序首条。 vehicleRequirementKindName与vehicleRequirementStatusName是两套不同的未知码兜底口径(前者给null,后者回落原码),不要用同一段兜底逻辑处理。- 🔴 同一个
status编码在不同kind上的中文名不同,前端不能自建 code→中文 映射表:PENDING_REVIEW在TRAVEL行下发「待提交车务」(推走它的是团期管理员整团一次的「提交车务」,该状态上没有逐户审核动作),在TRANSFER行下发「待审核」(有逐户审核动作)——这是 #8218 起的刻意分叉,声明见GroupVehicleHouseholdsRespVO/GroupBatchOrderItemRespVO的@ApiModelProperty。两侧均有实测样本:批次2104839654727618562的2104839729176514562户 TRANSFER 行 =PENDING_REVIEW/「待审核」;批次2104840641651556353的2104840685708525570户 TRAVEL 行 =PENDING_REVIEW/「待提交车务」。一律直接渲染后端下发的statusName。 - 权限码为
group-batch:view。
四、契约约束与正确调用方式
- 4 个新增
*Name字段(#8544)是只读展示字段:statusName/planRefreshStateName/blockedStageName/planRefreshStalledReasonName只在响应体里出现,PUT请求体GroupVehicleRequirementSaveReqVO未新增任何字段,回传这些字段会被忽略。 - PUT
/vehicle-requirement、POST/vehicle-requirement/withdraw、POST/vehicle-requirement/waive三个写端点与本文档「三、2」共用完全同一个GroupVehicleRequirementRespVO类:调用这三个端点后,响应体里同样带有 4 个新增*Name字段,字段语义、null 规则与「三、2」逐字一致,无需前端另写一套解析。 aggregate-draft的draft字段类型变更(#8544):TypeScript/接口类型定义如果之前直接复用了GroupVehicleRequirementSaveReqVO的类型作为draft的类型,需要改成新类型(多一个只读字段groups[].vehicleTypeName,其余字段名与类型逐一相同)。JSON 结构层面对已有解析代码零破坏,只有严格 schema 校验(如果有)需要放开这个新字段。- 未知/认不出的编码统一规则(本次涉及的所有
*Name字段):statusName/planRefreshStateName/blockedStageName/planRefreshStalledReasonName/draft.groups[].vehicleTypeName/vehicleRequirementKindName/vehicleRequirements[].kindName这一组字段认不出编码一律给null,不回落原编码。这与orders端点的vehicleRequirementStatusName(未知码回落原码,#8543/#8619 未改动此口径)以及团期列表页的团期状态中文名(同样回落原码)是刻意不同的两套口径,前端不要用同一段兜底组件处理。 vehicleSeatSummary[].confirmedSeats/confirmedCount与totalSeats/totalCount不相等是设计上允许的常态(#8545),不要写断言校验两者相等,也不要用其中一组数字反推另一组。orders端点判断某户是否提交了某类用车需求,一律遍历vehicleRequirements[]按kind过滤,不要依赖单值字段vehicleRequirementStatus/vehicleRequirementKind(#8619 起单值字段只表达展示序首条,可能丢失第二类需求的状态)。
六、边界行为
- 未登录访问:网关拦截,不进入本文档描述的业务逻辑。
- 权限不足(当前角色未获授团期权限,或该团期不在本人名下):所有 4 个端点统一报
589507。 - 团期不存在:所有 4 个端点统一报
589500。 GET .../vehicle-requirement团期尚未形成正式需求:data为null,HTTP 200,不是错误。planRefreshState/blockedStage/planRefreshStalledReason三列 DB 为NULL(绝大多数团期的正常态,从未走过受控重开):对应 3 个*Name字段一律为null。vehicleSeatSummary[]:团级从未确认过时confirmedSeats/confirmedCount恒为0(不是null);车务定的车型在逐户报的车型集合之外时会新增一行totalSeats=0/totalCount=0的记录。orders端点vehicleRequirements[]:该户没有任何活跃用车需求行时为空数组(不是null)。
六.5、枚举
所属字段:GroupVehicleRequirementRespVO.status / statusName
| 值 | 中文名 | 说明 |
|---|---|---|
| DRAFT | 草稿 | 团期管理员正在汇总逐户需求、编辑分组与逐日人数 |
| CONFIRMED | 已确认 | 整份确认通过,等待发车务 |
| DISPATCHED | 已发车务 | 已推送到车务侧,等待配车 |
| DONE | 配车完成 | 车务侧已完成整团配车 |
| PENDING_RECONFIRM | 待重新确认 | 确认后团期人数/成员发生变化,需要重新确认整份 |
| CANCELLED | 已取消 | - |
所属字段:GroupVehicleRequirementRespVO.planRefreshState / planRefreshStateName
| 值 | 中文名 | 说明 |
|---|---|---|
| null(列值 NULL) | (无中文名,字段为 null) | 从未登记过任何刷新,多数团期的正常态 |
| PENDING | 刷新中 | 刷新命令已登记,等 fleet 刷完 |
| DONE | 刷新完成 | 就绪回调已通过判定并应用,本轮刷新闭环 |
| FAILED | 刷新失败 | 已耗尽重试预算或被 fleet 判定性终结;不放行团期,出团门②持续拒绝 |
所属字段:GroupVehicleRequirementRespVO.planRefreshStalledReason / planRefreshStalledReasonName
| 值 | 中文名 | 说明 |
|---|---|---|
| STATE_FAILED | 刷新已被判死 | plan_refresh_state 已经是 FAILED |
| COMMAND_FAILED | 刷新命令已失败 | 状态列仍是 PENDING,但刷新命令已终态失败(回写钩子未落成) |
| TIMEOUT | 刷新超时 | 状态列仍是 PENDING、命令也未判死,但已超过本轮窗口时限(缺省 120 分钟) |
所属字段:GroupVehicleRequirementRespVO.blockedStage / blockedStageName
取值域是团期状态枚举 GroupBatchStatus(既有枚举,本次未新增值)的全域,本次只是给这个已有编码字段配了中文名。常见值举例:
| 值 | 中文名 | 说明 |
|---|---|---|
| RESOURCE_PREPARING | 资源准备中 | - |
| MATERIAL_PREPARING | 物料准备中 | - |
| PENDING_DEPARTURE | 待出发 | - |
| (其余 GroupBatchStatus 取值) | 对应中文名 | 认不出的编码给 null |
所属字段:orders[].vehicleRequirementKind / vehicleRequirementKindName、orders[].vehicleRequirements[].kind / kindName
VehicleRequirementKind 枚举本身固定只有 2 个值(本次未新增第三个值,放宽的是响应字段的实际观测取值范围,不是枚举定义):
| 值 | 中文名 | 说明 |
|---|---|---|
| TRAVEL | 行程用车 | 服务日冻结为行程日 |
| TRANSFER | 接送机 | 服务日取航班/车次日期,允许落在行程日窗外 |
六.6、修改前后对比
| 项 | 改前 | 改后 |
|---|---|---|
GroupVehicleRequirementRespVO 状态/刷新状态/阻断阶段/停滞归因 |
仅下发编码,前端自行映射中文 | 新增 4 个 *Name 字段直接下发中文名;认不出编码给 null |
aggregate-draft.draft 字段类型 |
写侧 GroupVehicleRequirementSaveReqVO(挂校验注解,多余的约束会渲染进 Swagger) |
只读读侧 GroupVehicleRequirementDraftRespVO(不挂校验注解,多一个只读字段 vehicleTypeName) |
draft.groups[].remark 逐户标签兜底 |
团号(客户名)→ 团号 → 客户名 → 订单号(裸雪花 ID) | 团号(客户名)→ 团号 → 客户名 → 订单号 → "未知户"(不再回落裸雪花 ID) |
requirement-summary.vehicleSeatSummary[] |
只有 totalSeats/totalCount(逐户已提交口径) |
并列新增 confirmedSeats/confirmedCount(团级已确认口径),可能新增车型行 |
orders[].vehicleRequirementStatus/Kind |
只按 TRAVEL 过滤取行;只提交接送机的户恒为 null/"未提交" |
取展示序首条活跃行(TRAVEL 优先、TRANSFER 次之);只提交接送机的户下发真实状态 |
orders[].vehicleRequirements |
不存在该字段 | 新增,0~2 条,逐类下发状态 |
六.7、影响评估
- 前端必改:如果曾对
draft.remark文本做正则匹配来提取订单号或高亮逐户标签,需要兼容新的"未知户"文案且不再假设会出现裸数字订单 ID。并且请不要按任何固定格式解析remark——本单只改新生成的汇总草稿,已确认落库的存量团期其GET .../vehicle-requirement返回的groups[].remark仍是改前格式(订单号前缀,形如HL20260929154809598: …),不做历史数据回写。该字段是给运营看的自由文本,两种格式会长期并存。 - 前端必改:如果曾假设
orders[].vehicleRequirementKind恒为TRAVEL来做图标/文案硬编码分支,需要改为按实际值(TRAVEL/TRANSFER)渲染,并优先改用vehicleRequirements[]数组按类判断。 - 前端可选增强:可以直接展示后端下发的 4 个新增中文名字段,替换掉前端此前自行维护的编码→中文映射表(如果有)。
- 前端需知悉但不需要立即改:
vehicleSeatSummary[]新增的两个字段、可能新增的车型行,只在页面展示这两个数字时才需要处理;不展示则忽略即可,不影响既有渲染。 - 零风险:所有新增字段都是在既有 JSON 对象上新增键,未删除、未改名任何既有字段(
aggregate-draft.draft虽改了后端类型,但 JSON 键集合与既有字段类型未变,由专门单测钉住);未做严格 schema 校验的前端代码可无感兼容。
七、不影响范围
GroupVehicleRequirementSaveReqVO(PUT 请求体)字段集未变,仍是原有编码字段集,本次新增的只读字段不参与保存也不会被写侧读取。GroupVehicleRequirementDraftRespVO除groups[].vehicleTypeName外的全部字段(顶层version/remark、groups[]内groupId/groupCode/vehicleType/serviceStartDate/serviceEndDate/seats/count/specialTags/days)未变,由GroupVehicleRequirementDraftRespVOFieldParityTest钉住与写侧字段集一致。VehicleRequirementKind枚举定义本身未变,仍只有TRAVEL/TRANSFER两个值。- 团期用房相关端点(
hotel-households等)不在本次改动范围内。 orders端点除本文档列出的 4 个字段外,其余 26 个字段未变(详见 changelog30_8543)。- 809121/809122/809123 三个错误码的触发条件收窄与文案改写属于 #8577,不在本次三张工单范围内,详见 changelog
30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md。
八、测试环境已验证
- hl-order-service-v3 dev-v3 分支已部署测试网关,HEAD
6373e5cf2(git merge-base --is-ancestor核实本单提交5f2f86bc96已在其祖先链内),jar mtime 2026-09-30 09:27:12,两个实例 Nacos 健康检查均为 healthy。 - 以下四条读口实测取样自团期批次
2104840641651556353(vehicleRequirements[] == []一条取样自2104830530031919106),2026-09-30 采集。 GET .../requirement-summary实测:vehicleSeatSummary[0]={"vehicleType":"mpv","vehicleTypeName":"商务车","totalSeats":14,"totalCount":2,"confirmedSeats":7,"confirmedCount":1}。GET .../vehicle-requirement/aggregate-draft实测:draft.groups非空,含vehicleTypeName: "商务车";groups[0].remark为「团号(客户名)」格式(26-3682(董海涛): …;26-0805(谢丽萍): …),未出现裸雪花订单 ID。GET .../orders实测:2 条子订单,每条vehicleRequirements键均存在且非null,长度分别为 2 与 1。另在一个无活跃用车需求行的户上实测该键为空数组[]而不是null(与GroupBatchConverter.toVehicleRequirementItems()从new ArrayList<>()起手、无 null 分支一致)。GET .../vehicle-requirement实测:顶层键包含status/statusName/planRefreshState/planRefreshStateName/blockedStage/blockedStageName/planRefreshStalledReason/planRefreshStalledReasonName共 8 个;观测样本status="DONE"→statusName="配车完成";另外三个 DB 列为NULL,对应*Name字段均实测为null。- 「认不出的编码 →
null」这条行为本轮没有实测样本:候选团期的plan_refresh_state/blocked_stage/plan_refresh_stalled_reason三列在库中全为NULL,测试环境里造不出一个库内存着字典外编码的自然样本。该行为由源码与单测两侧钉住:GroupVehiclePlanRefreshState.labelOf()、GroupBatchStatus.descOf()对未命中编码返回null(不回落编码原文)。前端按null兜底渲染即可。 - 单元测试:hl-order-service-v3 模块
Tests run: 561, Failures: 0, Errors: 0, Skipped: 0,38 个相关测试类逐类点名核对报告文件均存在(38/38 命中);nested_selector_census退出码 0(无@Nested类被静默漏跑)。 - ArchUnit/架构门禁测试:
Tests run: 167,全绿。
十、相关文档
docs/CODE_RULES.md§3(VO 命名与读写侧分离约定)- changelog
30_8543_团期订单Tab用车状态按需求类别拆分并统一文案-修改接口-管理后台.md(orders端点其余字段的完整文档,及vehicleRequirementKind字段本次改动前的基线状态) - changelog
30_8577_只提交接送机的户不再被判未提交用车需求-修改接口-管理后台.md(PUT/aggregate-draft/confirm-check 三处 809121/809122/809123 错误码本次收窄的完整文档)
关联 / 联系人
- 关联 Issue:#8544、#8545、#8619
- 关联 PR:#8625(#8544 #8545)、#8624(#8619)
- 联系人:wx