17 KiB
schema, ticket, title, consumer, change_type, author, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | change_type | author | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 5840 | 核单两报表统一实时路径 + 出参新增订单头 orderHeader | admin | 修改接口 | yaosutu(GIT) | deployed | pending | not_required | mmg | 2026-08-11 | 后端 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),按自身排期接入。 | 2026-08-11 | dev-v3 |
【✨ 修改接口·管理后台】核单两报表统一实时路径 + 出参新增订单头 orderHeader(#5840)
PR: #5854 | 服务: hl-order-service-v3 | 更新时间: 2026-08-11
1. 接口背景
核单工作台的两个报表接口 —— 单团核算表、主报账人报账表 —— 此前出参只有金额汇总与行列表,没有订单识别信息(订单号 / 团号 / 产品 / 客户 / 出团日期 / 定制师 / 出行人构成),报表弹窗的标题栏只能靠前端从订单详情接口另取数据拼装。
同时,两个接口此前存在快照读分叉:未完成核单的订单走实时组装,已完成核单的订单读 finalize 时落库的历史快照 JSON。两条路径的出参结构虽然对齐过,但任何出参字段演进都要同时维护两套组装代码,容易出现「未核单有某字段 / 已核单没有」的不一致。
本次变更两件事:
- 出参纯增量:两个接口出参统一新增
orderHeader订单头对象(字段全部来自 order_main 单表,无跨服务 JOIN)。 - 行为统一(对前端透明):删除快照读分叉,两个接口统一从 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 与相邻字段,其余金额 / 行列表结构与变更前一致故省略):
{
"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>
(无请求体)
响应(节选顶层结构):
{
"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>
(无请求体)
响应(节选):
{
"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>
(无请求体)
响应:
{ "code": 581007, "msg": "订单不存在", "data": null }
场景说明 B:房务管理员角色调用 → 581045(两接口同样拦截)。
请求:
GET /v3/admin/order/1956112233445566778/settlement/reports/reimbursement
Authorization: Bearer <房务角色 admin JWT>
(无请求体)
响应:
{ "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 链接
13.2 联系人
- 后端负责人: @yaosutu