From 92c9bb51c14110b5d2617a20aeb5af8b31caaf2a Mon Sep 17 00:00:00 2001 From: jw Date: Fri, 2 Oct 2026 14:43:23 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#8684=20=E9=A2=84=E6=94=AF?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=208=20=E4=B8=AA=E6=8E=A5=E5=8F=A3=E5=87=BA?= =?UTF-8?q?=E5=8F=82=E6=96=B0=E5=A2=9E=20canRevoke=EF=BC=88=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=8E=A5=E5=8F=A3=C2=B7=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs wx/HL#8684 · PR #8723 · merge 806058c66 --- ...¡¨出参补撤回标记canRevoke-修改接口-管理后台.md | 834 ++++++++++++++++++ 1 file changed, 834 insertions(+) create mode 100644 changelogs-v2/2026-10/02_8684_预支列表出参补撤回标记canRevoke-修改接口-管理后台.md diff --git a/changelogs-v2/2026-10/02_8684_预支列表出参补撤回标记canRevoke-修改接口-管理后台.md b/changelogs-v2/2026-10/02_8684_预支列表出参补撤回标记canRevoke-修改接口-管理后台.md new file mode 100644 index 00000000..08e755b3 --- /dev/null +++ b/changelogs-v2/2026-10/02_8684_预支列表出参补撤回标记canRevoke-修改接口-管理后台.md @@ -0,0 +1,834 @@ +--- +schema: "hl-changelog/v2" +ticket: "8684" +title: "预支相关 8 个接口出参新增 canRevoke:当前操作人点「撤回」能否成功,与撤回接口共用同一个判定" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "已合并 dev-v3(806058c66)并部署 TEST,自签低权限 token 经网关实测:三个列表逐角色取值、联表 created_by 探针、开关关闭→还原往返(md5 逐字节还原,渲染期间拒绝日志零新增)、按 canRevoke 抽样真实撤回。前端待改两处撤回按钮:订单详情预支弹窗 AdvanceModal.vue 与团期财务页签 FinanceTab.vue 的 v-if 改为 a.canRevoke;审批中心本来没有撤回按钮。" +updated_at: "2026-10-02" +base: "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>` + +#### 使用场景 + +订单详情的「预支」弹窗。前端据 `canRevoke` 决定每一行显不显示「撤回」按钮。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | 订单 ID | **不变** | +| page | Query | Integer | ❌ | ≥1 | **不变** | +| pageSize | Query | Integer | ❌ | ≥1 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].canRevoke | Boolean | 🆕 当前操作人点撤回能否成功,规则见上表 | +| 其余字段 | — | **不变**(`id` / `status` / `createdByName` 等) | + +#### 请求示例 + +```http +GET /v3/admin/order/2100743225424621570/advances?page=1&pageSize=20 HTTP/1.1 +Authorization: Bearer <本单定制师 token> +``` + +#### 响应示例 + +本单定制师看:自己申请的待审批为 `true`,管理员申请的待审批为 `false`,已通过的为 `false`。 + +```json +{ + "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`。 + +```json +{ + "code": 581008, + "message": "无权查看此订单", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 每行单独计算,同一页里可以有 `true` 也有 `false`。 +- 车务管理员能看这个列表,但看所有行都是 `false`。 + +--- + +### 2. 预支审批列表 `GET /v3/admin/order/advance-approvals/page` + +**VO**: `AdvanceApprovalPageReqVO` → `Result>` + +#### 使用场景 + +「财务管理 → 预支审批」列表。页面目前没有撤回按钮,字段供后续使用。只有超管、管理员、财务能进(不变)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| status | Query | String | ❌ | `SUBMITTED` / `APPROVED` / `REJECTED` / `PAID` | **不变** | +| page | Query | Integer | ❌ | ≥1 | **不变** | +| pageSize | Query | Integer | ❌ | ≥1 | **不变** | +| 其余筛选项 | Query | — | ❌ | — | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| records[].canRevoke | Boolean | 🆕 规则见上表;财务看别人申请的为 `false` | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```http +GET /v3/admin/order/advance-approvals/page?status=SUBMITTED&page=1&pageSize=20 HTTP/1.1 +Authorization: Bearer <财务 token> +``` + +#### 响应示例 + +```json +{ + "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`。 + +```json +{ + "code": 585008, + "message": "仅财务或管理员可查看预支审批、审批或驳回预支", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 本接口多查了一列申请人账号 ID 用于计算,**不出参**。 +- 团期级行(`scope=GROUP_BATCH`)同样按规则计算。 + +--- + +### 3. 团期预支记录(财务页签) `GET /v3/admin/order/group-batch/:groupBatchId/advances` + +**VO**: `List`(继承 `OrderAdvanceRespVO`) + +#### 使用场景 + +团期详情「财务」页签的预支记录。前端据 `canRevoke` 决定显不显示「撤回」。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| groupBatchId | Path | Long | ✅ | 团期 ID | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| [].canRevoke | Boolean | 🆕 规则见上表;团期管理员恒为 `false` | +| 其余字段 | — | **不变**(`scope` / `orderNo` / `teamNo` 等) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2104839654727618562/advances HTTP/1.1 +Authorization: Bearer <管理员 token> +``` + +#### 响应示例 + +```json +{ + "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`。 + +#### 错误响应 + +判权不变。团期不存在: + +```json +{ + "code": 589500, + "message": "团期不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 原来前端写的 `a.status === 'SUBMITTED' && !isGroupBatchManager` 可以整体换成 `a.canRevoke`。 +- 子订单级行与团期级行规则相同。 + +--- + +### 4. 发起订单级预支 `POST /v3/admin/order/:orderId/advance` + +**VO**: `CreateAdvanceReqVO` → `Result` + +#### 使用场景 + +订单详情「发起预支」。返回体里 `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` | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```json +{ + "payeeStaffId": "2100747615736897537", + "advanceType": "TICKET", + "amount": 180.00, + "purpose": "呼伦贝尔大草原景区门票代垫" +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "2105886435083206657", + "orderId": "2100743225424621570", + "status": "SUBMITTED", + "statusText": "待审批", + "amount": 180.0, + "canRevoke": true + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无。 + +#### 错误响应 + +判权与校验不变。金额超可用上限: + +```json +{ + "code": 585004, + "message": "预支金额超过可用余额上限", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 只是返回体多一个字段,创建逻辑不变。 + +--- + +### 5. 预支审批通过 `PUT /v3/admin/order/advance/:advanceId/approve` + +**VO**: `Result`(无请求体) + +#### 使用场景 + +审批中心「通过」。审批后状态是已通过,`canRevoke` 恒为 `false`。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| advanceId | Path | Long | ✅ | 预支 ID | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.canRevoke | Boolean | 🆕 恒 `false` | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```http +PUT /v3/admin/order/advance/2105886444679774210/approve HTTP/1.1 +Authorization: Bearer <财务 token> +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "2105886444679774210", + "status": "APPROVED", + "statusText": "已通过", + "approvedBy": "yaosutu", + "canRevoke": false + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无。 + +#### 错误响应 + +判权不变。预支不存在(财务 / 管理员): + +```json +{ + "code": 585000, + "message": "预支记录不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 审批逻辑与出纳待付款的生成不变。 + +--- + +### 6. 预支审批驳回 `PUT /v3/admin/order/advance/:advanceId/reject` + +**VO**: `RejectAdvanceReqVO` → `Result` + +#### 使用场景 + +审批中心「驳回」。驳回后状态是已驳回,`canRevoke` 恒为 `false`。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| advanceId | Path | Long | ✅ | 预支 ID | **不变** | +| reason | Body | String | ✅ | 驳回原因 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.canRevoke | Boolean | 🆕 恒 `false` | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```json +{ + "reason": "门票已由地接社统一采购,无需个人垫付" +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "2104861622562627585", + "status": "REJECTED", + "statusText": "已驳回", + "rejectReason": "门票已由地接社统一采购,无需个人垫付", + "canRevoke": false + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无。 + +#### 错误响应 + +判权不变。预支不存在(财务 / 管理员): + +```json +{ + "code": 585000, + "message": "预支记录不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 驳回逻辑不变。 + +--- + +### 7. 发起团期级预支 `POST /v3/admin/order/group-batch/:groupBatchId/advance` + +**VO**: `CreateGroupBatchAdvanceReqVO` → `Result` + +#### 使用场景 + +团期财务页签「发起预支」。返回体里 `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` | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```json +{ + "payeeStaffId": "2100747615736897537", + "advanceType": "CATERING", + "amount": 600.00, + "purpose": "满洲里套娃广场团餐代垫" +} +``` + +#### 响应示例 + +示例值(发起人本人调用): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "2105890177461317634", + "orderId": null, + "status": "SUBMITTED", + "statusText": "待审批", + "amount": 600.0, + "canRevoke": true + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无。 + +#### 错误响应 + +判权与校验不变。团期不存在: + +```json +{ + "code": 589500, + "message": "团期不存在", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 只是返回体多一个字段,创建逻辑与额度池不变。 + +--- + +### 8. 核单汇总快照 `GET /v3/admin/order/:orderId/settlement/summary` + +**VO**: `Result` + +#### 使用场景 + +核单页的汇总快照。`advanceSummary.records` 与预支列表共用同一个 VO,所以一并带上 `canRevoke`。这里只列已通过的预支,恒为 `false`。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | 订单 ID | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| advanceSummary.records[].canRevoke | Boolean | 🆕 恒 `false`(只含已通过) | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```http +GET /v3/admin/order/2100743225424621570/settlement/summary HTTP/1.1 +Authorization: Bearer <管理员 token> +``` + +#### 响应示例 + +```json +{ + "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 实打): + +```json +{ + "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`(撤回判权本身) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8684](https://git.1814.love/wx/HL/issues/8684) +- **PR**: [#8723](https://git.1814.love/wx/HL/pulls/8723) +- **Merge commit**: [806058c66](https://git.1814.love/wx/HL/commit/806058c66) + +### 联系人 + +- **后端负责人**: @jw