13 KiB
13 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 8384 | 团期预支防双花:团单子订单禁走订单级预支入口(新错误码 589558)+ 团期财务在途预支口径补 PAID | admin | yst | 修改接口 | deployed | verified | implemented | mmg | 577683cf59a5ce3bf74bc48e6682be745de638c1 | v2.1 | 2026-09-29 | 前端已交付(团期子订单隐藏订单级预支按钮,589558 拦截器透 message 兜底),详见 hl-admin v2.1 提交 577683cf。 | 2026-09-28 | dev-v3 |
【修改接口·管理后台】团期预支防双花 (#8384)
PR: #8479 | 服务: hl-order-service-v3 | 更新时间: 2026-09-28 16:30
1. 接口背景
团期预支走「统一池」口径:同一团期下所有预支(团期级 + 各子订单级)共用一个尾款池、共享一条额度上限。本次修复该模型下的两个「双花」缺口:
- 入口双花:团期产品的子订单(即订单挂在某个团期下的单)此前仍可以从「订单级预支」入口申请预支,同一笔尾款可能在订单级、团期级两个入口被重复支取。业务拍板:团期子订单只允许走团期级预支入口,订单级入口一律拒绝。
- 口径双花:团期财务 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 <token>
Content-Type: application/json
{
"payeeStaffId": 9876543210987654321,
"advanceType": "TRAVEL_EXPENSE",
"amount": 500.00,
"purpose": "门票垫付"
}
响应:
{
"code": 589558,
"message": "团期订单请通过团期预支入口申请",
"data": null
}
8.2 典型成功:普通散客订单正常创建预支
场景说明:订单不挂在任何团期下,状态为待出发/行程中、未结算完成 → 行为与改前完全一致。
请求:
POST /v3/admin/order/1111222233334444555/advance
Authorization: Bearer <token>
Content-Type: application/json
{
"payeeStaffId": 9876543210987654321,
"advanceType": "TRAVEL_EXPENSE",
"amount": 500.00,
"purpose": "门票垫付"
}
响应:
{
"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 <token>
(无请求体)
响应(改后):
{
"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
- PR: #8479
- Merge commit: 7bbc98c335
13.2 联系人
- 后端负责人: @yst