文件
hl-api-changelog/changelogs-v2/2026-10/02_8684_预支列表出参补撤回标记canRevoke-修改接口-管理后台.md
2026-10-02 15:26:44 +08:00

24 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 8684 预支相关 8 个接口出参新增 canRevoke:当前操作人点「撤回」能否成功,与撤回接口共用同一个判定 admin jw(GIT) 修改接口 deployed verified implemented mmg 41468f5297fc1d5c04b49772b76af73d2fd0c3bf v2.1 2026-10-02 已合并 dev-v3(806058c66)并部署 TEST,自签低权限 token 经网关实测:三个列表逐角色取值、联表 created_by 探针、开关关闭→还原往返(md5 逐字节还原,渲染期间拒绝日志零新增)、按 canRevoke 抽样真实撤回。前端待改两处撤回按钮:订单详情预支弹窗 AdvanceModal.vue 与团期财务页签 FinanceTab.vue 的 v-if 改为 a.canRevoke;审批中心本来没有撤回按钮。前端已交付(2026-10-02):两处撤回按钮 v-if 均改读 a.canRevoke,不再自拼 status+角色;存量行无键 undefined 不误显;spec FinanceTab 3 例+AdvanceModal 新建 2 例全绿,提交 41468f52。 2026-10-02 dev-v3

order-v3: 预支列表出参新增 canRevoke

服务: hl-order-service-v3 PR: #8723(已合入 dev-v3,合并提交 806058c66) Issue: #8684


⚠️ 关键变化

🟢 8 个接口的出参新增一个字段 canRevoke(Boolean):当前操作人现在点「撤回」能不能成功。其余入参、出参、判权、错误码全部不变。

🔴 同一行,不同人看到的值不同。 不要缓存后跨账号复用,也不要拿它当这笔预支本身的属性。

🟢 前端只需把撤回按钮的显示条件改成 a.canRevoke,不用自己判断角色、申请人和开关。


一、背景

#8517 之后,撤回待审批预支(DELETE /v3/admin/order/advance/:advanceId)只放行申请人本人和超管、管理员,其余返回 585009。但列表只返回申请人姓名,没有申请人账号 ID,也没有「能不能撤」的标记,前端只能按「待审批」显示撤回按钮:非申请人也能看到,点了才提示 585009。

本单在出参里补 canRevoke,由后端按撤回接口的同一套规则算好。撤回接口本身的判权不变。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 本单预支列表 GET /v3/admin/order/:orderId/advances 修改 records[] 新增 canRevoke
2 预支审批列表 GET /v3/admin/order/advance-approvals/page 修改 records[] 新增 canRevoke
3 团期预支记录(财务页签) GET /v3/admin/order/group-batch/:groupBatchId/advances 修改 每行新增 canRevoke
4 发起订单级预支 POST /v3/admin/order/:orderId/advance 修改 返回体新增 canRevoke
5 预支审批通过 PUT /v3/admin/order/advance/:advanceId/approve 修改 返回体新增 canRevoke(恒 false)
6 预支审批驳回 PUT /v3/admin/order/advance/:advanceId/reject 修改 返回体新增 canRevoke(恒 false)
7 发起团期级预支 POST /v3/admin/order/group-batch/:groupBatchId/advance 修改 返回体新增 canRevoke
8 核单汇总快照 GET /v3/admin/order/:orderId/settlement/summary 修改 advanceSummary.records[] 新增 canRevoke(只含已通过,恒 false)

三、接口详情

canRevoke 取值规则(8 个接口相同,按顺序判,命中即返回):

# 条件 取值
1 这笔预支不是待审批(status ≠ SUBMITTED) false
2 没有请求上下文(定时任务、消息消费等) false
3 当前角色是团期管理员 GROUP_BATCH_MANAGER false(撤回接口先返回 581008,不受开关影响)
4 当前角色是 SUPER_ADMIN / ADMIN true
5 当前账号就是申请人(创建人账号 ID 相等) true
6 其余:开关 advance.acl.enforce.role-guard 为 true(默认) false
6' 其余:开关已关闭 true(撤回接口回到 #8517 之前的行为)

创建人账号 ID 为空的存量行,只有第 4 条能得到 true。

1. 本单预支列表 GET /v3/admin/order/:orderId/advances

VO: PageParam → Result<PageResult<OrderAdvanceRespVO>>

使用场景

