文件
hl-api-changelog/changelogs-v2/2026-09/10_7347_finalize团期住宿户级闸门-修改接口-管理后台.md
T
Mimingguang 4281aa8aca
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): #7390/#7347 frontmatter 回写 not_required
2026-09-10 15:09:21 +08:00

16 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 7347 整单核单 finalize 新增团期住宿户级闸门,未安排好住宿不允许结算(新增错误码 584130) admin wx(GIT) 修改接口 deployed not_required not_required mmg not_required 2026-09-10 mmg: 仅 finalize 错误码集合扩大新增 584130,HTTP 恒 200 判 code,请求/成功响应/方法/路径一字未改;后端自答前端唯一事=给 584130 直接展示后端 message(不二次包装)。实证该诉求已被既有通用业务码分支覆盖:request.js:512-528 未知码取 data.message 原样弹(errorBus message.error),finalize 经 settlementService.js 调用无 silentError、catch 不改写 message,后端 message 自动原样弹出,新增映射反违「不二次包装」。旧口径清理点实证无(财务核单无按住宿派生行推断能否结算逻辑)。 2026-09-10 dev-v3

整单核单 finalize 新增团期住宿户级闸门(修改接口)

服务: hl-order-service-v3 Issue: #7347 日期: 2026-09-10 影响范围: 既有端点 POST /v3/admin/order/{orderId}/settlement/finalize(完成核单)的错误码集合扩大;不新增/不删除端点,请求体、成功响应体结构、方法、路径、网关路由一字未改


⚠️ 关键变化

  1. 团期子订单完成核单(finalize)新增一道硬阻断:该订单需要配房(needsHotel=true)但其住宿需求还没被房务推到 DONE 时,finalize 直接拒绝,新增业务错误码 584130。改前这种情况能直接结算通过(尤其是"一条住宿派生行都没有"的团期子订单,改前完全不受阻拦)。
  2. HTTP 状态码恒为 200,闸门以 Result.code = 584130 的业务错误体返回,前端必须判 code,不能按 HTTP 状态识别。
  3. 免房户(needsHotel=false)与非团期订单(productBatchId 为空)完全不受影响,行为与改前逐字节一致。
  4. 管理者已对测试库执行存量统计 SQL,结果为 0(团期子订单里没有一单会被新闸拦住),因此全量生效,不做灰度,没有需要提前处理的存量数据。
  5. 前端需要做的唯一事情:给 584130 加提示文案,见下方"前端提示文案建议"。不涉及任何请求/响应字段改动。

一、背景

团期房务批次(#7322–#7328)把住宿的行级确认接进了核单:某户某晚没分平,那一行就不能被确认(#7327 的 584129)。但整单核单完成(finalize)此前对住宿没有任何硬性闸门——一个团期子订单哪怕住宿完全没安排好,只要没有已录入的住宿行,照样能走完 finalize 结算掉(既有的住宿检查落在 SettlementCategoryCheckService,条件是 rowCount > 0,0 行时天然放行)。

本单在 performSubmitBlockingChecks(既有 finalize 阻断检查方法)里新增一道户级闸门:只要该团期子订单还需要配房,就必须等房务把户级住宿需求推到 DONE 才允许结算。闸门用户级谓词而不是团级 order_group_batch.hotel_ready——团级标志只要有一户没推平就是 false,会把整团所有户的结算一起冻住,违反"不让一户卡整团"的既有原则;户级谓词与房务侧完成判定 finalizeHotelRequirementDone 写的是同一个字段,两侧口径天然对齐。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 完成核单(finalize) POST /v3/admin/order/{orderId}/settlement/finalize 错误码集合扩大 新增可能返回 584130;请求体(无)、成功响应体结构、方法、路径均未改;网关路由零改动

三、接口详情

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

VO: 无请求体(Path 参数 orderId)→ Result<SettlementSubmitRespVO>(成功响应结构本单未改,字段集合不重复列出)

使用场景

核单员在核单页点击"完成核单",前端调用本端点。团期子订单在此新增一道住宿完成校验;本节判定入口与触发条件表见下方"业务边界"前的说明表。

入参

字段 位置 类型 必填 约束 说明
orderId Path Long 是 大于 0 订单 ID;本单未改

判定入口与触发条件(前端可直接照此做提示判定表):新闸位于 SettlementService.assertGroupHotelReadyForFinalize(OrderInfo),在既有的司机结算完成检查之后、CASH_PAID 缺凭证软预警之前触发(硬阻断,非软预警):

条件 判定结果
productBatchId == null(非团期订单) 不受本单影响,行为与改前完全一致
productBatchId != null 且 needsHotel != true(团期免房户) 放行,行为与改前一致
productBatchId != null 且 needsHotel == true,该户最新一条住宿需求 status == DONE 放行
productBatchId != null 且 needsHotel == true,住宿需求不存在(一条派生行都没有,改前的漏洞场景) 抛 584130
productBatchId != null 且 needsHotel == true,住宿需求存在但 status 为 PENDING / PROCESSING / PENDING_REVIEW / REJECTED_TO_CONSULTANT / REJECTED_TO_ADMIN(即非 DONE) 抛 584130
该户住宿派生行已被撤销转为 GROUP_BATCH_PLAN_REVOKED,但住宿需求本身仍非 DONE 抛 584130(REVOKED 行不构成"已安排好"的证据)

判定只读户级住宿需求的 status 字段,不看有没有派生行、也不解析日期判断"是否自助订房"——这些口径统一由房务侧的完成判定负责写 DONE,本闸只读结果。

出参

成功响应结构本单未改,Result<SettlementSubmitRespVO> 字段集合与改前完全一致(不重复列出全部字段,仅摘录与本单相关的部分):

字段 类型 说明
data.orderId Long 订单 ID;本单未改
data.finalSnapshotStatus String 核单终态快照状态;本单未改
data.warnings List<WarningItemVO> 软预警列表(如 CASH_PAID 缺凭证);本单未改,584130 不进这个列表,而是走错误响应

请求示例

POST /v3/admin/order/1000123/settlement/finalize

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

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "orderId": 1000123,
    "finalSnapshotStatus": "CONFIRMED",
    "warnings": []
  }
}

