54 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 | 8152 | 团期用车需求补车辆规格(座位/车辆数/特殊诉求/备注),新增子订单用车需求记录端点,汇总与预检补接送机口径 | admin | wx(GIT) | 修改接口 | deployed | not_required | verified | mmg | 14cb7110ab6072af1dc3ee767951771be9972436 | v2.1 | 2026-09-22 | 一次提交覆盖三张工单:#8152 团级乘车分组补车辆规格四字段 + 三个新错误码(809116/809117/809118);#8151 新增 GET .../requirement/vehicle-households 并给 requirement-summary 补 transferSummary;#8153 给 requirement/confirm-check 补 transferSubmitEnabled 与 transferDeclaredWithoutRequirement。gateway_status: not_required —— 本批零网关改动,/v3/admin/** 通配已配在 hl-gateway application.yml 的 order-service-v3 路由上,无新增 /admin/ 前缀。frontend_status: pending —— 新增端点与新增字段均需前端渲染,后端不代填。 前端已交付(hl-admin v2.1 @ 14cb7110):用车板块全链路——编辑弹窗规格四字段+paired rule 前置 809118、余座负值直显不 clamp 不拦提交;新建 VehicleSummarySection(两套字段名分开映射)与 VehicleHouseholdsSection(户数≠行数);confirm-check 接送机缺口提示不随免车/未开放短路。 订正残留已清(0b65814a06):VehicleHouseholdsSection 注释与 spec 夹具换车务口径真值,渲染面本就纯透传无行为变更。 | 2026-09-22 | dev-v3 |
order-v3 团期需求: 团级用车需求补车辆规格,新增子订单用车需求记录端点,汇总/预检补接送机口径
存放目录: 二期(v3) →
changelogs-v2/2026-09/服务: hl-order-service-v3(团期需求域)、hl-fleet-service(配车覆盖度基线,无对外接口变化) PR: 见文末「关联 / 联系人」 Issue: #8152 / #8151 / #8153 日期: 2026-09-22 影响范围: 管理后台「团期详情 → 查看需求」Tab 的用车板块(团级正式用车需求编辑弹窗、子订单用车需求记录、全团需求汇总、整体确认预检)
⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
一条口径不一致必须在页面上原样透出,不要自行抹平:
- 拦截口径不扣司机位:
PUT .../vehicle-requirement的座位充足性校验判的是seats × count < 该组最大单日乘车人数,总座位里含驾驶位。 - 回显口径扣司机位:响应里的
remainingPassengerSeats=max(0, seats × count − count)− 该组最大单日乘车人数,每辆车扣 1 个司机位。 - 于是 20 座 × 1 辆 / 20 人能保存成功,而返回的
remainingPassengerSeats是-1。
展示侧:remainingPassengerSeats 必须允许负数,不能 clamp 到 0。负值就是缺口,clamp 之后「刚好坐不下 1 人」与「刚好坐满」在页面上完全同形,运营拿不到补车信号。
🔴 提交侧(更要紧的一条):保存成功的分组,其 remainingPassengerSeats 可以为负数。这是「余座缺口」的展示值,不是校验失败信号,前端不得据此拦截提交。是否允许保存由后端 809116 判定(口径:seats × count 与该组最大乘车人数比较,不扣司机位);前端只需把负数如实显示为缺口,用于提示运营补车。
响应体里没有任何字段表示「这一单已经过了后端闸门」——remainingPassengerSeats 是唯一的座位信号。若前端按「负数 = 不合法」做提交拦截,20 座 × 1 辆 / 20 人这类组合在 UI 上就永远提交不了,而后端 API 是放行的:严口径事实上变成了闸门,且后端全绿、日志无异常、无人会发现。
一、背景(选填)
团级正式用车需求原先只填车型文本,车务拿到的是「35座大巴」这种字符串,排车时「几座 × 几辆」只能靠人问;同时团期管理员在「查看需求」Tab 里看不到定制师逐户报了什么(用房侧早有「汇总 + 子订单订房记录」上下两块,用车只有团级那一块),等于对着一个汇总数字签字。本批补齐三件事:团级分组的车辆规格、子订单用车需求记录端点、接送机(TRANSFER)在汇总与预检里的口径。
| 维度 | 本批之前 | 本批之后 |
|---|---|---|
| 团级乘车分组可填规格 | 仅车型文本 | 车型 + 座位数 + 车辆数 + 特殊诉求标签 + 备注 |
| 团期「查看需求」用车板块 | 只有团级正式需求一块 | 团级正式需求 + 子订单用车需求记录(新端点) |
requirement-summary 车侧口径 |
只统计行程用车(TRAVEL) | 车侧字段口径不变,接送机另挂 transferSummary |
| 整体确认预检的接送机信息 | 无 | transferSubmitEnabled + transferDeclaredWithoutRequirement 缺口名单 |
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 保存团期正式用车需求 | PUT | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement |
请求体新增 4 字段 + 响应新增 8 字段 + 3 个新错误码 | 分组元素补 seats/count/specialTags/remark |
| 2 | 读团期正式用车需求 | GET | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement |
响应新增 8 字段 | 与 1 共用同一响应体,原样回显 |
| 3 | 团期子订单用车需求记录 | GET | /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households |
新增接口 | 形态对齐既有 hotel-households |
| 4 | 全团需求汇总 | GET | /v3/admin/order/group-batch/{groupBatchId}/requirement-summary |
响应新增 transferSummary | 既有车侧字段零改动 |
| 5 | 整体确认需求缺失预检 | GET | /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check |
响应新增 2 字段 | 接送机开关 + 缺口名单 |
三、接口详情
1. 保存团期正式用车需求 PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement
VO: GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO
使用场景
团期详情「查看需求 → 团级正式用车需求」编辑弹窗点保存。语义是整份全量替换:未出现在本次提交里的分组会被移出当前版本。本批在分组元素里新增了 4 个可填字段,其余字段与提交语义均不变。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
| version | Body | Integer | ❌ | 首次保存传 null,其后回传上次拿到的值 | 乐观锁;不一致抛 809102 |
| remark | Body | String | ❌ | ≤500 | 整份需求备注 |
| groups | Body | Array | ✅ | 可以是空数组(@NotNull 非 @NotEmpty) |
全部乘车分组;有在团需车户却零分组抛 809103 |
| groups[].groupId | Body | Long | ❌ | 新增分组传 null | 带上它即声明「这就是库里那一组」,此时 groupCode 不得变更(否则 809104) |
| groups[].groupCode | Body | String | ✅ | ≤32,同一份内不得重复 | 直接作为车费 alloc_group |
| groups[].vehicleType | Body | String | ✅ | ≤64 | 车型文本/字典值,不设档位枚举 |
| groups[].serviceStartDate | Body | LocalDate | ✅ | yyyy-MM-dd |
本组服务开始日 |
| groups[].serviceEndDate | Body | LocalDate | ✅ | 不早于开始日 | 本组服务结束日 |
| groups[].seats | Body | Integer | ❌ | @Min(1);与 count 同填或同空 |
本批新增 该组单车座位数;存量分组可不传 |
| groups[].count | Body | Integer | ❌ | @Min(1);与 seats 同填或同空 |
本批新增 该组车辆数量;只填一半抛 809118 |
| groups[].specialTags | Body | Array<String> | ❌ | 取值须在字典 vehicle_special_demand 内 |
本批新增 特殊诉求标签编码数组;含字典外编码整份拒绝(809117) |
| groups[].remark | Body | String | ❌ | ≤500 | 本批新增 该组备注 / 其他诉求 |
| groups[].days | Body | Array | ✅ | 非空,且正好铺满本组服务日范围 | 逐日用车人数与成员 |
| groups[].days[].tripDate | Body | LocalDate | ✅ | 落在本组服务日范围内、不重复、不缺日 | 越界或重复抛 809105,缺日抛 809106 |
| groups[].days[].headcount | Body | Integer | ✅ | @Min(1),且 ≥ 当日成员户数 |
该组该日乘车人数(不是户数) |
| groups[].days[].memberOrderIds | Body | Array<Long> | ✅ | 非空,须全属本团在团户 | 该组该日实际乘车的子订单集合 |
出参 Result<GroupVehicleRequirementRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| requirementId | String | 正式需求主键(Long 序列化为字符串) |
| groupBatchId | String | 团期聚合主键(Long 序列化为字符串) |
| status | String | DRAFT / CONFIRMED / DISPATCHED / DONE / PENDING_RECONFIRM / CANCELLED;PUT 后为 DRAFT |
| version | Integer | 版本号,下次提交须回传 |
| remark | String | 整份备注(撤回/免车会追加,不覆盖) |
| confirmedBy | String | 整份确认人;DRAFT 时为 null |
| confirmedAt | LocalDateTime | 整份确认时间;DRAFT 时为 null |
| planRefreshState | String | 配车刷新状态原值;null = 从未登记过刷新 |
| planRefreshReplayCount | Integer | 人工受控重投累计次数,上限 5 |
| blockedStage | String | 团期被阻断阶段快照;null = 未阻断 |
| planRefreshStalled | Boolean | 刷新是否已停滞、不会自愈(恒非 null),判「要不要现在找人」看它 |
| planRefreshStalledReason | String | STATE_FAILED / COMMAND_FAILED / TIMEOUT;未停滞为 null |
| planRefreshTimeoutAt | LocalDateTime | 本轮刷新超时时刻;非 PENDING 为 null |
| planRefreshReplayExhausted | Boolean | 人工重投额度是否耗尽(恒非 null) |
| groups | Array | 全部乘车分组;整团免车态为空数组 |
| groups[].groupId | String | 分组主键(Long 序列化为字符串),下次提交同一组必须回传 |
| groups[].groupCode | String | 分组键 = 车费 alloc_group |
| groups[].vehicleType | String | 车型文本/字典值 |
| groups[].vehicleTypeName | String | 本批新增 车型中文名,后端下发,前端不自己映射编码 |
| groups[].serviceStartDate / serviceEndDate | LocalDate | 本组服务日范围 |
| groups[].seats | Integer | 本批新增 单车座位数;存量分组为 null(表示待填),不是 0 |
| groups[].count | Integer | 本批新增 车辆数量;存量分组为 null |
| groups[].specialTags | Array<SpecialTagItem> | 本批新增 特殊诉求标签(code + name);存量分组为空数组 |
| groups[].remark | String | 本批新增 该组备注;存量分组为 null |
| groups[].totalSeatCount | Integer | 本批新增 总座位数 = seats × count(含驾驶位);座位或数量缺一即为 null |
| groups[].maxHeadcount | Integer | 本批新增 该组 days 里的最大用车人数 |
| groups[].remainingPassengerSeats | Integer | 本批新增 余座 = 扣司机位后的可乘座位 − maxHeadcount;可为负;座位或数量缺一即为 null |
| groups[].specialTags[].code / name | String | 字典编码 / 中文名;字典查不到该编码时 name 为 null(不回落成编码) |
| groups[].days[].tripDate | LocalDate | 团期行程日 |
| groups[].days[].headcount | Integer | 该组该日用车人数 |
| groups[].days[].memberOrderIds | Array<String> | 成员子订单集合(Long 序列化为字符串) |
| groups[].days[].memberOrderCount | Integer | 当日成员户数 = memberOrderIds.size(),供填人数时对照 |
请求示例
{
"version": 3,
"remark": "9/13 起换大巴",
"groups": [
{
"groupId": "1867000000009",
"groupCode": "BUS",
"vehicleType": "35座大巴",
"serviceStartDate": "2026-09-12",
"serviceEndDate": "2026-09-13",
"seats": 19,
"count": 2,
"specialTags": ["child_seat"],
"remark": "含高速费",
"days": [
{ "tripDate": "2026-09-12", "headcount": 9, "memberOrderIds": [2101506167043985410] },
{ "tripDate": "2026-09-13", "headcount": 9, "memberOrderIds": [2101506167043985410] }
]
}
]
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"requirementId": "1867000000101",
"groupBatchId": "2101506167098511362",
"status": "DRAFT",
"version": 4,
"remark": "9/13 起换大巴",
"confirmedBy": null,
"confirmedAt": null,
"planRefreshState": null,
"planRefreshReplayCount": null,
"blockedStage": null,
"planRefreshStalled": false,
"planRefreshStalledReason": null,
"planRefreshTimeoutAt": null,
"planRefreshReplayExhausted": false,
"groups": [
{
"groupId": "1867000000009",
"groupCode": "BUS",
"vehicleType": "35座大巴",
"vehicleTypeName": "大巴",
"serviceStartDate": "2026-09-12",
"serviceEndDate": "2026-09-13",
"seats": 19,
"count": 2,
"specialTags": [{ "code": "child_seat", "name": "儿童安全座椅" }],
"remark": "含高速费",
"totalSeatCount": 38,
"maxHeadcount": 9,
"remainingPassengerSeats": 27,
"days": [
{
"tripDate": "2026-09-12",
"headcount": 9,
"memberOrderIds": ["2101506167043985410"],
"memberOrderCount": 1
},
{
"tripDate": "2026-09-13",
"headcount": 9,
"memberOrderIds": ["2101506167043985410"],
"memberOrderCount": 1
}
]
}
]
},
"success": true
}
空数据 / 降级响应
- 整团没有在团需车户时,
groups: []是合法提交,响应groups为空数组。 - 存量分组(本批之前建的)回显时
seats/count/remark为null、specialTags为[]、totalSeatCount/remainingPassengerSeats为null。null表示「这一组还没填过车辆规格」,与填了 0 是两件事(0 通不过@Min(1)),前端不要把 null 兜成 0。 - 车型中文名依赖车队侧车型库:查不到编码或车队不可用时
vehicleTypeName为null,接口仍 200,不阻断页面。
{ "code": 200, "message": "成功", "data": { "groups": [] }, "success": true }
错误响应
{
"code": 809116,
"message": "第 BUS 组座位数不足:19 座 × 1 辆 = 19 座,少于该组最大乘车人数 20 人",
"success": false,
"data": null
}
{
"code": 809118,
"message": "第 BUS 组的座位数与车辆数必须同时填写,或同时留空",
"success": false,
"data": null
}
{
"code": 809117,
"message": "第 BUS 组特殊诉求标签 wheelchair_lift 不在字典内,请从下拉项中选择",
"success": false,
"data": null
}
业务边界
- 报文里「第 {0} 组」的 {0} 填的是
groupCode,不是序号。809116/809117/809118 三条均如此,报文渲染出来形如「第 BUS 组…」,前端可直接展示、也可据此定位是哪一组填错。 - 一次只抛一条:整份提交的多条违规按校验遍历顺序收集,只抛第一条。前端改完一处再保存可能撞上下一条。
- 座位充足性判据 =
seats × count < 该组最大单日乘车人数,不扣司机位(拦截口径),与响应里扣司机位的remainingPassengerSeats(回显口径)刻意差一个司机位。这样「刚好坐满、司机另开一辆」的既有排法不会被一刀拦死。 - seats 与 count 同生同死:只填一个抛 809118;两个都不填 = 存量形态,座位校验整体跳过。
- 标签校验在事务外先做:
specialTags要调字典服务,校验不过时零写入。字典按 ACTIVE 项放行,运营新增字典项后立即可用(后端未硬编码枚举)。 - 标签编码会被 trim、丢空白、按首次出现去重保序后落库,前端重复提交同一编码不会报错,但回显只有一条。
- 整份审核粒度是「整份」,不支持增量补丁;整团不需要用车请走
POST .../vehicle-requirement/waive,不要提交空 groups 代替(有需车户时会撞 809103)。 - 乐观锁:
version不一致抛 809102,其报文第二个占位符不保证是数字(CAS 落空分支传的是说明文本),前端不要按数字解析。
2. 读团期正式用车需求 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement
VO: GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO
使用场景
打开团期「查看需求 → 团级正式用车需求」时读取回显,与保存端点共用同一个响应体(PUT / GET / withdraw / waive 四端点同体)。本批新增的 8 个字段在这里原样可读。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
无请求体。
出参 Result<GroupVehicleRequirementRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| data | Object | 结构与第 1 节「出参」逐字段相同;该团尚未形成正式用车需求时为 null |
| data.groups[].seats / count | Integer | 本批新增;存量分组为 null |
| data.groups[].specialTags | Array<SpecialTagItem> | 本批新增;存量分组为空数组 |
| data.groups[].vehicleTypeName | String | 本批新增;车队不可用或无此编码时为 null |
| data.groups[].totalSeatCount / maxHeadcount / remainingPassengerSeats | Integer | 本批新增;座位或数量缺一时前者与后者为 null |
请求示例
GET /v3/admin/order/group-batch/2101506167098511362/vehicle-requirement HTTP/1.1
Host: <网关域名>
Authorization: Bearer <token>
无请求体。
响应示例
{
"code": 200,
"message": "成功",
"data": {
"requirementId": "1867000000101",
"groupBatchId": "2101506167098511362",
"status": "CONFIRMED",
"version": 4,
"groups": [
{
"groupId": "1867000000010",
"groupCode": "SUV",
"vehicleType": "suv",
"vehicleTypeName": "SUV系列",
"serviceStartDate": "2026-09-12",
"serviceEndDate": "2026-09-12",
"seats": 20,
"count": 1,
"specialTags": [],
"remark": null,
"totalSeatCount": 20,
"maxHeadcount": 20,
"remainingPassengerSeats": -1,
"days": [
{
"tripDate": "2026-09-12",
"headcount": 20,
"memberOrderIds": ["2101506167043985410"],
"memberOrderCount": 1
}
]
}
]
},
"success": true
}
上面就是「关键变化」那条口径不一致的真实形态:20 座 × 1 辆载 20 人保存成功,余座 -1。
空数据 / 降级响应
该团尚未形成正式用车需求时 data 为 null(不是空对象),前端按「尚未创建」渲染:
{ "code": 200, "message": "成功", "data": null, "success": true }
错误响应
{
"code": 403,
"message": "无权限访问",
"success": false,
"data": null
}
业务边界
- 判权码
group-batch:view,与requirement-summary/hotel-households同码。 status不是恒定值,不要按「读到即 CONFIRMED」渲染。- 配车刷新观测块七个字段是只读投影,不接受回传、不参与任何提交;判「要不要现在找人」看
planRefreshStalled,别看planRefreshState。 - 存量团期读出来的分组
seats/count为 null 是正常形态(绝大多数历史团期都是),不要当异常画红。
3. 团期子订单用车需求记录 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households
VO: GroupVehicleRequirementSaveReqVO → GroupVehicleHouseholdsRespVO
使用场景
本批新增端点。 团期详情「查看需求」Tab 的用车板块下半块:上面是团级正式需求,下面就是本端点——定制师逐户报了什么。形态刻意对齐既有 .../requirement/hotel-households,前端可复用用房需求那套「每户一张卡」的展示组件。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
| kind | Query | String | ❌ | TRAVEL / TRANSFER |
需求类别过滤;不传则两类都返(前端默认不传) |
无请求体。
出参 Result<GroupVehicleHouseholdsRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| groupBatchId | String | 团期 ID(Long 序列化为字符串) |
| departDate | LocalDate | 团期出发日期;未定出发日时为 null |
| householdCount | int | 本列表户数,按 orderId 去重(一户同时报了两类只算 1 户) |
| vehicleRowCount | int | 需求行数(一户可能 TRAVEL + TRANSFER 两行),与 householdCount 刻意不等价 |
| countedHouseholdCount | int | 计入 requirement-summary 车侧汇总的户数(= 有活跃 TRAVEL 行的户数) |
| households | Array | 逐户明细,按 orderNo 升序 |
| households[].orderId | String | 子订单 ID(Long 序列化为字符串) |
| households[].orderNo | String | 子订单团号 |
| households[].customerName | String | 主联系人姓名 |
| households[].participantCount | int | 出行人数(成人 + 儿童 + 小童 + 婴儿) |
| households[].consultantId | String | 定制师 ID(Long 序列化为字符串);未指派为 null |
| households[].consultantName | String | 定制师姓名;未指派为 null |
| households[].countedInSummary | boolean | 该户是否计入车侧汇总(= 有活跃 TRAVEL 行);只报接送机的户为 false |
| households[].requirements | Array | 该户的用车需求行,0~2 条(TRAVEL / TRANSFER 各至多一条活跃行) |
| requirements[].requirementId | String | 需求行 ID(Long 序列化为字符串) |
| requirements[].kind | String | TRAVEL = 行程用车 / TRANSFER = 接送机 |
| requirements[].kindName | String | 类别中文名,后端下发,前端不自己映射 |
| requirements[].status | String | PENDING_REVIEW / PENDING / PROCESSING / DONE |
| requirements[].statusName | String | 状态中文名(车务口径);status 为空时为 null,遇未知状态码时原样回落该 code 字符串 |
| requirements[].fleet | Array<FleetItem> | 车队明细,定制师所报原貌,不做合并 |
| requirements[].specialTags | Array<SpecialTagItem> | 特殊诉求标签;未填为空列表 |
| requirements[].remark | String | 备注 / 其他诉求,≤500;未填为 null |
| requirements[].serviceDates | Array<LocalDate> | 本需求冻结的服务日期;未冻结时为空列表 |
| requirements[].headcount | Integer | 乘车人数(不含司机) |
| requirements[].totalSeatCount | Integer | 总座位数 = Σ(seats × count);fleet 为空时为 0 |
| requirements[].remainingPassengerSeats | Integer | 余座 = 可载客座位(已按每车扣 1 个司机位)− 乘车人数 |
| requirements[].pickupRequired | Boolean | 是否需要平台接机/接站;仅 TRANSFER 有意义 |
| requirements[].dropoffRequired | Boolean | 是否需要平台送机/送站;仅 TRANSFER 有意义 |
| requirements[].returnRemark | String | 打回原因;未被打回过为 null |
| requirements[].returnedAt | LocalDateTime | 打回时间;未被打回过为 null |
| fleet[].vehicleType | String | 车型编码(suv / mpv / bus / sedan);缺失为 null |
| fleet[].vehicleTypeName | String | 车型中文名;车队不可用或无此编码时为 null |
| fleet[].seats / count | Integer | 单车座位数 / 车辆数 |
| specialTags[].code / name | String | 标签编码(字典 vehicle_special_demand 的 value)/ 中文名;字典不可用或无此编码时 name 为 null |
请求示例
GET /v3/admin/order/group-batch/2101506167098511362/requirement/vehicle-households?kind=TRANSFER HTTP/1.1
Host: <网关域名>
Authorization: Bearer <token>
无请求体。不传 kind 时 URL 为 .../requirement/vehicle-households,两类都返。
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2101506167098511362",
"departDate": "2026-09-12",
"householdCount": 1,
"vehicleRowCount": 2,
"countedHouseholdCount": 1,
"households": [
{
"orderId": "2101506167043985410",
"orderNo": "GT-26-0081",
"customerName": "陈昊",
"participantCount": 4,
"consultantId": "10001",
"consultantName": "李四",
"countedInSummary": true,
"requirements": [
{
"requirementId": "2101506167043985411",
"kind": "TRAVEL",
"kindName": "行程用车",
"status": "DONE",
"statusName": "配车完成",
"fleet": [
{ "vehicleType": "suv", "vehicleTypeName": "SUV系列", "seats": 7, "count": 1 }
],
"specialTags": [{ "code": "child_seat", "name": "儿童安全座椅" }],
"remark": "含高速费",
"serviceDates": ["2026-09-12", "2026-09-13"],
"headcount": 4,
"totalSeatCount": 7,
"remainingPassengerSeats": 2,
"pickupRequired": null,
"dropoffRequired": null,
"returnRemark": null,
"returnedAt": null
},
{
"requirementId": "2101506167043985412",
"kind": "TRANSFER",
"kindName": "接送机",
"status": "PENDING",
"statusName": "待车队配",
"fleet": [
{ "vehicleType": "mpv", "vehicleTypeName": "商务车", "seats": 7, "count": 1 }
],
"specialTags": [],
"remark": null,
"serviceDates": ["2026-09-12"],
"headcount": 4,
"totalSeatCount": 7,
"remainingPassengerSeats": 2,
"pickupRequired": true,
"dropoffRequired": false,
"returnRemark": null,
"returnedAt": null
}
]
}
]
},
"success": true
}
注意这一例里 householdCount=1 而 vehicleRowCount=2——表头「共 N 户」必须取 householdCount,不能取 households.length 之外的行数。
空数据 / 降级响应
团里没有任何活跃用车需求(或按 kind 过滤后为空)时,三个计数为 0、households 为空数组,接口仍 200:
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2101506167098511362",
"departDate": "2026-09-12",
"householdCount": 0,
"vehicleRowCount": 0,
"countedHouseholdCount": 0,
"households": []
},
"success": true
}
车型中文名与标签中文名依赖车队侧与字典:不可用时对应 vehicleTypeName / name 为 null,其余字段照常返回,不 500、不阻断页面。
错误响应
{
"code": 403,
"message": "无权限访问",
"success": false,
"data": null
}
业务边界
- 判权码与既有
hotel-households/requirement-summary一致(group-batch:view)。 - 户数与行数是两个数:
householdCount按 orderId 去重,vehicleRowCount数行。照抄用房侧的写法在「一户两类」时会把表头写错。 - 被打回的需求行不在本列表内(打回 = 原地置 REJECTED_* 并失活)。这与用房侧「列出打回户并灰显」刻意不同,前端不要按用房那套去找
countedInSummary=false的打回户。 countedInSummary=false在本端点的含义是「该户没有活跃 TRAVEL 行」——典型就是只报了接送机的户。车侧汇总只统计行程用车。pickupRequired/dropoffRequired仅 TRANSFER 行有意义,TRAVEL 行上为 null。- 不传
kind= 两类都返,这与提交侧「不传按 TRAVEL」的缺省相反:只读筛选若沿用提交侧缺省,接送机会整类从页面消失且无提示。 - 🔴
statusName口径订正说明(曾误用房务枚举 label):本端点的statusName原实现直接取RequirementStatus枚举自带的label——该枚举被房、车两域共用,label是房务侧视角文案(PENDING的 label 字面量就是「待房务配」),枚举类自己的 javadoc 已写明「车需求场景下的展示文案由前端另行映射」。已由45adfd7ed(PR #8173,fix(order-v3): 用车逐户需求读口 statusName 改用车务文案,不再吐枚举的房务 label (#8151))修正为改调GroupBatchConverter.resolveRequirementStatusName(code, true),与团期其它用车读口(团级正式需求的vehicleRequirementStatusName等)同一口径。完整取值:
status |
statusName(车务口径) |
|---|---|
PENDING |
待车队配 |
PROCESSING |
配车中 |
DONE |
配车完成 |
PENDING_REVIEW |
待审核 |
REJECTED_TO_CONSULTANT |
已驳回定制师 |
REJECTED_TO_ADMIN |
已驳回管理员 |
| 其它未知码 | 原样回落该 code 字符串 |
空 / null |
null |
后两个驳回态在映射范围内,但被打回的需求行已失活、不出现在本端点的列表里(见前一条),故本列表实际只会出现前 4 行。
🔎 同源残留(写给后续做 grep 的人,两处均不影响运行):上述被订正的四个错误取值(待处理 / 处理中 / 已完成 / 待审核)在本文件之外还有两个落点,hl-admin 14cb7110 实读结果:
VehicleHouseholdsSection.vue:191的注释写着「四枚举:待审核/待处理/处理中/已完成」。不影响渲染:同文件:78是req.statusName为空则回落req.status再回落破折号的纯透传表达式,无任何字面分支;:65注释已明写「kindName/statusName 后端下发,禁自建编码→中文映射」。后端文案变了,页面显示跟着变。__tests__/VehicleHouseholdsSection.spec.js:39,57,111,113的夹具是statusName: '已完成'/'待处理'。不会变红:用例断言的是透传行为(输入什么就渲染什么),夹具换成任何字符串都绿。
两处都只是文本,改与不改都不影响功能。列在这里是因为下一个人 grep「待处理」时会同时撞上它们,得能分清哪些是错的、哪些是它们本该的样子。
▶ 责任归属写在明处:这四个取值是后端先写错在本交接件里、前端照着抄的。契约文档错与实现错同等有害,已由 e011960 订正。
4. 全团需求汇总 GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary
VO: GroupVehicleRequirementSaveReqVO → GroupRequirementSummaryRespVO
使用场景
团期「查看需求」Tab 顶部汇总块。本批只做一件事:新增 transferSummary(接送机汇总)。既有车侧字段(vehicleRequirementCount、vehicleSeatSummary)口径零改动,仍只统计行程用车。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
无请求体。
出参 Result<GroupRequirementSummaryRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| activeOrderCount 等既有字段 | int / Array | 本批不改,口径不变 |
| vehicleRequirementCount | int | 已提交用车需求的子订单数(既有,仍是行程用车口径) |
| vehicleSeatSummary | Array | 大巴座位汇总(既有,仍是行程用车口径) |
| transferSummary | Object | 本批新增 接送机汇总;恒非 null(空团也给空壳) |
| transferSummary.householdCount | int | 有活跃接送机需求的户数(按 orderId 去重) |
| transferSummary.pickupHouseholdCount | int | 其中需要平台接机/接站的户数(pickupRequired=true) |
| transferSummary.dropoffHouseholdCount | int | 其中需要平台送机/送站的户数(dropoffRequired=true) |
| transferSummary.headcount | int | 接送机总人数(各户 headcount 之和;未填人数的户按 0 计) |
| transferSummary.vehicleSeatSummary | Array<TransferSeatItem> | 按车型聚合的接送机座位/台数 |
| transferSummary.serviceDates | Array<LocalDate> | 接送机涉及的服务日期去重集合(升序);未冻结服务日的需求不贡献日期 |
| TransferSeatItem.vehicleType | String | 车型大类编码(suv / mpv / bus / sedan;需求里缺失时为「未知」) |
| TransferSeatItem.vehicleTypeName | String | 车型大类中文名;查不到或车队不可用时为 null |
| TransferSeatItem.seats | int | 合计座位数 = Σ(单车座位 × 台数) |
| TransferSeatItem.count | int | 合计车辆台数 |
请求示例
GET /v3/admin/order/group-batch/2101506167098511362/requirement-summary HTTP/1.1
Host: <网关域名>
Authorization: Bearer <token>
无请求体。
响应示例
{
"code": 200,
"message": "成功",
"data": {
"activeOrderCount": 6,
"vehicleRequirementCount": 5,
"vehicleSeatSummary": [
{ "vehicleType": "bus", "vehicleTypeName": "大巴", "totalSeats": 38, "totalCount": 2 }
],
"transferSummary": {
"householdCount": 2,
"pickupHouseholdCount": 2,
"dropoffHouseholdCount": 1,
"headcount": 7,
"vehicleSeatSummary": [
{ "vehicleType": "mpv", "vehicleTypeName": "商务车", "seats": 14, "count": 2 }
],
"serviceDates": ["2026-09-12", "2026-09-16"]
}
},
"success": true
}
空数据 / 降级响应
团里没有接送机需求(含空团)时 transferSummary 仍是对象、不是 null,计数为 0、数组为空——前端可以无条件 v-for,不必做 null 判断:
{
"code": 200,
"data": {
"transferSummary": {
"householdCount": 0,
"pickupHouseholdCount": 0,
"dropoffHouseholdCount": 0,
"headcount": 0,
"vehicleSeatSummary": [],
"serviceDates": []
}
},
"success": true
}
错误响应
{
"code": 403,
"message": "无权限访问",
"success": false,
"data": null
}
业务边界
- 判权码
group-batch:view(本端点暴露客户特殊需求)。 transferSummary与既有车侧字段是两套数:既有vehicleSeatSummary只含行程用车,接送机的座位/台数只在transferSummary.vehicleSeatSummary里。两者不要相加当「全团用车」展示,除非页面明确要合计。- 🔴 两组同域字段名不一致,必须写两套映射:接送机侧与行程车侧都是「按车型聚合的座位/台数」,字段含义完全相同,但名字不同(按工单接口表刻意为之,本批不改)。照既有字段名去取新对象会拿到
undefined:
| 含义 | 行程车侧 vehicleSeatSummary[](既有 VehicleSeatItem) |
接送机侧 transferSummary.vehicleSeatSummary[](新增 TransferSeatItem) |
|---|---|---|
| 车型编码 | vehicleType |
vehicleType(同名) |
| 车型中文名 | vehicleTypeName |
vehicleTypeName(同名) |
| 合计座位数 | totalSeats |
seats |
| 合计车辆台数 | totalCount |
count |
前端不要复用同一个渲染/取数函数,需要两套映射。
- 车型编码在需求里缺失时归到
"未知"一条,台数不丢。 headcount把未填人数的户按 0 计,所以它可能小于householdCount× 实际人数,只作汇总参考。
5. 整体确认需求缺失预检 GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check
VO: GroupVehicleRequirementSaveReqVO → GroupBatchRequirementCheckRespVO
使用场景
「查看需求」Tab 进入时、以及点「整体确认」前调用,据 ready 置灰按钮、据 missing / vehicleMissing 展示缺哪几户。本批新增两个接送机相关字段。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
无请求体。
出参 Result<GroupBatchRequirementCheckRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| ready / missing / vehicleMissing / vehicleWaived 等既有字段 | - | 本批不改 |
| transferSubmitEnabled | Boolean | 本批新增 当前环境是否开放接送机需求提交(配置 hl.order.requirement.transfer-kind-submit-enabled) |
| transferDeclaredWithoutRequirement | Array | 本批新增 在大交通声明了接送机、却没有活跃 TRANSFER 用车需求行的户;提示性,不进 ready、不阻断确认 |
| 该数组项 .orderId | String | 子订单 ID(Long 序列化为字符串) |
| 该数组项 .orderNo | String | 子订单号 |
| 该数组项 .customerName | String | 客户姓名 |
| 该数组项 .consultantId | String | 定制师 adminId(Long 序列化为字符串);未指派为 null |
| 该数组项 .consultantName | String | 定制师姓名快照;未指派为 null |
| 该数组项 .pickupRequired | Boolean | 大交通里声明了接机/接站 |
| 该数组项 .dropoffRequired | Boolean | 大交通里声明了送机/送站 |
| 该数组项 .pickupRemark | String | 已声明批次上的文字备注(多条去重后以「;」连接);无备注为 null |
请求示例
GET /v3/admin/order/group-batch/2101506167098511362/requirement/confirm-check HTTP/1.1
Host: <网关域名>
Authorization: Bearer <token>
无请求体。
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2101506167098511362",
"batchStatus": "RESOURCE_PREPARING",
"batchStatusName": "资源准备中",
"ready": true,
"missing": [],
"checkedResourceTypes": ["HOTEL", "VEHICLE"],
"vehicleWaived": true,
"vehicleMissing": [],
"groupVehicleRequirementId": "1867000000101",
"groupVehicleRequirementStatus": "CONFIRMED",
"groupVehicleRequirementVersion": 4,
"transferSubmitEnabled": false,
"transferDeclaredWithoutRequirement": [
{
"orderId": "60123456789001",
"orderNo": "HL2606010001",
"customerName": "陈昊",
"consultantId": "10001",
"consultantName": "李四",
"pickupRequired": true,
"dropoffRequired": false,
"pickupRemark": "航班 CA1234,落地 14:20"
}
]
},
"success": true
}
上面这一例同时演示了两条边界:vehicleWaived=true(整团免车)不清空缺口名单;transferSubmitEnabled=false 时名单也照报。
空数据 / 降级响应
无此类户时 transferDeclaredWithoutRequirement 是空数组、不是 null(null 与「一条都没有」在前端是两种渲染):
{
"code": 200,
"data": { "transferSubmitEnabled": true, "transferDeclaredWithoutRequirement": [] },
"success": true
}
错误响应
{
"code": 403,
"message": "无权限访问",
"success": false,
"data": null
}
业务边界
- 🔴
transferDeclaredWithoutRequirement不跟随vehicleWaived短路:整团免车豁免的是团级行程用车,与「某户自己声明了要接送机」无关。所以团期标了免车,这个名单照样可能非空——前端不要据vehicleWaived隐藏它。 - 🔴
transferSubmitEnabled=false时名单同样照报:缺口是事实,与能不能立刻补是两件事。此时前端应提示「当前环境未开放接送机需求提交」(定制师提交kind=TRANSFER会被 809004 拒),但不要因此隐藏名单——否则管理员会照着名单去催定制师,定制师撞一堵不会变的墙。 - 该名单是提示不是缺失:不进
ready、不阻断整团确认、也不并进vehicleMissing(后者语义严格是「TRANSFER 行已存在但服务日未补录」)。混在一起的代价是一个误填会把整团确认卡死,而运营没有手段标记「确认过了、不用管」。 - 本字段只在
confirm-check读口上有值;整团确认写口共用同一 VO,那条路径上它恒为空数组。 - 车侧新增的两个预检原因码会出现在
vehicleMissing[].reason里:GROUP_SPEC_INCOMPLETE(对应 809118)与GROUP_SEATS_INSUFFICIENT(对应 809116),detail字段与整团确认时抛出的报文逐字相同,可直接展示。
四、契约约束与正确调用方式(接口类必写)
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 payload 对照(PUT .../vehicle-requirement 的分组元素)
| 场景 | payload 片段 |
|---|---|
| ✅ 存量分组不填规格(两个都不传) | { "groupCode": "BUS", "seats": null, "count": null } → 座位校验整体跳过 |
| ✅ 规格填全且坐得下 | { "groupCode": "BUS", "seats": 19, "count": 2 },该组最大单日人数 36 → 38 ≥ 36 通过 |
| ✅ 刚好坐满(含驾驶位) | { "groupCode": "SUV", "seats": 20, "count": 1 },最大单日人数 20 → 20 < 20 不成立,通过,回显 remainingPassengerSeats: -1 |
| ❌ 只填座位数 | { "groupCode": "BUS", "seats": 19, "count": null } → 809118 |
| ❌ 只填车辆数 | { "groupCode": "BUS", "seats": null, "count": 2 } → 809118 |
| ❌ 座位不足 | { "groupCode": "BUS", "seats": 19, "count": 1 },最大单日人数 20 → 809116 |
| ❌ 座位数为 0 | { "groupCode": "BUS", "seats": 0, "count": 1 } → 400(@Min(1)),不是 809116 |
| ❌ 字典外标签 | { "groupCode": "BUS", "specialTags": ["wheelchair_lift"] }(字典无此项)→ 809117,整份零写入 |
切换状态时的必要动作
- 想把某组从「已填规格」改回「未填」,必须把
seats与count同时显式传 null,只清一个会撞 809118。 - 想清空某组特殊诉求,传
"specialTags": []或null均可(后端归一处理);不要传[""](空白项会被丢弃,但不要依赖这个)。 - 编辑存量团期时,把
GET拿到的seats/count(可能是 null)原样回传即可,后端不会因为它们是 null 而拒绝。
五、数据库行为
只写外部可观察行为:
| 前端提交(分组元素) | 再次 GET 读到 |
|---|---|
seats=19, count=2 |
seats: 19, count: 2, totalSeatCount: 38, remainingPassengerSeats: 38 − 2 − maxHeadcount |
seats=null, count=null |
seats: null, count: null, totalSeatCount: null, remainingPassengerSeats: null |
specialTags=["child_seat","child_seat"," "] |
specialTags: [{ "code": "child_seat", "name": "儿童安全座椅" }](trim + 丢空白 + 去重保序) |
remark="含高速费" |
remark: "含高速费" |
存量分组的四个新字段没有默认值:本批之前建的分组行读出来是 seats: null / count: null / specialTags: [] / remark: null,不是 0、不是空串。前端渲染必须区分「未填」与「填了值」。
整份替换语义不变:保存成功后未出现在本次提交里的分组被移出当前版本,其规格字段随之不可见。
六、边界行为
- 未登录 → 401(网关拦截)。
- 无
group-batch:view权限 → 403。 - 团期不存在 → 对应团期错误码,不 500。
- 车队服务 / 字典服务降级 →
vehicleTypeName、specialTags[].name为null,其余字段照常返回,不 500 不阻断页面(只读端点降级不回落成编码,避免前端把编码当中文展示)。 - 老数据兼容 → 存量分组无规格字段 →
seats/count/remark为 null、specialTags为[],不异常;存量团期打开编辑弹窗后原样保存不会被新校验拒绝。 - 生产环境:二期(order-v3 / fleet)尚未上线生产,本批路径在生产环境为 404。这是既有事实,不是本批引入的。
六.5、枚举 / 数据字典
kind(用车需求类别)
所属字段: GroupVehicleHouseholdsRespVO.households[].requirements[].kind、以及 vehicle-households 的 query 参数 kind | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
TRAVEL |
行程用车 | 团期行程期间的用车需求;计入 requirement-summary 既有车侧字段 |
TRANSFER |
接送机 | 接送机/接送站需求;计入 transferSummary,不进既有车侧字段 |
query 参数不传 = 两类都返。
status(子订单用车需求行状态)
所属字段: GroupVehicleHouseholdsRespVO.households[].requirements[].status | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
PENDING_REVIEW |
待审核 | 定制师已提交,等团期侧核对 |
PENDING |
待车队配 | 已进入处理队列 |
PROCESSING |
配车中 | 车务作业中 |
DONE |
配车完成 | 本需求行已闭环 |
被打回的需求行已失活,不出现在本端点的列表里;statusName 是车务口径中文名(见上表),status 为空时为 null,遇未知状态码时原样回落该 code 字符串。
specialTags(字典 vehicle_special_demand)
所属字段: GroupVehicleRequirementSaveReqVO.groups[].specialTags(提交,List<String> 编码)/ GroupVehicleRequirementRespVO.groups[].specialTags(回显,{code,name}) | 类型: String 编码
取值由运营在字典里维护、后端按字典实际内容校验,代码里不硬编码枚举——前端必须从字典下拉取值,不要自己写死一份列表。只放行字典里 ACTIVE 的项,含字典外编码整份拒绝(809117)。回显时 name 为 null 表示字典里查不到该编码(不回落成编码)。
vehicleType(车型大类编码)
所属字段: transferSummary.vehicleSeatSummary[].vehicleType、vehicle-households 的 fleet[].vehicleType | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
suv |
SUV系列 | 中文名取自车型管理库,后端下发 |
mpv |
商务车 | 同上 |
bus |
大巴 | 同上 |
sedan |
轿车 | 同上 |
未知 |
未知 | 需求里车型编码缺失时的归并项(仅汇总类字段出现,台数不丢) |
注意团级 GroupVehicleRequirementSaveReqVO.groups[].vehicleType 是自由文本/字典值、不设档位枚举(例:35座大巴),与上表的大类编码不是同一个取值空间。
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
PUT .../vehicle-requirement 请求 groups[] |
groupId / groupCode / vehicleType / serviceStartDate / serviceEndDate / days | 额外接受 seats、count、specialTags、remark(均非必填) |
响应 groups[] |
groupId / groupCode / vehicleType / 服务日 / days | 额外返回 vehicleTypeName、seats、count、specialTags、remark、totalSeatCount、maxHeadcount、remainingPassengerSeats |
requirement-summary |
无接送机信息 | 新增 transferSummary(恒非 null),既有车侧字段口径不变 |
requirement/confirm-check |
无接送机信息 | 新增 transferSubmitEnabled、transferDeclaredWithoutRequirement(空时为 []) |
vehicleMissing[].reason 取值 |
不含规格相关原因 | 新增 GROUP_SPEC_INCOMPLETE、GROUP_SEATS_INSUFFICIENT |
| 新端点 | 无 | GET .../requirement/vehicle-households |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 提交只填座位数或只填车辆数 | 接受(无此字段) | 809118 拒绝,整份零写入 |
| 提交坐不下的规格 | 接受(无此字段) | 809116 拒绝(判据不扣司机位) |
| 提交字典外特殊诉求标签 | 接受(无此字段) | 809117 拒绝,事务外先校验,零写入 |
| 存量团期原样保存 | 通过 | 仍通过(四个新字段非必填,null 时校验整体跳过) |
整团免车(vehicleWaived=true)时的接送机缺口 |
无此名单 | 名单照报,不随免车短路 |
六.7、影响评估
- 是否破坏向后兼容: 否。四个新请求字段全部非必填,不传即维持原行为;新增响应字段是增量。
- 前端是否必须同步上线: 是——新增端点与新增字段需要前端渲染;不改前端时页面仍可用,但团级分组无法填写车辆规格、看不到子订单用车记录与接送机汇总。
- 前端 workaround 清理点:
- 若此前在前端自行维护过一份
vehicle_special_demand标签中文映射表,可撤掉,改用后端下发的specialTags[].name。 - 若此前在前端自行维护过车型编码→中文的映射,可撤掉,改用
vehicleTypeName。 - 若此前把
remainingPassengerSeats之类余座数做过Math.max(0, x)处理,必须撤掉——负值是有效信号。 - 同理,不要新增「余座为负则禁止保存」的前端校验:那会把后端放行的组合在 UI 上锁死(见「关键变化」提交侧那条)。
- 若此前在前端自行维护过一份
七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- 仅影响: 管理后台「团期详情 → 查看需求」Tab 的用车板块(团级正式用车需求、子订单用车需求记录、全团汇总、整体确认预检)。
- 零影响:
requirement-summary的用房侧全部字段(dailyRoomBreakdown、hotelNeededOrderCount等)与hotel-households端点。requirement-summary既有车侧字段vehicleRequirementCount/vehicleSeatSummary的口径(仍只统计行程用车)。- 子订单侧用车需求提交端点的字段与校验(本批不改定制师提交侧)。
POST .../requirement/confirm(整团确认写口)的判决逻辑:新增的接送机缺口名单不进ready、不阻断确认。POST .../vehicle-requirement/withdraw/waive/reopen三个写端点的语义(它们与 PUT/GET 共用响应体,因此同样多出 8 个字段,但行为不变)。- 历史数据:存量分组不迁移,四个新字段保持 null,下次编辑保存时才可能写入。
八、测试环境已验证
网关路由:本批零网关改动。/v3/admin/** 通配已配在 hl-gateway 的 order-service-v3 路由上,新增的 .../requirement/vehicle-households 落在既有通配内,无新增 /admin/ 前缀需要配路由(故 gateway_status: not_required)。
自动化回归(分支 feat/8152-group-vehicle-structured,本批新增/改写的用例类):
GroupVehicleRequirementSaveTest 保存链路(含四个新字段落库与回显) ✓
GroupVehicleRequirementValidateTest 809116 / 809117 / 809118 三条校验 ✓
VehicleSeatCalculatorTest 余座口径(扣司机位、允许为负) ✓
GroupBatchVehicleHouseholdServiceTest vehicle-households 户数/行数/kind 过滤 ✓
GroupBatchRequirementSummaryTest transferSummary 聚合与空团空壳 ✓
GroupVehicleRequirementConfirmCheckTest transferSubmitEnabled / 缺口名单不短路 ✓
TransferDeclarationSupportTest 大交通声明 → 缺口名单推导 ✓
RequirementServiceVehicleKindsQueryTest TRAVEL / TRANSFER 分类取数 ✓
GroupDispatchCoverageCalculatorTest(fleet) 团级规格进配车覆盖度基线 ✓
RedLineArchTest 架构红线门禁 ✓
数据库变更:V20260922_401__order_group_vehicle_group_add_fleet_fields.sql,四列全部可空无默认值(存量行保持 null,见「五、数据库行为」)。
活体实测(测试服网关 https://api.test.1814.love:9443,2026-09-22 13:0x–13:1x;hl-order-service-v3 @ f1986f996,hl-fleet-service @ c4321f961)。取一个带真实接送机需求的团期(3 户,其中 pickup 2 户、dropoff 1 户,合计 6 人,车型汇总 suv / 15 座 / 3 辆)逐个请求:
| 端点 | HTTP | 业务 code | 实测要点 |
|---|---|---|---|
GET .../vehicle-requirement |
200 | 200 | 分组元素 8 个新字段全部出现:vehicleTypeName seats count specialTags remark totalSeatCount maxHeadcount remainingPassengerSeats。该团期分组属存量数据,除 maxHeadcount = 6 外新字段为 null / 空数组——存量行的 GET 侧兼容已实证 |
GET .../requirement/vehicle-households |
200 | 200 | householdCount = 3、vehicleRowCount = 3、countedHouseholdCount = 0、households[] 返回 3 条真实行(含 kind / status / fleet / serviceDates) |
同端点 ?kind=TRANSFER |
200 | 200 | 过滤参数被接受,返回同 3 条(均为 TRANSFER),无报错 |
GET .../requirement-summary |
200 | 200 | transferSummary 节点出现,6 个子字段全部有值:householdCount = 3 / pickupHouseholdCount = 2 / dropoffHouseholdCount = 1 / headcount = 6 / vehicleSeatSummary[] = 1 条 / serviceDates = ["2026-10-30"] |
GET .../requirement/confirm-check |
200 | 200 | transferSubmitEnabled = true(Boolean)、transferDeclaredWithoutRequirement = [](数组) |
⚠️ 上表最后一行的空数组是阴性对照,不是「该字段恒为空」:该团期的 3 户全都有 TRANSFER 需求行,本就不该进缺口名单。字段的类型与位置已固定,按契约对接即可;名单非空时的元素结构见本篇「三、接口详情」第 5 节的字段表。
十、相关文档
- 关联 Issue: wx/HL#8152、wx/HL#8151、wx/HL#8153
- 同批交接件:
changelogs-v2/2026-09/22_8154_团期管理员只读查看子订单与金额面端点收回-修复-管理后台.md(团期管理员权限行为变更) - 既有对位端点(形态参考):
changelogs-v2/2026-09/20_8046_团期用房新增子订单订房记录接口-新增接口-管理后台.md
关联 / 联系人
链接
联系人
- 后端负责人: @wx