订单详情的「预支」弹窗。前端据 canRevoke 决定每一行显不显示「撤回」按钮。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ 订单 ID 不变
page Query Integer ❌ ≥1 不变
pageSize Query Integer ❌ ≥1 不变

出参字段表

字段 类型 说明
records[].canRevoke Boolean 🆕 当前操作人点撤回能否成功,规则见上表
其余字段 — 不变(id / status / createdByName 等)

请求示例

GET /v3/admin/order/2100743225424621570/advances?page=1&pageSize=20 HTTP/1.1
Authorization: Bearer <本单定制师 token>

响应示例

本单定制师看:自己申请的待审批为 true,管理员申请的待审批为 false,已通过的为 false。

{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "id": "2105886435083206657",
        "orderId": "2100743225424621570",
        "teamNo": "26-8707",
        "payeeName": "刘大山",
        "payeeRole": "LEADER",
        "payeeRoleText": "导游",
        "advanceType": "TICKET",
        "amount": 180.0,
        "purpose": "呼伦贝尔大草原景区门票代垫",
        "status": "SUBMITTED",
        "statusText": "待审批",
        "createdByName": "admin",
        "canRevoke": true
      },
      {
        "id": "2105886437624999938",
        "orderId": "2100743225424621570",
        "teamNo": "26-8707",
        "payeeName": "刘大山",
        "advanceType": "CATERING",
        "amount": 120.0,
        "purpose": "额尔古纳湿地午餐代垫",
        "status": "SUBMITTED",
        "statusText": "待审批",
        "createdByName": "jw",
        "canRevoke": false
      }
    ],
    "total": 2,
    "page": 1,
    "pageSize": 20
  },
  "success": true
}

空数据 / 降级响应

无预支时 records 为空数组(不变)。开关 advance.acl.enforce.role-guard 关闭时,非团期管理员看待审批行都为 true。

错误响应

判权不变:非本单定制师、财务等无权角色仍返回 581008。

{
  "code": 581008,
  "message": "无权查看此订单",
  "data": null,
  "success": false
}

业务边界

  • 每行单独计算,同一页里可以有 true 也有 false。
  • 车务管理员能看这个列表,但看所有行都是 false。

2. 预支审批列表 GET /v3/admin/order/advance-approvals/page

VO: AdvanceApprovalPageReqVO → Result<PageResult<AdvanceApprovalPageItemRespVO>>

使用场景

「财务管理 → 预支审批」列表。页面目前没有撤回按钮,字段供后续使用。只有超管、管理员、财务能进(不变)。

入参字段表

字段 位置 类型 必填 约束 说明
status Query String ❌ SUBMITTED / APPROVED / REJECTED / PAID 不变
page Query Integer ❌ ≥1 不变
pageSize Query Integer ❌ ≥1 不变
其余筛选项 Query — ❌ — 不变

出参字段表

字段 类型 说明
records[].canRevoke Boolean 🆕 规则见上表;财务看别人申请的为 false
其余字段 — 不变

请求示例

GET /v3/admin/order/advance-approvals/page?status=SUBMITTED&page=1&pageSize=20 HTTP/1.1
Authorization: Bearer <财务 token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "id": "2105886435083206657",
        "orderId": "2100743225424621570",
        "orderNo": "HL20260918082629372",
        "teamNo": "26-8707",
        "scope": "ORDER",
        "amount": 180.0,
        "status": "SUBMITTED",
        "createdByName": "admin",
        "canRevoke": false
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "success": true
}

空数据 / 降级响应

无数据时 records 为空数组(不变)。开关关闭时财务看待审批行都为 true。

错误响应

判权不变:非超管 / 管理员 / 财务返回 585008。

{
  "code": 585008,
  "message": "仅财务或管理员可查看预支审批、审批或驳回预支",
  "data": null,
  "success": false
}

业务边界

  • 本接口多查了一列申请人账号 ID 用于计算,不出参。
  • 团期级行(scope=GROUP_BATCH)同样按规则计算。

3. 团期预支记录(财务页签) GET /v3/admin/order/group-batch/:groupBatchId/advances

VO: List<GroupBatchAdvanceItemVO>(继承 OrderAdvanceRespVO)

使用场景

团期详情「财务」页签的预支记录。前端据 canRevoke 决定显不显示「撤回」。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 团期 ID 不变

出参字段表

字段 类型 说明
[].canRevoke Boolean 🆕 规则见上表;团期管理员恒为 false
其余字段 — 不变(scope / orderNo / teamNo 等)

