From d362c3acb1a38b890a67e0e087fd5999777b2209 Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Tue, 11 Aug 2026 17:59:57 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20=E4=B8=BB=E6=8A=A5=E8=B4=A6?= =?UTF-8?q?=E4=BA=BA=E6=8A=A5=E8=B4=A6=E8=A1=A8=E5=87=BA=E5=8F=82=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=BB=9F=E4=B8=80=E5=AD=97=E6=AE=B5=EF=BC=88#5820/#58?= =?UTF-8?q?59=EF=BC=89=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 破坏性变更通知:顶层平铺汇总字段收拢进 baseInfo、expenseLines 统一 12 扁平字段、 删除 vehicleLines 与 6 个冗余字段、orderHeader 补 returnDate/travelerCount。 对应后端 PR #5839 + #5865,前端必须同步改造。 --- ...人报表出参重构统一字段-修改接口-管理后台.md | 563 ++++++++++++++++++ 1 file changed, 563 insertions(+) create mode 100644 changelogs-v2/2026-08/11_5820_主报账人报表出参重构统一字段-修改接口-管理后台.md diff --git a/changelogs-v2/2026-08/11_5820_主报账人报表出参重构统一字段-修改接口-管理后台.md b/changelogs-v2/2026-08/11_5820_主报账人报表出参重构统一字段-修改接口-管理后台.md new file mode 100644 index 0000000..acb6293 --- /dev/null +++ b/changelogs-v2/2026-08/11_5820_主报账人报表出参重构统一字段-修改接口-管理后台.md @@ -0,0 +1,563 @@ +--- +schema: "hl-changelog/v2" +ticket: "5820" +title: "主报账人报账表出参重构:顶层三维化 + 支出行统一扁平字段(破坏性变更)" +consumer: "admin" +change_type: "修改接口" +author: "yaosutu(GIT)" +backend_status: "deployed" +gateway_status: "pending" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "后端 PR #5839(#5820 出参重构)+ PR #5865(#5859 删 transferDirection + orderHeader 补 returnDate/travelerCount)均已 merge 到 dev-v3 并部署测试服。本次为破坏性出参变更:顶层平铺汇总字段全部移入 baseInfo 子对象、支出行 7 族稀疏字段统一为 12 个扁平字段、vehicleLines 顶层字段删除、baseInfo 删除 6 个冗余字段。前端必须同步改造后才能上线。" +updated_at: "2026-08-11" +base: "dev-v3" +--- + +# 【⚠️ 修改接口·管理后台】主报账人报账表出参重构:顶层三维化 + 支出行统一扁平字段(#5820 / #5859) + +## 1. 接口背景 + +核单结算域的「主报账人报账表」接口,供管理后台在订单核单时查看主报账人(通常是司机)的代收、垫付支出、预支与净额结算情况。 + +本次重构解决两个历史问题: + +1. **出参结构混乱**(#5820):原出参把 30+ 个汇总字段平铺在顶层,支出行按费用类别拆成 7 族各自一套字段名(住宿用 hotelName/roomTypeName/stayDate、餐食用 mealName/mealTypeName/mealDate、门票用 scenicName/specName/dayDate、车辆用 vehiclePlate/driverName/serviceDate/dailyPrice……),前端需要为每类支出写一套渲染逻辑,且存在 4 对语义重复的镜像字段。 +2. **订单抬头字段不全 + 方向字段冗余**(#5859):baseInfo.transferDirection 与 transferAmount 正负 / reporterNetAmount 表达的信息重复;orderHeader 缺返回日期与出行人总数。 + +## 2. 变更清单 + +| # | 变更 | 类型 | +|---|------|------| +| 1 | 顶层 30+ 平铺汇总字段全部收拢进 baseInfo 子对象 | ⚠️ 破坏性 | +| 2 | expenseLines 支出行由 7 族稀疏字段统一为同一套 12 个扁平字段 | ⚠️ 破坏性 | +| 3 | 顶层 vehicleLines 字段删除(车辆支出行并入 expenseLines,签单/公司直付不再重复列出) | ⚠️ 破坏性 | +| 4 | baseInfo 删除 6 个冗余字段:reconNetAmount / driverCollectedTailAmount / publicPrepaidAmount / advanceOutstandingAmount / transferStatus / transferDirection | ⚠️ 破坏性 | +| 5 | 报账口径明确为口径 A:expenseLines 只含报账人垫付(CASH_PAID)支出 | 🔧 行为变化 | +| 6 | orderHeader 新增 returnDate(返回日期)、travelerCount(出行人总数) | ✨ 新增字段 | + +## 3. 接口详情 + +| 项 | 值 | +|---|---| +| 方法 + 路径 | GET /v3/admin/order/{orderId}/settlement/reports/reimbursement | +| 接口名 | 查询主报账人报账表 | +| 使用场景 | 管理后台订单核单页,查看主报账人代收/垫付/预支/净额结算报表 | +| 认证 | 管理后台 JWT(/v3/admin/* 走网关鉴权) | +| 角色限制 | 房务角色(HOUSE)不可访问,调了会被拦截 | +| 幂等性 | 只读查询,幂等 | +| 限流 | 走网关默认限流,无接口级特殊限流 | + +## 4. 接口入参 + +### 4.1 路径参数 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| orderId | Long | 是 | 订单 ID,必须大于 0 | + +### 4.2 请求体 + +无请求体,无 Query 参数。 + +## 5. 出参字段 + +统一响应 Result,data 结构如下。 + +### 5.1 顶层结构 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| baseInfo | Object | 是 | 基础信息(汇总值 + 报账人 + 审计字段),恒下发对象 | +| orderHeader | Object | 否 | 订单头(订单号/团号/产品/客户/出团日期/定制师/出行人构成) | +| incomeLines | Array | 是 | 收入行(司机代收);无数据固定返回空数组 [] | +| expenseLines | Array | 是 | 支出行(统一扁平字段,仅报账人垫付支出);无数据固定返回空数组 [] | +| advanceLines | Array | 是 | 预支明细行(已审批预支逐条);无数据固定返回空数组 [] | + +> 顶层**不再有任何平铺的汇总金额字段**,也**不再有 vehicleLines**。 + +### 5.2 baseInfo 字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long(String) | 核单记录 ID(settlement_recon.recon_id);未生成时为空。Long 序列化为字符串防 JS 精度丢失 | +| orderId | Long(String) | 订单 ID,序列化为字符串 | +| reportStatus | String | 报表状态,枚举:GENERATED(已生成)/ CONFIRMED(已确认) | +| reportVersion | Integer | 报表版本号,固定从 1 开始 | +| primaryReporterId | Long(String) | 主报账人人员安排 ID | +| primaryReporterName | String | 主报账人姓名 | +| primaryReporterRole | String | 主报账人角色(如 DRIVER) | +| primaryReporterCollectedAmount | BigDecimal | 主报账人代收金额(收入行合计),保留两位小数 | +| approvedAdvanceAmount | BigDecimal | 已审批预支金额(预支行合计),保留两位小数 | +| reportablePaidCostAmount | BigDecimal | 可报账已付成本(支出行合计),保留两位小数 | +| reporterNetAmount | BigDecimal | 报账人净额(代收 + 预支 - 支出);正 = 报账人应转回公司,负 = 公司应补报账人 | +| primaryReporterDueAmount | BigDecimal | 主报账人应收尾款(核单口径),保留两位小数 | +| transferAmount | BigDecimal | 转账金额(净额绝对值),保留两位小数 | +| generatedBy | Long(String) | 生成人 ID | +| generatedByName | String | 生成人姓名 | +| generatedAt | String | 生成时间,格式 yyyy-MM-dd HH:mm:ss | +| confirmedBy | Long(String) | 确认人 ID;未确认时为空 | +| confirmedByName | String | 确认人姓名;未确认时为空 | +| confirmedAt | String | 确认时间,格式 yyyy-MM-dd HH:mm:ss;未确认时为空 | + +**转账方向判定**(替代被删的 transferDirection): + +- reporterNetAmount > 0 → 报账人应转回公司 +- reporterNetAmount < 0 → 公司应补报账人 +- transferAmount = |reporterNetAmount| + +### 5.3 orderHeader 字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| orderNo | String | 订单号 | +| teamNo | String | 团号(订金未支付为 null) | +| productName | String | 产品名 | +| productType | String | 产品类型枚举(如 CORE) | +| productTypeName | String | 产品类型中文名(字典 product_type 回填) | +| customerName | String | 客户姓名 | +| departDate | String | 出团日期,格式 yyyy-MM-dd | +| returnDate | String | **新增**:返回日期,格式 yyyy-MM-dd | +| consultantName | String | 定制师姓名 | +| travelerCount | Integer | **新增**:出行人总数(成人+儿童+幼童+婴儿,空档按 0 计) | +| travelerComposition | String | 出行人构成(数量为 0 的档不显示,全空为 null),如 "2大 1儿童 1幼童" | + +### 5.4 expenseLines 支出行字段表(统一扁平 12 字段) + +**所有分类共用同一套字段**,前端单 table 渲染即可,不用再按类别分叉。 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| category | String | 是 | 费用类别 code:HOTEL / TICKET / MEAL / VEHICLE / GUIDE / PHOTOGRAPHER / OTHER_EXPENSE / INSURANCE | +| categoryName | String | 是 | 费用类别中文名(字典 settlement_category) | +| itemName | String | 是 | 项目名(分类特有信息折叠,见下方折叠规则表) | +| unitPrice | BigDecimal | 否 | 单价,保留两位小数;**无单价概念的分类不输出该键** | +| quantity | BigDecimal | 否 | 数量(住宿=房间数,餐食=份数,门票=票数,车辆按天每行=1);**无数量概念的分类不输出该键** | +| amount | BigDecimal | 是 | 实际金额,保留两位小数 | +| reimburseAmount | BigDecimal | 是 | 报账金额,保留两位小数;人员行应报销与金额不同时分别给出,其余分类与 amount 相同 | +| paymentMethod | String | 是 | 付款方式;报账支出行固定 CASH_PAID | +| paymentMethodName | String | 是 | 付款方式中文名(字典 settlement_payment_method,缺值回退硬编码),如 "现金已付" | +| date | String | 是 | 业务日期,格式 yyyy-MM-dd(住宿=入住日,门票=游玩日,餐食=用餐日,车辆=服务日,人员=结算日) | +| remark | String | 否 | 备注;**无备注时不输出该键** | +| voucherUrls | Array | 否 | 凭证 URL 数组;**无凭证时不输出该键** | + +**itemName 折叠规则**(按 category): + +| category | itemName 格式 | 示例 | +|----------|--------------|------| +| HOTEL | 酒店名-房型 | 呼伦贝尔香格里拉大酒店-大床房 | +| TICKET | 景区名-规格 | 套娃景区-成人票 | +| MEAL | 餐食名(餐类型) | 手把肉套餐(午餐) | +| VEHICLE | 车牌 车型/司机 | 蒙A-E2E01 丰田普拉多/巴雅尔 | +| GUIDE / PHOTOGRAPHER | 人员姓名 | 巴特尔 | +| OTHER_EXPENSE / INSURANCE | 项目名 | 旅游意外险 | + +### 5.5 incomeLines 收入行字段表(本次未动) + +| 字段 | 类型 | 说明 | +|------|------|------| +| type | String | 行类型 code | +| typeName | String | 行类型中文名(字典 settlement_report_line_type) | +| receiptId | Long(String) | 线下收款记录 ID | +| amount | BigDecimal | 收款金额,保留两位小数 | +| channel | String | 收款渠道 code | +| channelName | String | 收款渠道中文名(PaymentChannelEnum 枚举 label) | +| payType | String | 收款款项类型 code | +| payTypeName | String | 收款款项类型中文名(PayType 枚举 label) | +| collectorStaffId | Long(String) | 收款人人员安排 ID | +| collectorName | String | 收款人姓名 | +| collectorRole | String | 收款人角色 code | +| collectorRoleName | String | 收款人角色中文名(字典 staff_role) | +| receivedAt | String | 收款时间,格式 yyyy-MM-dd HH:mm:ss | +| remark | String | 备注;无备注时为空 | + +### 5.6 advanceLines 预支明细行字段表(本次未动) + +| 字段 | 类型 | 说明 | +|------|------|------| +| type | String | 行类型 code | +| typeName | String | 行类型中文名(字典 settlement_report_line_type),如 "已审批预支" | +| advanceId | Long(String) | 预支单 ID | +| payeeStaffId | Long(String) | 借款对象人员安排 ID | +| payeeName | String | 借款对象姓名 | +| payeeRole | String | 借款对象角色 code | +| payeeRoleName | String | 借款对象角色中文名(字典 staff_role) | +| advanceType | String | 预支类型 code | +| advanceTypeName | String | 预支类型中文名(字典 advance_type),如 "住宿押金" | +| amount | BigDecimal | 预支金额,保留两位小数 | +| purpose | String | 预支用途 | +| voucherUrl | String | 凭证 URL | +| status | String | 预支状态 code | +| statusText | String | 预支状态中文名(AdvanceStatus 枚举 label),如 "已通过" | +| submittedAt | String | 提交时间,格式 yyyy-MM-dd HH:mm:ss | +| approvedAt | String | 审批时间,格式 yyyy-MM-dd HH:mm:ss | +| approvedBy | String | 审批人姓名 | + +## 6. 枚举 / 数据字典 + +| 字段 | 来源 | 取值 | +|------|------|------| +| baseInfo.reportStatus | 枚举 | GENERATED(已生成)/ CONFIRMED(已确认) | +| expenseLines.category | 字典 settlement_category | HOTEL(住宿)/ TICKET(门票/游玩项目)/ MEAL(餐食)/ VEHICLE(车辆)/ GUIDE(导游)/ PHOTOGRAPHER(摄影师)/ OTHER_EXPENSE(其他费用)/ INSURANCE(保险) | +| expenseLines.paymentMethod | 字典 settlement_payment_method | 报账支出行固定 CASH_PAID(现金已付) | +| incomeLines.channel | PaymentChannelEnum | 收款渠道枚举 | +| incomeLines.payType | PayType 枚举 | 收款款项类型枚举(如尾款) | +| advanceLines.status | AdvanceStatus 枚举 | 预支状态(如已通过) | +| orderHeader.productType | 字典 product_type | 产品类型 | + +**已删除的枚举字段**:transferStatus(#5816 连带下线)、transferDirection(#5859 删除,方向由 transferAmount 正负 / reporterNetAmount 表达)。 + +## 7. 错误码 + +| code | message | 触发场景 | +|------|---------|----------| +| 581007 | 订单不存在 | orderId 查不到订单 | +| 584088 | 核单凭证数据损坏,请联系管理员处理 | 快照读回时凭证数据异常(脏行),不会抛 500 | +| 400 | 订单 ID 必须大于 0 | orderId 路径参数校验失败 | + +## 8. 示例 + +### 8.1 典型成功 + +请求: + + GET /v3/admin/order/2087088947225038849/settlement/reports/reimbursement + +响应(已核单 CONFIRMED 订单,含住宿/门票/餐食/车辆支出 + 一条已审批预支): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "baseInfo": { + "id": "2087089146102255617", + "orderId": "2087088947225038849", + "reportStatus": "CONFIRMED", + "reportVersion": 1, + "primaryReporterId": "7001", + "primaryReporterName": "巴雅尔", + "primaryReporterRole": "DRIVER", + "primaryReporterCollectedAmount": 0, + "approvedAdvanceAmount": 3000.00, + "reportablePaidCostAmount": 1799.00, + "reporterNetAmount": -4799.00, + "primaryReporterDueAmount": 0, + "transferAmount": 4799.00, + "generatedBy": "2037350531801993218", + "generatedByName": "腰苏图", + "generatedAt": "2026-08-11 16:10:03", + "confirmedBy": "2037350531801993218", + "confirmedByName": "腰苏图", + "confirmedAt": "2026-08-11 16:10:03" + }, + "orderHeader": { + "orderNo": "HL20260811001", + "teamNo": "T20260811001", + "productName": "呼伦贝尔草原 5 日游", + "productType": "CORE", + "productTypeName": "核心产品", + "customerName": "张三", + "departDate": "2026-08-29", + "returnDate": "2026-09-02", + "consultantName": "李四", + "travelerCount": 5, + "travelerComposition": "2大 1儿童 1幼童" + }, + "incomeLines": [], + "expenseLines": [ + { + "category": "HOTEL", + "categoryName": "住宿", + "itemName": "呼伦贝尔香格里拉大酒店-大床房", + "unitPrice": 320.00, + "quantity": 1, + "amount": 320.00, + "reimburseAmount": 320.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现金已付", + "date": "2026-08-29", + "remark": "含早", + "voucherUrls": ["https://oss.example.com/voucher/hotel-1.jpg"] + }, + { + "category": "TICKET", + "categoryName": "门票/游玩项目", + "itemName": "套娃景区-成人票", + "unitPrice": 99.00, + "quantity": 1, + "amount": 99.00, + "reimburseAmount": 99.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现金已付", + "date": "2026-08-30" + }, + { + "category": "MEAL", + "categoryName": "餐食", + "itemName": "手把肉套餐(午餐)", + "unitPrice": 68.00, + "quantity": 5, + "amount": 340.00, + "reimburseAmount": 340.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现金已付", + "date": "2026-08-30" + }, + { + "category": "VEHICLE", + "categoryName": "车辆", + "itemName": "蒙A-E2E01 丰田普拉多/巴雅尔", + "unitPrice": 1000.00, + "quantity": 1, + "amount": 1000.00, + "reimburseAmount": 1000.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现金已付", + "date": "2026-08-29" + } + ], + "advanceLines": [ + { + "type": "APPROVED_ADVANCE", + "typeName": "已审批预支", + "advanceId": "5001", + "payeeStaffId": "7001", + "payeeName": "巴雅尔", + "payeeRole": "DRIVER", + "payeeRoleName": "司机", + "advanceType": "ACCOMMODATION_DEPOSIT", + "advanceTypeName": "住宿押金", + "amount": 3000.00, + "purpose": "酒店押金", + "voucherUrl": "https://oss.example.com/advance-v1.jpg", + "status": "APPROVED", + "statusText": "已通过", + "submittedAt": "2026-08-28 10:00:00", + "approvedAt": "2026-08-28 12:00:00", + "approvedBy": "财务丙" + } + ] + }, + "traceId": null, + "success": true +} +``` + +### 8.2 边界情况 + +**边界 1:零收入零预支零支出**(刚生成报表、尚未录任何行)——三个数组固定返回空数组,不是 null: + +```json +{ + "code": 200, + "data": { + "baseInfo": { + "id": "2087089146102255617", + "orderId": "2087088947225038849", + "reportStatus": "GENERATED", + "reportVersion": 1, + "primaryReporterId": "7001", + "primaryReporterName": "巴雅尔", + "primaryReporterRole": "DRIVER", + "primaryReporterCollectedAmount": 0, + "approvedAdvanceAmount": 0, + "reportablePaidCostAmount": 0, + "reporterNetAmount": 0, + "primaryReporterDueAmount": 0, + "transferAmount": 0, + "generatedBy": "2037350531801993218", + "generatedByName": "腰苏图", + "generatedAt": "2026-08-11 16:10:03", + "confirmedBy": null, + "confirmedByName": null, + "confirmedAt": null + }, + "orderHeader": { + "orderNo": "HL20260811001", + "teamNo": null, + "productName": "呼伦贝尔草原 5 日游", + "productType": "CORE", + "productTypeName": "核心产品", + "customerName": "张三", + "departDate": "2026-08-29", + "returnDate": "2026-09-02", + "consultantName": "李四", + "travelerCount": 0, + "travelerComposition": null + }, + "incomeLines": [], + "expenseLines": [], + "advanceLines": [] + }, + "success": true +} +``` + +**边界 2:人员行(GUIDE)+ 无凭证无备注 + 报销金额与金额不同**——unitPrice/quantity/remark/voucherUrls 键不输出: + +```json +{ + "category": "GUIDE", + "categoryName": "导游", + "itemName": "巴特尔", + "amount": 500.00, + "reimburseAmount": 450.00, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现金已付", + "date": "2026-08-31" +} +``` + +**边界 3:门票单价为 0**(免费票)——unitPrice: 0.0 正常输出,金额 0: + +```json +{ + "category": "TICKET", + "categoryName": "门票/游玩项目", + "itemName": "蓝房子(乌苏浪子湖)-成人票", + "unitPrice": 0.0, + "quantity": 1, + "amount": 0.0, + "reimburseAmount": 0.0, + "paymentMethod": "CASH_PAID", + "paymentMethodName": "现金已付", + "date": "2026-08-31" +} +``` + +### 8.3 业务失败 + +**订单不存在**: + + GET /v3/admin/order/999999999/settlement/reports/reimbursement + +```json +{ + "code": 581007, + "message": "订单不存在", + "success": false +} +``` + +**参数校验失败**(orderId = 0): + + GET /v3/admin/order/0/settlement/reports/reimbursement + +```json +{ + "code": 400, + "message": "订单 ID 必须大于 0", + "success": false +} +``` + +**房务角色访问被拦**(House 角色 JWT 调用): + +```json +{ + "code": 403, + "message": "无权限访问", + "success": false +} +``` + +## 9. 业务边界 + +**适用**: +- 订单已进入核单流程(已生成核单记录),查看主报账人维度的结算报表 +- 已核单(CONFIRMED)与核单中(GENERATED)订单均可调,报表实时计算 + +**不适用**: +- 未发起核单的订单:baseInfo.id 等审计字段为空,汇总金额为 0 +- 需要看整团(含非报账人支付)口径时用单团核算表接口 GET /v3/admin/order/{orderId}/settlement/reports/group,本接口是报账人视角 + +**特殊边界**: +- **报账口径 A**:expenseLines 只含报账人垫付(CASH_PAID)的支出;签单 / 公司直接付的车务费**不进** expenseLines,前端不要期待在支出行里看到所有成本 +- incomeLines 只含报账人代收,线上支付(微信等)不在此列 +- 旧快照兼容:历史已确认报表的旧快照数据读回时,旧扁平键静默忽略、落默认空 baseInfo,不会抛 500 + +## 10. 修改前后对比 + +### 10.1 顶层结构对比 + +| 维度 | 修改前 | 修改后 | +|------|--------|--------| +| 汇总字段 | 30+ 个平铺在顶层(id/orderId/reportStatus/primaryReporterCollectedAmount/...) | 全部收拢进 baseInfo 子对象 | +| 车辆支出 | 顶层独立 vehicleLines 数组(车辆专属字段) | 删除;车辆支出行并入 expenseLines(category=VEHICLE) | +| 订单头 | orderHeader(9 字段) | orderHeader(11 字段,新增 returnDate/travelerCount) | + +### 10.2 baseInfo 字段级对比(删除清单) + +| 删除字段 | 原位置 | 替代取值 | +|----------|--------|----------| +| reconNetAmount | 顶层平铺 | baseInfo.reporterNetAmount(同值镜像) | +| driverCollectedTailAmount | 顶层平铺 | baseInfo.primaryReporterCollectedAmount(同值镜像) | +| publicPrepaidAmount | 顶层平铺 | baseInfo.approvedAdvanceAmount(同口径) | +| advanceOutstandingAmount | 顶层平铺 | 无替代(该口径废弃,预支看 approvedAdvanceAmount + advanceLines) | +| transferStatus | baseInfo(#5816 连带下线) | 无替代(转账状态跟踪能力下线) | +| transferDirection | baseInfo | 由 reporterNetAmount 正负判定:正=报账人应转回公司,负=公司应补报账人;金额取 transferAmount | + +> 其余原顶层平铺字段(id/orderId/reportStatus/reportVersion/primaryReporter*/各金额/generated*/confirmed*)**字段名与语义不变**,仅位置从顶层移入 baseInfo。 + +### 10.3 expenseLines 字段级对比 + +| 修改前(按类别稀疏字段) | 修改后(统一扁平字段) | +|--------------------------|------------------------| +| HOTEL: hotelName / roomTypeName / stayDate / roomCount / unitPrice | itemName=「酒店-房型」/ date / quantity / unitPrice | +| TICKET: scenicName / specName / dayDate / ticketCount / ticketUnitPrice | itemName=「景区-规格」/ date / quantity / unitPrice | +| MEAL: mealName / mealTypeName / mealDate / quantity / unitPrice | itemName=「餐食名(餐类型)」/ date / quantity / unitPrice | +| VEHICLE: vehiclePlate / driverName / serviceDate / dailyPrice | itemName=「车牌 车型/司机」/ date / unitPrice(quantity 恒 1) | +| GUIDE/PHOTOGRAPHER: staffName / settledDate / amount / reimburseAmount | itemName=姓名 / date / amount / reimburseAmount | +| 各类各自的付款方式/备注/凭证字段名 | 统一 paymentMethod/paymentMethodName/remark/voucherUrls | + +### 10.4 行为级对比 + +| 场景 | 修改前 | 修改后 | +|------|--------|--------| +| 签单/公司直付车务费 | 出现在 vehicleLines | 不出现在本接口任何行(口径 A:只列报账人垫付) | +| 支出行渲染 | 前端按 7 族类别各写一套列 | 单 table 统一 12 字段渲染 | +| 调旧字段(如 data.reportStatus、data.vehicleLines、data.baseInfo.transferDirection) | 有值 | **undefined**(字段不存在,不报错静默丢失) | + +## 11. 影响评估 / 回滚 + +### 11.1 影响评估 + +- **破坏兼容性**:⚠️ 是。所有读顶层平铺汇总字段、读 vehicleLines、按类别分叉渲染支出行、读 transferStatus/transferDirection 的前端代码**全部失效**(取到 undefined)。 +- **前端必须同步上线**:是。前端需要: + 1. 汇总字段读取路径从 data.xxx 改为 data.baseInfo.xxx + 2. 支出行渲染改为单 table 统一字段(category/categoryName/itemName/unitPrice/quantity/amount/reimburseAmount/paymentMethod/paymentMethodName/date/remark/voucherUrls) + 3. 删除 vehicleLines 相关渲染 + 4. 转账方向展示改由 reporterNetAmount 正负 + transferAmount 推导 + 5. 4 个镜像字段改读保留字段(见 §10.2 替代表) +- **后端兼容**:旧快照数据读回不报错(静默忽略旧键),无需数据迁移。 + +### 11.2 回滚方案 + +- 后端回滚 = revert PR #5839 + PR #5865 两个 merge commit,重启 hl-order-service-v3。出参即恢复旧结构。 +- 前端回滚 = 切回旧版前端包。前后端必须同版本(新后端 + 旧前端 = 页面全空)。 +- 无 DDL,无数据迁移,回滚无残留风险。 + +## 12. 注意事项 + +1. **所有 Long ID 字段(id/orderId/primaryReporterId/receiptId/advanceId/各种 staffId/generatedBy/confirmedBy)序列化为 JSON 字符串**,前端按 string 处理,不要 Number() 转换(防 JS 精度丢失)。 +2. expenseLines 中 unitPrice / quantity / remark / voucherUrls 是**条件输出键**:无值时整个键不出现(NON_NULL),前端取值前判空。 +3. reimburseAmount 大多数分类与 amount 相同;只有人员行(GUIDE/PHOTOGRAPHER)应报销与金额可能不同,展示报账口径时以 reimburseAmount 为准。 +4. 转账方向不要再找 transferDirection 字段:用 reporterNetAmount > 0 判「报账人转回公司」、< 0 判「公司补报账人」,transferAmount 恒为绝对值。 +5. orderHeader.travelerComposition 全空档时为 null,teamNo 订金未支付时为 null,展示需兜底。 +6. 本接口与单团核算表 reports/group 是两个口径:本接口 = 主报账人视角(只含报账人垫付),group = 整团视角(含全部成本)。前端不要把两个接口的行混在一起渲染。 +7. 车辆支出行在 expenseLines 里按服务日**逐天一行**(quantity 恒 1),不是一车一行。 + +## 13. 关联 / 联系人 + +- Issue: + - https://git.1814.love:8443/wx/HL/issues/5820 (主报账人报账表 RespVO 重构) + - https://git.1814.love:8443/wx/HL/issues/5859 (删 transferDirection + orderHeader 补 returnDate/travelerCount) +- PR: + - https://git.1814.love:8443/wx/HL/pulls/5839 (#5820 出参重构) + - https://git.1814.love:8443/wx/HL/pulls/5865 (#5859 orderHeader 补字段 + 删 transferDirection) +- 服务:hl-order-service-v3(端口 8086) +- 后端负责人:腰苏图