--- schema: "hl-changelog/v2" ticket: "5840" title: "核单两报表统一实时路径 + 出参新增订单头 orderHeader" consumer: "admin" change_type: "修改接口" author: "yaosutu(GIT)" backend_status: "deployed" gateway_status: "pending" frontend_status: "not_required" frontend_owner: "mmg" frontend_ref: "" target_release: "" verified_at: "2026-08-11" status_note: "后端 PR #5854 已 merge 到 dev-v3(merge commit 452ebd63),已部署测试服并网关实调验证 orderHeader 出参生效(主实例 8086);两核单报表出参统一新增 orderHeader 订单头对象,同时去掉已核单订单读终态快照的分叉、统一实时计算。前端实证 not_required(2026-08-11,mmg):出参纯增量、向后兼容、前端非必须同步上线(§11.1)。grep+源码实证——两报表接口封装 getSettlementGroupReport/getSettlementReimbursementReport(orderV2.js:968/951)纯透传,新增 orderHeader 不破坏现有解包;ReportModal 标题栏/打印头取 props.order.teamNo/productName(ReportModal.vue:30/567),该 order 是核单详情页 getSettlementDetail 已加载的主对象(detail.vue:944),非为标题另发请求,§12「可清理重复请求(非强制)」情形本案不成立、无重复请求可清;报表金额行/明细走 reportState.data,前端当前零读取 orderHeader;行为统一(去快照分叉)对前端透明(§2/§10.2),reportStatus 取值不变,#5739 已清 STALE 死代码无残留;orderHeader null 字段不下发/对象恒下发的可空性约束对零读取的现有代码无影响。可选增强(非本次交付):未来若改由报表自身 orderHeader 承载标题栏(含 customerName/consultantName/departDate/travelerComposition),按自身排期接入。" updated_at: "2026-08-11" base: "dev-v3" --- # 【✨ 修改接口·管理后台】核单两报表统一实时路径 + 出参新增订单头 orderHeader(#5840) > **PR**: #5854 | **服务**: hl-order-service-v3 | **更新时间**: 2026-08-11 ## 1. 接口背景 核单工作台的两个报表接口 —— 单团核算表、主报账人报账表 —— 此前出参只有金额汇总与行列表,**没有订单识别信息**(订单号 / 团号 / 产品 / 客户 / 出团日期 / 定制师 / 出行人构成),报表弹窗的标题栏只能靠前端从订单详情接口另取数据拼装。 同时,两个接口此前存在**快照读分叉**:未完成核单的订单走实时组装,已完成核单的订单读 finalize 时落库的历史快照 JSON。两条路径的出参结构虽然对齐过,但任何出参字段演进都要同时维护两套组装代码,容易出现「未核单有某字段 / 已核单没有」的不一致。 本次变更两件事: 1. **出参纯增量**:两个接口出参统一新增 `orderHeader` 订单头对象(字段全部来自 order_main 单表,无跨服务 JOIN)。 2. **行为统一(对前端透明)**:删除快照读分叉,两个接口**统一从 tab 明细实时计算**。已核单订单的明细在核单完成时已冻结,实时计算结果与历史快照一致,前端对数据源切换无感知;`reportStatus` 取值与含义不变。 ## 2. 变更清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 查询单团核算表 | GET | /v3/admin/order/{orderId}/settlement/reports/group | 修改(出参纯增量 + 数据源统一) | 出参 `data` 新增 `orderHeader` 对象;已核单订单不再读终态快照,统一实时计算 | | 2 | 查询主报账人报账表 | GET | /v3/admin/order/{orderId}/settlement/reports/reimbursement | 修改(出参纯增量 + 数据源统一) | 出参 `data` 新增 `orderHeader` 对象;已核单订单不再读终态快照,统一实时计算 | 配套数据字典(前端可调 GET /admin/dict/data/{dictType} 动态渲染中文名): | 字典 type | 用途 | 本次状态 | |-----------|------|----------| | product_type | 产品类型中文名(orderHeader.productTypeName 回填源) | 已存在(不变) | ## 3. 接口详情 ### 3.1 查询单团核算表 - **使用场景**:核单工作台「单团核算」页签 / 报表弹窗,财务/运营查看单团收入成本毛利全貌 - **认证**:管理后台 JWT;房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)无权调用(返 581045) - **幂等性**:是(GET 只读) - **限流**:无 ### 3.2 查询主报账人报账表 - **使用场景**:核单工作台「主报账人报账」页签 / 报账表弹窗,财务查看主报账人代收 / 垫付 / 预支与转账结论 - **认证**:管理后台 JWT;房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)无权调用(返 581045) - **幂等性**:是(GET 只读) - **限流**:无 ## 4. 接口入参 ### 4.1 路径参数(两接口相同) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderId` | Long | ✅ | 订单 ID,必须 > 0,否则返参数校验错误 | ### 4.2 请求体字段 无请求体(两接口均为 GET)。 ## 5. 出参(响应) ### 5.1 单团核算表顶层结构(SettlementGroupReportRespVO) 响应类型:`Result`(`code=200` 表示成功)。 | 字段 | 类型 | 说明 | |------|------|------| | `id` | String | 报表 ID(Long 序列化为字符串,无落库记录时缺省) | | `orderId` | String | 订单 ID(Long 序列化为字符串) | | `reportStatus` | String | 报表状态:`GENERATED`=已生成 / `CONFIRMED`=已确认(取值与含义不变,见 §6.2) | | `orderHeader` | Object | ✨ **本次新增**:订单头,结构见 §5.3 | | `baseOrderAmount` / `otherIncomeAmount` / `discountAmount` / `adjustedReceivableAmount` / `paidAmount` / `actualRefundedAmount` / `netRevenueAmount` / `netReceivedAmount` / `outstandingAmount` | Number | 收入侧金额汇总(不变) | | `hotelCost` / `ticketCost` / `mealCost` / `vehicleCost` / `guideCost` / `photographerCost` / `otherExpenseCost` / `insurancePremium` / `totalCost` / `paidCost` / `unpaidCost` / `grossProfit` / `grossProfitRate` | Number | 成本与毛利汇总(不变) | | `travelerCount` / `perCapitaRevenue` / `perCapitaCost` / `perCapitaProfit` | Number | 人均指标(不变) | | `incomeLines` | Array | 收入行,固定 4 行(不变) | | `costCategories` | Array | 成本分类行,固定 8 行(不变) | | `generatedBy` / `generatedByName` / `generatedAt` / `confirmedBy` / `confirmedByName` / `confirmedAt` | String | 生成 / 确认审计字段(不变,未确认时确认字段缺省) | ### 5.2 主报账人报账表顶层结构(SettlementReimbursementReportRespVO,当前最终形态) 响应类型:`Result`(`code=200` 表示成功)。 | 字段 | 类型 | 说明 | |------|------|------| | `baseInfo` | Object | 基础信息:汇总值 + 主报账人 + 生成/确认审计字段(既有结构,本次不变) | | `orderHeader` | Object | ✨ **本次新增**:订单头,结构见 §5.3 | | `incomeLines` | Array | 收入行(司机代收),无数据固定返回空数组 `[]`(既有结构,本次不变) | | `expenseLines` | Array | 支出行(仅报账人垫付 CASH_PAID 支出),无数据固定返回空数组 `[]`(既有结构,本次不变) | | `advanceLines` | Array | 预支明细行(已审批预支逐条),无数据固定返回空数组 `[]`(既有结构,本次不变) | ### 5.3 订单头(SettlementOrderHeaderVO)✨ 本次新增,两接口共用 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `orderNo` | String | 是 | 订单号,如 `HL20260730001` | | `teamNo` | String | 否 | 团号;**订金未支付时为 null(该 key 不下发)** | | `productName` | String | 是 | 产品名,如 `呼伦贝尔草原 5 日游` | | `productType` | String | 是 | 产品类型枚举:`CORE` / `ROUTE` / `CUSTOM` / `GROUP`(见 §6.1) | | `productTypeName` | String | 否 | 产品类型中文名(字典 `product_type` 回填;字典缺失为 null,该 key 不下发) | | `customerName` | String | 是 | 客户姓名;**核单财务域看真名,不脱敏** | | `departDate` | String | 是 | 出团日期,格式 `yyyy-MM-dd` | | `consultantName` | String | 是 | 定制师姓名 | | `travelerComposition` | String | 否 | 出行人构成文案:成人→`大`、儿童→`儿童`、幼童→`幼童`、婴儿→`婴儿` 四档,数量为 0 的档不显示,空格拼接(如 `2大 1儿童 1幼童`);**四档全空为 null(该 key 不下发)** | > ⚠️ `orderHeader` 对象本身**永远下发**(两接口统一实时计算后不再出现「已核单无头」的情况);但其内部 null 字段因 VO 标注 `@JsonInclude(NON_NULL)` **整体缺省,key 不出现在 JSON 中**,前端按可选字段处理。 ## 6. 枚举 / 数据字典 ### 6.1 `orderHeader.productType`(字典 `product_type`) | 值 | 中文 | |----|------| | `CORE` | 核心产品 | | `ROUTE` | 自驾路书 | | `CUSTOM` | 私人定制 | | `GROUP` | 小蒙马 | ### 6.2 `reportStatus`(取值与含义不变) | 值 | 中文 | 说明 | |----|------|------| | `GENERATED` | 已生成 | 订单未完成核单 | | `CONFIRMED` | 已确认 | 订单已完成核单(SETTLED) | > 行为说明:改前 `CONFIRMED` 走终态快照回放、`GENERATED` 走实时组装;**改后两种状态统一实时计算**(已核单订单明细已冻结,实时结果与快照一致),前端无需区分数据源。 ## 7. 错误码 | code | 含义 | 触发场景 | |------|------|----------| | 400 | 参数校验失败 | `orderId` 缺失或 < 1 | | 581007 | 订单不存在 | `orderId` 对应订单不存在或已删除 | | 581045 | 房务角色无权查看订单详情,房务仅可配房 | 房务管理员 / 房务组长角色调用 | ## 8. 示例(3 组:典型 / 边界 / 异常) ### 8.1 典型成功(orderHeader 全字段填充) **请求**: GET /v3/admin/order/1956112233445566778/settlement/reports/group Authorization: Bearer (无请求体) **响应**(节选,仅展示本次新增的 orderHeader 与相邻字段,其余金额 / 行列表结构与变更前一致故省略): ```json { "code": 200, "data": { "id": "1958000000000000001", "orderId": "1956112233445566778", "reportStatus": "CONFIRMED", "orderHeader": { "orderNo": "HL20260730001", "teamNo": "T20260730001", "productName": "呼伦贝尔草原 5 日游", "productType": "CORE", "productTypeName": "核心产品", "customerName": "张三", "departDate": "2026-08-15", "consultantName": "李四", "travelerComposition": "2大 1儿童 1幼童" }, "baseOrderAmount": 12800.00 }, "msg": "" } ``` 主报账人报账表同一订单的 `orderHeader` 完全一致: **请求**: GET /v3/admin/order/1956112233445566778/settlement/reports/reimbursement Authorization: Bearer (无请求体) **响应**(节选顶层结构): ```json { "code": 200, "data": { "baseInfo": { "orderId": "1956112233445566778", "reportStatus": "CONFIRMED", "primaryReporterName": "司机甲", "transferDirection": "REPORTER_TO_COMPANY", "transferAmount": 700.00 }, "orderHeader": { "orderNo": "HL20260730001", "teamNo": "T20260730001", "productName": "呼伦贝尔草原 5 日游", "productType": "CORE", "productTypeName": "核心产品", "customerName": "张三", "departDate": "2026-08-15", "consultantName": "李四", "travelerComposition": "2大 1儿童 1幼童" }, "incomeLines": [], "expenseLines": [], "advanceLines": [] }, "msg": "" } ``` ### 8.2 边界情况(订金未支付 / 无出行人构成 → null 字段不下发) **场景说明**:订单订金未支付(`teamNo` 为 null)、出行人四档数量全为 0(`travelerComposition` 为 null)、产品类型字典缺失(`productTypeName` 为 null)—— 这三个 key **整体不出现在 JSON 中**(不是返回 null 值)。`orderHeader` 对象本身仍下发。 **请求**: GET /v3/admin/order/1956112233445566889/settlement/reports/group Authorization: Bearer (无请求体) **响应**(节选): ```json { "code": 200, "data": { "orderId": "1956112233445566889", "reportStatus": "GENERATED", "orderHeader": { "orderNo": "HL20260810002", "productName": "阿尔山秋色 3 日游", "productType": "CUSTOM", "customerName": "王五", "departDate": "2026-09-01", "consultantName": "赵六" } }, "msg": "" } ``` ### 8.3 业务失败(订单不存在 / 房务角色越权) **场景说明 A**:`orderId` 不存在 → 581007。 **请求**: GET /v3/admin/order/999999999/settlement/reports/group Authorization: Bearer (无请求体) **响应**: ```json { "code": 581007, "msg": "订单不存在", "data": null } ``` **场景说明 B**:房务管理员角色调用 → 581045(两接口同样拦截)。 **请求**: GET /v3/admin/order/1956112233445566778/settlement/reports/reimbursement Authorization: Bearer <房务角色 admin JWT> (无请求体) **响应**: ```json { "code": 581045, "msg": "房务角色无权查看订单详情,房务仅可配房", "data": null } ``` ## 9. 业务边界 - ✅ **适用场景**:订单存在即可调,未核单(`GENERATED`)与已核单(`CONFIRMED`)订单出参结构完全一致,**都含 `orderHeader`** - ❌ **不适用场景**:房务角色(ROOM_MANAGER / HOUSE_KEEPER_LEAD)调用 → 581045 - ⚠️ **特殊边界**: - `orderHeader.teamNo`:订金未支付的订单没有团号,该 key 不下发,前端渲染团号位置需做空态处理(如显示 `-`) - `orderHeader.travelerComposition`:四档(大 / 儿童 / 幼童 / 婴儿)数量全为 0 时该 key 不下发;不要假设其必存在 - `orderHeader.customerName` 为**真名不脱敏**(核单财务域需要核对真实客户),前端不要二次脱敏 - 已核单订单的明细在核单完成时已冻结,实时计算结果与历史快照一致;若核单后通过「反确认」重新打开,报表会随明细变动实时变化(与改前快照行为不同,但反确认本身就意味着数据要重算) ## 10. 修改前后对比 ### 10.1 字段级对比 | 字段 | 改前 | 改后 | |------|------|------| | `data.orderHeader`(两接口) | 无此字段 | ✨ 新增对象,结构见 §5.3;内部 null 字段不下发 | | 单团核算表其余顶层字段 / incomeLines / costCategories | 现状 | **不变** | | 主报账人报账表 baseInfo / incomeLines / expenseLines / advanceLines | 现状 | **不变** | ### 10.2 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 已核单订单(CONFIRMED)报表数据源 | 读 finalize 时落库的终态快照 JSON | 与未核单订单一致,统一从 tab 明细实时计算(明细已冻结,结果与快照一致) | | 两路径出参一致性 | 快照 / 实时两套组装代码,字段演进可能出现不一致 | 单一实时组装路径,出参永远含 `orderHeader`,不再出现「未核单有头 / 已核单无头」分叉 | | `reportStatus` 取值 | GENERATED / CONFIRMED | **不变** | ## 11. 影响评估 / 回滚 ### 11.1 影响评估 - **是否破坏向后兼容**:否。出参纯增量,原有字段名 / 类型 / 结构 / 金额口径零变化;老前端不读 `orderHeader` 不受影响。数据源切换对已核单订单结果无差异(明细已冻结) - **前端是否必须同步上线**:否。前端按自身排期接入订单头展示即可 ### 11.2 回滚方案 - **回滚方式**:revert PR #5854 - **回滚后清理**:无(无 DDL、无缓存、无脏数据;回滚后已核单订单恢复读终态快照) ## 12. 注意事项 - **null 字段整体缺省**:`orderHeader` 内部 null 字段(teamNo / productTypeName / travelerComposition)的 **key 不出现在 JSON 中**,前端按可选字段处理,不要断言 key 必存在、也不要按 `key in obj` 之外的 null 值逻辑处理 - `orderHeader` **对象本身永远下发**,不需要判空整个对象 - **两个接口的 orderHeader 完全一致**,前端可抽公共组件 / 公共 TS 类型复用 - **中文名渲染**:`productTypeName` 后端已回填中文名(字典 `product_type`),可直接展示;如需动态字典渲染,调 `GET /admin/dict/data/product_type` - 若前端此前为报表弹窗标题栏从订单详情接口另行拼装订单号 / 产品名等信息,接入 `orderHeader` 后可清理该重复请求(非强制) - 无历史 workaround 需要清理(本能力此前不存在) ## 13. 关联 / 联系人 ### 13.1 链接 - **Issue**: [#5840](https://git.1814.love:8443/wx/HL/issues/5840) - **PR**: [#5854](https://git.1814.love:8443/wx/HL/pulls/5854) - **Merge commit**: [452ebd63](https://git.1814.love:8443/wx/HL/commit/452ebd63fc1a34968e90f259e8e87a18007e2c74) ### 13.2 联系人 - **后端负责人**: @yaosutu