请求示例

GET /v3/admin/order/group-batch/2104839654727618562/advances HTTP/1.1
Authorization: Bearer <管理员 token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": "2105589347598368769",
      "scope": "GROUP_BATCH",
      "scopeName": "团期级",
      "orderId": null,
      "orderNo": null,
      "amount": 5000.0,
      "status": "SUBMITTED",
      "statusText": "待审批",
      "createdByName": "金卫",
      "canRevoke": true
    }
  ],
  "success": true
}

空数据 / 降级响应

无预支时返回空数组(不变)。开关关闭时,非团期管理员看待审批行都为 true,团期管理员仍为 false。

错误响应

判权不变。团期不存在:

{
  "code": 589500,
  "message": "团期不存在",
  "data": null,
  "success": false
}

业务边界

  • 原来前端写的 a.status === 'SUBMITTED' && !isGroupBatchManager 可以整体换成 a.canRevoke。
  • 子订单级行与团期级行规则相同。

4. 发起订单级预支 POST /v3/admin/order/:orderId/advance

VO: CreateAdvanceReqVO → Result<OrderAdvanceRespVO>

使用场景

订单详情「发起预支」。返回体里 canRevoke 对发起人本人为 true(刚建的待审批,自己可以撤)。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ 订单 ID 不变
payeeStaffId Body Long ✅ 本单人员 不变
advanceType Body String ✅ 字典 advance_type 不变
amount Body BigDecimal ✅ >0,不超可用上限 不变
purpose Body String ❌ — 不变
voucherUrl Body String ❌ — 不变

出参字段表

字段 类型 说明
data.canRevoke Boolean 🆕 发起人本人为 true
其余字段 — 不变

请求示例

{
  "payeeStaffId": "2100747615736897537",
  "advanceType": "TICKET",
  "amount": 180.00,
  "purpose": "呼伦贝尔大草原景区门票代垫"
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": "2105886435083206657",
    "orderId": "2100743225424621570",
    "status": "SUBMITTED",
    "statusText": "待审批",
    "amount": 180.0,
    "canRevoke": true
  },
  "success": true
}

空数据 / 降级响应

无。

错误响应

判权与校验不变。金额超可用上限:

{
  "code": 585004,
  "message": "预支金额超过可用余额上限",
  "data": null,
  "success": false
}

业务边界

  • 只是返回体多一个字段,创建逻辑不变。

5. 预支审批通过 PUT /v3/admin/order/advance/:advanceId/approve

VO: Result<OrderAdvanceRespVO>(无请求体)

使用场景

审批中心「通过」。审批后状态是已通过,canRevoke 恒为 false。

入参字段表

字段 位置 类型 必填 约束 说明
advanceId Path Long ✅ 预支 ID 不变

出参字段表

字段 类型 说明
data.canRevoke Boolean 🆕 恒 false
其余字段 — 不变

请求示例

PUT /v3/admin/order/advance/2105886444679774210/approve HTTP/1.1
Authorization: Bearer <财务 token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": "2105886444679774210",
    "status": "APPROVED",
    "statusText": "已通过",
    "approvedBy": "yaosutu",
    "canRevoke": false
  },
  "success": true
}

空数据 / 降级响应

无。

错误响应

判权不变。预支不存在(财务 / 管理员):

{
  "code": 585000,
  "message": "预支记录不存在",
  "data": null,
  "success": false
}

业务边界

  • 审批逻辑与出纳待付款的生成不变。

6. 预支审批驳回 PUT /v3/admin/order/advance/:advanceId/reject

VO: RejectAdvanceReqVO → Result<OrderAdvanceRespVO>

使用场景

审批中心「驳回」。驳回后状态是已驳回,canRevoke 恒为 false。

入参字段表

字段 位置 类型 必填 约束 说明
advanceId Path Long ✅ 预支 ID 不变
reason Body String ✅ 驳回原因 不变

出参字段表

字段 类型 说明
data.canRevoke Boolean 🆕 恒 false
其余字段 — 不变

请求示例

{
  "reason": "门票已由地接社统一采购,无需个人垫付"
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": "2104861622562627585",
    "status": "REJECTED",
    "statusText": "已驳回",
    "rejectReason": "门票已由地接社统一采购,无需个人垫付",
    "canRevoke": false
  },
  "success": true
}

空数据 / 降级响应

无。

错误响应

判权不变。预支不存在(财务 / 管理员):

