---
schema: "hl-changelog/v2"
ticket: "5342"
title: "核单报表取消中间确认并由 finalize 固化"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "pi:019fadb7-dac9-74bf-9581-058835251208"
frontend_ref: "1444fc7f0bf34efaec0ee9f775f7d529b609b847"
target_release: "v2.1"
verified_at: "2026-07-29T20:43:00+08:00"
status_note: "管理后台已删除两张报表的中间确认调用；finalize 请求结构按后续 #5343 最终契约收口，pnpm checkpoint 全部通过。"
updated_at: "2026-07-29"
base: "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> | 软预警列表；不阻塞完成核单 |

**提交行为**

1. 前端不再先调用任何报表“确认”接口。
2. 服务端按提交时的当前核单数据重新计算两张报表和所有汇总金额，不采信前端缓存的金额或指纹。
3. `transferDate`、`transferRef`、`advanceSettledFlag` 和 `signedVoucher` 与本次完成核单结果一并固化。
4. 成功后，两张 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 典型成功：查询实时主报账人报账表

**请求**

```http
GET /v3/admin/order/1914050000000001/settlement/reports/reimbursement
Authorization: Bearer <admin-jwt>
```

无请求体。

**响应**

```json
{
  "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 典型成功：查询实时单团核算表

**请求**

```http
GET /v3/admin/order/1914050000000001/settlement/reports/group
Authorization: Bearer <admin-jwt>
```

无请求体。

**响应**

```json
{
  "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 典型成功：完成核单并固化

**请求**

```http
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": "签字凭证已回收"
  }
}
```

**响应**

```json
{
  "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` 仍必须提交。

**请求**

```http
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"
      }
    ]
  }
}
```

**响应**

```json
{
  "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 异常：仍调用已删除的确认接口

**请求**

```http
POST /v3/admin/order/1914050000000001/settlement/reports/group/confirm
Authorization: Bearer <admin-jwt>
Content-Type: application/json

{
  "expectedSourceFingerprint": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
```

**响应**

```json
{
  "code": 404,
  "msg": "请求地址不存在",
  "data": null
}
```

### 8.6 异常：车辆费用尚未可核单

**请求**

```http
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"
      }
    ]
  }
}
```

**响应**

```json
{
  "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](https://git.1814.love:8443/wx/HL/issues/5342)
- **PR**: [#5345](https://git.1814.love:8443/wx/HL/pulls/5345)
- **Merge commit**: [eb9ecfafad](https://git.1814.love:8443/wx/HL/commit/eb9ecfafadde4cb9e2dbe5be8193abcf569ca44c)

### 13.2 联系人

- **后端负责人**: @yst
- **消费端**: v3 管理后台
