文件
hl-api-changelog/changelogs-v2/2026-09/11_7445_团期用车结算闸-修改接口-管理后台.md
T
Mimingguang 6827c24324
changelog-filename-gate / validate (push) Failing after 2s
chore(changelog): 7445/7446 前端核验 not_required 回写
7445 团期用车结算闸(584131+VEHICLE 软预警)与 7446 住宿结算闸判团口径
(584130 判据修正),经 grep 实证 hl-admin 前端均零改动:
- 前端无按码分支,request.js 通用 bizError 兜底透传后端 message 原文,
  584131/584130 文案已含订单号+需求状态+操作指引,透传即达标。
- detail.vue warnings 渲染只取 item.message 且兼容过渡期字符串,从不读
  settlementId/dayNumber 定位明细行,category=VEHICLE 项(恒 null)正常展示。
- 7446 契约一字未变仅触发人群变化,前端对 584130 无专属处理不受影响。
frontmatter 翻 frontend_status=not_required + owner=mmg + status_note 追加,
无业务 commit,ref 留空。
2026-09-11 19:20:10 +08:00

23 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 7445 整单核单 finalize 新增团期用车户级闸门(新增错误码 584131,warnings 新增 VEHICLE 取值) admin wx(GIT) 修改接口 deployed verified not_required mmg 2026-09-11 gateway_status=verified:2026-09-11 网关实测已完成(17:38 批次 + 18:36 补测批次,详见“八、测试环境已验证”),经 hl-gateway 网关调用 POST /v3/admin/order/{orderId}/settlement/finalize 验证 584131 硬阻断与放行两个分支。所有前置条件均为 SQL 直更需求表状态构造,非真实业务链路(配车)产生;另绕过与本单无关的 584310/584082/车费草稿冻结确认三道通用前置闸,其正确性未验证。前端 2026-09-11 闭环 not_required:changelog 自述需前端同步上线,但 grep 实证两个假设均不成立——(1) 新增错误码 584131 前端无按码分支,request.js 通用 bizError 兜底 message.error 透后端原文,文案已含订单号+需求状态+操作指引,透传即达标;(2) detail.vue warnings 渲染只 .map(item=>item.message) 且兼容过渡期字符串,从不读 settlementId/dayNumber 定位明细行,category=VEHICLE 项(settlementId/dayNumber 恒 null)只取 message「用车:未提交用车需求」正常展示不报 null。VEHICLE 软预警是过渡态(#7441 落地后升硬阻断),后端行为变更前端兜底天然兼容,无需预埋。零业务代码改动。 2026-09-11 dev-v3

order-v3: 整单核单 finalize 新增团期用车户级闸门

服务: hl-order-service-v3 PR: #7499 Issue: #7445 日期: 2026-09-11 影响范围: 既有端点 POST /v3/admin/order/{orderId}/settlement/finalize(完成核单)新增一道团期子订单用车户级校验;新增业务错误码 584131;成功响应 warnings[] 新增 category=VEHICLE 取值。请求体(无)、方法、路径、网关路由均未改。


⚠️ 关键变化

本次改动是什么:团期子订单在 finalize(完成核单)时,新增一道"该户用车是否已安排完成"的硬阻断校验——用车需求存在但状态不是 DONE 时,finalize 返回业务错误码 584131(HTTP 恒 200,按 code 判断)。这是 #7347(团期住宿户级闸门,584130)在车侧的对称件,判定入口是新增私有方法 SettlementService.assertGroupVehicleReadyForFinalize,排在住宿闸(584130)之后、CASH_PAID 软预警之前——若住宿与用车同时未就绪,先返 584130,不会同时返回两个码。

前端以前不知道的事,现在必须知道:

  1. 成功响应体 warnings[] 数组新增一种元素:category="VEHICLE"。这类预警项的 settlementId 与 dayNumber 恒为 null——与既有 HOTEL/TICKET 预警(这两者的 settlementId/dayNumber 恒非空)不同,前端若假设 warnings[] 每项都能按 settlementId 定位到某一行明细,会在这类新预警上取到 null 而出错。
  2. VEHICLE 预警不阻塞提交——它是"该团期子订单压根没提交过用车需求"这一种情形下的软提示(wx 2026-09-10 拍板的已知敞口,D-2 定案 A),finalize 仍会成功。这与 584131 硬阻断是两回事:同一订单不会同时出现 584131 报错和 VEHICLE 预警,二者互斥(有 active 需求行才可能触发 584131,没有需求行才触发预警)。
  3. 该软预警是过渡态:待 #7441 交付 vehicleWaived 后,其 D-C26 ② 会把这条分支从软预警升级为硬阻断(584131),届时 warnings[] 里不会再出现这条 VEHICLE 缺行提示。前端不要把这个取值当长期契约来做长期兼容设计。

一、背景

SettlementService.performSubmitBlockingChecks(finalize 事务内的阻断检查方法)此前对住宿有户级闸门(#7347,584130),但用车没有——通用品类门禁 SettlementCategoryCheckService.validateReadyForReport 对 VEHICLE 品类的校验条件是 rowCount() > 0 且 !lineItemsConfirmed(),零车费派生行时整个分支不进,于是"一条车费行都没有、用车需求还卡在 PENDING_REVIEW"的团期子订单能一路走完 finalize 把钱结掉。本单补上车侧对称闸门:只读户级 order_vehicle_requirement.status,status == DONE 才放行;不读车费派生行、不读实配行、不读镜像列 order_main.vehicle_control_status;只判 TRAVEL(行程用车),不判 TRANSFER(接送机,理由见"业务边界")。

与住宿闸不同的是:车侧的 needs_vehicle 自工单 #4499 起对所有新订单恒为 true(零信息量),若对"该户压根没提交过用车需求"也做硬阻断,会把"整团都不需要车"的团全部卡死在结算口且无任何逃生口。故 wx 2026-09-10 拍板取方案 A:需求行存在但未完成 = 硬阻断(584131);需求行缺失 = 软预警(不阻断),待 #7441 交付团级免车开关 vehicleWaived 后再升级。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 完成核单 POST /v3/admin/order/{orderId}/settlement/finalize 新增错误码 + 响应新增预警取值 团期子订单新增用车户级闸门(584131),warnings[].category 新增 VEHICLE

三、接口详情

1. 完成核单 POST /v3/admin/order/{orderId}/settlement/finalize

VO: Long(Path 参数 orderId,无请求体) → Result<SettlementSubmitRespVO>

使用场景

管理后台核单页点击「完成核单」按钮时调用,原子完成结算并推订单进终态。本单不改调用方式;团期子订单(group_batch_id 非空,经统一判团门面判定)在此次改动后,若该户 needs_vehicle=true 且用车需求未完成,会被本闸拦截。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long 是 大于等于1 订单 ID;团期子订单与核心订单共用本端点;本单未改

(finalize 本身无请求体,本单未新增/删除任何入参。)

出参字段表

Result,字段集合本单未增删,为保证本节自包含仍列出全部字段,并单列 warnings[] 子字段(本单实际改动点):

字段 类型 说明
summaryId Long 新写入的 settlement_summary 主键
finalSnapshotId Long 核单终态快照 ID
finalSnapshotVersionNo Integer 核单终态快照版本号
finalSnapshotStatus String 核单终态快照状态
orderId Long 订单 ID
settledAt LocalDateTime 核单完成时间
totalAmount BigDecimal 订单总金额快照
paidAmount BigDecimal 已付金额快照
balanceAmount BigDecimal 尾款金额快照
roomCost BigDecimal 住宿实际成本
ticketCost BigDecimal 门票实际成本
staffCost BigDecimal 人员费用实际成本
subsidyCost BigDecimal 补助实际成本
mealCost BigDecimal 餐食实际成本
vehicleCost BigDecimal 车辆基础服务总车费(按 Fleet 派车组合计)
otherExpenseCost BigDecimal 其他支出实际成本
insurancePremium BigDecimal 保险实际保费(未出单/已撤单=0)
totalActualCost BigDecimal 总实际成本
driverTransferAmount BigDecimal 给司机/主报账人转回金额
profitAmount BigDecimal 公司毛利
profitRate BigDecimal 毛利率(小数)
orderStatusAfter String 结算后订单状态
mqTriggered Boolean 当前版本固定为 false
warnings List 软预警列表,见下表

warnings[](WarningItemVO):

字段 类型 说明
settlementId Long 触发预警的核单明细行 id。category=VEHICLE 时恒为 null(既有 HOTEL/TICKET 恒非空),新增取值
category String 核单分类;新增取值 VEHICLE,枚举见"六.5"
dayNumber Integer 行程第几天。category=VEHICLE 时恒为 null(既有 HOTEL/TICKET 恒非空),新增取值
message String 预警文案;VEHICLE 缺行场景固定为「用车:未提交用车需求」

请求示例

POST /v3/admin/order/1934567890123456789/settlement/finalize
Authorization: Bearer {token}

(无请求体,仅 Path 参数 orderId;本单未改请求形态)

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "summaryId": "9600000000001",
    "finalSnapshotId": "9600000000002",
    "finalSnapshotVersionNo": 1,
    "finalSnapshotStatus": "FINALIZED",
    "orderId": "1934567890123456789",
    "settledAt": "2026-09-11T10:30:25",
    "totalAmount": "24800.00",
    "paidAmount": "24800.00",
    "balanceAmount": "0.00",
    "roomCost": "4280.00",
    "ticketCost": "3680.00",
    "staffCost": "14260.00",
    "subsidyCost": "720.00",
    "mealCost": "860.00",
    "vehicleCost": "5200.00",
    "otherExpenseCost": "1260.00",
    "insurancePremium": "180.00",
    "totalActualCost": "23120.00",
    "driverTransferAmount": "22940.00",
    "profitAmount": "1680.00",
    "profitRate": 0.0677,
    "orderStatusAfter": "待财务复核",
    "mqTriggered": false,
    "warnings": [
      {
        "settlementId": null,
        "category": "VEHICLE",
        "dayNumber": null,
        "message": "用车:未提交用车需求"
      }
    ]
  }
}

