---
schema: "hl-changelog/v2"
ticket: "5739"
title: "核单报表快照指纹彻底下线：报表出参删除 sourceFingerprint，reportStatus 不再返回 STALE"
consumer: "admin"
change_type: "修改接口"
author: "yaosutu(GIT)"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "mmg"
frontend_ref: "19d001a4"
target_release: ""
verified_at: ""
status_note: "前端已实现：清理 stale 死代码——returnDetailAdapter 不再投影 stale（注释更新 #5704/#5739 双批次口径）、ReportModal 删徽章「已过期」/error 态+「来源数据已变化请刷新」error 提示+报账表单 stale 守卫、detail.vue openReport 仅未生成时重拉去 stale 重载。reportStatus 只留 GENERATED/CONFIRMED 落库原值。grep 复核无残留；核单域定向 39/39 过 + checkpoint 精确文件集全过。前后端同批发布（前端先上），承接 #5704。"
updated_at: "2026-08-10"
base: "dev-v3"
---

# 【⚠️ 修改接口·管理后台】核单报表快照指纹彻底下线，出参删 sourceFingerprint、reportStatus 不再返回 STALE（#5739）

> **PR**: [#5764](https://git.1814.love:8443/wx/HL/pulls/5764) | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-09

## 1. 接口背景

本次是核单域指纹机制下线的**收尾批次**，承接 [#5704](https://git.1814.love:8443/wx/HL/issues/5704)（PR #5709，写入路径 6 端点指纹入参/出参下线）。

#5704 之后，核单报表（单团核算表 / 主报账人报账表）的读取路径仍残留一套「快照指纹」逻辑：报表落库时记录来源数据 sha256 指纹，前端每次 GET 报表时后端实时重算当前来源指纹，两者不一致就把 reportStatus 实时改报为 STALE（提示「数据已变化，需刷新」），并在出参中携带 sourceFingerprint。

指纹机制整体废弃后，该派生逻辑同步删除：

- 两个报表查询接口出参**删除 sourceFingerprint 字段**；
- reportStatus 枚举**删除 STALE 值**，只保留落库状态 GENERATED / CONFIRMED，接口不再做实时指纹比对。

> ⚠️ 本次为**出参删字段 + 枚举删值的硬破坏契约**：前端若还在读取 sourceFingerprint、或对 reportStatus === 'STALE' 写过特判（刷新提示 / 重新生成按钮等），必须清理后再与后端同批发布。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 前端动作 |
|---|---|---|---|---|---|
| 1 | 查询单团核算表 | GET | /v3/admin/order/{orderId}/settlement/reports/group | 删除出参 sourceFingerprint；reportStatus 枚举删除 STALE | 停读字段 / 清理 STALE 特判 |
| 2 | 查询主报账人报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement | 删除出参 sourceFingerprint；reportStatus 枚举删除 STALE | 停读字段 / 清理 STALE 特判 |

## 3. 接口详情

- **使用场景**：核单页面展示单团核算表（全团收入/成本/毛利核算）与主报账人报账表（主报账人收付对账与转账结论）。
- **认证**：需要管理后台登录态（Bearer Token）；房务角色（HOUSE）被 OrderViewGuard.assertNotHouseRole 拦截。
- **幂等性**：只读查询，天然幂等。
- **限流**：未声明接口专属限流。
- **方法/路径**：见 §2 变更清单。

## 4. 接口入参

### 4.1 路径参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| orderId | Long | 是 | 订单 ID，路径参数，必须大于 0 |

### 4.2 请求体字段

无（GET 请求，无请求体，无 Query 参数）。**入参零变化**。

## 5. 出参字段

### 5.1 GET /v3/admin/order/{orderId}/settlement/reports/group（单团核算表）

统一响应 Result 包装，data 字段如下（按响应 JSON 字段序）：

| 字段 | 类型 | 说明 |
|---|---|---|
| id | String(Long) | 报表记录 ID；报表未落库时可为 null |
| orderId | String(Long) | 订单 ID |
| reportStatus | String | 报表状态，枚举见 §6；**不再返回 STALE** |
| ~~sourceFingerprint~~ | - | **已删除**，前端不再收到此字段 |
| baseOrderAmount | String(BigDecimal) | 订单应收（基础订单金额） |
| otherIncomeAmount | String(BigDecimal) | 其他收入合计 |
| discountAmount | String(BigDecimal) | 优惠合计（负数） |
| adjustedReceivableAmount | String(BigDecimal) | 调整后应收 |
| paidAmount | String(BigDecimal) | 已收金额 |
| actualRefundedAmount | String(BigDecimal) | 实际退款金额 |
| netRevenueAmount | String(BigDecimal) | 净收入 |
| netReceivedAmount | String(BigDecimal) | 净收款 |
| outstandingAmount | String(BigDecimal) | 待收尾款 |
| hotelCost | String(BigDecimal) | 住宿成本 |
| ticketCost | String(BigDecimal) | 门票成本 |
| mealCost | String(BigDecimal) | 餐食成本 |
| vehicleCost | String(BigDecimal) | 车辆成本 |
| guideCost | String(BigDecimal) | 导游成本 |
| photographerCost | String(BigDecimal) | 摄影成本 |
| otherExpenseCost | String(BigDecimal) | 其他支出成本 |
| insurancePremium | String(BigDecimal) | 保费 |
| totalCost | String(BigDecimal) | 成本合计 |
| paidCost | String(BigDecimal) | 已付成本 |
| unpaidCost | String(BigDecimal) | 未付成本 |
| grossProfit | String(BigDecimal) | 毛利 |
| grossProfitRate | String(BigDecimal) | 毛利率 |
| travelerCount | Integer | 出行人数 |
| perCapitaRevenue | String(BigDecimal) | 人均收入 |
| perCapitaCost | String(BigDecimal) | 人均成本 |
| perCapitaProfit | String(BigDecimal) | 人均毛利 |
| incomeLines | Array | 收入行，固定 4 行；每项含 type(BASE_ORDER/OTHER_INCOME/DISCOUNT/ACTUAL_REFUND) / typeName / amount |
| costCategories | Array | 成本分类行，固定 8 行；每项含 category(HOTEL/TICKET/MEAL/VEHICLE/GUIDE/PHOTOGRAPHER/OTHER_EXPENSE/INSURANCE) / categoryName / amount |
| generatedBy | String(Long) | 生成人 ID |
| generatedByName | String | 生成人姓名 |
| generatedAt | String | 生成时间，格式 yyyy-MM-dd HH:mm:ss |
| confirmedBy | String(Long) | 确认人 ID；未确认为 null |
| confirmedByName | String | 确认人姓名 |
| confirmedAt | String | 确认时间 |

### 5.2 GET /v3/admin/order/{orderId}/settlement/reports/reimbursement（主报账人报账表）

统一响应 Result 包装，data 字段如下：

| 字段 | 类型 | 说明 |
|---|---|---|
| id | String(Long) | 报表记录 ID；未落库时可为 null |
| orderId | String(Long) | 订单 ID |
| reportStatus | String | 报表状态，枚举见 §6；**不再返回 STALE** |
| ~~sourceFingerprint~~ | - | **已删除**，前端不再收到此字段 |
| primaryReporterId | String(Long) | 主报账人人员安排 ID |
| primaryReporterName | String | 主报账人姓名 |
| primaryReporterRole | String | 主报账人角色（如 DRIVER） |
| reportVersion | Integer | 报表版本号 |
| driverCollectedTailAmount | String(BigDecimal) | 司机代收尾款 |
| approvedAdvanceAmount | String(BigDecimal) | 已审批预支金额 |
| reportablePaidCostAmount | String(BigDecimal) | 可报账已付成本 |
| reporterNetAmount | String(BigDecimal) | 报账人净额 |
| primaryReporterCollectedAmount | String(BigDecimal) | 主报账人已收款 |
| publicPrepaidAmount | String(BigDecimal) | 对公预付金额 |
| primaryReporterDueAmount | String(BigDecimal) | 主报账人应结金额 |
| advanceOutstandingAmount | String(BigDecimal) | 预支未销余额 |
| reconNetAmount | String(BigDecimal) | 对账净额 |
| transferDirection | String | 转账方向 |
| transferAmount | String(BigDecimal) | 转账金额 |
| incomeLines | Array | 收款行；每项含 type / typeName / receiptId / amount / channel / channelName / payType / payTypeName / collectorStaffId / collectorName / collectorRole / collectorRoleName / receivedAt / remark |
| expenseLines | Array | 支出行；每项含 kind / category / categoryName / amount / paymentMethod / paymentMethodName / voucherUrls / remark + 类别扩展字段（住宿 hotelName/roomTypeName 等） |
| advanceLines | Array | 预支行 |
| vehicleLines | Array | 已确认车辆逐日费用明细；**无数据固定返回空数组** |
| transferStatus | String | 转账状态 |
| transferDate | String | 转账日期，格式 yyyy-MM-dd |
| transferRef | String | 转账流水号 |
| advanceSettledFlag | Boolean | 预支是否已处理 |
| signedVoucher | Object | 签字凭证；含 files[{name,url}] + note |
| generatedBy | String(Long) | 生成人 ID |
| generatedByName | String | 生成人姓名 |
| generatedAt | String | 生成时间 |
| confirmedBy | String(Long) | 确认人 ID |
| confirmedByName | String | 确认人姓名 |
| confirmedAt | String | 确认时间 |

> 上述两表除删除 sourceFingerprint 外，其余字段名称、类型、语义均无变化。前端不要再读取 sourceFingerprint，读取结果恒为 undefined。

## 6. 枚举 / 数据字典

### reportStatus（两接口共用）

| 值 | 含义 | 本次变化 |
|---|---|---|
| GENERATED | 报表已生成（或实时计算态），未确认 | 不变 |
| CONFIRMED | 报表已随完成核单确认 | 不变 |
| ~~STALE~~ | 原语义：落库后来源数据发生变化（指纹不一致），实时派生 | **已删除，不再返回** |

**STALE 原触发场景（现已消亡）**：报表落库（生成/确认）后，订单的收退款、费用明细、车辆费用等来源数据又被修改（含 reopen 反确认后改数），原来 GET 报表时后端会比对落库指纹与实时指纹，不一致则把 reportStatus 改报 STALE。现在该实时比对整体移除，reportStatus 就是落库原值。

其余枚举/字典（incomeLines[].type、costCategories[].category、channel、payType、paymentMethod、transferDirection、transferStatus 等）取值与语义均无变化。

## 7. 错误码

本次**无新增、无删除错误码**。两接口既有的访问拦截（如房务角色 403、订单不存在）行为不变；finalize 前置的双报告已生成保护（584311 / 584313）不在本次范围、保持不变。

## 8. 示例

### 8.1 典型成功（单团核算表）

```http
GET /v3/admin/order/2084000000000002978/settlement/reports/group
Authorization: Bearer <token>
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "8801",
    "orderId": "2084000000000002978",
    "reportStatus": "GENERATED",
    "baseOrderAmount": "12800.00",
    "otherIncomeAmount": "500.00",
    "discountAmount": "-300.00",
    "adjustedReceivableAmount": "13000.00",
    "paidAmount": "13000.00",
    "actualRefundedAmount": "0.00",
    "netRevenueAmount": "13000.00",
    "netReceivedAmount": "13000.00",
    "outstandingAmount": "0.00",
    "hotelCost": "3600.00",
    "ticketCost": "1200.00",
    "mealCost": "800.00",
    "vehicleCost": "2000.00",
    "guideCost": "500.00",
    "photographerCost": "0.00",
    "otherExpenseCost": "100.00",
    "insurancePremium": "200.00",
    "totalCost": "8400.00",
    "paidCost": "7000.00",
    "unpaidCost": "1400.00",
    "grossProfit": "4600.00",
    "grossProfitRate": "0.3538",
    "travelerCount": 4,
    "perCapitaRevenue": "3250.00",
    "perCapitaCost": "2100.00",
    "perCapitaProfit": "1150.00",
    "incomeLines": [
      {"type": "BASE_ORDER", "typeName": "订单应收", "amount": "12800.00"},
      {"type": "OTHER_INCOME", "typeName": "其他收入", "amount": "500.00"},
      {"type": "DISCOUNT", "typeName": "优惠", "amount": "-300.00"},
      {"type": "ACTUAL_REFUND", "typeName": "实际退款", "amount": "0.00"}
    ],
    "costCategories": [
      {"category": "HOTEL", "categoryName": "住宿", "amount": "3600.00"},
      {"category": "TICKET", "categoryName": "门票", "amount": "1200.00"}
    ],
    "generatedBy": "1001",
    "generatedByName": "张三",
    "generatedAt": "2026-08-09 10:20:30",
    "confirmedBy": null,
    "confirmedByName": null,
    "confirmedAt": null
  },
  "success": true
}
```

> 注意：响应中已无 sourceFingerprint 字段；reportStatus 只会是 GENERATED 或 CONFIRMED。

### 8.2 边界（报账表无车辆明细 + 未落库实时态）

报表未落库（订单尚未生成过报账表）时接口仍正常返回实时计算结果，id 可为 null、reportStatus 固定 GENERATED、vehicleLines 固定空数组：

```http
GET /v3/admin/order/2084000000000002978/settlement/reports/reimbursement
Authorization: Bearer <token>
```

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": null,
    "orderId": "2084000000000002978",
    "reportStatus": "GENERATED",
    "primaryReporterId": "7001",
    "primaryReporterName": "司机甲",
    "primaryReporterRole": "DRIVER",
    "reportVersion": null,
    "driverCollectedTailAmount": "0.00",
    "approvedAdvanceAmount": "0.00",
    "reportablePaidCostAmount": "0.00",
    "reporterNetAmount": "0.00",
    "primaryReporterCollectedAmount": "0.00",
    "publicPrepaidAmount": "0.00",
    "primaryReporterDueAmount": "0.00",
    "advanceOutstandingAmount": "0.00",
    "reconNetAmount": "0.00",
    "transferDirection": null,
    "transferAmount": "0.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
  },
  "success": true
}
```

### 8.3 业务失败（订单不存在）

```http
GET /v3/admin/order/999999999/settlement/reports/group
Authorization: Bearer <token>
```

```json
{"code": 584xxx, "message": "订单不存在", "data": null, "success": false}
```

> 该失败路径与本次变更无关，仅作最小复现样例；具体错误码以订单域统一「订单不存在」码为准。

## 9. 业务边界

- ✅ 报表落库后再改来源数据（收款、费用、车辆费用等），reportStatus 保持落库原值（GENERATED / CONFIRMED），不再实时翻成 STALE；前端展示的报表金额仍是实时计算的当前值。
- ✅ reopen（反确认）后重新查报表，reportStatus 按落库记录返回，不再出现 STALE 中间态。
- ❌ 不要再依赖 reportStatus === 'STALE' 做「数据已变化」提示；该信号已彻底不存在，且没有替代字段（指纹机制整体废弃，核单为单人负责场景，见 #5704 背景）。
- ❌ 不要把历史缓存/本地存储里的 sourceFingerprint 回传到任何接口——报表两个 GET 接口本就无入参；写入侧接口的指纹入参已在 #5704 删除。

## 10. 修改前后对比

### 10.1 字段级对比

| 接口 | 字段 | 原来 | 现在 |
|---|---|---|---|
| GET reports/group | sourceFingerprint | 出参，64 位十六进制 sha256 | **已删除** |
| GET reports/reimbursement | sourceFingerprint | 出参，64 位十六进制 sha256 | **已删除** |
| 两接口 | reportStatus | GENERATED / CONFIRMED / STALE（实时派生） | 仅 GENERATED / CONFIRMED（落库原值） |

### 10.2 行为级对比

| 场景 | 原来 | 现在 |
|---|---|---|
| 报表落库后来源数据被修改，再 GET 报表 | reportStatus 实时改报 STALE | 返回落库原值（GENERATED 或 CONFIRMED） |
| reopen 反确认后 GET 报表 | 指纹不一致，reportStatus = STALE | 按落库状态返回，无 STALE |
| 出参字段 | 含 sourceFingerprint | 不再含该字段 |
| 报表金额数值 | 实时计算 | 实时计算（**不变**） |

## 11. 影响评估 / 回滚

### 11.1 影响评估

- **是否破坏向后兼容**：**是，硬破坏**。出参删除 sourceFingerprint 字段 + 枚举删除 STALE 值。旧前端若读取 sourceFingerprint 得到 undefined、若对 STALE 写了特判分支则永远不会再命中（静默失效，不报错）。
- **前端是否必须同步上线**：**必须同批**。前端需先停读 sourceFingerprint、清理 STALE 特判（刷新提示/禁用确认按钮/重新生成入口等），再与后端同批发布。
- **上线顺序边界**：前后端同批发布；若必须分先后，**先上前端（停读字段 + 清理 STALE 分支），再上后端**。旧前端 + 新后端不会报 500，但 STALE 相关 UI 逻辑成死代码。
- **数据库侧**：本批次后端同步 DROP 了 settlement 域 15 个指纹列（Flyway 迁移随服务部署执行），与前端无直接关系，但意味着**回滚后指纹无法恢复原值**（见 §11.2）。

### 11.2 回滚方案

- 代码回滚即恢复 sourceFingerprint 出参与 STALE 派生逻辑；但**数据库指纹列已 DROP**，回滚后重新计算的落库指纹为空，历史报表的 STALE 比对行为不可完全复原。
- 因此**回滚必须前后端同批回滚**，且接受「历史报表不再派生 STALE」的行为落差。数据侧无业务数据修复成本（指纹非业务数据）。

## 12. 注意事项

- 本次只动两个报表查询接口的出参与 reportStatus 枚举；接口路径、入参、金额数值计算逻辑**均未变化**。
- 与 #5704 的关系：#5704 下线**写入路径**（保存/确认/完成核单 6 个端点）的指纹入参与出参；本次 #5739 下线**报表读取路径**的指纹出参与 STALE 派生。两批共同完成指纹机制的整体下线，前端的指纹相关代码应已全部清除。
- 原分类确认状态 VO（SettlementCategoryChecksRespVO）中的 sourceFingerprint 字段本次也顺手删除、confirmStatus 的 STALE 派生同步移除；但该 VO 对应的 category-checks 接口**早在 PR #5324 已下线**，当前无任何端点返回它，前端无感，无需处理。
- finalize（完成核单）接口的 Swagger notes 中「提交双指纹」属文档残留，实际入参指纹字段已在 #5704 删除，以 #5704 changelog 为准。
- 小程序端（/v3/mp/*）不涉及本次变更，无需任何改动。

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#5739](https://git.1814.love:8443/wx/HL/issues/5739)
- **PR**: [#5764](https://git.1814.love:8443/wx/HL/pulls/5764)
- **Merge Commit**: [60e4409a](https://git.1814.love:8443/wx/HL/commit/60e4409a77ca81e203e456f063e3d8d91d8f48a1)
- **前置批次**: Issue [#5704](https://git.1814.love:8443/wx/HL/issues/5704) / PR [#5709](https://git.1814.love:8443/wx/HL/pulls/5709)（写入路径指纹下线，changelog 见 2026-08/08_5704）

### 13.2 联系人

- **后端负责人**: @yaosutu (yst)
