diff --git a/changelogs-v2/2026-08/09_5739_核单指纹下线-修改接口-管理后台.md b/changelogs-v2/2026-08/09_5739_核单指纹下线-修改接口-管理后台.md new file mode 100644 index 0000000..7434b65 --- /dev/null +++ b/changelogs-v2/2026-08/09_5739_核单指纹下线-修改接口-管理后台.md @@ -0,0 +1,362 @@ +--- +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: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "PR #5764 已合并 dev-v3;reports/group 与 reports/reimbursement 出参删除 sourceFingerprint 字段,reportStatus 枚举删除 STALE 值(原 reopen 后指纹不一致实时派生)。属出参删字段 + 枚举删值的硬破坏契约,前端需停读字段并清理 STALE 特判后与后端同批发布。" +updated_at: "2026-08-09" +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 +``` + +```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 +``` + +```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 +``` + +```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)