空数据 / 降级响应

本闸不产生独立的空态/降级响应:warnings 在没有任何软预警时为空数组 [],其余字段均为结算终态实值。本闸判定是同步内存/DB读取(不经 Feign/MQ),判据不可达时直接按错误响应处理,不发生静默降级。

{ "code": 200, "success": true, "data": { "warnings": [] } }

错误响应

新增错误码 584131(本单核心变更):

{
  "code": 584131,
  "message": "团期子订单 GB202609120007 的用车尚未安排完成(用车需求当前状态:PENDING),请等车务配车完成后再提交核单",
  "success": false,
  "data": null
}

若住宿闸与用车闸同时未就绪,先返回住宿侧既有错误码(闸序:住宿在前):

{
  "code": 584130,
  "message": "团期子订单 GB202609120007 的住宿尚未安排完成(住宿需求当前状态:PROCESSING),请等房务配房完成后再提交核单",
  "success": false,
  "data": null
}

message 模板(SettlementErrorCode.SETTLEMENT_GROUP_VEHICLE_NOT_READY,584131):团期子订单 {0} 的用车尚未安排完成(用车需求当前状态:{1}),请等车务配车完成后再提交核单。{0} = 订单号 orderNo;{1} = 用车需求当前 status 字面量(六个取值见"六.5")。该分支不会出现"需求缺失"的情况——需求缺失走软预警(见响应示例),不抛错误码。