(成功路径响应结构本单未改,仅摘录关键字段)

空数据 / 降级响应

不适用:本闸不产生空数据/降级语义,判定只有"放行"或"抛 584130"两种结果。

错误响应

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

message 模板(SettlementErrorCode.SETTLEMENT_GROUP_HOTEL_NOT_READY,SettlementErrorCode.java:339-342)为:

团期子订单 {0} 的住宿尚未安排完成(住宿需求当前状态:{1}),请等房务配房完成后再提交核单

两个占位符:{0} = 订单号(orderNo),{1} = 住宿需求当前状态。住宿需求行完全不存在时,{1} 填固定文案 未提交住宿需求(常量 SettlementService.GROUP_HOTEL_REQUIREMENT_ABSENT,逐字抄自源码,不是状态枚举值),而不是某个状态码。

业务边界

  • 闸门只对 productBatchId != null(团期子订单)生效,核心订单的房控闸是另一套逻辑(OrderService.needsHotel != true → 视为房控通过),本单不动它。
  • 闸门读取的是"该户最新一条住宿需求"(RequirementService.getLatestHotelRequirementByOrderId),走既有 Service 公共只读方法,未直连 Mapper。
  • 与既有"已录入住宿行全部 CONFIRMED"的行级检查(SettlementCategoryCheckService)并列不重叠:旧检查看的是已录入行的确认状态(行级),新闸看的是住宿需求 status(户级),两者判定顺序上新闸更早触发(performSubmitBlockingChecks 早于 SettlementReportFlowService.prepareFinalizationInCurrentTransaction)。

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

  • 必须按响应体 code == 584130 识别,不能按 HTTP 状态码识别——HTTP 恒为 200。
  • 建议直接展示后端 message,其中已包含订单号与住宿需求当前状态,无需前端自行拼接。
  • "该团期子订单是否可以完成核单"以 finalize 实际返回结果为准,前端不应该通过"是否存在住宿派生行"自行推断能否结算——这正是改前的漏洞场景(0 行时误以为可以结算)。

切换状态时的必要动作

无。本单不涉及任何需要调用方额外置空/切换的字段,闸门完全由后端根据现有数据判定。


五、数据库行为

  • 584130 在阻断检查阶段抛出,属于既有 finalize 阻断检查的一部分,抛出后不产生任何写入(不写核单快照、不写车辆冻结、不写日志表)。
  • 判定只读 order_main(productBatchId/needsHotel)与住宿需求表(status),无表结构变更、无 Flyway。

六、边界行为

  • 团期免房户(needsHotel=false):放行,不查住宿需求。
  • 团期需房户,住宿需求 status=DONE:放行。
  • 团期需房户,住宿需求缺失(零派生行):拒绝,584130,message 中状态位为"未提交住宿需求"。
  • 团期需房户,住宿需求非 DONE(PENDING/PROCESSING/PENDING_REVIEW/REJECTED_TO_CONSULTANT/REJECTED_TO_ADMIN):拒绝,584130,message 中状态位为该实际状态值。
  • 该户住宿派生行已转 GROUP_BATCH_PLAN_REVOKED,但需求 status 未到 DONE:仍拒绝,584130(REVOKED 行不算完成证据)。
  • 非团期订单:完全不受影响,无论住宿情况如何都走改前逻辑。
  • 全自订户(每一晚都自助订房)的 needsHotel 依然为 true,其住宿需求需先由房务侧完成判定推到 DONE 才能通过本闸;这条口径由另一单负责写 DONE,本单只读结果、不重复判定。

六.5、枚举 / 数据字典