{
  "code": 585000,
  "message": "预支记录不存在",
  "data": null,
  "success": false
}

业务边界

  • 驳回逻辑不变。

7. 发起团期级预支 POST /v3/admin/order/group-batch/:groupBatchId/advance

VO: CreateGroupBatchAdvanceReqVO → Result<OrderAdvanceRespVO>

使用场景

团期财务页签「发起预支」。返回体里 canRevoke 对发起人本人为 true;团期管理员发起的为 false(团期管理员撤回会先被 581008 拦下)。

入参字段表

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ 团期 ID 不变
payeeStaffId Body Long ✅ 团期人员 不变
advanceType Body String ✅ 字典 advance_type 不变
amount Body BigDecimal ✅ >0,不超团期统一池上限 不变
purpose Body String ❌ — 不变
voucherUrl Body String ❌ — 不变

出参字段表

字段 类型 说明
data.canRevoke Boolean 🆕 发起人本人为 true,团期管理员为 false
其余字段 — 不变

请求示例

{
  "payeeStaffId": "2100747615736897537",
  "advanceType": "CATERING",
  "amount": 600.00,
  "purpose": "满洲里套娃广场团餐代垫"
}

响应示例

示例值(发起人本人调用):

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": "2105890177461317634",
    "orderId": null,
    "status": "SUBMITTED",
    "statusText": "待审批",
    "amount": 600.0,
    "canRevoke": true
  },
  "success": true
}

空数据 / 降级响应

无。

错误响应

判权与校验不变。团期不存在:

{
  "code": 589500,
  "message": "团期不存在",
  "data": null,
  "success": false
}

业务边界

  • 只是返回体多一个字段,创建逻辑与额度池不变。

8. 核单汇总快照 GET /v3/admin/order/:orderId/settlement/summary

VO: Result<SettlementSummaryRespVO>

使用场景

核单页的汇总快照。advanceSummary.records 与预支列表共用同一个 VO,所以一并带上 canRevoke。这里只列已通过的预支,恒为 false。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ 订单 ID 不变

出参字段表

字段 类型 说明
advanceSummary.records[].canRevoke Boolean 🆕 恒 false(只含已通过)
其余字段 — 不变

请求示例

GET /v3/admin/order/2100743225424621570/settlement/summary HTTP/1.1
Authorization: Bearer <管理员 token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "settled": false,
    "orderId": "2100743225424621570",
    "teamNo": "26-8707",
    "advanceSummary": {
      "approvedAmount": "90.00",
      "records": [
        {
          "id": "2105886444679774210",
          "status": "APPROVED",
          "statusText": "已通过",
          "amount": 90.0,
          "canRevoke": false
        }
      ]
    }
  },
  "success": true
}

空数据 / 降级响应

没有已通过的预支时 records 为空数组(不变)。

错误响应

本接口没有业务错误码,订单不存在时也返回 200 和 settled=false 的空壳(行为不变)。网关层未带 token(TEST 2026-10-02 实打):

{
  "code": 401,
  "message": "缺少有效的 Authorization 头",
  "data": null,
  "success": false
}

业务边界

  • 前端不需要用这里的 canRevoke。

四、契约约束与正确调用方式

  • 撤回按钮显示条件改成 a.canRevoke,不要再自己拼「待审批 + 角色 + 是否团期管理员」。
  • canRevoke 跟着当前登录账号和当前角色变;切换角色后要重新拉列表。
  • canRevoke=true 只表示列表渲染那一刻能撤;点击时若已被别人审批,仍会返回 585005(状态不允许),照常提示即可。
  • 撤回接口的判权与错误码不变:非申请人、非管理员 585009,团期管理员 581008。

五、数据库行为

  • 零 DDL、零数据迁移、零写入。
  • 取值用预支记录既有的创建人账号 ID(提交时自动写入);审批中心的联表查询多 select 这一列,不出参。
  • 发起、审批、驳回的写库逻辑不变。

