--- schema: "hl-changelog/v2" ticket: "8384" title: "团期预支防双花:团单子订单禁走订单级预支入口(新错误码 589558)+ 团期财务在途预支口径补 PAID" consumer: "admin" author: "yst" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "implemented" frontend_owner: "mmg" frontend_ref: "577683cf59a5ce3bf74bc48e6682be745de638c1" target_release: "v2.1" verified_at: "2026-09-29" status_note: "前端已交付(团期子订单隐藏订单级预支按钮,589558 拦截器透 message 兜底),详见 hl-admin v2.1 提交 577683cf。" updated_at: "2026-09-28" base: "dev-v3" --- # 【修改接口·管理后台】团期预支防双花 (#8384) > **PR**: #8479 | **服务**: hl-order-service-v3 | **更新时间**: 2026-09-28 16:30 ## 1. 接口背景 团期预支走「统一池」口径:同一团期下所有预支(团期级 + 各子订单级)共用一个尾款池、共享一条额度上限。本次修复该模型下的两个「双花」缺口: 1. **入口双花**:团期产品的子订单(即订单挂在某个团期下的单)此前仍可以从「订单级预支」入口申请预支,同一笔尾款可能在订单级、团期级两个入口被重复支取。业务拍板:**团期子订单只允许走团期级预支入口,订单级入口一律拒绝**。 2. **口径双花**:团期财务 Tab 的「可支取余额」在计算扣减项时,漏算了「已付款但核单尚未扣回」的预支(状态 PAID),导致这笔钱在核单完成前被重复放出额度。本次把 PAID 补入扣减口径,与订单级预支上限的既有口径对齐。 ## 2. 变更清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 创建预支(订单级) | POST | `/v3/admin/order/{orderId}/advance` | 行为变更 | 团期子订单调用直接返回新错误码 **589558**,不再创建预支;普通散客订单行为不变 | | 2 | 团期财务总览(财务 Tab) | GET | `/v3/admin/order/group-batch/{groupBatchId}/finance` | 行为变更 | 「可支取余额」的统计口径补入 PAID 状态预支;字段结构零变化,返回值数值可能变小 | ## 3. 接口详情 ### 3.1 创建预支(订单级) - **使用场景**:订单详情页为单个订单的报账人申请预支借款(创建即进待审批) - **认证**:需管理后台 JWT - **幂等性**:否(同单并发创建由后端串行化) - **限流**:无 ### 3.2 团期财务总览(财务 Tab) - **使用场景**:团期详情页「财务」Tab,展示整团应收/已收/待收/预支与逐户明细 - **认证**:需管理后台 JWT + `group-batch:finance:view` 权限 - **幂等性**:是(只读查询) - **限流**:无 ## 4. 接口入参 ### 4.1 创建预支(POST `/v3/admin/order/{orderId}/advance`) #### 路径参数 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | orderId | Long | 必填 | 订单 ID | #### 请求体字段 | 字段 | 类型 | 必填 | 说明 | 校验规则 | |------|------|------|------|----------| | payeeStaffId | Long | 必填 | 借款对象(取借款对象候选下拉接口 `GET /v3/admin/order/{orderId}/advance/payee-candidates` 返回的 id) | 必须为本订单的在职人员 | | advanceType | String | 必填 | 预支借款类型(取数据字典 `advance_type` 的 dictValue) | 非空 | | amount | Number | 必填 | 预支金额 | 必须 > 0,且不超过可支取余额上限 | | purpose | String | 可选 | 用途说明 | — | | voucherUrl | String | 可选 | 凭证文件 URL | — | ### 4.2 团期财务总览(GET `/v3/admin/order/group-batch/{groupBatchId}/finance`) #### 路径参数 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | groupBatchId | Long | 必填 | 团期 ID | 无请求体、无 Query 参数。 ## 5. 出参(响应) ### 5.1 创建预支 → `OrderAdvanceRespVO` | 字段 | 类型 | 说明 | |------|------|------| | id | String(Long) | 预支 ID | | orderId | String(Long) | 订单 ID | | teamNo | String | 团号 | | payeeStaffId | String(Long) | 报账人 staff 分配 ID(快照) | | payeeName | String | 报账人姓名(快照) | | payeeRole | String | 报账人角色代码 | | payeeRoleText | String | 报账人角色中文文案 | | advanceType | String | 预支类型 | | amount | Number | 预支金额 | | purpose | String | 用途说明 | | voucherUrl | String | 凭证文件 URL | | status | String | 状态代码(枚举见 §6) | | statusText | String | 状态中文文案 | | rejectReason | String | 驳回原因(仅 REJECTED 有值) | | createdByName | String | 创建人姓名 | | createTime | String(日期时间) | 创建时间 | | submittedAt | String(日期时间) | 提交审批时间 | | approvedAt | String(日期时间) | 审批时间 | | approvedBy | String | 审批人姓名 | ### 5.2 团期财务总览 → `GroupBatchFinanceRespVO` | 字段 | 类型 | 说明 | |------|------|------| | receivableAmount | String(金额) | 整团应收 | | receivedAmount | String(金额) | 整团已收 | | unpaidAmount | String(金额) | 整团待收(本期总尾款,预支上限计算基数) | | advanceApproved | String(金额) | 已预支 = 本团期 APPROVED 状态预支合计(团期级 + 各子订单级) | | advancePending | String(金额) | 待审批预支 = 本团期 SUBMITTED 状态预支合计 | | advanceAvailable | String(金额) | **可支取余额** = 团期尾款池 −(待审批 + 已通过 + 已付款未核单),下限 0。**本次口径变化字段** | | items | Array | 逐户付款明细(一户一行,全量返回不分页) | | totals | Object | 整团合计行(totalPrice / paidAmount / unpaidAmount) | | withdrawnCount | Integer | 已退团户数 | | primaryPayeeName | String | 主报账人姓名(未设置为 null) | | secondaryPayeeName | String | 次报账人姓名(未设置为 null) | > 字段结构**零变化**;变化的是 `advanceAvailable` 的计算口径(见 §10)。 ## 6. 枚举 / 数据字典 ### 6.1 预支状态 status(`AdvanceStatus`) **所属字段**:`OrderAdvanceRespVO.status` | **类型**:`String` | 值 | 中文 | 说明 | |----|------|------| | `SUBMITTED` | 待审批 | 创建即进此态,占额度 | | `APPROVED` | 已通过 | 财务审批通过,占额度 | | `PAID` | 已支付 | 出纳已付款、核单未扣回,**仍占额度**(核单完成才释放) | | `REJECTED` | 已驳回 | 终态,不占额度 | ## 7. 错误码 | code | 含义 | 触发场景 | |------|------|----------| | **589558**(新增) | 团期订单请通过团期预支入口申请 | 对**团期子订单**(挂在团期下的订单)调用订单级创建预支接口 `POST /v3/admin/order/{orderId}/advance` 时直接返回,不创建预支 | | 585003 | 当前订单状态不允许预支 | 订单状态不在 {待出发, 行程中} 或已结算完成(既有行为不变;团单判断优先于此码) | | 585004 | 预支金额超过可用余额上限 | 申请金额 > 可支取余额上限(既有行为不变) | > 团单判断**先于**状态判断:团期子订单即使状态满足,也固定返回 589558,不会返回 585003。 ## 8. 示例(3 组:典型 / 边界 / 异常) ### 8.1 业务失败(新增场景):团期子订单调订单级预支入口 **场景说明**:订单是团期产品的子订单,前端仍调了订单级入口 → 返回 589558。 **请求**: ``` POST /v3/admin/order/1234567890123456789/advance Authorization: Bearer Content-Type: application/json ``` ```json { "payeeStaffId": 9876543210987654321, "advanceType": "TRAVEL_EXPENSE", "amount": 500.00, "purpose": "门票垫付" } ``` **响应**: ```json { "code": 589558, "message": "团期订单请通过团期预支入口申请", "data": null } ``` ### 8.2 典型成功:普通散客订单正常创建预支 **场景说明**:订单不挂在任何团期下,状态为待出发/行程中、未结算完成 → 行为与改前完全一致。 **请求**: ``` POST /v3/admin/order/1111222233334444555/advance Authorization: Bearer Content-Type: application/json ``` ```json { "payeeStaffId": 9876543210987654321, "advanceType": "TRAVEL_EXPENSE", "amount": 500.00, "purpose": "门票垫付" } ``` **响应**: ```json { "code": 200, "message": "success", "data": { "id": "7777888899990000111", "orderId": "1111222233334444555", "teamNo": "26-0480", "payeeStaffId": "9876543210987654321", "payeeName": "张领队", "payeeRole": "GUIDE", "payeeRoleText": "导游", "advanceType": "TRAVEL_EXPENSE", "amount": 500.00, "purpose": "门票垫付", "voucherUrl": null, "status": "SUBMITTED", "statusText": "待审批", "rejectReason": null, "createdByName": "李财务", "createTime": "2026-09-28T16:00:00", "submittedAt": "2026-09-28T16:00:00", "approvedAt": null, "approvedBy": null } } ``` ### 8.3 边界情况:团期财务 Tab 可支取余额口径 **场景说明**:某团期尾款池(unpaidAmount)= 7300.00;本团期有 1 笔预支已审批通过并已付款(PAID,金额 2000.00),核单尚未扣回。 **请求**: ``` GET /v3/admin/order/group-batch/5555666677778888999/finance Authorization: Bearer (无请求体) ``` **响应(改后)**: ```json { "code": 200, "message": "success", "data": { "receivableAmount": "40200.00", "receivedAmount": "32900.00", "unpaidAmount": "7300.00", "advanceApproved": "0.00", "advancePending": "0.00", "advanceAvailable": "5300.00", "items": [], "totals": { "totalPrice": "40200.00", "paidAmount": "32900.00", "unpaidAmount": "7300.00" }, "withdrawnCount": 0, "primaryPayeeName": "张领队", "secondaryPayeeName": null } } ``` > 对比:改前同一数据下 `advanceAvailable` 返回 `"7300.00"`(PAID 的 2000.00 未计入扣减);改后返回 `"5300.00"`。该笔 PAID 预支在核单完成扣回后才会释放额度。 ## 9. 业务边界 - 适用场景(订单级预支):不挂在任何团期下的普通散客订单,状态为待出发/行程中、未结算完成 → 行为与改前**完全一致** - 不适用场景(订单级预支):团期产品的子订单 → 固定返回 589558,须改用团期级预支入口(`POST /v3/admin/order/group-batch/{groupBatchId}/advance`,该接口逻辑本次不变) - 特殊边界(财务 Tab):团期下存在 PAID(已付款未核单)预支时,`advanceAvailable` 比改前变小;核单完成后该笔释放,`advanceAvailable` 回升。这是口径修正,不是数据异常 ## 10. 修改前后对比 ### 10.1 字段级对比 无字段增删 / 改名 / 类型变化。 ### 10.2 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 团期子订单调订单级预支入口 | 放行(只看本单状态/上限),可能与团期级重复支取同一笔尾款 | 直接返回 **589558**,不创建预支 | | 团期财务 Tab「可支取余额」扣减口径 | 只扣 SUBMITTED + APPROVED | 扣 SUBMITTED + APPROVED + **PAID**(已付款未核单也占额度) | | 普通散客订单订单级预支 | 原有校验链 | **不变** | | 团期级预支入口 | 原有逻辑 | **不变** | ## 11. 影响评估 / 回滚 ### 11.1 影响评估 - **是否破坏向后兼容**:行为收紧型变更。① 团期子订单此前能调通的订单级预支入口现在固定失败(589558)——若前端在团期子订单详情页暴露了订单级预支入口,需要处理该错误码;② 财务 Tab `advanceAvailable` 数值在存在 PAID 预支时变小,属口径修正 - **前端是否必须同步上线**:否。接口字段结构零变化;但建议尽快处理 589558 的提示与入口引导(把团期子订单的预支操作引导到团期级入口) - **影响已有数据**:无数据迁移;既有 PAID 预支自动按新口径计入扣减 ### 11.2 回滚方案 - **回滚方式**:revert merge commit `7bbc98c335`,重新部署 hl-order-service-v3 即恢复原行为 - **回滚后清理**:无需清理数据 / 缓存 - **回滚耗时**:约等于一次常规服务部署 ## 12. 注意事项 - 若前端此前对「团期子订单调订单级预支失败」有任何本地兜底 / workaround(如自行判断团单后隐藏入口但保留了调用),本次后端已硬拦,相关兜底不会再触发到创建成功分支 - 财务 Tab 的 `advanceAvailable` 请继续直接读接口返回值,不要本地自算(本次口径调整只发生在服务端,本地自算会与卡片对不上) - 需要重启的服务:**hl-order-service-v3**(测试服部署走 Deploy Panel 操作) ## 13. 关联 / 联系人 ### 13.1 链接 - **Issue**: [#8384](https://git.1814.love/wx/HL/issues/8384) - **PR**: [#8479](https://git.1814.love/wx/HL/pulls/8479) - **Merge commit**: [7bbc98c335](https://git.1814.love/wx/HL/commit/7bbc98c3359ef2fe31cd69e9c098a29d625caca4) ### 13.2 联系人 - **后端负责人**: @yst