30 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 | 8311 | 团期正式用车需求分组座位数改由车队字典档位下拉选择(可选可改),新增 809124 档位校验与汇总座位兜底诊断 | admin | wx(GIT) | 修改接口 | deployed | verified | verified | mmg | debb0bc2e22be878243ea8ffe89dd0b9091656a1 | v2.1 | 2026-09-24 | PR #8324 squash 合并 dev-v3(1638faf08)。部署:hl-order-service-v3 dev-v3 @ 1638faf08,2026-09-24 12:06:26 滚动部署两实例,deploy-status 读数 BEHIND=0/N STATE=ok;请求路径上 hl-gateway 与 hl-fleet-service 同为 ok、BEHIND 右侧 N。测试服网关真实 PUT 实测(groupBatchId=2102692937584513025,BUS 16座/SUV 5座 DRAFT):BUS 13 座 → code=809124「第 BUS 组的座位数 13 不在车型 bus 的可选档位 [12, 15, 16, 19] 内,请从下拉项中选择」;SUV 6 座 → code=809124「…不在车型 suv 的可选档位 [5, 7] 内…」;两次拒绝后回读 version 与 seats 均未变(零写入);原值(BUS 16/SUV 5)→ code=200 且落库一致。aggregate-draft 实测回 seatOptionAdjusted=[](当前测试团期没有非法档位子订单)与 draft BUS=16/SUV=5。定向测试 14 个类全绿(SaveTest 31 / DraftAggregatorTest 24 / AggregateDraftTest 12 / ConfirmCheckTest 19 等)。809123 已被同日并入的 #8219 占用,本单让号到 809124。 | 2026-09-24 mmg 交付:编辑弹窗座位数手输改档位下拉(选项取字典 seatOptions 按 typeKey,存量非法值合成占位回显 rule 前置拦,档位不可得不拦),切车型联动清空座位,既有组/汇总灌入组号只读(手增空白组仍可填),汇总诊断四数组改五数组 seatOptionAdjusted 按三种 reason 分文案带 [groupCode] 定位;809124 无按码分支拦截器透报文(带组号+档位列表)即引导;spec 44 例全绿(3 新例+既有下标适配) | 2026-09-24 | dev-v3 |
order-v3 团期需求: 分组座位数改为字典档位可选 + 809124 档位校验
存放目录: 二期(v3) →
changelogs-v2/2026-09/服务: hl-order-service-v3(团期需求域,groupbatch 包) PR: 待建 Issue: #8311 日期: 2026-09-24 影响范围: 管理后台「团期详情 → 查看需求」Tab 的正式用车需求编辑弹窗(含「自动汇总」)
⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
- 新增错误码 809124:
PUT .../vehicle-requirement现在会校验「该组的seats必须落在该组vehicleType在车队车型字典里的可选座位档位(seatOptions)内」,不在档位内整份提交被拒。 - 本条与子订单级早已存在的同款校验对齐:子订单用车需求的
fleet[].seats一直有这条(错误码 582024「座位数不在该车型大类可选座位数中,请检查车型库」);团级此前只校验「半填 809118」与「容量不足 809116」,收任意 ≥1 的座位数。本单把团级补齐到同一口径。 - 前端交互随之改变:座位数不再是自由输入框,改为下拉,选项取自
GET /admin/fleet/vehicle-types/list里该typeKey的seatOptions。这条校验是硬约束,不是提示——手输/绕过下拉提交字典外档位会被 809124 拒绝。 aggregate-draft的draft.groups[].seats语义变更:#8220 时它取「组内子订单报的最大单车座位」;现在取该车型大类档位内的值(子订单报的值在档位内就取组内最大,不在档位内或没报则兜底到该大类最大档),被兜底调整的户逐条列在新字段seatOptionAdjusted。前端必须展示该字段,否则「子订单报 13 座、草稿变成 19 座」这件事在页面上不可见。- 组号(
groupCode)仍由后端生成(车型大写 + 同车型拆组序号:BUS、BUS2…),表单不应提供输入框;同车型被拆成多组时,seatOptionAdjusted[]里带groupCode用于定位是哪一组。
一、背景(选填)
wx 2026-09-24 定案:正式用车需求编辑弹窗里「单车座位数」应由手输改为按车队字典档位下拉选择(可选、可改)。落地时发现团级缺少子订单级早就有的「座位数必须在 seatOptions 内」校验:不补这条,下拉只是页面上的自觉,直接调接口仍能把车队没有的档位(如大巴 13 座)写进正式需求,车务排车时对不上型号。本单同时把「自动汇总」的座位默认值改为落在档位内,并对被调整的户出诊断,避免静默改数。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 保存团期正式用车需求 | PUT | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement |
新增校验 + 新错误码 | 新增 809124(座位数不在该组车型大类可选档位内);档位不可得复用 809120 |
| 2 | 自动汇总正式用车需求草稿 | GET | /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft |
响应新增字段 + 既有字段语义变更 | 新增 seatOptionAdjusted[];draft.groups[].seats 改为档位内的值 |
| 3 | 车型大类全量列表(复用,不改) | — | — | 复用不改 | 座位下拉数据源(seatOptions)仍走 GET /admin/fleet/vehicle-types/list,本单未改它;契约见第八节说明 |
三、接口详情
1. 保存团期正式用车需求 PUT /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement
VO: GroupVehicleRequirementSaveReqVO → GroupVehicleRequirementRespVO
使用场景
团期详情「查看需求 → 团级正式用车需求」编辑弹窗点保存。语义仍是整份全量替换:未出现在本次提交里的分组会被移出当前版本。请求体与响应体结构均不变,本次只新增一条校验与一个错误码。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键 |
| version | Body | Integer | ❌ | 首次保存传 null,其后回传上次拿到的值 | 乐观锁;不一致抛 809102 |
| remark | Body | String | ❌ | ≤500 | 整份需求备注 |
| groups | Body | Array | ✅ | 可以是空数组 | 全部乘车分组;有在团需车户却零分组抛 809103 |
| groups[].groupId | Body | Long | ❌ | 新增分组传 null | 带上它即声明「这就是库里那一组」,此时 groupCode 不得变更(否则 809104) |
| groups[].groupCode | Body | String | ✅ | ≤32,同一份内不得重复 | 直接作为车费 alloc_group;表单不应给输入框,用后端生成的只读值 |
| groups[].vehicleType | Body | String | ✅ | ≤64,取值见 /admin/fleet/vehicle-types/list |
车型大类;不在字典内抛 809119 |
| groups[].serviceStartDate | Body | LocalDate | ✅ | yyyy-MM-dd |
本组服务开始日 |
| groups[].serviceEndDate | Body | LocalDate | ✅ | 不早于开始日 | 本组服务结束日 |
| groups[].seats | Body | Integer | ❌ | @Min(1);与 count 同填或同空;本次起必须落在该车型大类的 seatOptions 内 |
单车座位数(含驾驶位);下拉取值见 GET /admin/fleet/vehicle-types/list 的 seatOptions;存量分组(seats 与 count 皆 null)整条跳过本校验 |
| 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 | 团期聚合主键 |
| status | String | DRAFT / CONFIRMED / DISPATCHED / DONE / PENDING_RECONFIRM / CANCELLED;PUT 后为 DRAFT |
| version | Integer | 版本号,下次提交须回传 |
| remark | String | 整份备注 |
| confirmedBy / confirmedAt | String / LocalDateTime | 整份确认人/时间;DRAFT 时为 null |
| groups | Array | 全部乘车分组;整团免车态为空数组 |
| groups[].groupCode | String | 分组键 = 车费 alloc_group(后端生成,前端只读回显) |
| groups[].vehicleType / vehicleTypeName | String | 车型大类 key / 中文名 |
| groups[].seats | Integer | 单车座位数(含驾驶位);存量分组为 null |
| groups[].count | Integer | 车辆数量;存量分组为 null |
| groups[].totalSeatCount | Integer | 总座位数 = seats × count;缺一即 null |
| groups[].maxHeadcount | Integer | 该组 days 里的最大用车人数 |
| groups[].remainingPassengerSeats | Integer | 余座 = 扣司机座后的可乘座位 − maxHeadcount;可为负,是缺口展示值,不得据此拦截提交(见 22_8152) |
| groups[].days[] | Array | 逐日行程与成员(tripDate / headcount / memberOrderIds / memberOrderCount) |
请求示例
{
"version": 3,
"remark": "9/24 换 19 座",
"groups": [
{
"groupId": null,
"groupCode": "BUS",
"vehicleType": "bus",
"serviceStartDate": "2026-10-08",
"serviceEndDate": "2026-10-10",
"seats": 19,
"count": 1,
"specialTags": [],
"remark": "含高速费",
"days": [
{ "tripDate": "2026-10-08", "headcount": 9, "memberOrderIds": ["2102692937378992129"] },
{ "tripDate": "2026-10-09", "headcount": 9, "memberOrderIds": ["2102692937378992129"] },
{ "tripDate": "2026-10-10", "headcount": 9, "memberOrderIds": ["2102692937378992129"] }
]
}
]
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"requirementId": "2102750063359135746",
"groupBatchId": "2102749115823919105",
"status": "DRAFT",
"version": 4,
"remark": "9/24 换 19 座",
"confirmedBy": null,
"confirmedAt": null,
"groups": [
{
"groupId": "2102750063363330049",
"groupCode": "BUS",
"vehicleType": "bus",
"vehicleTypeName": "大巴系列",
"serviceStartDate": "2026-10-08",
"serviceEndDate": "2026-10-10",
"seats": 19,
"count": 1,
"specialTags": [],
"remark": "含高速费",
"totalSeatCount": 19,
"maxHeadcount": 9,
"remainingPassengerSeats": 9,
"days": [
{ "tripDate": "2026-10-08", "headcount": 9, "memberOrderIds": ["2102692937378992129"], "memberOrderCount": 1 }
]
}
]
},
"success": true
}
空数据 / 降级响应
- 整团没有在团需车户时
groups: []是合法提交,响应groups为空数组。 - 车队车型字典(含座位档位)取不到时,保存口 fail-closed:抛 809120「车队车型字典暂不可用,无法校验车型,请稍后重试」,本次提交零写入。
vehicleTypeName仍可降级为 null(只影响展示),不影响本单校验。
{ "code": 200, "message": "成功", "data": { "groups": [] }, "success": true }
错误响应
{
"code": 809124,
"message": "第 BUS 组的座位数 13 不在车型 bus 的可选档位 [12, 15, 16, 19] 内,请从下拉项中选择",
"success": false,
"data": null
}
业务边界
- 报文字段:{0}=组号(
groupCode,不是序号)、{1}=本次提交的座位数、{2}=车型大类 key、{3}=该大类的可选档位(升序、方括号包裹)。一次只抛一条,按校验遍历顺序收集。 - 校验顺序:座位档位(809124 / 809120)→ 半填(809118)→ 容量不足(809116)。档位不可得(报 809120)时不会吞掉同组的半填与容量判据,仍会继续判下去,避免「先提示稍后重试、补完车辆数才被告知半填」的来回。
- 存量分组(seats 与 count 皆 null)整条跳过:不查字典、不报 809124/809120/809116。这是为了让「不带 fleet 结构再 PUT 一次」的历史团期不会被一刀拦死。
- 只填了车辆数(seats 为 null)时不判档位:交给 809118 报半填,不会编出一条「座位数不在档位内」。
- 该车型大类在车队没有任何型号(
seatOptions为空)按「档位不可得」处理 → 809120,需先去车务补型号。 - 注意与子订单级错误码不同:子订单级同一件事是 582024,团级是 809124;两条链路的报文与码值刻意分开(团级报文必须带组号,一份十几组的整份提交被拒后要能定位是哪一组)。
2. 自动汇总正式用车需求草稿 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/aggregate-draft
VO: GroupVehicleAggregateDraftRespVO
使用场景
编辑弹窗点「自动汇总」时调用,拿到 draft 灌进表单、原样或编辑后 PUT 保存;四个(本次起为五个)诊断字段必须一并展示。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | Path | Long | ✅ | - | 团期聚合主键(不是产品班期 ID) |
出参 Result<GroupVehicleAggregateDraftRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| groupBatchId | String | 团期聚合主键 |
| currentStatus | String | 当前正式需求状态;未形成时为 null。只有 null 或 DRAFT 能保存 |
| draft | Object | 与保存请求体同形,可原样 PUT;groupId 恒为 null |
| draft.groups[].groupCode | String | 后端生成的组号(车型大写 + 拆组序号:BUS、BUS2…),前端只读展示,不要给输入框 |
| draft.groups[].vehicleType | String | 归一后的车型大类 key(suv2 → suv) |
| draft.groups[].seats | Integer | 本次起为档位内的值:子订单报的座位在档位内取组内最大,不在档位内或没报则兜底该大类最大档;档位不可得时退回旧规则(取子订单报的最大值) |
| draft.groups[].count | Integer | ceil(本组最忙那天的人数 / (seats − 1)),已扣司机座;seats < 2 时与 seats 一起为 null |
| draft.groups[].days[] | Array | 逐日明细,正好铺满本组首日到末日(headcount 为该日实时人数之和) |
| droppedFleetItems[] | Array | 没进草稿的车型项(多车型户只进主车型);必须提示 |
| staleHeadcountOrders[] | Array | 实时人数 ≠ 子订单冻结人数的户 |
| paddedOrderDays[] | Array | 为过 809109 补进分组的日期 |
| seatOptionAdjusted[] | Array | 本次新增:座位档被兜底调整的户,见下 |
| seatOptionAdjusted[].groupCode | String | 该户所在的草稿分组编码(同户同车型被拆成 BUS/BUS2 时用它区分) |
| seatOptionAdjusted[].orderId / orderNo | String / String | 所属子订单(雪花 ID 按字符串处理) |
| seatOptionAdjusted[].vehicleType | String | 车型大类(草稿里该组的 vehicleType) |
| seatOptionAdjusted[].originalSeats | Integer / null | 子订单报的单车座位数;没报时为 null |
| seatOptionAdjusted[].adoptedSeats | Integer | 草稿实际采用的座位数(该组档位内的值) |
| seatOptionAdjusted[].seatOptions | Integer[] | 该大类的可选档位(升序) |
| seatOptionAdjusted[].reason | String | SEATS_NOT_IN_OPTIONS / SEATS_MISSING / SEATS_MISSING_ADOPTED_GROUP_VALUE |
| violations[] | Array | 草稿预检违规(与保存同一份校验内核):code / reason / detail / groupCode / tripDate / orderId |
请求示例
GET /v3/admin/order/group-batch/2102749115823919105/vehicle-requirement/aggregate-draft
Authorization: Bearer <admin token>
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2102749115823919105",
"currentStatus": null,
"draft": {
"version": null,
"remark": null,
"groups": [
{
"groupId": null,
"groupCode": "BUS",
"vehicleType": "bus",
"serviceStartDate": "2026-10-08",
"serviceEndDate": "2026-10-10",
"seats": 19,
"count": 1,
"specialTags": [],
"remark": "HL20260924001: 备注原文",
"days": [
{ "tripDate": "2026-10-08", "headcount": 9, "memberOrderIds": ["2102692937378992129"] }
]
}
]
},
"droppedFleetItems": [],
"staleHeadcountOrders": [],
"paddedOrderDays": [],
"seatOptionAdjusted": [
{
"groupCode": "BUS",
"orderId": "2102692937378992129",
"orderNo": "HL20260924001",
"vehicleType": "bus",
"originalSeats": 13,
"adoptedSeats": 19,
"seatOptions": [12, 15, 16, 19],
"reason": "SEATS_NOT_IN_OPTIONS"
}
],
"violations": []
},
"success": true
}
空数据 / 降级响应
- 草稿没有可汇总内容时
draft.groups为空数组,seatOptionAdjusted为空数组,接口仍 200。 - 有需车户还没提交行程用车需求时报 809121 并逐户列出(不是空草稿)。
- 某车型大类在车队没有型号时,汇总口不 fail-closed:退回旧规则(取子订单报的座位)、不出
seatOptionAdjusted,同时在violations里给出 809120,让页面能提示「这类车型还没在车务建档」。整张字典不可用(Feign 降级)仍直接抛 809120。保存口对同一情形是 fail-closed——这条差异是有意的:汇总是只读草稿,卡住它等于管理员连看都看不到。
{ "code": 200, "message": "成功", "data": { "draft": { "groups": [] }, "seatOptionAdjusted": [], "violations": [] }, "success": true }
错误响应
{
"code": 809121,
"message": "团期 2102749115823919105 有 2 户缺少可汇总的行程用车需求,暂不能自动汇总:HL20260924001(2102692937378992129):未提交行程用车需求、HL20260924002(2102692937378992130):车型均不在车型字典内",
"success": false,
"data": null
}
业务边界
reason的三种取值对前端提示语不同:SEATS_NOT_IN_OPTIONS= 「你报的 N 座这台车没有,已改用 M 座」;SEATS_MISSING= 「这户没报座位,跟字典最大档 M 座走」;SEATS_MISSING_ADOPTED_GROUP_VALUE= 「这户没报座位,采用的值 M 来自同组其它户报的合法档位(不是字典最大档)」。不要把后两者渲染成「系统按字典给你选了 M 座」。- 座位是单车属性,多户报的值不相加(沿用 #8220)。
- 拆组逻辑不变:同车型按连续日期段拆组,第二段起组号加序号;诊断行按所属组带
groupCode。 draft.groups[].seats即使已被兜底到档位内,保存时仍会按新校验复核(同一条 809124);本端点不替代保存侧校验。
四、契约约束与正确调用方式(接口类必写)
- 座位下拉的数据源是
GET /admin/fleet/vehicle-types/list返回项里的seatOptions(该大类下型号seats去重升序),按typeKey取(SUV 这类在库里的 typeKey 可能是suv2,返回的vehicleType是归一后的suv,见 23_8221)。不要在前端写死档位枚举。 - 切换车型时必须同步换档位并清空旧值,否则旧档位会带着新车型一起提交 → 809124。
✅ 正确 / ❌ 错误 payload 对照(分组元素)
// ✅ 大巴档位 12/15/16/19 中取值,seats 与 count 成对
{ "groupCode": "BUS", "vehicleType": "bus", "seats": 19, "count": 1 }
// ❌ 13 不在大巴档位内 → 809124
{ "groupCode": "BUS", "vehicleType": "bus", "seats": 13, "count": 1 }
// ❌ 只填座位数、车辆数漏填 → 809118(不是 809124)
{ "groupCode": "BUS", "vehicleType": "bus", "seats": 19 }
// ✅ 存量分组形态:两列都不传,整条跳过档位与容量校验
{ "groupCode": "BUS", "vehicleType": "bus" }
切换状态时的必要动作
- 保存成功后响应
version会 +1,下次保存必须回传新值,否则 809102。 - 「自动汇总」会整份覆盖当前编辑内容,前端须二次确认后再灌入。
五、数据库行为(涉及写操作时必写)
- 无表结构变更、无 Flyway。
- 809124 / 809120 都在写库之前抛出:整份零写入(校验失败时响应
data为 null,可回读同一份数据确认未变)。
六、边界行为
- 存量分组不批量重算、不迁移:只有它下次出现在某次 PUT 的
groups数组里才会按新校验复核(两列皆 null 时仍整条跳过)。 - 同一份提交里多个组同车型时,字典只查一次(不是每组一次 Feign)。
- 车队车型字典服务超时/降级:保存口 fail-closed 报 809120;汇总口退回旧座位规则并在
violations里报 809120。
六.5、枚举 / 数据字典(接口出现枚举时必写)
错误码
| 码 | 触发 | 报文 |
|---|---|---|
| 809124 | 该组 seats 不在该组 vehicleType 的可选档位内 |
第 {0} 组的座位数 {1} 不在车型 {2} 的可选档位 {3} 内,请从下拉项中选择 |
| 809120(复用) | 车队车型字典不可用 / 该大类在车队没有型号(档位不可得) | 车队车型字典暂不可用,无法校验车型,请稍后重试 |
| 809118(不变) | seats 与 count 只填了一个 |
第 {0} 组的座位数与车辆数必须同时填写,或同时留空 |
| 809116(不变) | 扣司机座后可载客座位少于该组最大单日人数 | 第 {0} 组座位数不足:{1} 座 × {2} 辆,扣除 {3} 个司机座后可载客 {4} 人,少于该组最大乘车人数 {5} 人 |
seatOptionAdjusted[].reason
| 取值 | 含义 | 建议提示语 |
|---|---|---|
SEATS_NOT_IN_OPTIONS |
子订单报了座位但不在该大类档位内 | 该户报的 {originalSeats} 座本车型没有,草稿改用 {adoptedSeats} 座(可选 {seatOptions}) |
SEATS_MISSING |
子订单没报座位,草稿按字典最大档兜底 | 该户没报座位,草稿按 {adoptedSeats} 座(字典最大档)计 |
SEATS_MISSING_ADOPTED_GROUP_VALUE |
子订单没报座位,草稿采用的值来自同组其它户报的合法档位 | 该户没报座位,草稿跟随本组 {adoptedSeats} 座计 |
六.6、修改前后对比(修改/删除类接口必写,新增跳过)
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| 保存接口请求/响应字段 | — | 无新增、无删除,结构不变 |
aggregate-draft 响应 |
无 seatOptionAdjusted |
新增 seatOptionAdjusted[](8 个子字段) |
draft.groups[].seats |
组内子订单报的最大单车座位 | 该大类档位内的值(在档位内取组内最大、否则兜底最大档;档位不可得退回旧规则) |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 提交字典内档位(如 bus 19 座) | 放行 | 放行(不变) |
| 提交字典外档位(如 bus 13 座) | 放行 | 809124 拒绝 |
| 提交座位数但车型大类在车队没有型号 | 放行 | 809120 拒绝(保存口);汇总口退回旧规则 + violations 里报 809120 |
| 存量分组(seats/count 皆 null)整份再提交 | 跳过座位相关校验 | 不变,仍整条跳过 |
| 预检/整团确认侧 | 不校档位 | 不变(该入口读的是库里已存值,且被 doConfirm 在事务内调用,见「七、不影响范围」) |
六.7、影响评估(修改/删除类必写)
- 是否破坏向后兼容:请求/响应结构不变,但同一份 payload 可能从 200 变成 809124 —— 凡是座位数不在该大类档位内的提交(含手输、含车型切换后未更新座位数)都会被拒。809124 是 HTTP 200 下的业务失败。
- 前端是否必须同步上线:必须。座位数必须改成档位下拉(否则运营按老习惯手输就会撞 809124);
seatOptionAdjusted必须展示(否则草稿改座位这件事不可见);组号输入框应改成只读展示。 - 存量数据影响:不批量重算、不迁移。库里的历史非法档位只会在下一次保存/汇总时暴露:保存被 809124 拒(需管理员改成档位内值);预检与整团确认口不重判档位,不会把已确认流程卡死。
七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- 仅影响:
PUT .../vehicle-requirement的座位档位校验(新增 809124);GET .../vehicle-requirement/aggregate-draft的seats取值与新增seatOptionAdjusted。 - 零影响:
- 两个接口的请求/响应字段结构(除新增
seatOptionAdjusted)。 - 既有错误码 809116 / 809118 / 809117 / 809119 / 809120 / 809121 / 809102 等其余判据与报文。
GET .../vehicle-requirement(读取回显)不触发校验,原样返回库里已落值。- 整团确认预检(
GET .../requirement/confirm-check)与confirm写口不重判座位档位:它们读的是库里已存值,且确认链路在事务内,不在那里调字典 Feign。即「历史非法档位不会卡住确认」,只会在下次编辑保存时要求修正。 GET .../requirement/vehicle-households、GET .../requirement-summary未改动。- 子订单级用车需求(582024 那条)未改动。
- fleet 服务与网关:零改动(
/admin/fleet/vehicle-types/list复用既有只读端点)。 - 数据库:无表变更、无 Flyway。
- 两个接口的请求/响应字段结构(除新增
八、测试环境已验证
部署读数(测试服 deploy-status.sh,2026-09-24 12:07 读取):
SERVICE BRANCH COMMIT BEHIND DEPLOYED_AT STATE
hl-order-service-v3 dev-v3 1638faf08 0/N 2026-09-24 12:06:26 ok
hl-gateway dev-v3 c238f38c3 182/Y 2026-09-21 14:44:55 ok
hl-fleet-service dev-v3 cc70c9ba3 6/N 2026-09-24 10:49:54 ok
被测服务 BEHIND=0/N STATE=ok;请求路径上的 hl-gateway 与断言依赖的 hl-fleet-service 同为 ok,两行的 BEHIND 右侧字母均为 N(落后提交未触及这两个服务本身)。
网关真实请求与响应(https://api.test.1814.love,groupBatchId=2102692937584513025,当前 DRAFT v3:BUS 16 座 ×1、SUV 5 座 ×1):
① 原值提交 BUS 16 座 / SUV 5 座(均在档位内)
PUT /v3/admin/order/group-batch/2102692937584513025/vehicle-requirement
→ http 200, code=200, message=成功;回读 version 2→3,seats 仍为 16/5 ✓
② BUS 提交 13 座(bus 档位 12/15/16/19)
→ http 200, code=809124
message=第 BUS 组的座位数 13 不在车型 bus 的可选档位 [12, 15, 16, 19] 内,请从下拉项中选择 ✓
拒绝后回读:version 仍为 3、seats 仍为 16/5、remark 未变(整份零写入)✓
③ SUV 提交 6 座(suv 档位 5/7)
→ http 200, code=809124
message=第 SUV 组的座位数 6 不在车型 suv 的可选档位 [5, 7] 内,请从下拉项中选择 ✓
④ GET /v3/admin/order/group-batch/2102692937584513025/vehicle-requirement/aggregate-draft
→ code=200;draft.groups = BUS(bus, 16 座, 1 辆, 3 天) + SUV(suv, 5 座, 1 辆, 3 天)
seatOptionAdjusted = [](该团期子订单报的座位都合法,无兜底调整)
violations = [];droppedFleetItems 1 条、stale/padded 0 条 ✓
定向测试逐类读数(mvn -o -pl hl-order-service-v3 -am test -Dtest='…' -DfailIfNoTests=false -Dhl.surefire.failIfNoTests=false,14 个类合计 0 failures / 0 errors / 0 skipped,BUILD SUCCESS):
| 测试类 | Tests run |
|---|---|
| GroupVehicleRequirementSaveTest | 31 |
| GroupVehicleDraftAggregatorTest | 24 |
| GroupVehicleRequirementAggregateDraftTest | 12 |
| GroupVehicleRequirementConfirmCheckTest | 19 |
| GroupVehicleRequirementLockContractTest | 3 |
| GroupBatchRequirementServiceTest | 73 |
| GroupBatchRequirementServiceConfirmVehicleTest | 17 |
| GroupBatchRequirementServiceCheckVehicleTest | 17 |
| GroupBatchRequirementConfirmReflectionGuardTest | 4 |
| FleetVehicleTypeNameLoaderTest | 9 |
| TransactionalRemoteCallArchTest | 1 |
| ErrorCodeUniquenessGuardTest | 3 |
| RequirementGroupBatchErrorCodeRangeTest | 4 |
| ErrorCodeTemplateNumberFormattingTest | 1 |
十、相关文档
- 自动汇总草稿端点:#8220 →
changelogs-v2/2026-09/23_8220_团期正式行程用车需求自动汇总草稿-新增接口-管理后台.md - 车辆规格四字段(seats/count/specialTags/remark):#8152 →
changelogs-v2/2026-09/22_8152_团期用车需求补车辆规格与接送机汇总-修改接口-管理后台.md - 座位充足性校验扣司机座(809116):#8278 →
changelogs-v2/2026-09/23_8278_团级用车分组座位校验扣司机座-修改接口-管理后台.md - 座位数下拉先例(子订单级):#4856 / #4871 →
changelogs-v2/2026-07/50_4856_订单调整用车座位数按车型型号下拉-管理后台.md - 车型大类字典与 SUV 回显归一:#8202 / #8221 →
changelogs-v2/2026-09/
关联 / 联系人
链接
- Issue: wx/HL#8311
- 后端 PR: 待建
联系人
- 后端: jw / wx