文件
hl-api-changelog/changelogs-v2/2026-09/24_8311_团期分组座位数改档位下拉与809124校验-修改接口-管理后台.md
T
Mimingguang 2e3b0e892c
changelog-filename-gate / validate (push) Failing after 2s
chore(changelog): #8311 前端已交付 verified(ref debb0bc2)
2026-09-24 12:28:19 +08:00

30 KiB
原始文件 Blame 文件历史

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/

关联 / 联系人

链接

联系人

  • 后端: jw / wx