diff --git a/changelogs-v2/2026-09/28_8384_团期预支防双花-修改接口-管理后台.md b/changelogs-v2/2026-09/28_8384_团期预支防双花-修改接口-管理后台.md new file mode 100644 index 00000000..aebc6fdb --- /dev/null +++ b/changelogs-v2/2026-09/28_8384_团期预支防双花-修改接口-管理后台.md @@ -0,0 +1,314 @@ +--- +schema: "hl-changelog/v2" +ticket: "8384" +title: "团期预支防双花:团单子订单禁走订单级预支入口(新错误码 589558)+ 团期财务在途预支口径补 PAID" +consumer: "admin" +author: "yst" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "" +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