orderHeader 拍平 + 审计字段下线 + outstandingAmount 替代 primaryReporterDueAmount + finalize warnings 结构化 + summary 新增 settled,以 #5916 合并后最终态为准
39 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 | 5916 | 核单两报表出参瘦身合并版(#5876 + #5916):orderHeader 拍平 + 审计字段下线 + finalize warnings 结构化 + summary 新增 settled(破坏性变更) | admin | 修改接口 | yst | deployed | pending | pending | 本份是 #5876(PR #5881)与 #5916(PR #5920)两个 PR 对核单报表接口的合并 changelog,以 #5916 合并后的最终出参为准。两份 PR 均已 merge 到 dev-v3 并部署测试服。破坏性:reimbursement 删顶层 orderHeader 块(11 抬头字段拍平进 baseInfo)+ baseInfo 删 10 字段(reportStatus/reportVersion/generated*/confirmed*/transferAmount/primaryReporterDueAmount)+ 新增 outstandingAmount;group 删 orderHeader 块(10 抬头字段平铺顶层)+ 删 reportStatus/generated*/confirmed* 共 8 字段;finalize warnings 由字符串数组改对象数组;summary 新增 settled。此前 #5876 单独那份(12_5876)的 finalize/summary 内容并入本份,前端按本份最终结构一次改到位即可。 | 2026-08-12 | dev-v3 |
【⚠️ 修改接口·管理后台】核单两报表出参瘦身(合并 #5876 + #5916 最终态)
1. 接口背景
核单结算域的 4 个管理后台接口,近期经过两次连续变更(#5876 → #5916),本份 changelog 合并描述累计最终态,前端按本份一次改到位即可,不要分两次对接:
- 主报账人报账表(reimbursement):核单页查看主报账人(通常是司机)的代收、垫付支出、预支与净额结算情况。
- 单团核算表(group):核单页 / 财务页查看整团收入、成本、毛利核算。
- 完成核单(finalize):核单页点「完成核单」提交,原子冻结核单事实并生成核单汇总。
- 核单汇总快照(summary):核单页 / 财务报表页读取整单金额、成本、毛利汇总。
两次变更分别做了什么:
- #5876(PR #5881):finalize 出参 warnings 由字符串数组改为对象数组(可定位明细行);summary 出参新增 settled 字段(可靠判「是否已核单」)。
- #5916(PR #5920,最终态):两报表出参瘦身 —— 顶层 orderHeader 子块下线(抬头字段拍平),报表状态 / 生成确认审计字段出参下线,reimbursement 的尾款口径由「主报账人维度」换成「整单未收尾款」。
2. 变更清单
| # | 接口 | 变更 | 类型 |
|---|---|---|---|
| 1 | reimbursement | 顶层 orderHeader 子块删除,11 个订单抬头字段拍平进 baseInfo(orderNo/teamNo/productName/productType/productTypeName/customerName/departDate/returnDate/consultantName/travelerCount/travelerComposition) | ⚠️ 破坏性 |
| 2 | reimbursement | baseInfo 删除 10 个字段:reportStatus / reportVersion / generatedBy / generatedByName / generatedAt / confirmedBy / confirmedByName / confirmedAt / transferAmount / primaryReporterDueAmount | ⚠️ 破坏性 |
| 3 | reimbursement | baseInfo 新增 outstandingAmount(整单未收尾款,口径同 financial-overview),替代已删的 primaryReporterDueAmount | ✨ 新增字段 |
| 4 | group | 顶层 orderHeader 子块删除,10 个订单抬头字段平铺到顶层(orderNo/teamNo/productName/productType/productTypeName/customerName/departDate/returnDate/consultantName/travelerComposition) | ⚠️ 破坏性 |
| 5 | group | 删除 8 个字段:reportStatus / generatedBy / generatedByName / generatedAt / confirmedBy / confirmedByName / confirmedAt,及 orderHeader 块整体 | ⚠️ 破坏性 |
| 6 | finalize | 出参 warnings 由 List 改为 List(每条带 settlementId / category / dayNumber / message)(#5876 引入,本份仍有效) | ⚠️ 破坏性 |
| 7 | summary | 出参新增 settled 字段(Boolean):已核单 = true,未核单空壳 = false(#5876 引入,本份仍有效) | ✨ 新增字段 |
无入参变化、无 DDL(报表状态 / 版本列仍保留在 DB,仅出参不再下发)。
3. 接口详情
3.1 查主报账人报账表
| 项 | 值 |
|---|---|
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement |
| 接口名 | 查询主报账人报账表 |
| 使用场景 | 管理后台订单核单页,查看主报账人代收/垫付/预支/净额结算报表 |
| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) |
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
| 幂等性 | 只读查询,幂等 |
| 限流 | 走网关默认限流,无接口级特殊限流 |
3.2 查单团核算表
| 项 | 值 |
|---|---|
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/group |
| 接口名 | 查询单团核算表 |
| 使用场景 | 管理后台核单页 / 财务页,查看整团收入、成本、毛利核算 |
| 认证 | 管理后台 JWT |
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
| 幂等性 | 只读查询,幂等 |
| 限流 | 走网关默认限流 |
3.3 完成核单
| 项 | 值 |
|---|---|
| 方法 + 路径 | POST /v3/admin/order/{orderId}/settlement/finalize |
| 接口名 | 完成核单 |
| 使用场景 | 管理后台核单页,明细核对完成后提交,原子冻结核单事实并生成核单汇总快照 |
| 认证 | 管理后台 JWT |
| 角色限制 | 房务角色(HOUSE)不可访问,调了会被 403 拦截 |
| 幂等性 | 非幂等写操作;重复提交会被「缺少核单终态快照 / 快照已变化」类错误码拦截,前端不要自动重试 |
| 限流 | 走网关默认限流 |
3.4 查核单汇总快照
| 项 | 值 |
|---|---|
| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/summary |
| 接口名 | 查核单汇总快照 |
| 使用场景 | 管理后台核单页 / 财务报表页读取整单金额、成本、毛利汇总 |
| 认证 | 管理后台 JWT |
| 角色限制 | 无接口级角色限制(网关鉴权通过即可) |
| 幂等性 | 只读查询,幂等 |
| 限流 | 走网关默认限流 |
4. 接口入参
4.1 路径参数
| 接口 | 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| 全部 4 个接口 | orderId | Long | 是 | 订单 ID,必须大于 0(否则 400「订单 ID 必须大于 0」) |
4.2 请求体 / Query
4 个接口均无请求体、无 Query 参数。
5. 出参字段
5.1 reimbursement 出参(Result,最终结构)
顶层结构(只有 4 个字段,不再有 orderHeader 块、不再有平铺汇总字段):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| baseInfo | Object | 是 | 基础信息(订单抬头 + 报表汇总 + 报账人),恒下发对象 |
| incomeLines | Array | 是 | 收入行(司机/报账人代收);无数据固定返回空数组 [] |
| expenseLines | Array | 是 | 支出行(统一扁平字段,仅报账人垫付 CASH_PAID 支出);无数据固定返回空数组 [] |
| advanceLines | Array | 是 | 预支明细行(已审批预支逐条);无数据固定返回空数组 [] |
baseInfo 字段表(最终 21 字段):
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long(String) | 核单记录 ID(settlement_recon.recon_id),序列化为字符串;未生成时不输出 |
| orderId | Long(String) | 订单 ID,序列化为字符串 |
| orderNo | String | 订单号 |
| teamNo | String | 团号(订金未支付时不输出) |
| productName | String | 产品名 |
| productType | String | 产品类型枚举(如 CORE) |
| productTypeName | String | 产品类型中文名(字典 product_type 回填) |
| customerName | String | 客户姓名 |
| departDate | String | 出团日期,格式 yyyy-MM-dd |
| returnDate | String | 返回日期,格式 yyyy-MM-dd |
| consultantName | String | 定制师姓名 |
| travelerCount | Integer | 出行人总数(成人+儿童+幼童+婴儿,空档按 0 计) |
| travelerComposition | String | 出行人构成(数量为 0 的档不显示,全空为 null),如「2大 1儿童 1幼童」 |
| primaryReporterId | Long(String) | 主报账人人员安排 ID,序列化为字符串 |
| primaryReporterName | String | 主报账人姓名 |
| primaryReporterRole | String | 主报账人角色(如 DRIVER) |
| primaryReporterCollectedAmount | BigDecimal | 主报账人代收金额(收入行合计),保留两位小数 |
| approvedAdvanceAmount | BigDecimal | 已审批预支金额(预支行合计),保留两位小数 |
| reportablePaidCostAmount | BigDecimal | 可报账已付成本(支出行合计),保留两位小数 |
| reporterNetAmount | BigDecimal | 报账人净额(代收 + 预支 - 支出);正 = 报账人应转回公司,负 = 公司应补报账人 |
| outstandingAmount | BigDecimal | 本次新增:整单未收尾款(口径同核单应收财务总览 outstandingAmount),保留两位小数 |
注:baseInfo 带 @JsonInclude(NON_NULL),null 字段不下发该 key,前端读取要兜底。
转账方向判定(transferAmount / transferDirection 均已删,前端自行推导):
- reporterNetAmount > 0 → 报账人应转回公司
- reporterNetAmount < 0 → 公司应补报账人
- 转账金额 = reporterNetAmount 的绝对值
incomeLines 元素字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| type | String | 行类型,当前仅 DRIVER_CASH_RECEIPT(司机/报账人现金收款) |
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
| receiptId | Long(String) | 线下收款记录 ID,序列化为字符串 |
| amount | BigDecimal | 收款金额,保留两位小数 |
| channel | String | 收款渠道:DRIVER_CASH / BANK_TRANSFER / CONSULTANT_COLLECTION |
| channelName | String | 收款渠道中文名(枚举 label) |
| payType | String | 收款款项类型:DEPOSIT / FULL / BALANCE |
| payTypeName | String | 款项类型中文名(枚举 label) |
| collectorStaffId | Long(String) | 收款人人员安排 ID,序列化为字符串 |
| collectorName | String | 收款人姓名 |
| collectorRole | String | 收款人角色(如 DRIVER) |
| collectorRoleName | String | 收款人角色中文名(字典 staff_role) |
| receivedAt | String | 收款时间,格式 yyyy-MM-dd HH:mm:ss |
| remark | String | 备注;无备注时不输出 |
expenseLines 元素字段表(统一 12 字段,所有费用分类共用):
| 字段 | 类型 | 说明 |
|---|---|---|
| category | String | 费用类别:HOTEL / TICKET / MEAL / VEHICLE / GUIDE / PHOTOGRAPHER / OTHER_EXPENSE / INSURANCE |
| categoryName | String | 费用类别中文名(字典 settlement_category) |
| itemName | String | 项目名(分类特有信息折叠:住宿=酒店-房型,门票=景区-规格,餐食=餐食名(餐类型),车辆=车牌 车型/司机,人员=姓名,其他=项目名) |
| unitPrice | BigDecimal | 单价,保留两位小数;无单价概念的分类不输出 |
| quantity | BigDecimal | 数量(住宿=房间数,餐食=份数,门票=票数,车辆按天每行=1);无数量概念的分类不输出 |
| amount | BigDecimal | 实际金额,保留两位小数 |
| reimburseAmount | BigDecimal | 报账金额,保留两位小数;人员行应报销与金额不同时分别给出,其余分类同 amount |
| paymentMethod | String | 付款方式;报账支出行固定 CASH_PAID |
| paymentMethodName | String | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码) |
| date | String | 业务日期(住宿=入住日,门票=游玩日,餐食=用餐日,车辆=服务日,人员=结算日),格式 yyyy-MM-dd;无日期概念的分类不输出 |
| remark | String | 备注;无备注时不输出 |
| voucherUrls | Array | 凭证 URL 数组;无凭证时不输出该键 |
advanceLines 元素字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| type | String | 行类型,当前仅 APPROVED_ADVANCE(已审批预支) |
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
| advanceId | Long(String) | 预支单 ID,序列化为字符串 |
| payeeStaffId | Long(String) | 借款对象人员安排 ID,序列化为字符串 |
| payeeName | String | 借款对象姓名 |
| payeeRole | String | 借款对象角色(如 DRIVER) |
| payeeRoleName | String | 借款对象角色中文名(字典 staff_role) |
| advanceType | String | 预支类型(如 ACCOMMODATION_DEPOSIT) |
| advanceTypeName | String | 预支类型中文名(字典 advance_type) |
| amount | BigDecimal | 预支金额,保留两位小数 |
| purpose | String | 预支用途 |
| voucherUrl | String | 凭证 URL |
| status | String | 预支状态:SUBMITTED / APPROVED / REJECTED |
| statusText | String | 预支状态中文名(枚举 label) |
| submittedAt | String | 提交时间,格式 yyyy-MM-dd HH:mm:ss |
| approvedAt | String | 审批时间,格式 yyyy-MM-dd HH:mm:ss |
| approvedBy | String | 审批人姓名 |
5.2 group 出参(Result,最终结构:全部平铺顶层)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long(String) | 单团核算记录 ID,序列化为字符串 |
| orderId | Long(String) | 订单 ID,序列化为字符串 |
| orderNo | String | 订单号 |
| teamNo | String | 团号(订金未支付为 null) |
| productName | String | 产品名 |
| productType | String | 产品类型枚举(如 CORE) |
| productTypeName | String | 产品类型中文名(字典 product_type 回填) |
| customerName | String | 客户姓名 |
| departDate | String | 出团日期,格式 yyyy-MM-dd |
| returnDate | String | 返回日期,格式 yyyy-MM-dd |
| consultantName | String | 定制师姓名 |
| travelerComposition | String | 出行人构成(数量为 0 的档不显示,全空为 null) |
| baseOrderAmount | BigDecimal | 订单应收(基础订单金额) |
| otherIncomeAmount | BigDecimal | 其他收入合计 |
| discountAmount | BigDecimal | 优惠金额(负数) |
| adjustedReceivableAmount | BigDecimal | 调整后应收 = baseOrderAmount + otherIncomeAmount + discountAmount |
| paidAmount | BigDecimal | 已收金额 |
| actualRefundedAmount | BigDecimal | 实际退款金额 |
| netRevenueAmount | BigDecimal | 净收入 = adjustedReceivableAmount - actualRefundedAmount |
| netReceivedAmount | BigDecimal | 净实收 = paidAmount - actualRefundedAmount |
| outstandingAmount | BigDecimal | 未收尾款(口径同 financial-overview) |
| hotelCost | BigDecimal | 住宿成本 |
| ticketCost | BigDecimal | 门票成本 |
| mealCost | BigDecimal | 餐食成本 |
| vehicleCost | BigDecimal | 车辆成本 |
| guideCost | BigDecimal | 导游成本 |
| photographerCost | BigDecimal | 摄影成本 |
| otherExpenseCost | BigDecimal | 其他支出成本 |
| insurancePremium | BigDecimal | 保险保费 |
| totalCost | BigDecimal | 总成本 |
| paidCost | BigDecimal | 已付成本 |
| unpaidCost | BigDecimal | 未付成本 |
| grossProfit | BigDecimal | 毛利 = netRevenueAmount - totalCost |
| grossProfitRate | BigDecimal | 毛利率(小数,netRevenueAmount=0 时填 0) |
| travelerCount | Integer | 出行人总数 |
| perCapitaRevenue | BigDecimal | 人均收入(travelerCount=0 时为 null) |
| perCapitaCost | BigDecimal | 人均成本 |
| perCapitaProfit | BigDecimal | 人均毛利 |
| incomeLines | Array | 收入行,固定 4 行(见下) |
| costCategories | Array | 成本分类行,固定 8 行(见下) |
incomeLines 元素(固定 4 行):
| 字段 | 类型 | 说明 |
|---|---|---|
| type | String | BASE_ORDER(订单应收)/ OTHER_INCOME(其他收入)/ DISCOUNT(优惠,负数)/ ACTUAL_REFUND(实际退款,负数) |
| typeName | String | 行类型中文名(字典 settlement_report_line_type) |
| amount | BigDecimal | 金额,保留两位小数;DISCOUNT / ACTUAL_REFUND 为负数 |
| details | Array | 收入逐项明细;无逐项时为空数组 [] |
costCategories 元素(固定 8 行):
| 字段 | 类型 | 说明 |
|---|---|---|
| category | String | HOTEL / TICKET / MEAL / VEHICLE / GUIDE / PHOTOGRAPHER / OTHER_EXPENSE / INSURANCE |
| categoryName | String | 费用类别中文名(字典 settlement_category) |
| amount | BigDecimal | 金额,保留两位小数 |
| lines | Array | 成本逐项明细(分类专属行结构,前端按 category 判别窄化);无逐项时为空数组 [] |
5.3 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) | 触发预警的核单明细行 id(settlement_hotel / settlement_ticket 主键),序列化为字符串;前端据此锚定/跳转具体明细行 |
| category | String | 核单分类枚举名,当前仅 HOTEL(住宿)/ TICKET(门票/游玩项目)两类会触发预警 |
| dayNumber | Integer | 行程第几天 |
| message | String | 预警文案,如「住宿 D2 现付缺凭证」 |
5.4 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 | 车辆基础服务总车费 |
| 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(空壳时也有值),含 approvedAmount(String,默认 "0")+ records(Array,默认 []) |
未核单空壳时:settled=false,仅 orderId 与 advanceSummary(默认空对象)有值,其余字段全部为 null。
6. 枚举 / 数据字典
| 字段 | 来源 | 取值 |
|---|---|---|
| expenseLines[].category / costCategories[].category | settlement_category 字典 | HOTEL(住宿)/ TICKET(门票)/ MEAL(餐食)/ VEHICLE(车辆)/ GUIDE(导游)/ PHOTOGRAPHER(摄影)/ OTHER_EXPENSE(其他支出)/ INSURANCE(保险) |
| expenseLines[].paymentMethod | settlement_payment_method 字典 | CASH_PAID(现金已付)/ COMPANY_PAID(公司直付)/ SIGNED(签单);报账支出行固定 CASH_PAID |
| incomeLines[].type / advanceLines[].type | settlement_report_line_type 字典 | DRIVER_CASH_RECEIPT / APPROVED_ADVANCE / BASE_ORDER / OTHER_INCOME / DISCOUNT / ACTUAL_REFUND |
| incomeLines[].channel(reimbursement) | PaymentChannelEnum 枚举 | DRIVER_CASH / BANK_TRANSFER / CONSULTANT_COLLECTION |
| incomeLines[].payType(reimbursement) | PayType 枚举 | DEPOSIT(订金)/ FULL(全款)/ BALANCE(尾款) |
| collectorRole / payeeRole / primaryReporterRole | staff_role 字典 | DRIVER(司机)/ GUIDE(导游)等 |
| advanceLines[].advanceType | advance_type 字典 | 如 ACCOMMODATION_DEPOSIT(住宿押金)等 |
| advanceLines[].status | AdvanceStatus 枚举 | SUBMITTED / APPROVED / REJECTED |
| productType | product_type 字典 | 如 CORE(核心产品)等 |
| warnings[].category | SettlementCategory 枚举 | 全量同 settlement_category;当前预警只会出现 HOTEL / TICKET |
| finalSnapshotStatus | 快照状态枚举 | FINALIZED(已定稿) |
本次无枚举值增删;category 只是从隐含在预警文案里变成显式字段。
7. 错误码
7.1 reimbursement / group(两报表)
| code | message | 触发场景 |
|---|---|---|
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
| 581007 | 订单不存在 | orderId 查不到订单 |
7.2 finalize
| code | message | 触发场景 |
|---|---|---|
| 581007 | 订单不存在 | orderId 查不到订单 |
| 584081 | 对账数据不一致:已付金额与支付流水和线下收款合计不符,禁止带病结算 | paid_amount 与实际入账总额不符 |
| 584085 | 存在辅助人员结算未完成,请全部结算后提交 | 任一非主报账司机费用行未结算完成或缺转账凭证号 |
| 584321 | 当前订单缺少核单终态快照,请重新完成核单 | 缺终态快照(含重复提交场景) |
| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 |
| 403 | 无权限访问 | 房务角色(HOUSE)JWT 调用 |
finalize 还有人员费用 / 其他收入未就绪等前置门禁错误码(584xxx 段),非本次变更,按响应 message 直接提示即可。
7.3 summary
只读接口,无业务错误码;orderId 查不到快照时返回 200 + 空壳对象(settled=false),不会报「订单不存在」。
8. 示例
8.1 典型成功
reimbursement(最终结构:抬头在 baseInfo 内,无 orderHeader 块):
GET /v3/admin/order/2087088947225038849/settlement/reports/reimbursement
{
"code": 200,
"message": "成功",
"data": {
"baseInfo": {
"id": "6001",
"orderId": "2087088947225038849",
"orderNo": "HL20260730001",
"teamNo": "T20260730001",
"productName": "呼伦贝尔草原 5 日游",
"productType": "CORE",
"productTypeName": "核心产品",
"customerName": "张三",
"departDate": "2026-07-30",
"returnDate": "2026-08-03",
"consultantName": "李四",
"travelerCount": 5,
"travelerComposition": "2大 2儿童 1幼童",
"primaryReporterId": "7001",
"primaryReporterName": "司机甲",
"primaryReporterRole": "DRIVER",
"primaryReporterCollectedAmount": 2000.00,
"approvedAdvanceAmount": 500.00,
"reportablePaidCostAmount": 1800.00,
"reporterNetAmount": 700.00,
"outstandingAmount": 0.00
},
"incomeLines": [
{
"type": "DRIVER_CASH_RECEIPT",
"typeName": "司机现金收款",
"receiptId": "8001",
"amount": 2000.00,
"channel": "DRIVER_CASH",
"channelName": "报账人收款",
"payType": "BALANCE",
"payTypeName": "尾款",
"collectorStaffId": "7001",
"collectorName": "司机甲",
"collectorRole": "DRIVER",
"collectorRoleName": "司机",
"receivedAt": "2026-07-30 18:20:30",
"remark": "尾款现金"
}
],
"expenseLines": [
{
"category": "HOTEL",
"categoryName": "住宿",
"itemName": "草原明珠大酒店-标间",
"unitPrice": 400.00,
"quantity": 3,
"amount": 1200.00,
"reimburseAmount": 1200.00,
"paymentMethod": "CASH_PAID",
"paymentMethodName": "现金已付",
"date": "2026-07-30",
"remark": "含早",
"voucherUrls": ["https://oss/v1.jpg"]
}
],
"advanceLines": [
{
"type": "APPROVED_ADVANCE",
"typeName": "已审批预支",
"advanceId": "5001",
"payeeStaffId": "7001",
"payeeName": "司机甲",
"payeeRole": "DRIVER",
"payeeRoleName": "司机",
"advanceType": "ACCOMMODATION_DEPOSIT",
"advanceTypeName": "住宿押金",
"amount": 500.00,
"purpose": "酒店押金",
"voucherUrl": "https://oss/advance-v1.jpg",
"status": "APPROVED",
"statusText": "已通过",
"submittedAt": "2026-07-28 10:00:00",
"approvedAt": "2026-07-28 12:00:00",
"approvedBy": "财务丙"
}
]
},
"success": true
}
group(最终结构:抬头平铺顶层,无 orderHeader 块):
GET /v3/admin/order/2087088947225038849/settlement/reports/group
{
"code": 200,
"message": "成功",
"data": {
"id": "6002",
"orderId": "2087088947225038849",
"orderNo": "HL20260730001",
"teamNo": "T20260730001",
"productName": "呼伦贝尔草原 5 日游",
"productType": "CORE",
"productTypeName": "核心产品",
"customerName": "张三",
"departDate": "2026-07-30",
"returnDate": "2026-08-03",
"consultantName": "李四",
"travelerComposition": "2大 2儿童 1幼童",
"baseOrderAmount": 12800.00,
"otherIncomeAmount": 600.00,
"discountAmount": -200.00,
"adjustedReceivableAmount": 13200.00,
"paidAmount": 13200.00,
"actualRefundedAmount": 0.00,
"netRevenueAmount": 13200.00,
"netReceivedAmount": 13200.00,
"outstandingAmount": 0.00,
"hotelCost": 3600.00,
"ticketCost": 1500.00,
"mealCost": 900.00,
"vehicleCost": 2500.00,
"guideCost": 800.00,
"photographerCost": 0.00,
"otherExpenseCost": 300.00,
"insurancePremium": 180.00,
"totalCost": 9780.00,
"paidCost": 9780.00,
"unpaidCost": 0.00,
"grossProfit": 3420.00,
"grossProfitRate": 0.2591,
"travelerCount": 5,
"perCapitaRevenue": 2640.00,
"perCapitaCost": 1956.00,
"perCapitaProfit": 684.00,
"incomeLines": [
{ "type": "BASE_ORDER", "typeName": "订单应收", "amount": 12800.00, "details": [] },
{ "type": "OTHER_INCOME", "typeName": "其他收入", "amount": 600.00, "details": [] },
{ "type": "DISCOUNT", "typeName": "优惠", "amount": -200.00, "details": [] },
{ "type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": 0.00, "details": [] }
],
"costCategories": [
{ "category": "HOTEL", "categoryName": "住宿", "amount": 3600.00, "lines": [] },
{ "category": "TICKET", "categoryName": "门票", "amount": 1500.00, "lines": [] },
{ "category": "MEAL", "categoryName": "餐食", "amount": 900.00, "lines": [] },
{ "category": "VEHICLE", "categoryName": "车辆", "amount": 2500.00, "lines": [] },
{ "category": "GUIDE", "categoryName": "导游", "amount": 800.00, "lines": [] },
{ "category": "PHOTOGRAPHER", "categoryName": "摄影", "amount": 0.00, "lines": [] },
{ "category": "OTHER_EXPENSE", "categoryName": "其他支出", "amount": 300.00, "lines": [] },
{ "category": "INSURANCE", "categoryName": "保险", "amount": 180.00, "lines": [] }
]
},
"success": true
}
8.2 边界情况
边界 1:订金未支付、无团号——reimbursement 的 baseInfo 因 NON_NULL 不输出 teamNo 键;group 顶层 teamNo 为 null。前端两处都要兜底:
"baseInfo": {
"orderId": "2087088947225038850",
"orderNo": "HL20260801002",
"productName": "库布其沙漠 2 日游",
"outstandingAmount": 3200.00
}
边界 2:无任何明细行——incomeLines / expenseLines / advanceLines 均为空数组 [],不是 null:
{ "incomeLines": [], "expenseLines": [], "advanceLines": [] }
边界 3:finalize 无软预警 + summary 查未核单订单——warnings 为空数组 [];summary 返回空壳(settled=false,除 orderId / advanceSummary 外全 null):
{ "code": 200, "data": { "settled": false, "id": null, "orderId": "2087088947225038850", "totalAmount": null, "advanceSummary": { "approvedAmount": "0", "records": [] } }, "success": true }
8.3 业务失败
finalize 辅助人员结算未完成:
{ "code": 584085, "message": "存在辅助人员结算未完成,请全部结算后提交", "success": false }
房务角色访问报表被拦(House 角色 JWT 调 reimbursement / group / finalize):
{ "code": 403, "message": "无权限访问", "success": false }
订单不存在:
{ "code": 581007, "message": "订单不存在", "success": false }
9. 业务边界
适用:
- reimbursement:订单核单页查看主报账人结算报表;支出行只含报账人垫付(CASH_PAID)支出,签单/公司直付不进本列表
- group:整团收入成本毛利核算;incomeLines 固定 4 行、costCategories 固定 8 行恒下发(金额为 0 的行也在)
- finalize:核单明细已逐步保存完成、核对无误后提交
- summary:随时读取汇总;未核单订单也可调(返回空壳),前端据此展示「尚未核单」占位
不适用:
- reimbursement:想看待收整单尾款时不要再用已删的 primaryReporterDueAmount(主报账人维度),改读 outstandingAmount(整单维度)
- finalize:明细未保存完整 / 辅助人员结算未完成 / 对账不一致的订单(被 584xxx 门禁拦截,见 §7)
- summary:想看明细行级数据时不要用它,走各分类明细接口;本接口只有汇总快照
特殊边界:
- 报表的「报表状态 / 生成人 / 确认人」审计信息不再下发(DB 仍保留),前端如曾展示「已生成/已确认」徽标需删除该 UI
- warnings 是软预警,不阻塞 finalize 提交;有预警也照常返回 200 完成核单
- 预警仅覆盖 CASH_PAID(现付)且 voucher_urls 为空的住宿 / 门票明细行;签单、公司直付等其他支付方式不产生预警
- summary 空壳的 advanceSummary 恒为默认对象(approvedAmount="0"、records=[]),不是 null,不能拿它当判空依据
10. 修改前后对比
「修改前」= 两次变更前的旧结构(含 orderHeader 子块 + 审计字段);「修改后」= 本份最终态。
10.1 字段级对比
| 接口 | 字段 | 修改前 | 修改后 |
|---|---|---|---|
| reimbursement | 顶层 orderHeader | Object 子块(11 个抬头字段) | 删除,11 字段拍平进 baseInfo |
| reimbursement | baseInfo.reportStatus / reportVersion | 有 | 删除 |
| reimbursement | baseInfo.generatedBy / generatedByName / generatedAt | 有 | 删除 |
| reimbursement | baseInfo.confirmedBy / confirmedByName / confirmedAt | 有 | 删除 |
| reimbursement | baseInfo.transferAmount | 有(净额绝对值) | 删除,前端用 reporterNetAmount 绝对值自取 |
| reimbursement | baseInfo.primaryReporterDueAmount | 有(主报账人维度应收尾款) | 删除,由 outstandingAmount 替代 |
| reimbursement | baseInfo.outstandingAmount | 无 | 新增:整单未收尾款(口径同 financial-overview) |
| group | 顶层 orderHeader | Object 子块(10 个抬头字段) | 删除,10 字段平铺到顶层 |
| group | reportStatus | 有 | 删除 |
| group | generatedBy / generatedByName / generatedAt | 有 | 删除 |
| group | confirmedBy / confirmedByName / confirmedAt | 有 | 删除 |
| finalize | warnings | List,元素如「住宿 D2 现付缺凭证」字符串 | List,元素为对象 { settlementId, category, dayNumber, message } |
| summary | settled | 无此字段 | 新增 Boolean:已核单 = true,未核单空壳 = false |
10.2 行为级对比
| 场景 | 修改前 | 修改后 |
|---|---|---|
| 读订单抬头(两报表) | 读 orderHeader.orderNo 等 | 读 baseInfo.orderNo(reimbursement)/ 顶层 orderNo(group) |
| 展示「已生成/已确认」徽标 | 读 reportStatus / confirmedAt | 字段已删除,恒取不到值,UI 必须删除该徽标 |
| 展示转账金额/方向 | 读 transferAmount / 推导方向 | 用 reporterNetAmount 正负推导方向,金额取绝对值 |
| 展示应收尾款(reimbursement) | 读 primaryReporterDueAmount(主报账人维度) | 读 outstandingAmount(整单维度,与财务总览一致) |
| 同日多条同类缺凭证预警 | 多条相同文案,无法区分哪一行 | 每条带独立 settlementId,可定位/跳转具体明细行 |
| 前端渲染 finalize 预警 | 直接渲染字符串 | 必须读 warnings[].message |
| 前端判「是否已核单」(summary) | 判 data.id == null(不可靠) | 判 data.settled === false |
11. 影响评估 / 回滚
11.1 影响评估
- 破坏兼容性:⚠️ 破坏性。删 orderHeader 块 + 删 baseInfo/顶层多字段 + finalize warnings 结构变化,按旧结构取值的前端代码会拿到 undefined / 渲染异常。
- 前端必须同步上线:是。前端需要:
- reimbursement 抬头取值从 orderHeader.xxx 改 baseInfo.xxx;group 抬头取值从 orderHeader.xxx 改顶层 xxx
- 删除「报表状态 / 生成人 / 确认人」相关 UI(字段已删,恒 undefined)
- 转账金额改取 reporterNetAmount 绝对值,方向按 reporterNetAmount 正负推导
- 应收尾款改读 outstandingAmount(注意口径已从主报账人维度变整单维度,展示文案如有「主报账人应收」字样需同步改)
- finalize warnings 渲染改读 item.message,可用 settlementId + category 做行锚定
- summary 判「是否已核单」改读 settled 字段
- 后端兼容:零 DDL、无数据迁移;DB 的 report_status / 版本 / 审计列保留,finalize 幂等与反确认逻辑不受影响。
- 本份覆盖旧份:此前 2026-08-12 推送的 12_5876_核单finalize-summary出参整改-修改接口-管理后台.md 内容已并入本份;两报表结构以本份为准。
11.2 回滚方案
- 后端回滚 = 依次 revert PR #5920 的 merge commit(b912ff4876)与 PR #5881 的 merge commit(125bb9c96e),重启 hl-order-service-v3。只 revert b912ff4876 则回到 #5876 后的中间态(orderHeader 块恢复 + 审计字段恢复,warnings/settled 保留)。
- 前端回滚 = 切回旧版前端包。注意新旧前后端要配套:新前端 + 旧后端时 baseInfo.orderNo / outstandingAmount / settled 取不到值。
- 零 DDL,回滚无残留风险。
12. 注意事项
- 所有 Long 主键字段序列化为 JSON 字符串(id / orderId / primaryReporterId / receiptId / collectorStaffId / advanceId / payeeStaffId / settlementId 等,防 JS 精度丢失),前端按 string 处理,不要 Number() 转换。
- reimbursement 的 baseInfo / incomeLines / expenseLines / advanceLines 均带 @JsonInclude(NON_NULL):null 字段不下发该 key(不是下 null),前端读取必须兜底,不能假设 key 恒存在。
- group 出参不做 NON_NULL 裁剪,teamNo 等可空字段会下发 null,注意与 reimbursement 的差异。
- expenseLines 支出行只含报账人垫付(CASH_PAID)支出;签单 / 公司直付不在本列表,不要拿 expenseLines 合计去对整单成本(整单成本看 group 报表)。
- group 的 incomeLines 固定 4 行、costCategories 固定 8 行恒下发(含金额 0 的行),前端按 type / category 取值渲染即可,不要按数组下标硬编码。
- outstandingAmount 是整单未收尾款(与核单应收财务总览同口径),不是主报账人维度;两报表该字段口径一致。
- warnings 无预警时返回空数组 [] 不是 null;category 当前只会出现 HOTEL / TICKET,做映射表建议按全量 settlement_category 枚举写,缺值兜底显示原 code。
- summary 判空只认 settled:settled === false 即「尚未核单」,此时除 orderId / advanceSummary 外全部字段为 null,展示必须兜底。
- finalize 是写操作且有前置门禁,失败按错误码 message 提示,不要静默重试;重复提交会被快照类错误码拦截。
- finalize / summary 的金额类字段序列化为字符串;两报表(reimbursement / group)的金额类字段为 JSON 数值(BigDecimal 两位小数),两类接口序列化风格不同,前端注意区分。
13. 关联 / 联系人
- Issue #5876:wx/HL#5876
- Issue #5916:wx/HL#5916
- PR #5881:wx/HL#5881
- PR #5920:wx/HL#5920
- Commit(#5876):https://git.1814.love:8443/wx/HL/commit/125bb9c96e
- Commit(#5916 最终态):https://git.1814.love:8443/wx/HL/commit/b912ff4876
- 服务:hl-order-service-v3(端口 8086)
- 后端负责人:yst(腰苏图)