新增错误码(SettlementErrorCode,hl-order-service-v3/src/main/java/com/hulalv/order/settlement/errorcode/SettlementErrorCode.java:339-342)

code 常量名 message 模板 触发条件
584130 SETTLEMENT_GROUP_HOTEL_NOT_READY 团期子订单 {0} 的住宿尚未安排完成(住宿需求当前状态:{1}),请等房务配房完成后再提交核单 团期子订单需要配房,且住宿需求当前状态不是 DONE(含需求缺失)

住宿需求状态枚举(RequirementStatus,本单只读不改,供理解 {1} 占位取值)

value label 是否放行本闸
PENDING 待房务配 否
PROCESSING 配房中 否
DONE 配房完成 是(唯一放行值)
PENDING_REVIEW 待审核 否
REJECTED_TO_CONSULTANT 驳回 否
REJECTED_TO_ADMIN 驳回 否
(需求行不存在) —— 否,message 状态位显示"未提交住宿需求"

前端提示文案建议

  • 直接展示 message 原文即可(已含订单号 + 当前状态),无需二次包装。
  • 若需要更醒目的引导,可在 message 之外追加一句操作指引,例如:"请前往房务模块查看该团期订单的住宿安排进度,配房完成后再回来提交核单。"
  • 不建议把 584130 与其他 40xxxx/58xxxx 段错误码合并成同一个通用提示——该码语义明确(住宿未完成),合并展示会丢失"该去催房务"这条关键信息。

六.6、修改前后对比

行为级对比

场景 改前 改后
团期需房户,一条住宿派生行都没有 可以直接结算通过(改前存在的漏洞) 拒绝,584130
团期需房户,住宿需求非 DONE(有派生行但未分平) 可能继续核单(仅受行级 CONFIRMED 检查约束,0 行时不受约束) 拒绝,584130
团期需房户,住宿需求 DONE 可核单 行为不变,仍可核单
团期免房户 可核单 行为不变
非团期订单 可核单(受核心订单自己的房控闸约束) 行为不变,核心房控闸逻辑本单未动
该户住宿派生行已被撤销为 GROUP_BATCH_PLAN_REVOKED 若无行级检查约束可能通过 拒绝,584130(除非需求 status 已是 DONE)

字段级对比

无字段变化。请求体(无)、成功响应体 SettlementSubmitRespVO 的字段集合均未改动,仅错误响应的 code 取值范围新增了 584130。


六.7、影响评估

  • 是否破坏向后兼容:否。端点方法/路径/请求体/成功响应体结构均未变化;免房户、DONE 户、非团期订单的行为与改前逐字节一致。
  • 前端是否必须同步上线:需要新增 584130 的提示文案,否则该错误会被前端当成未知错误码兜底展示(用户体验较差,但不会导致请求失败或数据错误)。
  • 前端 workaround 清理点:如果前端此前依赖"住宿派生行是否存在"来判断该团期子订单是否可结算,需要清理——这个判断口径本身就是改前的漏洞,不应再使用;一律以 finalize 实际返回结果为准。

七、不影响范围

  • 仅影响:POST /v3/admin/order/{orderId}/settlement/finalize 端点在团期子订单(productBatchId != null)且需要配房(needsHotel=true)时的错误码集合。
  • 零影响:
    • 无新增/修改/删除任何其他端点;网关路由零改动。
    • 免房户(needsHotel=false)与非团期订单(productBatchId 为空)的 finalize 行为与改前完全一致。
    • 成功响应体 SettlementSubmitRespVO 字段集合未变。
    • 不读取团级 order_group_batch.hotel_ready 字段,不会出现"一户卡整团"的情况。
    • 不涉及表结构变更、Flyway、数据迁移。
    • 既有的住宿"已录入行全部 CONFIRMED"行级检查(SettlementCategoryCheckService)逻辑未改,新闸与它并列而非替代。

八、测试环境已验证

  • 存量影响评估:管理者已对测试库执行存量统计 SQL(统计"团期子订单中会被新闸拦住"的数量),结果为 0——没有任何存量团期子订单处于会被新闸拦截的状态。因此本单全量生效,不做灰度,无需推平存量数据。
  • 部署状态:测试服已部署 HEAD 18c1df126,该 HEAD 即本单的实现提交本身(PR #7433)。
  • 网关验证:端点路径/方法未变,无需新增网关路由配置;沿用既有 /v3/admin/order/** 路由规则。
  • 兼容性结论:端点结构不变,仅扩展业务错误码集合,前端只需新增对 584130 的分支处理。

十、相关文档

  • 关联 Issue: wx/HL#7347
  • 前置/关联依赖:#7327(团期住宿行级确认,584129 与本单 584130 相邻但语义不同)、#7325(户级住宿完成判定,本单读取其写入的 status 字段)

关联 / 联系人

链接

联系人

  • 后端负责人: @wx
  • 前端负责人: @mmg