---
schema: "hl-changelog/v2"
ticket: "5343"
title: "核单确认收口到完成核单"
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: "管理后台已删除旧报表 confirm 流程，完成核单改为提交双指纹与嵌套 reimbursementConfirmation；pnpm checkpoint 全部通过。"
updated_at: "2026-07-29"
base: "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&lt;object&gt; | 否 | 主报账人代收明细，结构见下表 |
| `expenseLines` | array&lt;object&gt; | 否 | 主报账成本明细，结构见下表 |
| `advanceLines` | array&lt;object&gt; | 否 | 已审批预支明细，结构见下表 |
| `vehicleLines` | array&lt;object&gt; | 否 | 车辆独立明细；没有独立行时为 `[]` |
| `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 会回到实时视图；前端必须重新取得指纹。

**典型请求**

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

无请求体。

**典型响应**

```json
{
  "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&lt;object&gt; | 否 | 收入汇总行 |
| `costCategories` | array&lt;object&gt; | 否 | 成本分类汇总 |
| `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 会生成新的实时结果和指纹。

**典型请求**

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

无请求体。

**典型响应**

```json
{
  "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&lt;object&gt; | 业务必填 | 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&lt;string&gt; | 软预警列表；无预警为 `[]` |

**错误与业务边界**

- 缺 body、非法 JSON、`remark` 超长、双指纹格式错误，或缺少 `reimbursementConfirmation`、`advanceSettledFlag`、`signedVoucher`：返回 `400`。
- 双指纹任一与当前冻结事实不一致：返回 `584315`，须重新 GET 两张报表。
- `transferRef` 条件不满足或超过 128，凭证 `files`/文件项/`url` 无效，或 `name`/`note` 超长：返回 `584317`。
- 单团核算的 `outstandingAmount != 0`：返回 `584082`，不能完成核单。
- 完全相同的终态请求重试返回原 `summaryId`、`finalSnapshotId` 和版本号，不产生新版本。
- 已有当前终态时，双指纹、规范化凭据或 `remark` 任一不同：返回 `584316`。
- 任一失败不留下部分完成结果。

**典型请求：净报账金额非 0**

```http
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": "司机现场签字后上传"
    }
  }
}
```

**典型响应**

```json
{
  "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`**

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

**边界响应**

```json
{
  "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**

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

**异常响应**

```json
{
  "code": 584317,
  "message": "当前报告状态不允许执行该操作",
  "data": null,
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": false
}
```

**异常请求：缺少 `signedVoucher`**

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

**异常响应**

```json
{
  "code": 400,
  "message": "参数校验失败",
  "data": null,
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": false
}
```

### 3.4 已删除：确认主报账表

- **原方法与路径**：`POST /v3/admin/order/{orderId}/settlement/reports/reimbursement/confirm`
- **当前契约**：接口已删除，无有效请求体或成功响应。
- **前端动作**：删除请求封装、按钮、loading、重试和错误忽略逻辑。

**请求示例**

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

{}
```

**响应示例**

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

### 3.5 已删除：确认单团核算表

- **原方法与路径**：`POST /v3/admin/order/{orderId}/settlement/reports/group/confirm`
- **当前契约**：接口已删除，无有效请求体或成功响应。
- **前端动作**：删除请求封装、按钮、loading、重试和错误忽略逻辑。

**请求示例**

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

{}
```

**响应示例**

```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](https://git.1814.love:8443/wx/HL/issues/5343)
- **PR**: [#5347](https://git.1814.love:8443/wx/HL/pulls/5347)
- **Merge commit**: [a892a6b56a](https://git.1814.love:8443/wx/HL/commit/a892a6b56a2c3c0c2e4e345156096ac1ac5750c0)

### 13.2 联系人

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