---
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<SettlementGroupReportRespVO>`（`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<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
