From 251748d813b0531d34e8c8a0cf06f1ff9fda05a8 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Fri, 11 Sep 2026 18:58:27 +0800 Subject: [PATCH] =?UTF-8?q?docs(7445,7446):=20=E4=B8=A4=E4=BB=BD=E4=BA=A4?= =?UTF-8?q?=E6=8E=A5=E4=BB=B6=E8=A1=A5=E7=BD=91=E5=85=B3=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=EF=BC=8Cgateway=5Fstatus=20=E8=BD=AC=20verif?= =?UTF-8?q?ied=20Refs=20#7445=20Refs=20#7446?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两单的 §八「测试环境已验证」原先都写着「本节当前为空,网关实测尚未进行」, frontmatter 的 gateway_status 卡在 pending、pre-push 校验器报 E_GATEWAY_PENDING。 2026-09-11 网关实测已完成,本次把结果写进两份交接件: - #7445 四组(17:38 三组 + 18:36 补一组):团期单 + 用车 PENDING → 584131; 推 DONE → 200;散客单两类需求都 PENDING → 200(两闸均不误触发); 车费派生行全部软删 + 需求 PROCESSING → 仍 584131 - #7446 四组:方向甲 + 住宿 PENDING → 584130;推 DONE → 200; 待回填老单(product 非空 + group 为空 + 团期存活)→ 仍 584130;散客单 → 200 两份都如实写明了两条取证边界,不许读的人误推: 1. 全部前置都是 SQL 直更造出来的(直接改需求 status),不是走真实配车/配房链路产生的 2. 取证路上绕过了三道与本单无关的通用前置闸(584310 八分类未确认、584082 待收尾款、 车费草稿冻结确认),手段是 SQL 标 CONFIRMED / 清零应收;这三道闸自身的正确性未被验证 gateway_status pending→verified、verified_at 填 2026-09-11、status_note 改为如实描述。 frontend_status 保持 pending 未动。 --- ...1_7445_团期用车结算闸-修改接口-管理后台.md | 379 ++++++++++++++++++ ...46_住宿结算闸判团口径-修改接口-管理后台.md | 307 ++++++++++++++ 2 files changed, 686 insertions(+) create mode 100644 changelogs-v2/2026-09/11_7445_团期用车结算闸-修改接口-管理后台.md create mode 100644 changelogs-v2/2026-09/11_7446_住宿结算闸判团口径-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/11_7445_团期用车结算闸-修改接口-管理后台.md b/changelogs-v2/2026-09/11_7445_团期用车结算闸-修改接口-管理后台.md new file mode 100644 index 00000000..1aa7f37b --- /dev/null +++ b/changelogs-v2/2026-09/11_7445_团期用车结算闸-修改接口-管理后台.md @@ -0,0 +1,379 @@ +--- +schema: "hl-changelog/v2" +ticket: "7445" +title: "整单核单 finalize 新增团期用车户级闸门(新增错误码 584131,warnings 新增 VEHICLE 取值)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-11" +status_note: "gateway_status=verified:2026-09-11 网关实测已完成(17:38 批次 + 18:36 补测批次,详见“八、测试环境已验证”),经 hl-gateway 网关调用 POST /v3/admin/order/{orderId}/settlement/finalize 验证 584131 硬阻断与放行两个分支。所有前置条件均为 SQL 直更需求表状态构造,非真实业务链路(配车)产生;另绕过与本单无关的 584310/584082/车费草稿冻结确认三道通用前置闸,其正确性未验证。" +updated_at: "2026-09-11" +base: "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` + +#### 使用场景 + +管理后台核单页点击「完成核单」按钮时调用,原子完成结算并推订单进终态。本单不改调用方式;团期子订单(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 缺行场景固定为「用车:未提交用车需求」 | + +#### 请求示例 + +```http +POST /v3/admin/order/1934567890123456789/settlement/finalize +Authorization: Bearer {token} +``` + +(无请求体,仅 Path 参数 orderId;本单未改请求形态) + +#### 响应示例 + +```json +{ + "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),判据不可达时直接按错误响应处理,不发生静默降级。 + +```json +{ "code": 200, "success": true, "data": { "warnings": [] } } +``` + +#### 错误响应 + +新增错误码 584131(本单核心变更): + +```json +{ + "code": 584131, + "message": "团期子订单 GB202609120007 的用车尚未安排完成(用车需求当前状态:PENDING),请等车务配车完成后再提交核单", + "success": false, + "data": null +} +``` + +若住宿闸与用车闸同时未就绪,先返回住宿侧既有错误码(闸序:住宿在前): + +```json +{ + "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](https://git.1814.love:8443/wx/HL/issues/7445) +- 关联 PR: [wx/HL#7499](https://git.1814.love:8443/wx/HL/pulls/7499) +- 前置/关联依赖:`#7347`(住宿侧同形闸门,584130,本单车侧对称件)、`#7446`(同一方法体内住宿闸判团口径统一,与本单共用私有方法 isGroupSubOrder,#7445 先合、#7446 随后统一)、`#7441`(尚未合入,其 D-C26 ② 将把本单"缺行软预警"分支升级为硬阻断并接入 vehicleWaived) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7445](https://git.1814.love:8443/wx/HL/issues/7445) +- **PR**: [#7499](https://git.1814.love:8443/wx/HL/pulls/7499) +- **Merge commit**: [bacd1ac86](https://git.1814.love:8443/wx/HL/commit/bacd1ac86cdeba0f368f1f01b92ed2f5a47aa4a2) + +### 联系人 + +- **后端负责人**: @wx diff --git a/changelogs-v2/2026-09/11_7446_住宿结算闸判团口径-修改接口-管理后台.md b/changelogs-v2/2026-09/11_7446_住宿结算闸判团口径-修改接口-管理后台.md new file mode 100644 index 00000000..1de72e48 --- /dev/null +++ b/changelogs-v2/2026-09/11_7446_住宿结算闸判团口径-修改接口-管理后台.md @@ -0,0 +1,307 @@ +--- +schema: "hl-changelog/v2" +ticket: "7446" +title: "住宿结算闸归团判据改走统一门面,修正已降级的 productBatchId 误用(584130 触发人群变更)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-11" +status_note: "gateway_status=verified:2026-09-11 17:38 批次网关实测已完成(详见“八、测试环境已验证”),经 hl-gateway 网关调用 POST /v3/admin/order/{orderId}/settlement/finalize 验证四组场景(含 584130 硬阻断与放行分支)。所有前置条件均为 SQL 直更需求表状态构造,非真实业务链路(配房)产生;另绕过与本单无关的 584310/584082/车费草稿冻结确认三道通用前置闸,其正确性未验证;本单“团期已软删放行”这一唯一取舍点未在本次网关实测中单独覆盖。" +updated_at: "2026-09-11" +base: "dev-v3" +--- + +# order-v3: 住宿结算闸归团判据改走统一门面 + +> **服务**: hl-order-service-v3 +> **PR**: #7504 +> **Issue**: #7446 +> **日期**: 2026-09-11 +> **影响范围**: 既有端点 `POST /v3/admin/order/{orderId}/settlement/finalize`(完成核单)中住宿结算闸(584130)的归团判据修正;签名、错误码值/符号/message 文案、响应结构均未改,仅 584130 的触发人群变化。 + +--- + +## ⚠️ 关键变化 + +本次只改判据,不改契约:`POST /v3/admin/order/{orderId}/settlement/finalize` 的住宿结算闸(错误码 584130)判断"这单是不是团期子订单"的依据,从 `order.getProductBatchId() == null` 改为统一判团门面 `OrderService.resolveGroupBatchLinks`(经新增私有方法 isGroupSubOrder 转发;#7445 已在同一方法体建立该方法,本单复用)。 + +**之前的 changelog(#7347,见十节链接)说"闸门只对 productBatchId 非空的订单生效"——这句话现在不再准确**,判团依据已改为统一门面。签名、错误码值/符号、message 文案与两个占位参数、响应体结构**均未改变**,但 584130 实际覆盖的订单范围变了: + +1. 一部分此前被静默漏判为非团期从而放行的订单(`group_batch_id` 非空但 `product_batch_id` 为空,即"脱离产品排期独立建团"的团单),现在会被 584130 正确拦截。 +2. 一部分此前因裸列判断误判为团期从而阻断的订单(`product_batch_id` 非空、`group_batch_id` 为空、且所属团期**已被软删**),现在会被判定为非团期,改为放行——这是本单唯一一处放松结算闸的已知取舍(详见"六.6")。 + +前端**无需修改任何代码**:错误码、message 文案、响应结构一字未变,只是该错误码出现的订单范围变了。 + +--- + +## 一、背景 + +`SettlementService.assertGroupHotelReadyForFinalize`(#7347/PR #7433 引入的住宿户级闸门,方法体内新增于本单之前)原判团短路条件是 `order.getProductBatchId() == null`。但 `product_batch_id` 自 Issue #7083 起已被显式降级为"产品侧排期溯源 + 迁移期两跳回退通路"、**不再作归团判别**——`order_main` 表的列 COMMENT 与 `OrderInfo.java` 的字段 javadoc 均明确记载归团判别唯一依据是 `group_batch_id`。用错判据会让闸门在"建团脱离产品排期"的一部分团单上静默不生效,#7347 想堵的洞在这批订单上依然成立。 + +本单**不是简单地把 getProductBatchId 换成 getGroupBatchId**——单列直换会在另一个方向开新洞:`group_batch_id` 是 #7083 后加列,存量靠回填脚本、增量靠创单同事务回写、漏网靠对账 Job 兜底,三者都不是瞬时完成的,回填窗口期内团期仍存活但 `group_batch_id` 尚未回填的老单,裸判会把它们误判成普通单直接放行。本单改调仓内已有的统一判团口径 `OrderService.resolveGroupBatchLinks`(一跳 `group_batch_id`、为空再两跳回退 `product_batch_id`),两个方向都不漏。与 #7445(同一方法体新增的用车闸)已协调统一使用同一私有方法 `isGroupSubOrder`,避免同一方法体内相邻两道闸对同一订单给出相反的"是不是团单"结论。 + +--- +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 完成核单 | POST | `/v3/admin/order/{orderId}/settlement/finalize` | 判据修正(584130 触发人群变更) | 住宿结算闸归团判据由已降级的 productBatchId 改走统一判团门面 | + +--- + +## 三、接口详情 + +### 1. 完成核单 `POST /v3/admin/order/{orderId}/settlement/finalize` + +**VO**: `Long(Path 参数 orderId,无请求体) → Result` + +#### 使用场景 + +管理后台核单页点击「完成核单」按钮时调用,原子完成结算并推订单进终态。本单不改调用方式、不改请求/响应结构,只改住宿闸内部的归团判据,因此本节按模板要求自包含列出请求/响应契约(与 #7445 changelog 重复列出属正常,两单各自独立可读)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | 是 | 大于等于1 | 订单 ID;本单未改 | + +(finalize 本身无请求体,本单未新增/删除任何入参。) + +#### 出参字段表 + +`Result`,字段集合本单**未增删任何字段**(本单只改内部判据,不改响应结构),为保证本节自包含仍列出关键字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| orderId | Long | 订单 ID | +| finalSnapshotStatus | String | 核单终态快照状态 | +| totalAmount | BigDecimal | 订单总金额快照 | +| paidAmount | BigDecimal | 已付金额快照 | +| roomCost | BigDecimal | 住宿实际成本 | +| totalActualCost | BigDecimal | 总实际成本 | +| orderStatusAfter | String | 结算后订单状态 | +| warnings | List | 软预警列表;本单不改其结构与产出逻辑 | + +(完整字段清单共 24 个,见 `SettlementSubmitRespVO`;本单未增删任何字段,故不重复列出全部,只摘录关键项用于自包含理解。) + +#### 请求示例 + +```http +POST /v3/admin/order/1934567890123456789/settlement/finalize +Authorization: Bearer {token} +``` + +(无请求体,仅 Path 参数 orderId;本单未改请求形态) + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "orderId": "1934567890123456789", + "finalSnapshotStatus": "FINALIZED", + "totalAmount": "24800.00", + "paidAmount": "24800.00", + "roomCost": "4280.00", + "totalActualCost": "23120.00", + "orderStatusAfter": "待财务复核", + "warnings": [] + } +} +``` + +#### 空数据 / 降级响应 + +本闸不产生独立的空态/降级响应;判团查询是既有门面的纯本地只读查询(同库同服务,非 Feign/MQ),不发生外部降级。warnings 为空时返回空数组: + +```json +{ "code": 200, "success": true, "data": { "warnings": [] } } +``` + +#### 错误响应 + +584130 错误码本身未变,仅摘录触发该码的一个示例(判团结果来自统一门面而非旧的 productBatchId 判据): + +```json +{ + "code": 584130, + "message": "团期子订单 GB202609120007 的住宿尚未安排完成(住宿需求当前状态:PROCESSING),请等房务配房完成后再提交核单", + "success": false, + "data": null +} +``` + +message 模板(SettlementErrorCode.SETTLEMENT_GROUP_HOTEL_NOT_READY,584130,本单未改):团期子订单 {0} 的住宿尚未安排完成(住宿需求当前状态:{1}),请等房务配房完成后再提交核单。{0} = 订单号 orderNo;{1} = 住宿需求当前 status 字面量,需求缺失时固定文案"未提交住宿需求"。 + +#### 业务边界 + +- 判团口径统一为 `OrderService.resolveGroupBatchLinks`(经私有方法 isGroupSubOrder 转发),不再使用已降级的 productBatchId;该方法同时被 #7445 新增的用车闸复用,是全类唯一的判团落点。 +- needsHotel 半条件判断依旧前置:短路顺序为"先 needsHotel、后判团",needs_hotel=0/null 时直接放行、不发判团查询(与改动前一致,未变)。 +- 已知取舍:所属团期已被软删的订单(product_batch_id 非空、group_batch_id 为空、团期已软删)此前会因 productBatchId 非空而进闸判定住宿状态,改判后判定为非团期、跳闸放行——这是本单唯一一处放松结算闸的行为变更,详见"六.6"。 +- 待回填窗口内的老单(product_batch_id 非空、group_batch_id 为空、团期存活)判团口径经统一门面的两跳回退仍判定为团单,闸门继续生效,不受影响。 +- 判定语义本身完全未变:仍只读户级住宿需求 status,仍只认 DONE 放行,不读住宿派生行,不解析行程日自行判断"是否自助订房"。 + +--- + +## 四、契约约束与正确调用方式 + +### 正确 / 错误 调用结果对照(本单不改请求 payload,用订单形态代替) + +| 场景(订单形态) | 结果 | +|------|------| +| group_batch_id 非空 + product_batch_id 非空(常规团单),住宿需求非 DONE | 584130(不变) | +| group_batch_id 非空 + product_batch_id 为空(脱离产品排期建团),住宿需求非 DONE | 584130(改前静默漏判放行,本单修正为阻断) | +| product_batch_id 非空 + group_batch_id 为空 + 团期存活(回填窗口老单),住宿需求非 DONE | 584130(不变,两跳回退保住) | +| product_batch_id 非空 + group_batch_id 为空 + 团期已软删,住宿需求非 DONE | 放行(改前 584130,改后判定为非团单,已知取舍) | +| 两列皆空(核心散客单) | 放行(不变) | +| needs_hotel=0/null | 放行(不变,且改后连判团查询都不发) | + +### 切换状态时的必要动作 + +无需前端做任何动作。本单不改请求 payload、不改错误码、不改响应结构,仅内部判据修正;前端现有对 584130 的处理逻辑无需调整,唯一影响是该错误码出现的订单范围变了(不改变前端识别/展示逻辑)。 + +--- + +## 五、数据库行为 + +本单不写数据库。判团查询是既有门面 `OrderService.resolveGroupBatchLinks` 的纯只读查询(同库同服务本地查询,非 Feign/MQ),不新增表、不新增列、不新增索引、不写 Flyway。判定不通过时 finalize 在写入结算数据之前即中止、整个事务回滚,不产生部分写入。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- needs_hotel=0/null → 直接放行,不发判团查询(短路顺序:先 needsHotel、后判团,未变) +- 判团查询本身若异常,走既有全局异常处理,本单不新增降级分支 +- 团期已软删的订单 → 判定为非团期,放行(已知取舍,见"六.6") +- 待回填窗口老单(团期存活但 group_batch_id 未回填)→ 判定为团期,闸门继续生效(不受回填进度影响) + +--- + +## 六.5、枚举 + +本单不新增、不改变任何响应字段的枚举取值。584130 的两个 message 占位符沿用既有定义,未变: + +### 584130 message 占位符 {1}(com.hulalv.order.requirement.enums.RequirementStatus) + +**所属字段**: 错误响应 message 文本内嵌值(非独立 JSON 字段) | **类型**: `String` + +| 值 | 中文 | 是否放行本闸 | +|----|------|------| +| PENDING | 待房务配 | 否 | +| PROCESSING | 配房中 | 否 | +| DONE | 配房完成 | 是(唯一放行值) | +| PENDING_REVIEW | 待审核 | 否 | +| REJECTED_TO_CONSULTANT | 驳回 | 否 | +| REJECTED_TO_ADMIN | 驳回 | 否 | + +### warnings[].category(com.hulalv.order.settlement.enums.SettlementCategory,本单未改动,供自包含参照) + +**所属字段**: `warnings[].category` | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| HOTEL | 住宿 | 既有取值,本单未改 | +| TICKET | 门票/游玩项目 | 既有取值,本单未改 | +| VEHICLE | 车辆 | 既有取值(由 #7445 同批引入,本单未涉及) | + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +无字段级变化。请求 payload(无)、响应体 `SettlementSubmitRespVO` 的字段集合均未改动;584130 的错误码值、符号、message 文案、两个占位参数均未改;仅错误响应触发人群变化。 + +### 行为级对比 + +| 订单形态 | 改前 | 改后 | +|---|---|---| +| group_batch_id 非空、product_batch_id 非空(绝大多数团单) | 进闸 | 进闸(不变) | +| group_batch_id 非空、product_batch_id 为空(脱离产品排期建团) | 跳闸(漏判) | 进闸(本单修正) | +| product_batch_id 非空、group_batch_id 为空、团期存活(回填窗口老单) | 进闸 | 进闸(不变,靠门面两跳回退保住) | +| product_batch_id 非空、group_batch_id 为空、团期已软删 | 进闸 | 跳闸(行为变更,已知取舍,见下方说明) | +| 两列皆空(核心散客单) | 跳闸 | 跳闸(不变) | +| needs_hotel 为 0/null | 跳闸 | 跳闸(不变,且改后连判团查询都不发) | + +**关于"团期已软删"这一行的取舍说明**:门面对团期已软删的订单返回"不归团"(反查通道被软删过滤),于是这批订单从"今天进闸"变为"跳闸"。这仍是行为变更。之所以可接受:软删团期不再提供子订单视图是 #7083 已确立的系统级口径,团都解散了还卡住结算不符合业务预期;工单要求先量化受影响订单数再合并(工单 AC-1 的方向乙 SQL),量化结果见工单本身,本 changelog 不重复贴库查数据。 + +--- + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 是(覆盖人群变化——一部分订单从放行变阻断,一部分从阻断变放行;签名、错误码值/符号、响应结构本身不变)。 +- **前端是否必须同步上线**: 否。前端无需改代码;错误码、message 文案、响应结构一字未变,前端现有对 584130 的处理逻辑无需调整。 +- **前端 workaround 清理点**: 无。 + +--- + +## 七、不影响范围 + +- **仅影响**: `POST /v3/admin/order/{orderId}/settlement/finalize` 中住宿结算闸(assertGroupHotelReadyForFinalize)的归团判据。 +- **零影响**: + - 584130 的错误码值/符号/message 文案/两个占位参数——完全不变。 + - 响应结构 SettlementSubmitRespVO 的任何字段——不增删。 + - 住宿闸的判定语义本身(只读户级需求 status/只认 DONE 放行/不看派生行/不解析行程日)——逐字节未变。 + - #7445 新增的用车闸(同一方法体内相邻代码,但两者是独立判定,互不影响各自触发条件)。 + - `POST /v3/admin/order/{orderId}/settlement/submit` 及分步保存/草稿接口。 + - 核心(非团期)订单的 finalize 行为。 + - fleet 侧、hl-common-*、网关路由、权限点——均未改动。 + +--- + +## 八、测试环境已验证 + +**取证环境**(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 网关实调。 + +四组请求/响应: + +| 场景 | 前置 | code | message 原文 | +|---|---|---|---| +| 方向甲(group_batch_id 非空 + product_batch_id 为空)+ 住宿 PENDING | 订单 HL20260819230221927;SQL 直更住宿需求为 PENDING | **584130** | 团期子订单 HL20260819230221927 的住宿尚未安排完成(住宿需求当前状态:PENDING),请等房务配房完成后再提交核单 | +| 同订单住宿推 DONE | SQL 直更回 DONE | **200** | 成功(finalSnapshotStatus=FINALIZED) | +| 待回填老单(product_batch_id 非空 + group_batch_id 为空 + 团期存活) | 订单 HL20260819224314154;团期 deleted_at IS NULL 已实测 | **584130** | 团期子订单 HL20260819224314154 的住宿尚未安排完成(住宿需求当前状态:PENDING),请等房务配房完成后再提交核单 | +| 核心散客单 | 订单 HL20260819223840144,两列均 NULL | **200** | 成功 | + +**取证边界(如实说明,不得省略)**: + +1. 以上所有前置条件均为 SQL 直更需求表 status 构造,不是配房链路真实产生的业务状态;配房链路本身未在本次取证中被验证。本单唯一的放松取舍点——"团期已被软删的订单改为放行"(见"六.6")——未在本次网关实测中单独覆盖。 +2. 取证过程中用 SQL 绕过了与本单无关的三道通用前置闸:584310(八个核单分类未全部确认)、584082(待收尾款)、车费草稿冻结确认——手段是把明细行标记 CONFIRMED、把应收金额清零对齐已付。这三道闸自身的正确性未在本次取证中验证。 + +backend_status: "deployed" 代表代码已合并 dev-v3 并随服务部署(合并提交 da3bd7854,2026-09-11 10:42:41,PR #7504);gateway_status 现更新为 verified,依据即上述 2026-09-11 17:38 网关实测。存量影响量化(工单 AC-1 的方向甲/方向乙/方向丙三条 SQL 结果)由工单正文本身承载,本节不重复贴库查数据。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7446](https://git.1814.love:8443/wx/HL/issues/7446) +- 关联 PR: [wx/HL#7504](https://git.1814.love:8443/wx/HL/pulls/7504) +- 前置/关联依赖:`#7347`(原始需求单,PR #7433 引入了本单要修正的判据缺陷,本单是其判据订正件而非同一工单的延续)、`#7445`(同一方法体内新增的用车闸,与本单共用私有方法 isGroupSubOrder,#7445 先合、#7446 随后统一判团口径)、`#7449`(判团口径统一,收口全仓其余 23 处同类 productBatchId 误用,本单不处理,另建单处理) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7446](https://git.1814.love:8443/wx/HL/issues/7446) +- **PR**: [#7504](https://git.1814.love:8443/wx/HL/pulls/7504) +- **Merge commit**: [da3bd7854](https://git.1814.love:8443/wx/HL/commit/da3bd785463425fbdfebf409385fa77ff69752f9) + +### 联系人 + +- **后端负责人**: @wx