六、边界行为

  • 定时任务、消息消费等无请求上下文的场景算出来恒为 false(这些场景不渲染列表)。
  • 计算 canRevoke 不打 ADVANCE_ACL_DENY 日志;只有真调撤回被拒时才打。
  • 缺角色的 token 若正好是申请人本人,canRevoke=true,与撤回接口一致(#8517 有意的设计)。

六.6、修改前后对比

场景 改前 改后
非申请人、非管理员看待审批行 按钮显示,点了 585009 canRevoke=false,前端可隐藏
申请人本人 / 管理员看待审批行 按钮显示,能撤 canRevoke=true
团期管理员看团期页签 前端用 !isGroupBatchManager 自己藏 canRevoke=false
非待审批行 前端按状态藏 canRevoke=false
开关关闭 前端不知道开关状态 非团期管理员都为 true,跟撤回接口一致

六.7、影响评估

  • 是否破坏向后兼容:否,纯新增字段。
  • 前端是否必须同步上线:否。不改前端时行为与改前相同(按钮照旧显示);改了才能隐藏点不了的按钮。
  • 回滚:revert PR #8723 后重新部署 order-v3。

七、不影响范围

  • 撤回接口 DELETE /v3/admin/order/advance/:advanceId 的判权与返回:不变。
  • 各接口的入参、判权、其余出参:不变。
  • 出纳付款、核单计算:不变。
  • 小程序端:无影响。

八、测试环境已验证

环境:TEST(https://api.test.1814.love) 验证时间:2026-10-02 13:03~13:30 构建身份:order-v3 部署 dev-v3 @ 806058c66(本单合并提交),12:57 完成。零写入判据:部署后连查 6 次订单级预支列表,每行都带 canRevoke 键(旧字节没有这个键)。 身份:自签 token 直打网关,用 TEST 真实账号 ID 配对应角色;TEST 上没有在用的财务账号,财务用 role=FINANCE 的自签 token。

8.1 造数

在待出发订单 HL20260918082629372(定制师 1001)上:C1 定制师申请门票 180(待审批)、C2 管理员 jw 申请餐费 120(待审批)、C3 定制师申请门票 90 后由财务审批(已通过)。另有该单既有的已付款、已驳回各一笔。

8.2 三个列表逐角色取值

列表 查看人 C1(1001 申请,待审批) C2(jw 申请,待审批) 已通过 / 已付款 / 已驳回
订单级 本单定制师 1001 true false 均 false
订单级 管理员 true true 均 false
订单级 车务管理员 false false 均 false
审批中心 财务 false false 已通过 false
审批中心 管理员 true true 已通过 false

团期财务页签(团期级待审批一笔,jw 申请):财务 false、团期管理员 false、管理员 true。 联表探针:财务角色、账号 ID 设为 1001 查审批中心,C1 为 true、C2 为 false,证明审批中心带出了创建人。 核单汇总:只含 C3,canRevoke=false。

8.3 按 canRevoke 抽样真实撤回

操作 结果
财务、其他定制师撤 C1;车务、定制师 1001 撤 C2(均为 false) 均 585009,未删除
团期管理员撤 C1 581008
定制师 1001 撤 C1、管理员撤 C2(均为 true) 均 200,已软删

8.4 nacos 回滚开关往返

态 订单级:定制师 / 车务看 C2 团期页签:财务 / 团期管理员 审批中心:财务看 C1、C2、团期级
A 默认 false / false false / false 全 false
B 关闭(10.5 秒生效) true / true true / false 全 true
C 还原(7.7 秒生效) false / false false / false 全 false
  • 发布带 casMd5,还原写在 finally 里;还原后 md5 与原值同为 c2206934960057f70b7173159046dc54。
  • 两个实例的 ADVANCE_ACL_DENY 计数在三态的列表渲染前后都是 0;随后 4 次被拒的真实撤回让计数各 +2,证明计数有效。

8.5 回归(零写入)

不存在的预支 ID 调审批 / 驳回 / 撤回:定制师、车务 585008 / 585008 / 585009;财务 585000 / 585000 / 585009;管理员三个 585000;团期管理员三个 581008。与 #8517 验收读数一致,前后表行数不变。

本地证据

项 读数
相关 16 个测试类定向 265/0/0/0
一致性矩阵 canRevoke 与撤回守卫在 144 种角色 × 账号 × 创建人 × 开关组合下逐条一致
变异 删掉审批中心联表那一列 → 1 例红;让 canRevoke 对财务放宽 → 2 例红;已还原
order-v3 全量(有 Docker,两半) 1115 个可执行测试类全部有报告;红 2 个类、hl-finance 红 25 个类,在基底 3bad2ad27 上读数与用例名逐条一致,本单零新增

十、相关文档

  • Issue #8684;PR #8723
  • 前置:Issue #8517(撤回判权本身)

关联 / 联系人

链接

联系人

  • 后端负责人: @jw