18 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 | 8220 | 团期正式行程用车需求自动汇总草稿 | admin | jw(GIT) | 新增接口 | deployed | verified | verified | mmg | ee98b96654fd250159ee4a3b0067e5f1c88d3b75 | v2.1 | 2026-09-23 | 后端已部署 TEST 并经网关实测;待 mmg 在编辑弹窗接入「自动汇总」+ 乘车户全选 + 人数随所选户汇总;前端已交付:编辑弹窗「自动汇总」灌草稿+四诊断展示+大类收敛(suv→suv2)+version 用草稿值+非 DRAFT 禁保存;spec 41 例全绿,checkpoint 13 项过 | 2026-09-23 | dev-v3 |
团期正式行程用车需求自动汇总草稿(#8220)
服务: hl-order-service-v3(经网关调用,无需关心服务端口) PR: #8273 Issue: #8220 日期: 2026-09-23 影响范围: 管理后台团期详情「用车」Tab 的正式行程用车需求编辑弹窗
⚠️ 关键变化
新增只读端点,按各子订单已提交的行程用车(TRAVEL)需求,自动汇总出团级正式用车需求草稿。草稿形状与保存端点 PUT .../vehicle-requirement 的请求体完全一致,前端可以直接灌进编辑弹窗、原样保存。
四条会直接影响你怎么写代码的点,按严重度排:
- 🔴 草稿之外的四个诊断字段必须展示,不能只看
draft。droppedFleetItems里是没进草稿的车型需求(一户报了多个车型时只保留主车型),不提示的话,管理员一保存,这些需求就从团级正式需求里消失了。另外三个字段(staleHeadcountOrders/paddedOrderDays/violations)见业务边界。 - 🔴 车型回显是归一后的大类 key(
suv/bus/mpv/sedan),与 #8221 交接件里提过的是同一个问题:车型下拉的 value 是 fleet typeKeysuv2,拿suv逐字去比会判成「非字典值,请重选」,并被前端校验拦住保存。判「是否字典值」要按归一后的大类比,否则所有 SUV 组汇总出来都会被弹窗拦下。 - 有户还没提交行程用车需求时,本端点直接返回业务错误
809121,报文逐户列出「订单号(orderId):原因」。这时不要尝试拼一份草稿,应提示管理员去催这些户。 - 成功码是
code: 200;业务失败也返回 HTTP 200,一律按body.code判。
一、背景(选填)
wx 2026-09-23 团期详情页「用车」Tab 反馈:「正式的行程用车需求要根据子订单的行程用车需求自动汇总 自动填写」,以及「得有个全选的按钮,根据选的乘车户自动把人数算出来」。此前编辑弹窗不拉取任何子订单需求数据,日期预填的是团期自己的出发/结束日,其余字段全靠手填。汇总规则由管理者于 2026-09-23 定案(#8220 评论)。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 自动汇总正式用车需求草稿 | GET | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft |
新增接口 | 只读,返回可原样保存的草稿与汇总诊断 |
三、接口详情
1. 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft
VO: GroupVehicleAggregateDraftRespVO
使用场景
管理员在编辑弹窗里点「自动汇总」时调用。拿到 data.draft 后填进弹窗,同时展示四个诊断字段;管理员确认后,把 draft 原样(或编辑后)交给 PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 保存。
弹窗里如果已经有编辑内容,点「自动汇总」前请二次确认「这将覆盖当前编辑内容」。本端点不管现在有没有正式需求,都返回完整汇总,要不要覆盖由前端交互决定。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
groupBatchId |
Path | Long | ✅ | — | 团期 ID(group_batch_id),🔴 不是产品班期 ID |
出参 Result<GroupVehicleAggregateDraftRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
groupBatchId |
String(雪花 ID) | 团期 ID |
currentStatus |
String / null | 当前正式需求状态;还没形成时为 null。只有 null 或 DRAFT 时才能保存(其余状态保存会报 809101 / 809115),前端据此决定「保存」按钮是否可用 |
draft |
Object | 汇总草稿,与保存请求体同形,恒非 null |
draft.version |
Integer / null | 当前正式需求的乐观锁版本;未形成时为 null。保存时原样带上 |
draft.remark |
String / null | 整份备注,汇总时恒为 null |
draft.groups[] |
Array | 乘车分组,没有可汇总内容时为空数组 |
draft.groups[].groupId |
Long / null | 恒为 null(汇总产物一律是新组) |
draft.groups[].groupCode |
String | 车型大写,同车型按连续日期段拆组时第二段起加序号:BUS、BUS2… |
draft.groups[].vehicleType |
String | 归一后的车型大类 key:bus / suv / mpv / sedan |
draft.groups[].serviceStartDate |
String(yyyy-MM-dd) |
本组首日 |
draft.groups[].serviceEndDate |
String(yyyy-MM-dd) |
本组末日 |
draft.groups[].seats |
Integer / null | 组内各户保留车型项里最大的单车座位数;各户都没填座位时,与 count 一起为 null |
draft.groups[].count |
Integer / null | ceil(本组最忙那天的人数 / (seats − 1)),已扣除司机座 |
draft.groups[].specialTags |
String[] | 组内各户特殊诉求编码的并集(去重、保持顺序) |
draft.groups[].remark |
String / null | 逐户「订单号: 定制师备注」和「订单号 另报 suv 7座×1」拼接而成,超过 500 字截断。仅供人眼留底 |
draft.groups[].days[] |
Array | 逐日明细,正好铺满本组首日到末日 |
draft.groups[].days[].tripDate |
String(yyyy-MM-dd) |
日期 |
draft.groups[].days[].headcount |
Integer | 当天在组各户的实时人数之和 |
draft.groups[].days[].memberOrderIds |
String[](雪花 ID) | 当天在组的子订单 ID。🔴 19 位雪花 ID,前端一律按字符串处理 |
droppedFleetItems[] |
Array | 没进草稿的车型项,见业务边界第 1 条 |
droppedFleetItems[].orderId / orderNo |
String / String | 所属子订单 |
droppedFleetItems[].vehicleType / seats / count |
String / Integer / Integer | 被丢弃项(子订单原值) |
droppedFleetItems[].keptVehicleType |
String | 该户被归入的主车型 |
droppedFleetItems[].reason |
String | NOT_PRIMARY_TYPE(非主车型)/ VEHICLE_TYPE_NOT_IN_DICT(车型不在车型字典内) |
staleHeadcountOrders[] |
Array | 实时人数与子订单需求提交时冻结的人数不一致的户:orderId / orderNo / frozenHeadcount / liveHeadcount |
paddedOrderDays[] |
Array | 为了覆盖该户「出发~返回」每一天而补进分组、但不在该户行程用车服务日里的日期:orderId / orderNo / dates[] |
violations[] |
Array | 草稿按保存时同一套校验预检出的问题:code / reason / detail / groupCode / tripDate / orderId。正常应为空数组 |
请求示例
GET /v3/admin/order/group-batch/2102692937584513025/vehicle-requirement/aggregate-draft
Authorization: Bearer <admin token>
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2102692937584513025",
"currentStatus": null,
"draft": {
"version": null,
"remark": null,
"groups": [
{
"groupId": null,
"groupCode": "BUS",
"vehicleType": "bus",
"serviceStartDate": "2026-11-24",
"serviceEndDate": "2026-11-26",
"seats": 16,
"count": 1,
"specialTags": [],
"remark": "HL20260923173356872: 8220 甲户多车型;HL20260923173356872 另报 suv 5座×1;HL20260923173401731: 8220 乙户",
"days": [
{
"tripDate": "2026-11-24",
"headcount": 5,
"memberOrderIds": [
"2102692937378992129",
"2102692957587165185"
]
},
{
"tripDate": "2026-11-25",
"headcount": 5,
"memberOrderIds": [
"2102692937378992129",
"2102692957587165185"
]
},
{
"tripDate": "2026-11-26",
"headcount": 5,
"memberOrderIds": [
"2102692937378992129",
"2102692957587165185"
]
}
]
},
{
"groupId": null,
"groupCode": "SUV",
"vehicleType": "suv",
"serviceStartDate": "2026-11-24",
"serviceEndDate": "2026-11-26",
"seats": 5,
"count": 1,
"specialTags": [],
"remark": "HL20260923173405165: 8220 丙户",
"days": [
{
"tripDate": "2026-11-24",
"headcount": 2,
"memberOrderIds": [
"2102692971709370370"
]
},
{
"tripDate": "2026-11-25",
"headcount": 2,
"memberOrderIds": [
"2102692971709370370"
]
},
{
"tripDate": "2026-11-26",
"headcount": 2,
"memberOrderIds": [
"2102692971709370370"
]
}
]
}
]
},
"droppedFleetItems": [
{
"orderId": "2102692937378992129",
"orderNo": "HL20260923173356872",
"vehicleType": "suv",
"seats": 5,
"count": 1,
"keptVehicleType": "bus",
"reason": "NOT_PRIMARY_TYPE"
}
],
"staleHeadcountOrders": [],
"paddedOrderDays": [],
"violations": []
},
"success": true
}
示例说明:示例值取自测试环境一次真实调用,不构成可复现夹具。
空数据 / 降级响应
团里没有需要用车的户,也没有任何 TRAVEL 需求时,返回空草稿(groups 是空数组,不是 null):
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2102692937584513025",
"currentStatus": null,
"draft": { "version": null, "remark": null, "groups": [] },
"droppedFleetItems": [],
"staleHeadcountOrders": [],
"paddedOrderDays": [],
"violations": []
},
"success": true
}
车型字典(车队服务)不可用时不降级,返回 809120,见错误响应。
错误响应
有户缺少可汇总的行程用车需求(未提交 / 被打回未重提 / 车型都不在字典内 / 推不出日期 / 人数为 0):
{
"code": 809121,
"message": "团期 2102692937584513025 有 1 户缺少可汇总的行程用车需求,暂不能自动汇总:HL20260923173405165(2102692971709370370):未提交行程用车需求",
"data": null,
"success": false
}
车型字典暂不可用:
{
"code": 809120,
"message": "车队车型字典暂不可用,无法校验车型,请稍后重试",
"success": false,
"data": null
}
无权限(当前角色没有 group-batch:demand:confirm):
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"success": false,
"data": null
}
业务边界
- 多车型户:一户的行程用车同时报了多个车型(如 bus×1 + suv×1)时,这一户只进「主车型」组。主车型取座位数×辆数最大的那一项,相等时取车型编码字典序小的,所以同一份数据每次汇总结果都相同。其余车型进
droppedFleetItems,🔴 前端必须醒目提示「以下车型需求未包含在草稿中」。这样做是因为同一户同一天只能属于一个分组(809108)。 - 拆组:同一车型的日期如果断开(比如甲 10-08
10-09、乙 10-1210-13),会拆成BUS/BUS2两组,不会产出某天零人的逐日行。 - 补日:团级保存要求每个需车户「出发~返回」的每一天都被分组覆盖(809109)。子订单行程用车的服务日如果比这个范围窄,多出来的日期也会把该户算进车,并列在
paddedOrderDays里。前端提示「以下户在这些日期原本未报用车,已按行程补入」。 - 人数:逐日人数用订单实时人数,与户列表显示的人数一致。实时人数与子订单需求提交时冻结的人数不一致时,该户进
staleHeadcountOrders,说明那户的子订单用车需求已经过期,车务在子订单侧看到的还是旧值。 - 座位与车数:
count按扣掉司机座的口径算,所以草稿在团级(809116)和子订单级两种座位校验下都成立。 violations为空也不保证保存一定成功:currentStatus不是 null 或DRAFT、或者汇总之后有人改过正式需求(version 变了),保存照样会被拒。- 只汇总行程用车(TRAVEL),接送机(TRANSFER)不进团车。
- 本端点零写入:不取锁、不进事务,调多少次都不影响数据。
四、契约约束与正确调用方式
✅ 正确 / ❌ 错误请求对照
| 场景 | 请求 | 预期(HTTP 恒 200) |
|---|---|---|
| ✅ 各户都已提交 | GET .../{groupBatchId}/vehicle-requirement/aggregate-draft |
code: 200,draft.groups 非空 |
| ✅ 原样保存 | PUT .../{groupBatchId}/vehicle-requirement,请求体 = data.draft |
code: 200 |
| ❌ 有户未提交 | 同上 GET | code: 809121,报文列出未提交的户 |
| ❌ 路径传成产品班期 ID | GET .../{productBatchId}/vehicle-requirement/aggregate-draft |
团期不存在的错误码(589500) |
前端「乘车户全选 + 人数自动汇总」的取数口径(wx 反馈第 ③ 条)
- 户清单与每户人数:用既有的
GET /v3/admin/order/group-batch/{groupBatchId}/requirement/vehicle-households?kind=TRAVEL,人数取households[].participantCount。它与本端点草稿里的逐日人数同源,都是订单实时人数。 - 🔴 不要用分页的团期订单列表做「全选」:那个列表单页上限 200,户数超过 200 时会漏选。
vehicle-households不分页,单次最多 500 户。 - 某天的人数 = 当天勾选各户的
participantCount之和。汇总草稿里的days[].headcount就是按这个口径算的,可以直接对照。
五、数据库行为
本接口只读,没有任何数据库写入,不新增表、不改表结构,也没有 Flyway 脚本。测试环境实测:调用前后,团级正式需求相关表与子订单用车需求表的行数和 update_time 都没有变化(见第八节)。
六、边界行为
- 鉴权:需要
group-batch:demand:confirm,与GET/PUT .../vehicle-requirement同一个权限码。不带 token 时网关返回 401;角色没有该权限时返回code: 589507。 - 网关路由:沿用现有的
- Path=/v3/admin/**→lb://hl-order-service-v3,本单不新增路由。 - 团期不存在 → 589500。
- 有户缺少可汇总需求 → 809121;车型字典不可用 → 809120。
六.5 枚举 / 数据字典
droppedFleetItems[].reason
所属字段: droppedFleetItems[].reason | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
NOT_PRIMARY_TYPE |
非主车型 | 该户多车型,只保留主车型 |
VEHICLE_TYPE_NOT_IN_DICT |
车型不在字典 | 车型归一后不在车队车型字典内 |
六.6 修改前后对比
无,本接口为新增。
六.7 影响评估
- 破坏兼容:否,新增接口
- 前端同步上线要求:否,不接入也不影响现有编辑与保存流程
- 新增错误码:
809121(有户缺少可汇总的行程用车需求)
七、不影响范围
- 仅影响:团期正式行程用车需求编辑弹窗的「自动汇总」入口
- 零影响:
- 正式用车需求的保存 / 读取 / 撤回 / 免车 / 确认(校验语义一处未改)
- 子订单用车需求的提交与审核
- 接送机(TRANSFER)链路
八、测试环境已验证
真实网关调用(https://api.test.1814.love),hl-order-service-v3 已部署 dev-v3 @ 9b60bc62b(本单合并提交,两个实例都在 17:21 滚动重启)。夹具全部自建:产品班期 2102692899017912323,团期 2102692937584513025,三户订单甲 / 乙 / 丙。
GET .../2102692937584513025/vehicle-requirement/aggregate-draft (三户都没提交 TRAVEL)
→ 809121,报文列出 3 户 ✓;甲户提交后列 2 户 ✓;乙户提交后列 1 户 ✓
GET .../aggregate-draft (三户都已提交:甲 bus 16×1 + suv2 5×1、乙 bus 12×1、丙 suv2 5×1)
→ 200:BUS 组(甲 + 乙,seats=16,count=1,每天 5 人)+ SUV 组(丙);
droppedFleetItems = 甲户 suv 5×1(NOT_PRIMARY_TYPE);violations = [] ✓
PUT .../2102692937584513025/vehicle-requirement 请求体 = data.draft 原样
→ 200,version=1;GET 回读后逐字段比对与草稿一致 ✓(再汇总一次、带 version=1 原样保存 → 200,version=2 ✓)
零写入:调汇总前后各读一次 order_group_vehicle_requirement / _group / _group_day / order_vehicle_requirement
的全表行数与 MAX(update_time),以及本团相关行 → 两轮前后完全一致 ✓
丙户另外提交了 TRANSFER(mpv,11-23 接机)→ 草稿里没有 mpv,也没有 11-23 ✓
不带 token → code 401「缺少有效的 Authorization 头」 ✓
定制师角色(无 group-batch:demand:confirm)→ code 589507 ✓
九、相关历史 PR
- #8221(车型字典与存量车型归一):本端点输出的车型同样要通过 809119
十、相关文档
- 关联 Issue: wx/HL#8220
- 关联 PR: wx/HL#8273
关联 / 联系人
链接
联系人
- 后端负责人: @jw