hl-api-changelog/changelogs-v2/2026-08/11_5840_核单两报表加订单头orderHeader-修改接口-管理后台.md
2026-08-11 17:39:37 +08:00

348 行
17 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

---
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-v3merge 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<SettlementGroupReportRespVO>``code=200` 表示成功)。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | String | 报表 IDLong 序列化为字符串,无落库记录时缺省) |
| `orderId` | String | 订单 IDLong 序列化为字符串) |
| `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<SettlementReimbursementReportRespVO>``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 <admin JWT>
(无请求体)
**响应**(节选,仅展示本次新增的 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 <admin JWT>
(无请求体)
**响应**(节选顶层结构):
```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 <admin JWT>
(无请求体)
**响应**(节选):
```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 <admin JWT>
(无请求体)
**响应**
```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