From 690046d0b27be197e0415a2fb4f9540dd3e8f524 Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Tue, 11 Aug 2026 17:04:52 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20=E6=A0=B8=E5=8D=95=E4=B8=A4?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E7=BB=9F=E4=B8=80=E5=AE=9E=E6=97=B6=E8=B7=AF?= =?UTF-8?q?=E5=BE=84+=E5=87=BA=E5=8F=82=E6=96=B0=E5=A2=9E=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E5=A4=B4orderHeader=EF=BC=88#5840=EF=BC=89=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=90=8E=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PR #5854 / Issue #5840 / merge commit 452ebd63 - GET /v3/admin/order/{orderId}/settlement/reports/group 出参加 orderHeader - GET /v3/admin/order/{orderId}/settlement/reports/reimbursement 出参加 orderHeader - 两报表去掉快照读分叉统一实时计算(对前端透明,reportStatus 取值不变) --- ...报表加订单头orderHeader-修改接口-管理后台.md | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 changelogs-v2/2026-08/11_5840_核单两报表加订单头orderHeader-修改接口-管理后台.md diff --git a/changelogs-v2/2026-08/11_5840_核单两报表加订单头orderHeader-修改接口-管理后台.md b/changelogs-v2/2026-08/11_5840_核单两报表加订单头orderHeader-修改接口-管理后台.md new file mode 100644 index 0000000..c13dd31 --- /dev/null +++ b/changelogs-v2/2026-08/11_5840_核单两报表加订单头orderHeader-修改接口-管理后台.md @@ -0,0 +1,347 @@ +--- +schema: "hl-changelog/v2" +ticket: "5840" +title: "核单两报表统一实时路径 + 出参新增订单头 orderHeader" +consumer: "admin" +change_type: "修改接口" +author: "yaosutu(GIT)" +backend_status: "pending" +gateway_status: "pending" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "后端 PR #5854 已 merge 到 dev-v3(merge commit 452ebd63),尚未部署测试服;两核单报表出参统一新增 orderHeader 订单头对象,同时去掉已核单订单读终态快照的分叉、统一实时计算。前端待接入订单头展示。" +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