业务边界

  • 本闸只对团期子订单(经统一判团门面 OrderService.resolveGroupBatchLinks 判定,与 #7446 共用同一私有方法 isGroupSubOrder)且 needs_vehicle=true 生效;非团期订单与 needs_vehicle=false/null(工单 #4062~#4499 之间创建的存量订单)整体跳过,不发起判团查询,行为与改动前逐字节一致。
  • 判定顺序:先判 needs_vehicle(内存字段零成本),后判团(要发一次库查)——两处判断都为真才继续查用车需求。
  • 闸序固定:住宿闸(584130)→ 用车闸(584131)→ CASH_PAID 软预警。两闸同时未就绪只返回先触发的住宿闸错误码。
  • 判定只读户级 order_vehicle_requirement 当前 active 行的 status;不读车费派生行、不读实配行 order_vehicle_assignment、不读镜像列 order_main.vehicle_control_status——删除派生行、清空实配行都不能绕过闸门。
  • 只判 TRAVEL(行程用车),不判 TRANSFER(接送机):结算车费日快照本身固定按 TRAVEL 取,闸门与账对齐;接送机需求未完成不会拦住本次结算。
  • 缺行软预警是已知的临时敞口(不是遗漏):待 #7441 落地 vehicleWaived 后升级为硬阻断,届时 warnings[] 不再出现该 VEHICLE 缺行项。
  • 并发与幂等:判定与后续结算写入在同一 finalize 事务、同一把锁内,不存在 TOCTOU 窗口;本闸是只读判定,不加 @Idempotent。
  • 上线顺序约束(不由本接口契约体现,前端无需处理,仅供知悉):本闸放行条件 status=DONE 今天只由逐户派车回调生产;#7441 的 D-C22(团车完成回写户级 DONE)必须先于 #7442(团级配车通电)上生产,否则团车配好的团会被本闸全部拦死。这条约束不影响本单契约本身。

四、契约约束与正确调用方式

正确 / 错误 调用结果对照

场景(订单形态) 结果
团期子订单,用车需求 status=DONE(正确) 200 成功,warnings 无车侧项
团期子订单,用车需求 status 为 PENDING/PROCESSING/PENDING_REVIEW/REJECTED_TO_CONSULTANT/REJECTED_TO_ADMIN(错误) 584131(HTTP 仍 200)
团期子订单,无 active 用车需求行(提示) 200 成功,warnings 新增一条 category=VEHICLE 软预警(不阻断)
非团期订单(正确) 200 成功,行为与改动前完全一致
团期子订单但 needs_vehicle=0/null(存量单,正确) 200 成功,不查用车需求,行为与改动前完全一致

切换状态时的必要动作

无需前端主动切换任何请求字段——finalize 无请求体,本闸完全由后端按订单当前状态判定。前端唯一需要改的是响应解析:warnings[] 渲染逻辑不能假设 settlementId/dayNumber 恒非空(category=VEHICLE 时两者为 null),需按 category 分支处理,VEHICLE 项直接展示 message 整体提示,不尝试用 settlementId 定位某一行明细。


五、数据库行为

本单不新增任何数据库写操作。闸门是纯只读判定(读取该团期子订单当前用车需求状态与归团关系),随 finalize 既有事务执行;若判定不通过(584131),finalize 在写入结算数据之前即中止、整个事务回滚,不产生部分写入,不影响 finalize 对住宿/门票/人员等既有数据的写入行为。不新增表、不新增列、不写 Flyway、不改索引。


六、边界行为

  • 未登录 → 401(网关拦截)
  • 订单不存在 / 状态不允许核单 → 沿用 finalize 既有前置校验,本单未改动
  • 团期子订单但 needs_vehicle=0/null → 直接放行,不查用车需求
  • 非团期订单 → 直接放行,不查用车需求
  • 用车需求行缺失 → 不阻断,warnings 追加一条 category=VEHICLE 软预警(见上)
  • 下游判团门面/需求门面本身不可用(同步内存/DB 调用,非 Feign/MQ)→ 按既有全局异常处理返回错误,本单不新增降级分支
  • 老数据兼容:存量老响应结构不受影响,warnings[] 新增取值属"新增元素"而非"改变既有元素结构",向前兼容读取(未识别 category 的前端旧代码按未知分类兜底展示即可,不会因该字段崩溃,只是 settlementId/dayNumber 为 null 需要前端自身做好判空)

六.5、枚举

warnings[].category(com.hulalv.order.settlement.enums.SettlementCategory)

所属字段: warnings[].category | 类型: String

SettlementCategory 枚举全量有 8 个取值(原型八个核单明细 Tab),但当前 finalize 的 performSubmitBlockingChecks 只会产出以下 3 种到 warnings[],其余 5 个(MEAL/GUIDE/PHOTOGRAPHER/OTHER_INCOME/OTHER_EXPENSE)不会出现在本字段里:

值 中文 说明
HOTEL 住宿 既有取值,CASH_PAID 现付缺凭证软预警,settlementId/dayNumber 恒非空
TICKET 门票/游玩项目 既有取值,同上,恒非空
VEHICLE 车辆 本单新增取值,团期子订单缺失用车需求行时的软预警,settlementId/dayNumber 恒为 null

584131 message 占位符 {1}(com.hulalv.order.requirement.enums.RequirementStatus)

所属字段: 错误响应 message 文本内嵌值(非独立 JSON 字段) | 类型: String

值 中文 是否放行本闸
PENDING 待房务配 否
PROCESSING 配房中 否
DONE 配房完成 是(唯一放行值)
PENDING_REVIEW 待审核 否
REJECTED_TO_CONSULTANT 驳回 否
REJECTED_TO_ADMIN 驳回 否

(label 文案是历史上的房务侧措辞,车需求场景下前端应另行映射展示文案,不要直接透出 PROCESSING 对应"配房中"这种误导性文案。)


六.6、修改前后对比

字段级对比

字段 改前 改后
warnings[].category 可能取值 HOTEL / TICKET HOTEL / TICKET / VEHICLE(新增)
warnings[].settlementId 恒非空 HOTEL/TICKET 恒非空;VEHICLE 恒为 null(新增分支)
warnings[].dayNumber 恒非空 HOTEL/TICKET 恒非空;VEHICLE 恒为 null(新增分支)
错误码集合 无 584131 新增 584131 SETTLEMENT_GROUP_VEHICLE_NOT_READY
其余响应字段 无变化 无变化

行为级对比

行为 改前 改后
团期子订单 + needs_vehicle=1 + 用车需求非 DONE(有需求行) 200 成功,结算完成 584131(HTTP 仍 200)
团期子订单 + 用车需求 DONE 200 成功 200 成功(不变)
团期子订单 + 无 active 用车需求行 200 成功,warnings 无车侧项 200 成功,warnings 新增一条 category=VEHICLE 缺行预警
非团期订单 200 成功 200 成功(完全不变,本闸不进)
团期子订单 + needs_vehicle=0(存量单) 200 成功 200 成功(不变,本闸不进)
用车需求非 DONE + 车费派生行被清空/撤销 200 成功(通用品类门禁 rowCount>0 分支不进) 584131(本闸只读需求 status,删行不能绕过)

六.7、影响评估

  • 是否破坏向后兼容: 是。团期子订单在特定状态下(用车需求存在但未完成)由"可结算"变为"584131 阻断"——这正是本单的设计目的,用于堵住"用车没配好也能结算"的洞。
  • 前端是否必须同步上线: 是。需要新增对 584131 的错误提示(建议直接展示后端 message,已含订单号与需求状态);warnings[] 渲染逻辑必须兼容 category=VEHICLE 时 settlementId/dayNumber 为 null 的情形,否则可能因假设非空而报错或渲染异常。
  • 前端 workaround 清理点: 无(本单是新增闸门,不涉及清理旧 workaround)。

七、不影响范围

  • 仅影响: POST /v3/admin/order/{orderId}/settlement/finalize 端点在团期子订单(且 needs_vehicle=true)时的错误码集合与 warnings[] 取值集合。
  • 零影响:
    • 核心(非团期)订单的 finalize 行为——完全不变。
    • needs_vehicle=0/null 的存量团期订单——完全不变。
    • finalize 内其余既有检查(住宿闸 584130、GUIDE/PHOTOGRAPHER 结算完成检查、CASH_PAID 软预警、金额汇总写入)——未改动。
    • POST /v3/admin/order/{orderId}/settlement/submit 及分步保存/草稿接口——本闸只挂在 finalize。
    • fleet 侧、hl-common-*、hl-gateway 路由——本单只改 hl-order-service-v3 一个服务,/v3/admin/** 路由沿用既有通配,未新增路由配置。
    • 权限点——未新增。
    • 响应体除 warnings[] 新增取值外的其余字段结构——未增删。

八、测试环境已验证

取证环境(2026-09-11 17:38 批次,hl-gateway、hl-order-service-v3 当时一致停在 dev-v3 f035b85be):

hl-gateway               dev-v3   f035b85be  0/N   2026-09-11 17:25:31   ok
hl-order-service-v3      dev-v3   f035b85be  0/N   2026-09-11 17:24:23   ok

端点:POST /v3/admin/order/{orderId}/settlement/finalize,角色 ADMIN,经 hl-gateway 网关实调(非绕网关直连服务)。

17:38 批次三组:

场景 前置 code message 原文
团期子订单 + 用车需求 PENDING 订单 HL20260819230641736,group_batch_id 非空;用车需求 status 由 SQL 直更为 PENDING 584131 团期子订单 HL20260819230641736 的用车尚未安排完成(用车需求当前状态:PENDING),请等车务配车完成后再提交核单
同订单需求推 DONE 同上,SQL 直更回 DONE 200 成功(finalSnapshotStatus=FINALIZED、orderStatusAfter=待财务复核)
核心散客单 订单 HL20260819223840144,两个 batch 列均为 NULL,住宿与用车需求都压成 PENDING 200 成功——两道团期闸均未触发(若误触发必返 584130/584131)

18:36 补测批次一组(服务已滚至 02e998fbf,四行部署状态均 0/N):

场景 前置 code message 原文
车费派生行全部软删 + 需求 PROCESSING order_settlement_vehicle_fee 活跃 0 行(请求前实测),需求 status='PROCESSING' 584131 团期子订单 HL20260819230641736 的用车尚未安排完成(用车需求当前状态:PROCESSING),请等车务配车完成后再提交核单

取证边界(如实说明,不得省略):

  1. 以上所有前置条件均为 SQL 直更需求表 status(第二批次另直更车费派生行)构造,不是配车链路真实产生的业务状态;配车/车务回调链路本身未在本次取证中被验证。
  2. 取证过程中用 SQL 绕过了与本单无关的三道通用前置闸:584310(八个核单分类未全部确认)、584082(待收尾款)、车费草稿冻结确认——手段是把明细行标记 CONFIRMED、把应收金额清零对齐已付。这三道闸自身的正确性未在本次取证中验证。

backend_status: "deployed" 代表代码已合并 dev-v3 并随服务部署(合并提交 bacd1ac86,2026-09-11 10:16:01,PR #7499);gateway_status 现更新为 verified,依据即上述 2026-09-11 17:38 与 18:36 两批网关实测。


十、相关文档

  • 关联 Issue: wx/HL#7445
  • 关联 PR: wx/HL#7499
  • 前置/关联依赖:#7347(住宿侧同形闸门,584130,本单车侧对称件)、#7446(同一方法体内住宿闸判团口径统一,与本单共用私有方法 isGroupSubOrder,#7445 先合、#7446 随后统一)、#7441(尚未合入,其 D-C26 ② 将把本单"缺行软预警"分支升级为硬阻断并接入 vehicleWaived)

关联 / 联系人

链接

联系人

  • 后端负责人: @wx