修改原因:#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_*。
35 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 | 5343 | 核单确认收口到完成核单 | admin | 修改接口 | deployed | verified | implemented | pi:019fadb7-dac9-74bf-9581-058835251208 | 1444fc7f0bf34efaec0ee9f775f7d529b609b847 | v2.1 | 2026-07-29T20:43:00+08:00 | 管理后台已删除旧报表 confirm 流程,完成核单改为提交双指纹与嵌套 reimbursementConfirmation;pnpm checkpoint 全部通过。 | 2026-07-29 | dev-v3 |
⚠️【修改接口·管理后台】核单确认收口到完成核单 (#5343)
PR: #5347 | 服务: hl-order-service-v3 | 更新时间: 2026-07-29
1. 接口背景
主报账表和单团核算表不再各自提供“确认”写操作。页面先通过两张 GET 报表取得同一轮核单事实对应的两个 sourceFingerprint,再由“完成核单”一次提交双指纹、转账信息、预支处理标志和签字凭证。
本文纠正并取代 29_5342_核单报表取消中间确认并由finalize固化-修改接口-管理后台.md 中关于 finalize 请求的说明:双指纹没有删除,仍是 finalize 必填字段;转账与凭证字段必须放在必填的 reimbursementConfirmation 对象内。
2. 变更清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询主报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement |
行为明确 | 返回主报账数据及 sourceFingerprint,该指纹必须回传给 finalize |
| 2 | 查询单团核算表 | GET | /v3/admin/order/{orderId}/settlement/reports/group |
行为明确 | 返回单团核算数据及 sourceFingerprint,该指纹必须回传给 finalize |
| 3 | 完成核单 | POST | /v3/admin/order/{orderId}/settlement/finalize |
请求与行为修改 | 必填双指纹和嵌套 reimbursementConfirmation;成功后一次完成核单 |
| 4 | 确认主报账表 | POST | /v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm |
删除接口 | 路由继续保持删除,不得调用 |
| 5 | 确认单团核算表 | POST | /v3/admin/order/{orderId}/settlement/reports/group/confirm |
删除接口 | 路由继续保持删除,不得调用 |
3. 接口详情
3.1 查询主报账表
- 方法与路径:
GET /v3/admin/order/{orderId}/settlement/reports/reimbursement - 使用场景:展示主报账表,并在调用 finalize 前取得最新主报账指纹
- 认证:管理后台 JWT;房务角色不可访问
- 幂等性:幂等,只读
- 限流:无接口级特殊限流
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
String/Long | 是 | 订单 ID,必须大于 0 |
请求体
无。
响应字段
data 字段 |
JSON 类型 | 可空 | 说明 |
|---|---|---|---|
id |
string | 是 | 报账表记录 ID |
orderId |
string | 否 | 订单 ID |
reportStatus |
string | 否 | 报表状态,见 §6.1 |
sourceFingerprint |
string | 否 | 64 位小写十六进制 SHA-256;传给 finalize 的 reimbursementExpectedSourceFingerprint |
primaryReporterId |
string | 是 | 主报账人 ID |
primaryReporterName |
string | 是 | 主报账人姓名 |
primaryReporterRole |
string | 是 | 主报账人角色 |
reportVersion |
integer | 否 | 报账表结构版本 |
driverCollectedTailAmount |
number | 否 | 主报账人代收尾款 |
approvedAdvanceAmount |
number | 否 | 已审批预支金额 |
reportablePaidCostAmount |
number | 否 | 可报账的已付成本 |
reporterNetAmount |
number | 否 | 主报账人净额;决定转账日期和流水是否必填 |
primaryReporterCollectedAmount |
number | 否 | 主报账人代收金额 |
publicPrepaidAmount |
number | 否 | 公共预支金额 |
primaryReporterDueAmount |
number | 否 | 主报账人应报账金额 |
advanceOutstandingAmount |
number | 否 | 未结清预支金额 |
reconNetAmount |
number | 否 | 报账净额 |
transferDirection |
string | 否 | 转账方向,见 §6.2 |
transferAmount |
number | 否 | 应转账金额的绝对值 |
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 的凭证一致 |
generatedBy |
string | 是 | 历史生成操作人 ID |
generatedByName |
string | 是 | 历史生成操作人姓名 |
generatedAt |
string(date-time) | 是 | 历史生成时间 |
confirmedBy |
string | 是 | 完成核单操作人 ID |
confirmedByName |
string | 是 | 完成核单操作人姓名 |
confirmedAt |
string(date-time) | 是 | 完成核单时间 |
incomeLines[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string | 当前为 DRIVER_CASH_RECEIPT |
receiptId |
string | 收款记录 ID |
amount |
number | 收款金额 |
channel |
string | 收款渠道 |
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 |
number | 已审批金额 |
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 |
number | 当前行实际成本 |
paymentMethod |
string | 当前报账明细使用 CASH_PAID |
不同 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。
错误与业务边界
orderId <= 0返回400。- 房务角色或无订单访问权限返回
403/对应订单访问错误。 - 未完成核单时返回当前核单事实的实时视图和当前指纹。
- 已完成核单时返回当前有效终态版本中的报账表和该版本指纹。
- 管理员反确认后再次 GET 会回到实时视图;前端必须重新取得指纹。
典型请求
GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement
Authorization: Bearer <admin-jwt>
无请求体。
典型响应
{
"code": 200,
"message": "成功",
"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": [],
"expenseLines": [],
"advanceLines": [],
"vehicleLines": [],
"transferStatus": null,
"transferDate": null,
"transferRef": null,
"advanceSettledFlag": null,
"signedVoucher": null,
"generatedBy": null,
"generatedByName": null,
"generatedAt": null,
"confirmedBy": null,
"confirmedByName": null,
"confirmedAt": null
},
"traceId": "a1b2c3d4-e5f6-7890",
"success": true
}
3.2 查询单团核算表
- 方法与路径:
GET /v3/admin/order/{orderId}/settlement/reports/group - 使用场景:展示单团核算表,并在调用 finalize 前取得最新单团指纹
- 认证:管理后台 JWT;房务角色不可访问
- 幂等性:幂等,只读
- 限流:无接口级特殊限流
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
String/Long | 是 | 订单 ID,必须大于 0 |
请求体
无。
响应字段
data 字段 |
JSON 类型 | 可空 | 说明 |
|---|---|---|---|
id |
string | 是 | 单团核算表记录 ID |
orderId |
string | 否 | 订单 ID |
reportStatus |
string | 否 | 报表状态,见 §6.1 |
sourceFingerprint |
string | 否 | 64 位小写十六进制 SHA-256;传给 finalize 的 groupExpectedSourceFingerprint |
baseOrderAmount |
number | 否 | 订单基础金额 |
otherIncomeAmount |
number | 否 | 其他收入金额 |
discountAmount |
number | 否 | 优惠金额 |
adjustedReceivableAmount |
number | 否 | 调整后应收金额 |
paidAmount |
number | 否 | 已收金额 |
actualRefundedAmount |
number | 否 | 实际退款金额 |
netRevenueAmount |
number | 否 | 净收入 |
netReceivedAmount |
number | 否 | 净已收 |
outstandingAmount |
number | 否 | 待收金额;不为 0 时不能 finalize |
hotelCost |
number | 否 | 住宿成本 |
ticketCost |
number | 否 | 门票/游玩项目成本 |
mealCost |
number | 否 | 餐食成本 |
vehicleCost |
number | 否 | 车辆成本 |
guideCost |
number | 否 | 导游/领队成本 |
photographerCost |
number | 否 | 摄影成本 |
otherExpenseCost |
number | 否 | 其他支出成本 |
insurancePremium |
number | 否 | 保险保费 |
totalCost |
number | 否 | 总成本 |
paidCost |
number | 否 | 已付成本 |
unpaidCost |
number | 否 | 未付成本 |
grossProfit |
number | 否 | 毛利 |
grossProfitRate |
number | 否 | 毛利率,小数形式 |
travelerCount |
integer | 否 | 出行人数 |
perCapitaRevenue |
number | 否 | 人均收入 |
perCapitaCost |
number | 否 | 人均成本 |
perCapitaProfit |
number | 否 | 人均利润 |
incomeLines |
array<object> | 否 | 收入汇总行 |
costCategories |
array<object> | 否 | 成本分类汇总 |
generatedBy |
string | 是 | 历史生成操作人 ID |
generatedByName |
string | 是 | 历史生成操作人姓名 |
generatedAt |
string(date-time) | 是 | 历史生成时间 |
confirmedBy |
string | 是 | 完成核单操作人 ID |
confirmedByName |
string | 是 | 完成核单操作人姓名 |
confirmedAt |
string(date-time) | 是 | 完成核单时间 |
incomeLines[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string | BASE_ORDER、OTHER_INCOME、DISCOUNT 或 ACTUAL_REFUND |
amount |
number | 金额;优惠和实际退款以负数返回 |
costCategories[] 字段
| 字段 | 类型 | 说明 |
|---|---|---|
category |
string | HOTEL、TICKET、MEAL、VEHICLE、GUIDE、PHOTOGRAPHER、OTHER_EXPENSE 或 INSURANCE |
amount |
number | 分类成本 |
错误与业务边界
orderId <= 0返回400。- 房务角色或无订单访问权限返回
403/对应订单访问错误。 - 未完成核单时返回实时视图;已完成核单时返回当前有效终态版本。
- 管理员反确认后,下一次 GET 会生成新的实时结果和指纹。
典型请求
GET /v3/admin/order/1914050000000001/settlement/reports/group
Authorization: Bearer <admin-jwt>
无请求体。
典型响应
{
"code": 200,
"message": "成功",
"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": 5200.00,
"guideCost": 800.00,
"photographerCost": 600.00,
"otherExpenseCost": 1200.00,
"insurancePremium": 180.00,
"totalCost": 16800.00,
"paidCost": 16800.00,
"unpaidCost": 0.00,
"grossProfit": 8200.00,
"grossProfitRate": 0.328,
"travelerCount": 5,
"perCapitaRevenue": 5000.00,
"perCapitaCost": 3360.00,
"perCapitaProfit": 1640.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": 5200.00},
{"category": "GUIDE", "amount": 800.00},
{"category": "PHOTOGRAPHER", "amount": 600.00},
{"category": "OTHER_EXPENSE", "amount": 1200.00},
{"category": "INSURANCE", "amount": 180.00}
],
"generatedBy": null,
"generatedByName": null,
"generatedAt": null,
"confirmedBy": null,
"confirmedByName": null,
"confirmedAt": null
},
"traceId": "a1b2c3d4-e5f6-7890",
"success": true
}
3.3 完成核单
- 方法与路径:
POST /v3/admin/order/{orderId}/settlement/finalize - 使用场景:两张报表核对完成后,一次提交双指纹和主报账凭据
- 认证:管理后台 JWT;房务角色不可访问
- 幂等性:严格幂等,比较双指纹、规范化后的凭据和
remark - 限流:无接口级特殊限流
路径参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orderId |
String/Long | 是 | 订单 ID,必须大于 0 |
请求体字段
| 字段 | JSON 类型 | 必填 | 校验与规范化 |
|---|---|---|---|
remark |
string/null | 否 | 最长 500;去除首尾空格,空串按 null 比较 |
reimbursementExpectedSourceFingerprint |
string | 是 | 必须等于主报账 GET 返回的 64 位小写十六进制 sourceFingerprint |
groupExpectedSourceFingerprint |
string | 是 | 必须等于单团 GET 返回的 64 位小写十六进制 sourceFingerprint |
reimbursementConfirmation |
object | 是 | 主报账转账、预支和签字凭据 |
reimbursementConfirmation.transferDate |
string(date)/null | 条件必填 | reporterNetAmount != 0 时必填;净额为 0 时可为 null |
reimbursementConfirmation.transferRef |
string/null | 条件必填 | 去除首尾空格后最长 128;净额非 0 时长度必须为 1~128 |
reimbursementConfirmation.advanceSettledFlag |
boolean | 是 | 必须明确传值,false 合法 |
reimbursementConfirmation.signedVoucher |
object | 是 | 缺失返回 400 |
reimbursementConfirmation.signedVoucher.files |
array<object> | 业务必填 | 1~9 项;为 null、空数组、超过 9 项或含 null 项返回 584317 |
reimbursementConfirmation.signedVoucher.files[].url |
string | 业务必填 | 去除首尾空格后长度 1~1024;不符合返回 584317 |
reimbursementConfirmation.signedVoucher.files[].name |
string/null | 否 | 去除首尾空格;空串归一化为 null;非空最长 255 |
reimbursementConfirmation.signedVoucher.note |
string/null | 否 | 去除首尾空格;空串归一化为 null;非空最长 500 |
transferStatus 不得提交。finalize 成功后,报账终态中的 transferStatus 固定为 COMPLETED。
签字凭证文件按规范化后的 url、name 升序稳定保存。不得依赖请求数组原顺序进行严格幂等判断。
响应字段
data 字段 |
JSON 类型 | 说明 |
|---|---|---|
summaryId |
string | 核单汇总 ID |
finalSnapshotId |
string | 核单终态快照 ID |
finalSnapshotVersionNo |
integer | 终态版本号;首次为 1,反确认后再次 finalize 为上一版本 + 1 |
finalSnapshotStatus |
string | 成功固定为 FINALIZED |
orderId |
string | 订单 ID |
settledAt |
string(date-time) | ISO-8601 核单完成时间 |
totalAmount |
string | 订单总金额快照 |
paidAmount |
string | 已付金额快照 |
balanceAmount |
string | 尾款金额快照 |
roomCost |
string | 住宿实际成本 |
ticketCost |
string | 门票实际成本 |
staffCost |
string | 人员费用实际成本 |
subsidyCost |
string | 补助实际成本 |
mealCost |
string | 餐食实际成本 |
vehicleCost |
string | 车辆成本 |
otherExpenseCost |
string | 其他支出实际成本 |
insurancePremium |
string | 保险实际保费 |
totalActualCost |
string | 总实际成本 |
driverTransferAmount |
string | 给司机/主报账人转回金额 |
profitAmount |
string | 公司毛利 |
profitRate |
number | 毛利率;订单总金额为 0 时为 0 |
orderStatusAfter |
string | 成功后为 待财务复核 |
mqTriggered |
boolean | 当前固定为 false |
warnings |
array<string> | 软预警列表;无预警为 [] |
错误与业务边界
- 缺 body、非法 JSON、
remark超长、双指纹格式错误,或缺少reimbursementConfirmation、advanceSettledFlag、signedVoucher:返回400。 - 双指纹任一与当前冻结事实不一致:返回
584315,须重新 GET 两张报表。 transferRef条件不满足或超过 128,凭证files/文件项/url无效,或name/note超长:返回584317。- 单团核算的
outstandingAmount != 0:返回584082,不能完成核单。 - 完全相同的终态请求重试返回原
summaryId、finalSnapshotId和版本号,不产生新版本。 - 已有当前终态时,双指纹、规范化凭据或
remark任一不同:返回584316。 - 任一失败不留下部分完成结果。
典型请求:净报账金额非 0
POST /v3/admin/order/1914050000000001/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"remark": "主报账人与单团核算均已核对",
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
"reimbursementConfirmation": {
"transferDate": "2026-07-29",
"transferRef": "FT202607290001",
"advanceSettledFlag": false,
"signedVoucher": {
"files": [
{
"name": "司机签字报账单.pdf",
"url": "https://oss.example.com/settlement/driver-signed-20260729.pdf"
}
],
"note": "司机现场签字后上传"
}
}
}
典型响应
{
"code": 200,
"message": "成功",
"data": {
"summaryId": "9600000000001",
"finalSnapshotId": "9600000000002",
"finalSnapshotVersionNo": 1,
"finalSnapshotStatus": "FINALIZED",
"orderId": "1914050000000001",
"settledAt": "2026-07-29T10:30:25",
"totalAmount": "24800.00",
"paidAmount": "24800.00",
"balanceAmount": "0.00",
"roomCost": "4280.00",
"ticketCost": "3680.00",
"staffCost": "7000.00",
"subsidyCost": "720.00",
"mealCost": "860.00",
"vehicleCost": "5200.00",
"otherExpenseCost": "1200.00",
"insurancePremium": "180.00",
"totalActualCost": "23120.00",
"driverTransferAmount": "22940.00",
"profitAmount": "1680.00",
"profitRate": 0.0677,
"orderStatusAfter": "待财务复核",
"mqTriggered": false,
"warnings": []
},
"traceId": "a1b2c3d4-e5f6-7890",
"success": true
}
边界请求:reporterNetAmount = 0
POST /v3/admin/order/1914050000000002/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"remark": null,
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
"reimbursementConfirmation": {
"transferDate": null,
"transferRef": null,
"advanceSettledFlag": false,
"signedVoucher": {
"files": [
{
"name": null,
"url": "https://oss.example.com/settlement/zero-net-signed.jpg"
}
],
"note": null
}
}
}
边界响应
{
"code": 200,
"message": "成功",
"data": {
"summaryId": "9600000000011",
"finalSnapshotId": "9600000000012",
"finalSnapshotVersionNo": 1,
"finalSnapshotStatus": "FINALIZED",
"orderId": "1914050000000002",
"settledAt": "2026-07-29T10:35: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": []
},
"traceId": "a1b2c3d4-e5f6-7890",
"success": true
}
异常请求:凭证包含空 URL
POST /v3/admin/order/1914050000000001/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
"reimbursementConfirmation": {
"transferDate": "2026-07-29",
"transferRef": "FT202607290001",
"advanceSettledFlag": true,
"signedVoucher": {
"files": [
{"name": "签字单.pdf", "url": " "}
]
}
}
}
异常响应
{
"code": 584317,
"message": "当前报告状态不允许执行该操作",
"data": null,
"traceId": "a1b2c3d4-e5f6-7890",
"success": false
}
异常请求:缺少 signedVoucher
POST /v3/admin/order/1914050000000001/settlement/finalize
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{
"reimbursementExpectedSourceFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"groupExpectedSourceFingerprint": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
"reimbursementConfirmation": {
"transferDate": "2026-07-29",
"transferRef": "FT202607290001",
"advanceSettledFlag": true
}
}
异常响应
{
"code": 400,
"message": "参数校验失败",
"data": null,
"traceId": "a1b2c3d4-e5f6-7890",
"success": false
}
3.4 已删除:确认主报账表
- 原方法与路径:
POST /v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm - 当前契约:接口已删除,无有效请求体或成功响应。
- 前端动作:删除请求封装、按钮、loading、重试和错误忽略逻辑。
请求示例
POST /v3/admin/order/1914050000000001/settlement/reports/reimbursement/confirm
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{}
响应示例
{
"code": 404,
"message": "请求地址不存在",
"data": null,
"success": false
}
3.5 已删除:确认单团核算表
- 原方法与路径:
POST /v3/admin/order/{orderId}/settlement/reports/group/confirm - 当前契约:接口已删除,无有效请求体或成功响应。
- 前端动作:删除请求封装、按钮、loading、重试和错误忽略逻辑。
请求示例
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
Authorization: Bearer <admin-jwt>
Content-Type: application/json
{}
响应示例
{
"code": 404,
"message": "请求地址不存在",
"data": null,
"success": false
}
4. 接口入参汇总
| 接口 | 入参 |
|---|---|
| 主报账 GET | 路径参数 orderId;无请求体 |
| 单团 GET | 路径参数 orderId;无请求体 |
| finalize | 路径参数 orderId;请求体必须包含两个指纹及 reimbursementConfirmation |
| 两个旧 confirm | 已删除,无有效入参 |
双指纹映射必须严格如下:
| 来源 | finalize 字段 |
|---|---|
主报账 GET 的 data.sourceFingerprint |
reimbursementExpectedSourceFingerprint |
单团 GET 的 data.sourceFingerprint |
groupExpectedSourceFingerprint |
5. 出参汇总
- 两张 GET 均返回
Result<报表对象>,其中sourceFingerprint是 finalize 的提交凭据。 - finalize 返回
Result<SettlementSubmitRespVO>,完整字段见 §3.3。 - 两个旧 confirm 不再返回业务成功响应,只会命中不存在的路由。
- 金额序列化以各字段表和示例为准:finalize 的金额字段为字符串,两张 GET 的金额字段为 JSON number。
6. 枚举 / 数据字典
6.1 reportStatus
所属字段:两张报表响应 reportStatus | 类型:string
| 值 | 中文 | 说明 |
|---|---|---|
GENERATED |
实时结果 | 当前不存在有效终态,按当前核单事实计算 |
CONFIRMED |
已固化 | 返回当前有效终态版本中的报表 |
STALE |
历史过期 | 兼容历史报表状态,不用于当前 finalize |
6.2 transferDirection
所属字段:主报账响应 transferDirection | 类型:string
| 值 | 中文 | 说明 |
|---|---|---|
REPORTER_TO_COMPANY |
报账人转公司 | reporterNetAmount > 0 |
COMPANY_TO_REPORTER |
公司转报账人 | reporterNetAmount < 0 |
BALANCED |
已平衡 | reporterNetAmount = 0 |
6.3 category
所属字段: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 |
尚未固化 | 实时报表可为空 |
transferStatus 只出现在响应中,不是 finalize 入参。
7. 错误码
| code | 含义 | 触发场景 |
|---|---|---|
400 |
请求/参数校验失败 | orderId <= 0、缺请求体、非法 JSON、双指纹格式错误、缺 reimbursementConfirmation/advanceSettledFlag/signedVoucher、remark 超长 |
403 |
无访问权限 | 房务角色或无权访问当前订单 |
404 |
路由不存在 | 调用两个已删除的报表 confirm 接口 |
584082 |
存在待收尾款 | 单团核算 outstandingAmount != 0 |
584100 |
车辆费用暂时不可用 | 报表查询或 finalize 当前无法取得可核单车辆费用 |
584101 |
车辆事实未完成 | 存在未完结派车或未确认车辆费用 |
584102 |
缺少车辆费用 | 有用车需求但没有可核单车辆费用 |
584315 |
核单来源数据已变化 | 车辆候选与冻结事实不一致,或任一双指纹过期 |
584316 |
并发或严格幂等冲突 | 终态重试请求不同、并发完成/反确认冲突 |
584317 |
转账条件或签字凭证不合法 | 净额非 0 缺日期/流水、流水超长、files/文件项/url 无效、name/note 超长 |
584320 |
核单明细未准备好 | 当前分类数据不能用于报账或 finalize |
584321 |
缺少当前终态 | 后续财务复核缺少 current FINALIZED 终态 |
584325 |
双指纹兜底校验失败 | finalize 发现双指纹不完整或不合法 |
584326 |
终态组合不一致 | 当前终态、关联汇总或订单终态不匹配 |
8. 示例索引
| 场景 | 位置 |
|---|---|
| 主报账 GET 典型请求与响应 | §3.1 |
| 单团 GET 典型请求与响应 | §3.2 |
| finalize 净额非 0 典型成功 | §3.3 |
| finalize 净额为 0 合法边界 | §3.3 |
| finalize 凭证 URL 非法返回 584317 | §3.3 |
finalize 缺 signedVoucher 返回 400 |
§3.3 |
| 两个旧 confirm 返回 404 | §3.4、§3.5 |
9. 业务边界
- 必须先分别 GET 两张报表,再把两个
sourceFingerprint一一映射到 finalize;不能复用旧指纹、互换字段或只传一个。 - 任一核单事实变化后,旧双指纹都会失效;收到
584315后必须重新 GET 两张表。 outstandingAmount必须为0才能 finalize。reporterNetAmount != 0时,transferDate和非空transferRef同时必填;净额为0时二者可为null。advanceSettledFlag=false是有效业务值,不等同于缺失。signedVoucher始终必填,且files必须有 1~9 个合法文件项。- 完全相同请求重试严格幂等;任何双指纹、规范化凭据或
remark差异均返回584316。 - 管理员反确认使当前终态失效后,两张 GET 重新返回实时结果;再次 finalize 必须使用新双指纹,成功响应的
finalSnapshotVersionNo为上一版本 + 1。 - finalize 成功后订单进入“待财务复核”。既有财务复核接口
POST /v3/admin/order/{orderId}/settlement/confirm的请求/响应结构未在本次变更:请求仅含可选confirmRemark;当前没有独立财务角色校验;成功data为orderId、settlementStatus=COMPLETED、settledAt、flowStatus=SETTLED。其复核前提为当前有效FINALIZED终态及其关联汇总,旧报表 confirm 状态不参与判断。
10. 修改前后对比
10.1 字段级对比
| 接口/字段 | 修改前或错误说明 | 当前正确契约 |
|---|---|---|
| finalize 双指纹 | #5342 通知误写为删除 | 两个字段均必填 |
reimbursementExpectedSourceFingerprint |
误写为不再回传 | 来自主报账 GET 的 sourceFingerprint |
groupExpectedSourceFingerprint |
误写为不再回传 | 来自单团 GET 的 sourceFingerprint |
reimbursementConfirmation |
#5342 把内部字段错误提升到 finalize 顶层 | 必填嵌套对象 |
transferDate |
误写为 finalize 顶层 | 位于 reimbursementConfirmation |
transferRef |
误写为 finalize 顶层 | 位于 reimbursementConfirmation,trim 后最长 128 |
advanceSettledFlag |
误写为 finalize 顶层 | 位于 reimbursementConfirmation,必填 boolean |
signedVoucher |
误写为 finalize 顶层 | 位于 reimbursementConfirmation,必填 object |
transferStatus |
可能沿用旧 confirm 传值 | finalize 不接收,成功后固定为 COMPLETED |
10.2 行为级对比
| 行为 | 修改前 | 当前 |
|---|---|---|
| 主报账确认 | 独立 POST confirm | 接口删除,由 finalize 一次完成 |
| 单团确认 | 独立 POST confirm | 接口删除,由 finalize 一次完成 |
| finalize 前的数据校验 | 分散在两个 confirm | 两张 GET 取双指纹,finalize 一次校验 |
| 重复 finalize | 旧流程语义不明确 | 完全相同返回原结果,任一差异返回 584316 |
| 反确认后再次核单 | 可能沿用旧报表结果 | 重新 GET 新指纹,再 finalize 生成版本号 + 1 |
11. 影响评估 / 回滚
11.1 影响评估
- 是否破坏向后兼容:是。两个 POST confirm 已删除,finalize 的双指纹及嵌套凭据均为必填。
- 前端是否必须同步上线:是。按 #5342 错误契约提交会因缺双指纹或缺
reimbursementConfirmation返回400/业务错误。 - 查询兼容性:两张 GET 的字段结构保持,
sourceFingerprint的用途明确为 finalize 必填凭据。
11.2 回滚说明
- 前后端必须使用同一版核单流程;不能混用“独立 confirm”和“finalize 双指纹”两套调用顺序。
- 若后端契约回滚,前端也需同步恢复对应请求模型与调用链,不能只单独回滚一端。
12. 注意事项
- 删除两个报表确认按钮及对应请求、loading、重试、错误忽略代码。
- 保留两个 GET 返回的
sourceFingerprint,并在点击完成核单前保存当前两份值。 - finalize 请求模型必须新增必填
reimbursementConfirmation,其余凭据字段不得放在顶层。 - 不要发送
transferStatus;页面在 finalize 成功后按响应/重新 GET 展示终态。 - 不要继续沿用 #5342 通知中的“删除双指纹”“finalize 顶层凭据字段”实现。
- 对
584315进行刷新两张报表后重试;对584316不要静默覆盖终态。
13. 关联 / 联系人
13.1 链接
- Issue: #5343
- PR: #5347
- Merge commit: a892a6b56a
13.2 联系人
- 后端负责人: @yst
- 消费端: v3 管理后台