--- schema: "hl-changelog/v2" ticket: "5820" title: "主报账人报账表出参重构:顶层三维化 + 支出行统一扁平字段(破坏性变更)" consumer: "admin" change_type: "修改接口" author: "yaosutu(GIT)" backend_status: "deployed" gateway_status: "pending" frontend_status: "implemented" frontend_owner: "mmg" frontend_ref: "fd070dc2" target_release: "" verified_at: "2026-08-11" status_note: "前端已实现(2026-08-11,mmg,fd070dc2),仅 driver/reimbursement 分支,group 单团核算未重构零改动:①adaptSettlementReport 的 reportStatus/confirmedAt 改优先读 baseInfo 回退顶层(不改则 confirmed 恒 false);②driver 汇总卡 6 字段改读 baseInfo,公共预支 publicPrepaidAmount→approvedAdvanceAmount,删 transferDirection 改 reporterNetAmount 正负推导(正=报账人应转回公司/负=公司应补报账人,金额取 transferAmount);③REPORT_DETAIL_CONFIG.driver 删 vehicleLines 项(车辆并入 expenseLines category=VEHICLE 逐天一行);④REPORT_FIELD_LABELS 补 date/reimburseAmount,expenseLines 12 扁平明细列数据驱动自适应。orderHeader 新增 returnDate/travelerCount 未接入(ReportModal 标题栏取 props.order 不读 orderHeader,按本次范围不接)。测试:ReportModal.spec 重构 fixture+1 baseInfo 方向推导用例,returnDetailAdapter.spec +4 baseInfo 兼容用例,settlement 91+4 全绿,checkpoint 精确文件集全过。" 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) - 后端负责人:腰苏图