修改原因:#5310、#5320、#5342、#5343 已由管理后台完成消费并通过最终验证。 修改内容:将四份 changelog 标记为 implemented,记录 v2.1 业务提交、目标版本、验证时间和最终契约说明。 实际验证:业务仓库 pnpm checkpoint 全部通过;业务提交 1444fc7f0bf34efaec0ee9f775f7d529b609b847 已推送 origin/v2.1。 Changelog:changelogs-v2/2026-07/28_5310_*;changelogs-v2/2026-07/29_5320_*、5342_*、5343_*。
36 KiB
schema, ticket, title, consumer, 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 | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 5342 | 核单报表取消中间确认并由 finalize 固化 | admin | 修改接口 | deployed | verified | implemented | pi:019fadb7-dac9-74bf-9581-058835251208 | 1444fc7f0bf34efaec0ee9f775f7d529b609b847 | v2.1 | 2026-07-29T20:43:00+08:00 | 管理后台已删除两张报表的中间确认调用;finalize 请求结构按后续 #5343 最终契约收口,pnpm checkpoint 全部通过。 | 2026-07-29 | dev-v3 |
【修改接口·管理后台】核单报表取消中间确认并由 finalize 固化 (#5342)
PR: #5345 | 服务: hl-order-service-v3 | 更新时间: 2026-07-29 13:28
1. 接口背景
核单流程不再要求用户分别确认“主报账人报账表”和“单团核算表”。核单未完成时,两张报表查询接口按当前业务数据实时返回;点击完成核单时,前端一次性提交转账、垫资结清和签字凭证信息,服务端按提交时的当前数据重新计算并固化终态。核单完成后,两张报表查询接口只返回该次完成核单时固化的内容,后续来源数据变化不会改写该终态结果。
变更接口(2. 变更清单)
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询主报账人报账表 | GET | /v3/admin/order/:orderId/settlement/reports/reimbursement |
行为修改 | 未完成核单时实时计算;完成核单后读取终态结果 |
| 2 | 查询单团核算表 | GET | /v3/admin/order/:orderId/settlement/reports/group |
行为修改 | 未完成核单时实时计算;完成核单后读取终态结果 |
| 3 | 确认主报账人报账表 | POST | /v3/admin/order/:orderId/settlement/reports/reimbursement/confirm |
删除 | 接口下线,调用返回业务码 404 |
| 4 | 确认单团核算表 | POST | /v3/admin/order/:orderId/settlement/reports/group/confirm |
删除 | 接口下线,调用返回业务码 404 |
| 5 | 完成核单 | POST | /v3/admin/order/:orderId/settlement/finalize |
请求与行为修改 | 请求体改为必填;删除两个客户端指纹;新增转账、垫资和签字凭证字段;提交时实时重算并固化终态 |
3. 接口详情
3.1 查询主报账人报账表
- 方法 + 路径:
GET /v3/admin/order/:orderId/settlement/reports/reimbursement - 使用场景: 打开核单报账表或刷新核单数据
- 认证: 管理后台 JWT;房务角色不可访问
- 幂等性: 幂等,只读
- 请求体: 无
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
String/Long | 是 | 订单 ID,必须大于 0 |
响应字段
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
id |
String | 是 | 报账表记录 ID;实时报表和终态快照中可为 null |
orderId |
String | 否 | 订单 ID |
reportStatus |
String | 否 | 报表状态,见 §6.1 |
sourceFingerprint |
String | 否 | 当前报表来源指纹,仅用于识别数据版本;前端不再回传 |
primaryReporterId |
String | 是 | 主报账人 ID |
primaryReporterName |
String | 是 | 主报账人姓名 |
primaryReporterRole |
String | 是 | 主报账人角色 |
reportVersion |
Integer | 否 | 报账表结构版本 |
driverCollectedTailAmount |
Decimal | 否 | 主报账人代收尾款 |
approvedAdvanceAmount |
Decimal | 否 | 已审批垫资金额 |
reportablePaidCostAmount |
Decimal | 否 | 可报账的已付成本 |
reporterNetAmount |
Decimal | 否 | 报账人净额;大于 0 表示报账人应转给公司,小于 0 表示公司应转给报账人 |
primaryReporterCollectedAmount |
Decimal | 否 | 主报账人代收金额 |
publicPrepaidAmount |
Decimal | 否 | 公共预支金额 |
primaryReporterDueAmount |
Decimal | 否 | 主报账人应报账金额 |
advanceOutstandingAmount |
Decimal | 否 | 未结清垫资金额 |
reconNetAmount |
Decimal | 否 | 报账净额 |
transferDirection |
String | 否 | 转账方向,见 §6.2 |
transferAmount |
Decimal | 否 | 应转账金额的绝对值 |
incomeLines |
Array<Object> | 否 | 主报账人代收明细,字段见下表 |
expenseLines |
Array<Object> | 否 | 现金已付成本明细,字段随费用分类变化,字段见下表 |
advanceLines |
Array<Object> | 否 | 已审批垫资明细,字段见下表 |
vehicleLines |
Array<Object> | 否 | 车辆独立明细;当前返回空数组,车辆金额已进入费用分类和汇总金额 |
transferStatus |
String | 是 | 未完成核单时为 null;终态为 COMPLETED |
transferDate |
String/date | 是 | 转账日期,格式 YYYY-MM-DD |
transferRef |
String | 是 | 转账流水号 |
advanceSettledFlag |
Boolean | 是 | 垫资是否结清 |
signedVoucher |
Object | 是 | 签字凭证;结构同 finalize 请求的 signedVoucher |
generatedBy |
String | 是 | 历史生成操作人 ID;实时/终态模式下可为 null |
generatedByName |
String | 是 | 历史生成操作人姓名;实时/终态模式下可为 null |
generatedAt |
String/date-time | 是 | 历史生成时间;实时/终态模式下可为 null |
confirmedBy |
String | 是 | 完成核单操作人 ID;未完成核单时为 null |
confirmedByName |
String | 是 | 完成核单操作人姓名;未完成核单时为 null |
confirmedAt |
String/date-time | 是 | 完成核单时间;未完成核单时为 null |
incomeLines[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
type |
String | 固定为 DRIVER_CASH_RECEIPT |
receiptId |
String | 收款记录 ID |
amount |
Decimal | 收款金额 |
channel |
String | 收款渠道;当前参与报账的值为 DRIVER_CASH |
payType |
String/null | 支付类型 |
collectorStaffId |
String/null | 收款人员 ID |
collectorName |
String/null | 收款人员姓名 |
collectorRole |
String/null | 收款人员角色 |
receivedAt |
String/date-time/null | 收款时间 |
remark |
String/null | 备注 |
advanceLines[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
type |
String | 固定为 APPROVED_ADVANCE |
advanceId |
String | 垫资记录 ID |
payeeStaffId |
String/null | 收款人员 ID |
payeeName |
String/null | 收款人员姓名 |
payeeRole |
String/null | 收款人员角色 |
advanceType |
String/null | 垫资类型 |
amount |
Decimal | 已审批金额 |
purpose |
String/null | 用途 |
voucherUrl |
String/null | 垫资凭证地址 |
status |
String | 垫资状态 |
submittedAt |
String/date-time/null | 提交时间 |
approvedAt |
String/date-time/null | 审批时间 |
approvedBy |
String/null | 审批人 ID |
expenseLines[] 公共字段
| 字段 | 类型 | 说明 |
|---|---|---|
category |
String | 费用分类,见 §6.3 |
kind |
String | 明细类型,例如 HOTEL、TICKET、MEAL、VEHICLE_FEE、STAFF:DRIVER |
amount |
Decimal | 当前行实际成本 |
paymentMethod |
String | 当前仅包含 CASH_PAID 行 |
expenseLines[] 会按 kind 追加以下分类字段:
HOTEL:hotelAssignmentId、hotelId、roomTypeId、dayNumber、stayDate、hotelName、roomType、roomTypeName、roomCount、unitPrice、plannedCost、sourceType、sourceId、voucherUrls、remark。TICKET:sourceType、scenicAssignmentId、dayNumber、dayDate、scenicName、specName、ticketCount、ticketUnitPrice、sellPrice、totalAmount、plannedCost、voucherUrls、remark。MEAL:mealType、mealDate、mealName、quantity、unitPrice、voucherUrls、remark。VEHICLE_FEE:sourceRecordType、sourceDetailId、serviceDate、vehicleId、vehiclePlate、vehicleModelId、vehicleModelName、driverId、driverName、startDate、endDate、dailyPrice、paymentTypeCode、paymentTypeName。STAFF:*:staffRole、staffId、staffName、totalPlannedCost、voucherUrls、reimburse、settleStatus、settledDate、transferRef、detail、remark。EXPENSE:*:expenseType、projectName、expenseDate、voucherUrls、remark。SUBSIDY:*:subsidyType、projectName、expenseDate、voucherUrls、remark。
行为
- 订单不存在当前终态快照时,每次请求均按当前核单数据计算,
reportStatus=GENERATED。 - 订单存在当前终态快照时,返回完成核单时固化的报账表,
reportStatus=CONFIRMED。 sourceFingerprint继续返回,但不再作为任何前端确认或 finalize 入参。
3.2 查询单团核算表
- 方法 + 路径:
GET /v3/admin/order/:orderId/settlement/reports/group - 使用场景: 打开单团核算表或刷新核算结果
- 认证: 管理后台 JWT;房务角色不可访问
- 幂等性: 幂等,只读
- 请求体: 无
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
String/Long | 是 | 订单 ID,必须大于 0 |
响应字段
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
id |
String | 是 | 单团核算表记录 ID;实时报表和终态快照中可为 null |
orderId |
String | 否 | 订单 ID |
reportStatus |
String | 否 | 报表状态,见 §6.1 |
sourceFingerprint |
String | 否 | 当前报表来源指纹,仅用于识别数据版本;前端不再回传 |
baseOrderAmount |
Decimal | 否 | 订单基础金额 |
otherIncomeAmount |
Decimal | 否 | 其他收入金额 |
discountAmount |
Decimal | 否 | 优惠金额 |
adjustedReceivableAmount |
Decimal | 否 | 调整后应收金额 |
paidAmount |
Decimal | 否 | 已收金额 |
actualRefundedAmount |
Decimal | 否 | 实际退款金额 |
netRevenueAmount |
Decimal | 否 | 净收入 |
netReceivedAmount |
Decimal | 否 | 净已收 |
outstandingAmount |
Decimal | 否 | 待收金额 |
hotelCost |
Decimal | 否 | 住宿成本 |
ticketCost |
Decimal | 否 | 门票/游玩项目成本 |
mealCost |
Decimal | 否 | 餐食成本 |
vehicleCost |
Decimal | 否 | 车辆成本 |
guideCost |
Decimal | 否 | 导游/领队成本 |
photographerCost |
Decimal | 否 | 摄影成本 |
otherExpenseCost |
Decimal | 否 | 其他支出成本 |
insurancePremium |
Decimal | 否 | 保险保费 |
totalCost |
Decimal | 否 | 总成本 |
paidCost |
Decimal | 否 | 已付成本 |
unpaidCost |
Decimal | 否 | 未付成本 |
grossProfit |
Decimal | 否 | 毛利 |
grossProfitRate |
Decimal | 否 | 毛利率,小数形式 |
travelerCount |
Integer | 否 | 出行人数 |
perCapitaRevenue |
Decimal | 否 | 人均收入 |
perCapitaCost |
Decimal | 否 | 人均成本 |
perCapitaProfit |
Decimal | 否 | 人均利润 |
incomeLines |
Array<Object> | 否 | 收入汇总行,固定结构见下表 |
costCategories |
Array<Object> | 否 | 成本分类汇总,固定结构见下表 |
generatedBy |
String | 是 | 历史生成操作人 ID;实时/终态模式下可为 null |
generatedByName |
String | 是 | 历史生成操作人姓名;实时/终态模式下可为 null |
generatedAt |
String/date-time | 是 | 历史生成时间;实时/终态模式下可为 null |
confirmedBy |
String | 是 | 完成核单操作人 ID;未完成核单时为 null |
confirmedByName |
String | 是 | 完成核单操作人姓名;未完成核单时为 null |
confirmedAt |
String/date-time | 是 | 完成核单时间;未完成核单时为 null |
incomeLines[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
type |
String | BASE_ORDER、OTHER_INCOME、DISCOUNT 或 ACTUAL_REFUND |
amount |
Decimal | 金额;优惠和实际退款以负数返回 |
costCategories[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
category |
String | HOTEL、TICKET、MEAL、VEHICLE、GUIDE、PHOTOGRAPHER、OTHER_EXPENSE 或 INSURANCE |
amount |
Decimal | 分类成本 |
行为
- 订单不存在当前终态快照时,每次请求均按当前核单数据计算,
reportStatus=GENERATED。 - 订单存在当前终态快照时,返回完成核单时固化的单团核算表,
reportStatus=CONFIRMED。 sourceFingerprint继续返回,但不再作为任何前端确认或 finalize 入参。
3.3 完成核单
- 方法 + 路径:
POST /v3/admin/order/:orderId/settlement/finalize - 使用场景: 用户检查实时主报账表和单团核算表后,点击完成核单
- 认证: 管理后台 JWT;需要核单资金写权限;房务角色不可访问
- 幂等性: 已存在当前终态快照时,重复请求返回已有终态结果,不重新生成新终态
- 限流: 无接口级特殊限流
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
String/Long | 是 | 订单 ID,必须大于 0 |
请求体字段
| 字段 | 类型 | 必填 | 说明 | 校验规则 |
|---|---|---|---|---|
remark |
String | 否 | 核单整体备注 | 最多 500 字符 |
transferDate |
String/date | 条件必填 | 转账日期 | reporterNetAmount != 0 时必填;格式 YYYY-MM-DD |
transferRef |
String | 条件必填 | 转账流水号 | reporterNetAmount != 0 时必须为非空字符串;最多 128 字符 |
advanceSettledFlag |
Boolean | 是 | 垫资是否结清;必须明确传 true 或 false |
不可为 null |
signedVoucher |
Object | 是 | 签字凭证 | 不可为 null |
signedVoucher.files |
Array<Object> | 是 | 签字凭证文件 | 1~9 项;重复 URL 按规范化后的 URL 去重并保留首项 |
signedVoucher.files[].name |
String | 否 | 文件名 | 最多 255 字符 |
signedVoucher.files[].url |
String | 是 | 文件地址 | 非空;最多 1024 字符;必须是带有效主机名的绝对 http/https URL |
signedVoucher.note |
String | 否 | 签字凭证备注 | 最多 500 字符 |
以下字段已经删除,前端不得继续发送:
| 删除字段 | 原类型 | 迁移方式 |
|---|---|---|
reimbursementExpectedSourceFingerprint |
String | 删除本地缓存和提交逻辑;完成核单时不再回传报账表指纹 |
groupExpectedSourceFingerprint |
String | 删除本地缓存和提交逻辑;完成核单时不再回传单团核算表指纹 |
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
summaryId |
String | 核单汇总 ID |
finalSnapshotId |
String | 核单终态快照 ID |
finalSnapshotVersionNo |
Integer | 终态快照版本号,从 1 开始 |
finalSnapshotStatus |
String | 当前终态固定为 FINALIZED |
orderId |
String | 订单 ID |
settledAt |
String/date-time | 核单完成时间 |
totalAmount |
String/Decimal | 订单总金额快照 |
paidAmount |
String/Decimal | 已付金额快照 |
balanceAmount |
String/Decimal | 尾款金额快照 |
roomCost |
String/Decimal | 住宿实际成本 |
ticketCost |
String/Decimal | 门票实际成本 |
staffCost |
String/Decimal | 人员费用实际成本 |
subsidyCost |
String/Decimal | 补助实际成本 |
mealCost |
String/Decimal | 餐食实际成本 |
vehicleCost |
String/Decimal | 车辆基础服务总车费 |
otherExpenseCost |
String/Decimal | 其他支出实际成本 |
insurancePremium |
String/Decimal | 保险实际保费 |
totalActualCost |
String/Decimal | 总实际成本 |
driverTransferAmount |
String/Decimal | 给司机/主报账人转回金额 |
profitAmount |
String/Decimal | 公司毛利 |
profitRate |
Decimal | 毛利率;订单总金额为 0 时返回 0 |
orderStatusAfter |
String | 当前返回 待财务复核 |
mqTriggered |
Boolean | 当前固定返回 false;完成核单不发布结算 MQ |
warnings |
Array<String> | 软预警列表;不阻塞完成核单 |
提交行为
- 前端不再先调用任何报表“确认”接口。
- 服务端按提交时的当前核单数据重新计算两张报表和所有汇总金额,不采信前端缓存的金额或指纹。
transferDate、transferRef、advanceSettledFlag和signedVoucher与本次完成核单结果一并固化。- 成功后,两张 GET 报表接口返回本次固化结果。
3.4 已删除的报表确认接口
以下接口不再有可用请求契约:
| 原接口 | 原请求字段 | 当前结果 |
|---|---|---|
POST /v3/admin/order/:orderId/settlement/reports/reimbursement/confirm |
expectedSourceFingerprint、transferStatus、transferDate、transferRef、advanceSettledFlag、signedVoucher |
业务码 404 |
POST /v3/admin/order/:orderId/settlement/reports/group/confirm |
expectedSourceFingerprint |
业务码 404 |
前端必须删除这两个请求,不要用忽略 404、重试或降级继续调用的方式兼容。
4. 接口入参
4.1 通用路径参数
| 接口 | 字段 | 类型 | 必填 | 规则 |
|---|---|---|---|---|
| 两张 GET 报表、finalize | orderId |
String/Long | 是 | 必须大于 0 |
4.2 请求体变化总览
| 接口 | 修改前 | 修改后 |
|---|---|---|
| 主报账表确认 | 独立 POST 提交报账表指纹及凭证 | 接口删除 |
| 单团核算表确认 | 独立 POST 提交单团核算表指纹 | 接口删除 |
| finalize | 请求体可缺省;主要提交两个报告指纹和可选 remark |
请求体必填;提交 remark、transferDate、transferRef、advanceSettledFlag、signedVoucher;不再提交任何指纹 |
5. 出参
5.1 报表查询
- 两张 GET 接口的字段结构保持不变。
- 未完成核单时返回最新实时计算结果。
- 完成核单后返回完成核单时固化的结果。
- 报表
sourceFingerprint仍存在于出参,但只表示数据版本,前端不得再将其用于确认或 finalize。
5.2 完成核单
- finalize 响应字段结构保持
SettlementSubmitRespVO。 finalSnapshotId、finalSnapshotVersionNo、finalSnapshotStatus标识本次固化结果。mqTriggered的当前契约为固定false,前端不得用该字段判断是否需要等待 MQ。
6. 枚举 / 数据字典
6.1 reportStatus
所属字段: 两张报表响应 reportStatus | 类型: String
| 值 | 中文 | 当前语义 |
|---|---|---|
GENERATED |
实时结果 | 订单未完成核单,响应按当前数据实时计算 |
CONFIRMED |
已固化 | 订单已完成核单,响应来自终态结果 |
STALE |
历史过期状态 | 仅兼容历史报表数据;新实时查询流程不要求前端据此重新生成或确认 |
6.2 transferDirection
所属字段: 主报账人报账表 transferDirection | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
REPORTER_TO_COMPANY |
报账人转给公司 | reporterNetAmount > 0 |
COMPANY_TO_REPORTER |
公司转给报账人 | reporterNetAmount < 0 |
BALANCED |
已平衡 | reporterNetAmount = 0 |
6.3 报账费用分类
所属字段: expenseLines[].category、costCategories[].category | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
HOTEL |
住宿 | 住宿成本 |
TICKET |
门票/游玩项目 | 门票及游玩项目成本 |
MEAL |
餐食 | 餐食成本 |
VEHICLE |
车辆 | 车辆成本 |
GUIDE |
导游/领队 | 导游及领队成本 |
PHOTOGRAPHER |
摄影 | 摄影成本 |
OTHER_EXPENSE |
其他支出 | 其他支出成本 |
INSURANCE |
保险 | 保险保费;仅用于单团核算表成本分类 |
6.4 finalSnapshotStatus
所属字段: finalize 响应 finalSnapshotStatus | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
FINALIZED |
已固化 | 当前核单终态有效 |
6.5 transferStatus
所属字段: 主报账人报账表 transferStatus | 类型: String/null
| 值 | 中文 | 说明 |
|---|---|---|
COMPLETED |
已完成 | finalize 成功后固化到终态报账表 |
null |
未固化 | 尚未完成核单的实时报表 |
7. 错误码
| code | 含义 | 触发场景 |
|---|---|---|
400 |
请求参数校验失败 | orderId <= 0、finalize 缺请求体/必填字段、字段超长、凭证文件数量不在 1~9 |
403 |
无访问权限 | 房务角色或无权访问当前订单 |
404 |
接口不存在 | 继续调用两个已删除的 /confirm 接口 |
584051 |
当前核单状态不允许提交结算 | review 状态不是待核单或核单中 |
584056 |
订单已结算,不能重复提交 | 已存在结算汇总但缺少可返回的当前终态 |
584071 |
无权访问该订单(公司隔离) | 当前管理员不能查看该订单 |
584077 |
存在未确认的其他收入 | finalize 前其他收入未确认 |
584078 |
其他收入关联附加费已失效或金额不一致 | finalize 前其他收入与当前附加费不一致 |
584079 |
存在未纳入核单分类的有效附加费 | finalize 前还有有效附加费未进入核单 |
584081 |
对账数据不一致 | 已付金额与有效支付、线下收款合计不一致 |
584082 |
存在待收尾款 | 单团核算 outstandingAmount != 0 |
584085 |
存在辅助人员结算未完成 | 辅助人员未全部完成结算或缺转账流水 |
584092 |
存在未确认的人员费用 | 人员费用确认状态未全部完成 |
584097 |
凭证 URL 格式或数量不合法 | URL 不是有效绝对 http/https 地址、为空、过长或数量超限 |
584100 |
车辆总车费暂时不可用 | 报表查询或 finalize 暂时无法取得车辆费用 |
584101 |
存在未完结派车或未确认车辆总车费 | 当前车辆数据尚不能用于核单 |
584102 |
当前用车需求没有可核单的车辆总车费 | 有用车需求但没有可用车辆费用 |
584315 |
核单来源数据已变化,请刷新后重新确认 | finalize 提交期间当前用车需求发生变化 |
584316 |
核单报告发生并发变化,请刷新后重试 | 同一订单并发完成核单发生冲突 |
584317 |
当前报告状态不允许执行该操作 | 报账人净额非 0 但缺转账日期/流水,或签字凭证不可用 |
584320 |
核单分类明细尚未保存完整 | 当前分类数据不能用于报账和 finalize |
验证证据(8. 示例)
8.1 典型成功:查询实时主报账人报账表
请求
GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement
Authorization: Bearer <admin-jwt>
无请求体。
响应
{
"code": 200,
"msg": "success",
"data": {
"id": null,
"orderId": "1914050000000001",
"reportStatus": "GENERATED",
"sourceFingerprint": "7a0e84a9f9d0cb411f9cff8d6a0d1c047726bb05b68c8e23af5629afdbdd67c1",
"primaryReporterId": "3001",
"primaryReporterName": "示例报账人",
"primaryReporterRole": "DRIVER",
"reportVersion": 1,
"driverCollectedTailAmount": 2000.00,
"approvedAdvanceAmount": 500.00,
"reportablePaidCostAmount": 1200.00,
"reporterNetAmount": 1300.00,
"primaryReporterCollectedAmount": 2000.00,
"publicPrepaidAmount": 1200.00,
"primaryReporterDueAmount": 800.00,
"advanceOutstandingAmount": 500.00,
"reconNetAmount": 1300.00,
"transferDirection": "REPORTER_TO_COMPANY",
"transferAmount": 1300.00,
"incomeLines": [
{
"type": "DRIVER_CASH_RECEIPT",
"receiptId": "9100000000001",
"amount": 2000.00,
"channel": "DRIVER_CASH",
"payType": "CASH",
"collectorStaffId": "3001",
"collectorName": "示例报账人",
"collectorRole": "DRIVER",
"receivedAt": "2026-07-28T15:30:00",
"remark": "示例代收尾款"
}
],
"expenseLines": [
{
"category": "HOTEL",
"kind": "HOTEL",
"hotelAssignmentId": "9200000000001",
"hotelId": "1001",
"roomTypeId": "2001",
"dayNumber": 1,
"stayDate": "2026-07-20",
"hotelName": "示例酒店",
"roomType": "STANDARD",
"roomTypeName": "标准间",
"roomCount": 2,
"unitPrice": 300.00,
"plannedCost": 600.00,
"amount": 600.00,
"paymentMethod": "CASH_PAID",
"sourceType": "HOUSE_ASSIGNMENT",
"sourceId": "9200000000001",
"voucherUrls": ["https://oss.example.com/vouchers/hotel-1.pdf"],
"remark": null
}
],
"advanceLines": [
{
"type": "APPROVED_ADVANCE",
"advanceId": "9300000000001",
"payeeStaffId": "3001",
"payeeName": "示例报账人",
"payeeRole": "DRIVER",
"advanceType": "PUBLIC",
"amount": 500.00,
"purpose": "行程公共支出",
"voucherUrl": "https://oss.example.com/vouchers/advance-1.pdf",
"status": "APPROVED",
"submittedAt": "2026-07-19T10:00:00",
"approvedAt": "2026-07-19T11:00:00",
"approvedBy": "10001"
}
],
"vehicleLines": [],
"transferStatus": null,
"transferDate": null,
"transferRef": null,
"advanceSettledFlag": null,
"signedVoucher": null,
"generatedBy": null,
"generatedByName": null,
"generatedAt": null,
"confirmedBy": null,
"confirmedByName": null,
"confirmedAt": null
}
}
8.2 典型成功:查询实时单团核算表
请求
GET /v3/admin/order/1914050000000001/settlement/reports/group
Authorization: Bearer <admin-jwt>
无请求体。
响应
{
"code": 200,
"msg": "success",
"data": {
"id": null,
"orderId": "1914050000000001",
"reportStatus": "GENERATED",
"sourceFingerprint": "651cb6708a49f169e2ccb1b455267def9cc1a69d06935f9d9eeb41919897e0fb",
"baseOrderAmount": 24800.00,
"otherIncomeAmount": 500.00,
"discountAmount": 300.00,
"adjustedReceivableAmount": 25000.00,
"paidAmount": 25000.00,
"actualRefundedAmount": 0.00,
"netRevenueAmount": 25000.00,
"netReceivedAmount": 25000.00,
"outstandingAmount": 0.00,
"hotelCost": 4280.00,
"ticketCost": 3680.00,
"mealCost": 860.00,
"vehicleCost": 1260.00,
"guideCost": 800.00,
"photographerCost": 600.00,
"otherExpenseCost": 300.00,
"insurancePremium": 180.00,
"totalCost": 11960.00,
"paidCost": 11960.00,
"unpaidCost": 0.00,
"grossProfit": 13040.00,
"grossProfitRate": 0.521600,
"travelerCount": 5,
"perCapitaRevenue": 5000.00,
"perCapitaCost": 2392.00,
"perCapitaProfit": 2608.00,
"incomeLines": [
{
"type": "BASE_ORDER",
"amount": 24800.00
},
{
"type": "OTHER_INCOME",
"amount": 500.00
},
{
"type": "DISCOUNT",
"amount": -300.00
},
{
"type": "ACTUAL_REFUND",
"amount": 0.00
}
],
"costCategories": [
{
"category": "HOTEL",
"amount": 4280.00
},
{
"category": "TICKET",
"amount": 3680.00
},
{
"category": "MEAL",
"amount": 860.00
},
{
"category": "VEHICLE",
"amount": 1260.00
},
{
"category": "GUIDE",
"amount": 800.00
},
{
"category": "PHOTOGRAPHER",
"amount": 600.00
},
{
"category": "OTHER_EXPENSE",
"amount": 300.00
},
{
"category": "INSURANCE",
"amount": 180.00
}
],
"generatedBy": null,
"generatedByName": null,
"generatedAt": null,
"confirmedBy": null,
"confirmedByName": null,
"confirmedAt": null
}
}
8.3 典型成功:完成核单并固化
请求
POST /v3/admin/order/1914050000000001/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"remark": "核单完成",
"transferDate": "2026-07-29",
"transferRef": "BANK-20260729-001",
"advanceSettledFlag": true,
"signedVoucher": {
"files": [
{
"name": "签字单.pdf",
"url": "https://oss.example.com/vouchers/signed-20260729.pdf"
}
],
"note": "签字凭证已回收"
}
}
响应
{
"code": 200,
"msg": "success",
"data": {
"summaryId": "9400000000001",
"finalSnapshotId": "9400000000002",
"finalSnapshotVersionNo": 1,
"finalSnapshotStatus": "FINALIZED",
"orderId": "1914050000000001",
"settledAt": "2026-07-29T13:28:22",
"totalAmount": "25000.00",
"paidAmount": "25000.00",
"balanceAmount": "0.00",
"roomCost": "4280.00",
"ticketCost": "3680.00",
"staffCost": "1400.00",
"subsidyCost": "0.00",
"mealCost": "860.00",
"vehicleCost": "1260.00",
"otherExpenseCost": "300.00",
"insurancePremium": "180.00",
"totalActualCost": "11960.00",
"driverTransferAmount": "11780.00",
"profitAmount": "13040.00",
"profitRate": 0.5216,
"orderStatusAfter": "待财务复核",
"mqTriggered": false,
"warnings": []
}
}
8.4 边界:报账人净额为 0
当最新 reporterNetAmount=0 时,transferDate 和 transferRef 可以省略;advanceSettledFlag 和 signedVoucher 仍必须提交。
请求
POST /v3/admin/order/1914050000000002/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"remark": "收支已平衡",
"advanceSettledFlag": false,
"signedVoucher": {
"files": [
{
"name": "签字单.jpg",
"url": "https://oss.example.com/vouchers/signed-balanced.jpg"
}
]
}
}
响应
{
"code": 200,
"msg": "success",
"data": {
"summaryId": "9400000000011",
"finalSnapshotId": "9400000000012",
"finalSnapshotVersionNo": 1,
"finalSnapshotStatus": "FINALIZED",
"orderId": "1914050000000002",
"settledAt": "2026-07-29T13:40:00",
"totalAmount": "0.00",
"paidAmount": "0.00",
"balanceAmount": "0.00",
"roomCost": "0.00",
"ticketCost": "0.00",
"staffCost": "0.00",
"subsidyCost": "0.00",
"mealCost": "0.00",
"vehicleCost": "0.00",
"otherExpenseCost": "0.00",
"insurancePremium": "0.00",
"totalActualCost": "0.00",
"driverTransferAmount": "0.00",
"profitAmount": "0.00",
"profitRate": 0,
"orderStatusAfter": "待财务复核",
"mqTriggered": false,
"warnings": []
}
}
8.5 异常:仍调用已删除的确认接口
请求
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"expectedSourceFingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
响应
{
"code": 404,
"msg": "请求地址不存在",
"data": null
}
8.6 异常:车辆费用尚未可核单
请求
POST /v3/admin/order/1914050000000001/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"transferDate": "2026-07-29",
"transferRef": "BANK-20260729-001",
"advanceSettledFlag": true,
"signedVoucher": {
"files": [
{
"name": "签字单.pdf",
"url": "https://oss.example.com/vouchers/signed-20260729.pdf"
}
]
}
}
响应
{
"code": 584101,
"msg": "存在未完结派车或未确认车辆总车费,暂不能核单",
"data": null
}
9. 业务边界
- 报表 GET 的“实时”以每次请求时可用于核单的当前数据为准,前端不要把上一次响应当作提交凭据。
- 完成核单前,前端可以重复查询两张报表;无需执行任何“生成”或“确认”步骤。
- finalize 不接收前端金额。页面展示金额与提交时权威数据发生变化时,以提交时重新计算结果为准。
- 报账人净额不为
0时,必须同时提交transferDate和非空transferRef。 signedVoucher.files原始数组必须为1~9项;URL 会去除首尾空格、规范化并按 URL 去重。- 完成核单成功后,两张报表进入终态读取;只有业务上的核单反确认使当前终态失效后,查询才重新进入实时模式。
- finalize 成功响应中的
warnings是软预警,不表示提交失败。 - 已有当前终态时重复调用 finalize 返回已有终态,不会依据本次请求改写已固化的转账或凭证信息。
10. 修改前后对比
10.1 字段级对比
| 接口/字段 | 修改前 | 修改后 |
|---|---|---|
| finalize 请求体 | 可缺省 | 必填 |
remark |
可选,最多 500 字符 | 保持不变 |
reimbursementExpectedSourceFingerprint |
finalize 必填 | 删除 |
groupExpectedSourceFingerprint |
finalize 必填 | 删除 |
transferDate |
在主报账表确认接口提交 | 移至 finalize;报账人净额非 0 时必填 |
transferRef |
在主报账表确认接口提交 | 移至 finalize;报账人净额非 0 时必填,最多 128 字符 |
advanceSettledFlag |
在主报账表确认接口提交 | 移至 finalize,必填 |
signedVoucher |
在主报账表确认接口提交 | 移至 finalize,必填 |
signedVoucher.files |
原确认接口字段 | finalize 中要求 1~9 项 |
signedVoucher.files[].name |
原确认接口未明确长度 | 最多 255 字符 |
signedVoucher.files[].url |
原确认接口未明确长度 | 必填;最多 1024 字符;绝对 http/https URL |
signedVoucher.note |
原确认接口未明确长度 | 最多 500 字符 |
mqTriggered |
示例和历史说明可能按 true 理解 |
当前固定 false |
10.2 行为级对比
| 行为 | 修改前 | 修改后 |
|---|---|---|
| 主报账表 | 先读取,再调用独立 confirm | GET 实时读取;不再确认 |
| 单团核算表 | 主报账表确认后再读取并 confirm | GET 实时读取;不再确认 |
| 数据变化处理 | 前端携带两张报表指纹,指纹过期时刷新重试 | 前端不携带指纹;finalize 按提交时数据重算 |
| 转账/垫资/签字信息 | 主报账表 confirm 时提交 | finalize 时一次提交 |
| 完成核单后查询 | 依赖已确认报表记录 | 返回完成核单时固化的终态结果 |
| 重复 finalize | 依赖旧报告确认门禁 | 已有当前终态时返回已有结果 |
11. 影响评估 / 回滚
11.1 影响评估
- 是否破坏向后兼容: 是。两个 POST 确认接口删除,finalize 请求字段和必填规则改变。
- 前端是否必须同步调整: 是。旧页面继续调用
/confirm会收到业务码404;旧 finalize 请求缺少新必填字段会收到业务码400。 - 查询字段兼容性: 两张 GET 报表的顶层字段结构保持不变,但数据时效语义变为“未终态实时、终态固定”。
11.2 回滚说明
- 如果接口契约回滚,前端需要同步恢复两次确认请求和两个指纹字段。
- 前后端不能混用新旧流程:新版前端不再保留报表确认指纹,旧版后端仍会要求指纹和独立确认。
12. 注意事项
- 删除“确认主报账表”“确认单团核算表”按钮、请求封装、loading 状态、重试逻辑和指纹缓存。
- 页面展示仍调用两个 GET 接口;无需在详情加载时调用任何写接口。
- “完成核单”按钮直接提交 finalize 新请求体。
- 不要把 GET 返回的
sourceFingerprint填回 finalize。 - 不要继续发送已删除字段;即使服务端当前可能忽略未知 JSON 字段,前端类型和请求对象也应删除。
signedVoucher不是可选附件:至少需要一个有效 URL。mqTriggered=false是当前固定契约,不要显示“MQ 触发失败”或据此轮询。
13. 关联 / 联系人
13.1 链接
- Issue: #5342
- PR: #5345
- Merge commit: eb9ecfafad
13.2 联系人
- 后端负责人: @yst
- 消费端: v3 管理后台