hl-api-changelog/changelogs-v2/2026-08/12_5876_核单finalize-summary出参整改-修改接口-管理后台.md
2026-08-12 09:12:56 +08:00

20 KiB

schema, ticket, title, consumer, change_type, author, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer change_type author backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 5876 核单 finalize warnings 结构化带行id + summary 新增 settled 标识warnings 出参破坏性变更) admin 修改接口 yst deployed pending pending 后端 PR #5881 已 merge 到 dev-v3 并部署测试服。finalize 出参 warnings 由字符串数组改为对象数组(破坏性,按字符串渲染的前端必须改读 message 字段;summary 出参新增 settled 字段,判是否已核单改读 settled,不要再判 id==null。 2026-08-12 dev-v3

⚠️ 修改接口·管理后台】核单 finalize warnings 结构化 + summary 新增 settled 标识(#5876

1. 接口背景

核单结算域的两个管理后台接口:

  1. 完成核单 finalize:核单页点「完成核单」时提交,原子冻结核单事实并生成核单汇总。出参带 warnings 软预警(现付缺凭证等,不阻塞提交),前端用于提示定制师补传凭证。
  2. 核单汇总快照 summary:核单页 / 财务报表页读取整单金额、成本、毛利汇总。

本次整改两个出参可读性问题:

  1. warnings 无法定位行旧结构warnings 是 List<String>,文案形如「住宿 D2 现付缺凭证」。同一天有多条住宿/门票明细时,前端拿到的多条文案完全相同,无法区分是哪一行缺凭证,也无法跳转/锚定到具体明细行。
  2. summary 空壳无法可靠判空(旧行为):订单未核单时接口返回一个空壳对象,此前前端用 id == null 判「尚未核单」,但 advanceSummary 字段恒有默认空对象(不是 null,单靠字段判空容易误判。

2. 变更清单

# 变更 类型
1 finalize 出参 warningsList<String> 改为 List<WarningItemVO>(每条带 settlementId / category / dayNumber / message ⚠️ 破坏性
2 summary 出参新增 settled 字段Boolean已核单 = true,未核单空壳 = false 新增字段

无入参变化、无删除字段、无 DDL。

3. 接口详情

3.1 完成核单

方法 + 路径 POST /v3/admin/order/{orderId}/settlement/finalize
接口名 完成核单
使用场景 管理后台核单页,明细核对完成后提交,原子冻结核单事实并生成核单汇总快照
认证 管理后台 JWT/v3/admin/* 走网关鉴权)
角色限制 房务角色HOUSE不可访问,调了会被 403 拦截
幂等性 非幂等写操作;重复提交会被「缺少核单终态快照 / 快照已变化」类错误码拦截,前端不要自动重试
限流 走网关默认限流,无接口级特殊限流

3.2 查核单汇总快照

方法 + 路径 GET /v3/admin/order/{orderId}/settlement/summary
接口名 查核单汇总快照
使用场景 管理后台核单页 / 财务报表页读取整单金额、成本、毛利汇总
认证 管理后台 JWT
角色限制 无接口级角色限制(网关鉴权通过即可)
幂等性 只读查询,幂等
限流 走网关默认限流

4. 接口入参

4.1 路径参数

接口 参数 类型 必填 说明
finalize orderId Long 订单 ID,必须大于 0否则 400「订单 ID 必须大于 0」
summary orderId Long 订单 ID

4.2 请求体 / Query

两个接口均无请求体、无 Query 参数。

5. 出参字段

5.1 finalize 出参Result,本次仅 warnings 变化)

字段 类型 说明
summaryId Long(String) 新写入的 settlement_summary 主键,序列化为字符串
finalSnapshotId Long(String) 核单终态快照 ID,序列化为字符串
finalSnapshotVersionNo Integer 核单终态快照版本号
finalSnapshotStatus String 核单终态快照状态,固定 FINALIZED
orderId Long(String) 订单 ID,序列化为字符串
settledAt String 核单完成时间,格式 yyyy-MM-ddTHH:mm:ss
totalAmount String 订单总金额快照BigDecimal 序列化为字符串,下同)
paidAmount String 已付金额快照
balanceAmount String 尾款金额快照
roomCost String 住宿实际成本
ticketCost String 门票实际成本
staffCost String 人员费用实际成本
subsidyCost String 补助实际成本
mealCost String 餐食实际成本
vehicleCost String 车辆基础服务总车费
otherExpenseCost String 其他支出实际成本
insurancePremium String 保险实际保费(未出单/已撤单 = 0
totalActualCost String 总实际成本
driverTransferAmount String 给司机/主报账人转回金额(有付款类型的分类仅计 CASH_PAID,不含保险
profitAmount String 公司毛利 = totalAmount - totalActualCost
profitRate Number 毛利率小数,totalAmount=0 时填 0
orderStatusAfter String 结算后订单状态,如「待财务复核」
mqTriggered Boolean 当前版本固定 false;完成核单不发布结算 MQ
warnings Array 本次变更:软预警对象数组(原来为字符串数组);无预警时为空数组 []

WarningItemVO 字段表

字段 类型 说明
settlementId Long(String) 触发预警的核单明细行 idsettlement_hotel / settlement_ticket 主键),序列化为字符串;前端据此锚定/跳转具体明细行
category String 核单分类枚举名,当前仅 HOTEL住宿/ TICKET门票/游玩项目)两类会触发预警
dayNumber Integer 行程第几天
message String 预警文案,与旧字符串元素一致,如「住宿 D2 现付缺凭证」

5.2 summary 出参Result,本次新增 settled

字段 类型 说明
settled Boolean 本次新增是否已核单。summary 快照存在 = true;未核单空壳 = false
id Long settlement_summary 主键;未核单时为 null
orderId Long 订单 ID;空壳时也有值
totalAmount String 订单总金额快照BigDecimal 序列化为字符串,下同)
paidAmount String 已付金额快照
balanceAmount String 尾款金额快照
roomCost String 住宿实际成本
ticketCost String 门票实际成本
staffCost String 人员费用实际成本
subsidyCost String 补助实际成本
mealCost String 餐食实际成本
vehicleCost String 车辆基础服务总车费(按 Fleet 派车组合计)
otherExpenseCost String 其他支出实际成本
insurancePremium String 保险实际保费
refundTotal String 返还合计(只读展示,不进利润公式)
totalActualCost String 总实际成本
driverTransferAmount String 给司机/主报账人转回金额(仅计 CASH_PAID
profitAmount String 公司毛利
profitRate Number 毛利率(小数)
settledBy Long 核单人 id;未核单时为 null
settledByName String 核单人姓名快照
settledAt String 核单完成时间,格式 yyyy-MM-ddTHH:mm:ss
remark String 核单备注
finalSnapshotId Long(String) 当前核单终态快照 ID;未完成核单或已重新打开时为 null
finalSnapshotVersionNo Integer 当前核单终态快照版本号;无当前快照时为 null
finalSnapshotStatus String 有当前快照时固定 FINALIZED,否则为 null
finalizedAt String 当前核单终态快照完成时间;无当前快照时为 null
finalizedByName String 当前核单终态快照操作人姓名;无当前快照时为 null
advanceSummary Object 已审批通过的订单预支汇总;恒下发对象,不是 null(空壳时也有值)

advanceSummary 子对象字段表

字段 类型 说明
approvedAmount String 已审批通过预支合计金额,默认 "0"
records Array 已审批通过预支记录列表,默认空数组 [];元素结构同预支接口出参

未核单空壳时:settled=false,仅 orderIdadvanceSummary(默认空对象)有值,其余字段全部为 null

6. 枚举 / 数据字典

字段 来源 取值
warnings[].category SettlementCategory 枚举 全量HOTEL住宿/ TICKET门票/游玩项目)/ MEAL餐食/ VEHICLE车辆/ GUIDE导游/ PHOTOGRAPHER摄影/ OTHER_INCOME其他收入/ OTHER_EXPENSE其他支出当前预警只会出现 HOTEL / TICKET
finalSnapshotStatus 快照状态枚举 FINALIZED已定稿
orderStatusAfter 订单状态中文文案 如「待财务复核」

本次无枚举值增删;category 只是从隐含在文案里变成显式字段。

7. 错误码

7.1 finalize

code message 触发场景
581007 订单不存在 orderId 查不到订单
584081 对账数据不一致:已付金额与支付流水和线下收款合计不符,禁止带病结算 paid_amount 与实际入账总额不符(存量脏数据或并发漂移)
584085 存在辅助人员结算未完成,请全部结算后提交 任一非主报账司机费用行未结算完成或缺转账凭证号
584321 当前订单缺少核单终态快照,请重新完成核单 缺终态快照(含重复提交场景)
400 订单 ID 必须大于 0 orderId 路径参数校验失败
403 无权限访问 房务角色HOUSEJWT 调用

finalize 还有人员费用 / 其他收入未就绪等前置门禁错误码584xxx 段),非本次变更,按响应 message 直接提示即可。

7.2 summary

只读接口,无业务错误码;orderId 查不到快照时返回 200 + 空壳对象settled=false不会报「订单不存在」

8. 示例

8.1 典型成功

finalize核单完成,D2 住宿与 D2 门票各一条现付缺凭证,同日两行靠 settlementId 区分):

POST /v3/admin/order/2087088947225038849/settlement/finalize
{
  "code": 200,
  "message": "成功",
  "data": {
    "summaryId": "9600000000001",
    "finalSnapshotId": "9600000000002",
    "finalSnapshotVersionNo": 1,
    "finalSnapshotStatus": "FINALIZED",
    "orderId": "2087088947225038849",
    "settledAt": "2026-08-12T10: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": "9600000000003",
        "category": "HOTEL",
        "dayNumber": 2,
        "message": "住宿 D2 现付缺凭证"
      },
      {
        "settlementId": "9600000000011",
        "category": "TICKET",
        "dayNumber": 2,
        "message": "门票 D2 现付缺凭证"
      }
    ]
  },
  "success": true
}

summary(已核单订单):

GET /v3/admin/order/2087088947225038849/settlement/summary
{
  "code": 200,
  "message": "成功",
  "data": {
    "settled": true,
    "id": "9600000000001",
    "orderId": "2087088947225038849",
    "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",
    "refundTotal": "2260.00",
    "totalActualCost": "23120.00",
    "driverTransferAmount": "22940.00",
    "profitAmount": "1680.00",
    "profitRate": 0.0677,
    "settledBy": "30001",
    "settledByName": "李定制师",
    "settledAt": "2026-08-12T10:30:25",
    "remark": "核单完成 出行 7 天 0 投诉",
    "finalSnapshotId": "9600000000002",
    "finalSnapshotVersionNo": 1,
    "finalSnapshotStatus": "FINALIZED",
    "finalizedAt": "2026-08-12T10:30:25",
    "finalizedByName": "财务终审员",
    "advanceSummary": {
      "approvedAmount": "2000.00",
      "records": []
    }
  },
  "success": true
}

8.2 边界情况

边界 1finalize 无任何软预警——warnings 为空数组,不是 null

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

边界 2summary 查未核单订单——返回空壳对象HTTP 200,不报错,仅 orderId / advanceSummary 有值,其余全 null,settled=false

GET /v3/admin/order/2087088947225038850/settlement/summary
{
  "code": 200,
  "message": "成功",
  "data": {
    "settled": false,
    "id": null,
    "orderId": "2087088947225038850",
    "totalAmount": null,
    "paidAmount": null,
    "balanceAmount": null,
    "roomCost": null,
    "ticketCost": null,
    "staffCost": null,
    "subsidyCost": null,
    "mealCost": null,
    "vehicleCost": null,
    "otherExpenseCost": null,
    "insurancePremium": null,
    "refundTotal": null,
    "totalActualCost": null,
    "driverTransferAmount": null,
    "profitAmount": null,
    "profitRate": null,
    "settledBy": null,
    "settledByName": null,
    "settledAt": null,
    "remark": null,
    "finalSnapshotId": null,
    "finalSnapshotVersionNo": null,
    "finalSnapshotStatus": null,
    "finalizedAt": null,
    "finalizedByName": null,
    "advanceSummary": {
      "approvedAmount": "0",
      "records": []
    }
  },
  "success": true
}

边界 3同一天同分类多条缺凭证——旧结构下两条文案完全相同无法区分;新结构每条带独立 settlementId,可逐条定位/跳转:

"warnings": [
  { "settlementId": "9600000000003", "category": "HOTEL", "dayNumber": 2, "message": "住宿 D2 现付缺凭证" },
  { "settlementId": "9600000000005", "category": "HOTEL", "dayNumber": 2, "message": "住宿 D2 现付缺凭证" }
]

8.3 业务失败

finalize 辅助人员结算未完成

POST /v3/admin/order/2087088947225038849/settlement/finalize
{
  "code": 584085,
  "message": "存在辅助人员结算未完成,请全部结算后提交",
  "success": false
}

finalize 订单不存在

POST /v3/admin/order/999999999/settlement/finalize
{
  "code": 581007,
  "message": "订单不存在",
  "success": false
}

finalize 房务角色访问被拦House 角色 JWT 调用):

{
  "code": 403,
  "message": "无权限访问",
  "success": false
}

9. 业务边界

适用

  • finalize订单核单明细已逐步保存完成、核对无误后点「完成核单」提交
  • summary核单页 / 财务报表页随时读取汇总;未核单订单也可调(返回空壳),前端据此展示「尚未核单」占位

不适用

  • finalize明细未保存完整 / 辅助人员结算未完成 / 对账不一致的订单(被 584xxx 门禁拦截,见 §7
  • summary想看明细行级数据时不要用它,走各分类明细接口;本接口只有汇总快照

特殊边界

  • warnings 是软预警,不阻塞 finalize 提交;有预警也照常返回 200 完成核单
  • 预警仅覆盖 CASH_PAID现付且 voucher_urls 为空的住宿 / 门票明细行;签单、公司直付等其他支付方式不产生预警
  • summary 空壳的 advanceSummary 恒为默认对象approvedAmount="0"、records=[]),不是 null,不能拿它当判空依据

10. 修改前后对比

10.1 字段级对比

接口 字段 修改前 修改后
finalize warnings List<String>,元素如 "住宿 D2 现付缺凭证" List<WarningItemVO>,元素为对象 { settlementId, category, dayNumber, message }
summary settled 无此字段 新增 Boolean已核单 = true,未核单空壳 = false

10.2 行为级对比

场景 修改前 修改后
同日多条同类缺凭证 多条相同文案,无法区分哪一行 每条带独立 settlementId,可定位/跳转具体明细行
前端渲染预警 直接渲染字符串 必须读 warnings[].message;可用 settlementId + category 做行锚定
前端判「是否已核单」 data.id == null不可靠,advanceSummary 恒非 null 易误导) data.settled === false
调旧结构(如把 warnings[i] 当字符串拼接) 正常显示文案 显示 [object Object] 或报错(字段结构已变)

11. 影响评估 / 回滚

11.1 影响评估

  • 破坏兼容性⚠️ 部分破坏。finalize 的 warnings 结构变化是破坏性的:原按字符串数组渲染 warnings 的前端代码会显示异常。summary 仅新增字段,向后兼容。
  • 前端必须同步上线:是(针对 finalize warnings 渲染处)。前端需要:
    1. warnings 渲染从「直接渲染字符串」改为读 item.message
    2. (可选)用 item.settlementId + item.category 实现点击预警跳转/高亮对应明细行
    3. 判「是否已核单」改读 settled 字段,删除 id == null 判空逻辑
  • 后端兼容:无 DDL、无数据迁移;未消费 warnings 的前端功能不受影响。

11.2 回滚方案

  • 后端回滚 = revert PR #5881 的 merge commit125bb9c96e,重启 hl-order-service-v3,warnings 恢复字符串数组、settled 字段消失。
  • 前端回滚 = 切回旧版前端包。注意新旧前后端要配套:新前端 + 旧后端时 warnings[].message 取不到值、settled 恒 undefined。
  • 零 DDL,回滚无残留风险。

12. 注意事项

  1. warnings 元素里的 settlementId 序列化为 JSON 字符串Long 防 JS 精度丢失),前端按 string 处理,不要 Number() 转换。
  2. warnings 无预警时返回空数组 [],不是 null,渲染前正常遍历即可。
  3. category 当前只会出现 HOTEL / TICKET;SettlementCategory 枚举还有其他值MEAL / VEHICLE / GUIDE 等),前端如做映射表建议按全量枚举写,缺值兜底显示原 code。
  4. summary 判空只认 settledsettled === false 即「尚未核单」,此时除 orderId / advanceSummary 外全部字段为 null,展示必须兜底,不要直接渲染金额。
  5. advanceSummary 恒为对象(未核单也是 { approvedAmount: "0", records: [] }),不要拿它判「是否已核单」。
  6. finalize 是写操作且有前置门禁(对账一致、人员结算完成等),失败按错误码 message 提示,不要静默重试;重复提交会被快照类错误码拦截。
  7. finalize / summary 两个接口的金额类字段totalAmount / paidAmount / 各 cost / profitAmount 等)均序列化为字符串,前端展示直接用,计算需自行转数值。

13. 关联 / 联系人