diff --git a/changelogs-v2/2026-09/29_8517_预支审批驳回撤回补角色守卫-修改接口-管理后台.md b/changelogs-v2/2026-09/29_8517_预支审批驳回撤回补角色守卫-修改接口-管理后台.md new file mode 100644 index 00000000..04fbe959 --- /dev/null +++ b/changelogs-v2/2026-09/29_8517_预支审批驳回撤回补角色守卫-修改接口-管理后台.md @@ -0,0 +1,475 @@ +--- +schema: "hl-changelog/v2" +ticket: "8517" +title: "预支审批中心列表与审批 / 驳回只放行财务与管理员(585008),撤回只放行申请人本人与管理员(585009),带 nacos 回滚开关" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "not_required" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "已合并 dev-v3(e51832e8d)并部署 TEST,自签 token 经网关实测:列表 11 角色矩阵、不存在 advanceId 的三个写接口、真实预支的审批 / 驳回 / 撤回正反路径、nacos 开关关闭→还原往返(md5 逐字节还原)与服务端 ADVANCE_ACL_DENY 日志正反两面。前端判 not_required:审批中心菜单与通过 / 驳回按钮本来只授超管 / 管理员 / 财务;新错误码由 request.js 拦截器统一弹 message;撤回按钮对非申请人仍显示,点了会提示 585009,出参里没有申请人 id 供前端判断显隐(本单不改出参)。" +updated_at: "2026-09-29" +base: "dev-v3" +--- + +# order-v3: 预支审批中心列表与审批 / 驳回 / 撤回补角色守卫 + +**服务**: hl-order-service-v3 +**PR**: `#8531`(已合入 `dev-v3`,合并提交 `e51832e8d`) +**Issue**: #8517 + +--- + +## ⚠️ 关键变化 + +🔴 **审批中心列表、审批通过、驳回:只放行超管、管理员、财务,其余角色返回 `585008`。** 改前任意后台账号都能看全公司预支、能审批(订单级预支审批通过会生成出纳待付款),只有团期管理员被挡。 + +🔴 **撤回:只放行申请人本人和超管、管理员,其余返回 `585009`。** 财务也不能撤别人的预支,要拦应走驳回并填原因。 + +🟢 **四个接口的入参、出参、成功码全部不变。** 有权限的调用方行为与改前一致。 + +--- + +## 一、背景 + +预支按设计由财务审批:管理后台「预支审批」菜单和通过 / 驳回按钮只授给超管、管理员、财务。但这四个接口此前只拒团期管理员,界面上进不去的角色直接调接口就能审批、查看全量预支、撤掉别人待审批的预支。本单补上接口层的判权。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 修改 | **新增判权**:只放行 `SUPER_ADMIN` / `ADMIN` / `FINANCE`,其余 `585008`;出参不变 | +| 2 | 预支审批通过 | PUT | `/v3/admin/order/advance/:advanceId/approve` | 修改 | **新增判权**:同上,其余 `585008`;出参不变 | +| 3 | 预支审批驳回 | PUT | `/v3/admin/order/advance/:advanceId/reject` | 修改 | **新增判权**:同上,其余 `585008`;出参不变 | +| 4 | 撤回待审批预支 | DELETE | `/v3/admin/order/advance/:advanceId` | 修改 | **新增判权**:只放行申请人本人与 `SUPER_ADMIN` / `ADMIN`,其余 `585009`;出参不变 | + +--- + +## 三、接口详情 + +### 1. 预支审批列表 `GET /v3/admin/order/advance-approvals/page` + +**VO**: `AdvanceApprovalPageReqVO` → `Result>` + +#### 使用场景 + +管理后台「财务管理 → 预支审批」页的列表。**本单起只有超管、管理员、财务能查。** + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| page | Query | Integer | ❌ | ≥1 | 页码。**行为不变** | +| pageSize | Query | Integer | ❌ | ≥1 | 每页条数。**行为不变** | +| status | Query | String | ❌ | `SUBMITTED` / `APPROVED` / `REJECTED` / `PAID` | 缺省 `SUBMITTED`。**行为不变** | +| 其余筛选项 | Query | — | ❌ | — | orderId / keyword / scope / payeeName / createdByName / 提交时间区间。**行为不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | `PageResult` | **结构完全不变**(`records` / `total` / `page` / `pageSize`) | + +#### 请求示例 + +```http +GET /v3/admin/order/advance-approvals/page?page=1&pageSize=20&status=SUBMITTED HTTP/1.1 +Authorization: Bearer <财务账号 token> +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [], + "total": 0, + "page": 1, + "pageSize": 20 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无数据时 `records` 为空数组、`total` 为 0(行为不变)。**降级**:nacos `advance.acl.enforce.role-guard` 置 `false` 时回到改前行为(任意后台角色可查),但服务端每次仍留一条 `ADVANCE_ACL_DENY` 日志(`enforced=false`)。开关默认 `true`。 + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| **585008** | `ADVANCE_APPROVAL_FORBIDDEN` | 当前角色不是超管 / 管理员 / 财务(含缺角色的 token) | 🆕 新增 | +| 585005 | `ADVANCE_STATUS_ILLEGAL` | `status` 不是合法取值 | 不变 | + +`585008` 实打响应体(TEST,2026-09-29,定制师): + +```json +{ + "code": 585008, + "message": "仅财务或管理员可查看预支审批、审批或驳回预支", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 判权在查询之前,被拒时不返回任何数据。 +- 团期管理员也返回 `585008`(本接口改前对它不设防)。 + +--- + +### 2. 预支审批通过 `PUT /v3/admin/order/advance/:advanceId/approve` + +**VO**: `OrderAdvanceRespVO`(无请求体,出参 `Result`) + +#### 使用场景 + +预支审批页「通过」按钮:待审批(`SUBMITTED`)→ 已审批(`APPROVED`);订单级预支同时生成出纳待付款。**本单起只有超管、管理员、财务能批。** + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| advanceId | Path | Long | ✅ | 预支 ID | **行为不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | `OrderAdvanceRespVO` | **结构完全不变**;`status=APPROVED`,`approvedBy` 为审批人姓名 | + +#### 请求示例 + +```http +PUT /v3/admin/order/advance/2104861625016287233/approve HTTP/1.1 +Authorization: Bearer <财务账号 token> +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "2104861625016287233", + "status": "APPROVED", + "approvedBy": "jw", + "rejectReason": null + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无空数据语义。**降级**:nacos 开关置 `false` 时回到改前行为(除团期管理员外任意后台角色可批),仍留 `ADVANCE_ACL_DENY` 日志。团期管理员的 `581008` 不读该开关,关掉也照拒。 + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| **585008** | `ADVANCE_APPROVAL_FORBIDDEN` | 当前角色不是超管 / 管理员 / 财务。**先于预支存在性校验**,预支不存在也返回本码 | 🆕 新增 | +| 581008 | `ORDER_VIEW_FORBIDDEN` | 团期管理员(排在最前) | 不变 | +| 585000 | `ADVANCE_NOT_FOUND` | 有权角色操作不存在的预支 | 不变 | +| 585005 | `ADVANCE_STATUS_ILLEGAL` | 预支不是待审批 | 不变 | + +```json +{ + "code": 585008, + "message": "仅财务或管理员可查看预支审批、审批或驳回预支", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 被拒时零写入:预支状态不变,也不会生成出纳待付款。 +- 定制师审批自己申请的预支同样返回 `585008`。 +- 本单不限制「管理员 / 财务审批自己申请的预支」(职责分离不在本单范围)。 + +--- + +### 3. 预支审批驳回 `PUT /v3/admin/order/advance/:advanceId/reject` + +**VO**: `RejectAdvanceReqVO` → `Result` + +#### 使用场景 + +预支审批页「驳回」按钮:待审批(`SUBMITTED`)→ 已驳回(`REJECTED`),须填驳回原因。**本单起只有超管、管理员、财务能驳。** + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| advanceId | Path | Long | ✅ | 预支 ID | **行为不变** | +| reason | Body | String | ✅ | 非空 | 驳回原因。**行为不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | `OrderAdvanceRespVO` | **结构完全不变**;`status=REJECTED`,`rejectReason` 为驳回原因 | + +#### 请求示例 + +```http +PUT /v3/admin/order/advance/2104861622562627585/reject HTTP/1.1 +Authorization: Bearer <财务账号 token> +Content-Type: application/json + +{"reason": "门票已由地接社统一采购,无需个人垫付"} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "id": "2104861622562627585", + "status": "REJECTED", + "approvedBy": "jw", + "rejectReason": "门票已由地接社统一采购,无需个人垫付" + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无空数据语义。降级口径同「审批通过」。 + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| **585008** | `ADVANCE_APPROVAL_FORBIDDEN` | 当前角色不是超管 / 管理员 / 财务;先于存在性校验 | 🆕 新增 | +| 581008 | `ORDER_VIEW_FORBIDDEN` | 团期管理员 | 不变 | +| 585000 / 585005 | — | 预支不存在 / 不是待审批 | 不变 | + +```json +{ + "code": 585008, + "message": "仅财务或管理员可查看预支审批、审批或驳回预支", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 被拒时零写入:状态与驳回原因都不变。 + +--- + +### 4. 撤回待审批预支 `DELETE /v3/admin/order/advance/:advanceId` + +**VO**: `Result`(无请求体) + +#### 使用场景 + +订单详情「预支」弹窗、团期详情「财务」页签里的「撤回」按钮:撤掉一笔还没审批的预支。**本单起只有申请人本人和超管、管理员能撤。** + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| advanceId | Path | Long | ✅ | 预支 ID | **行为不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | `null` | **不变** | + +#### 请求示例 + +```http +DELETE /v3/admin/order/advance/2104861626131980289 HTTP/1.1 +Authorization: Bearer <申请人本人 token> +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": null, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无空数据语义。降级:nacos 开关置 `false` 时回到改前行为(除团期管理员外任意后台角色可撤),仍留日志。 + +#### 错误响应 + +| 码 | 符号 | 触发 | 本单 | +|----|------|------|------| +| **585009** | `ADVANCE_REVOKE_FORBIDDEN` | 既不是这笔预支的申请人,也不是超管 / 管理员(**财务也返回本码**)。非管理员撤不存在的预支也返回本码 | 🆕 新增 | +| 581008 | `ORDER_VIEW_FORBIDDEN` | 团期管理员 | 不变 | +| 585000 | `ADVANCE_NOT_FOUND` | 超管 / 管理员撤不存在的预支 | 不变 | +| 585005 | `ADVANCE_STATUS_ILLEGAL` | 预支已审批 / 已驳回 | 不变 | + +```json +{ + "code": 585009, + "message": "仅申请人本人或管理员可撤回该预支", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 「申请人」指提交这笔预支的后台账号,按账号 ID 判定,不按姓名。 +- 被拒时零写入:预支不删。 +- 期初结转等没有申请人记录的存量预支,只有超管、管理员能撤。 + +--- + +## 四、契约约束与正确调用方式 + +- 新错误码 `585008` / `585009` 直接透出 `message` 即可。 +- 撤回按钮目前对所有能看到待审批预支的人显示;非申请人点击会收到 `585009`。出参里没有申请人账号 ID,本单不改出参,按钮显隐暂不能按申请人精确控制。 +- 同时挂财务与其它角色的账号,要切到财务角色才能进审批中心(后端按当前角色判定)。 + +--- + +## 五、数据库行为 + +- 零 DDL、零数据迁移。 +- 撤回的「申请人」取预支记录既有的创建人字段(提交预支时自动写入);TEST 现存预支该字段全部有值。 +- 被拒绝的请求不写库。 + +--- + +## 六、边界行为 + +- 有权限的调用方:与改前完全一致。 +- 缺角色的 token:列表 / 审批 / 驳回返回 `585008`;撤回只要是申请人本人仍可撤。 +- 系统内部调用(定时任务、消息消费等无请求的场景)不受限制。 +- 团期管理员:审批 / 驳回 / 撤回仍先返回 `581008`;列表返回 `585008`。 + +## 六.6、修改前后对比 + +| 角色 | 列表 | 审批 / 驳回 | 撤回别人的预支 | 撤回自己的预支 | +|------|------|------|------|------| +| `SUPER_ADMIN` / `ADMIN` | 正常 → 正常 | 正常 → 正常 | 正常 → 正常 | 正常 → 正常 | +| `FINANCE` | 正常 → 正常 | 正常 → 正常 | **正常 → 585009** | 正常 → 正常 | +| `CUSTOMIZER` | **正常 → 585008** | **正常 → 585008** | **正常 → 585009** | 正常 → 正常 | +| 车务 / 房务 / 房务组长 / 运营 / 客服 | **正常 → 585008** | **正常 → 585008** | **正常 → 585009** | 正常 → 正常 | +| `GROUP_BATCH_MANAGER` | **正常 → 585008** | 581008 → 不变 | 581008 → 不变 | 581008 → 不变 | + +## 六.7、影响评估 + +- **是否破坏向后兼容**:对有权限的调用方否;对无权限的调用方,原来能成功的请求改为返回新错误码。 +- **前端是否必须同步上线**:否。 +- **回滚**:nacos `advance.acl.enforce.role-guard` 置 `false`,TEST 实测 5.6 秒内生效,不需重发服务。 + +--- + +## 七、不影响范围 + +- 创建预支 `POST /v3/admin/order/:orderId/advance`、借款对象候选、本单预支列表 `GET /v3/admin/order/:orderId/advances`:判权不变。 +- 团期发起预支、团期财务页签的预支列表:判权不变。 +- 财务出纳付款:本单未改。 +- 小程序端:无影响。 + +--- + +## 八、测试环境已验证 + +**环境**:TEST(`https://api.test.1814.love`) **验证时间**:2026-09-29 17:08~17:15 +**构建身份**:order-v3 部署 `dev-v3 @ e51832e8d`(本单合并提交),17:08 完成。零写入判据:旧字节里定制师查审批列表返回 200 和数据,新字节才会返回 `585008`;部署后连打 6 次全部 `585008`,同时管理员 200。 +**身份**:自签 token 直打网关,未只用超管;TEST 上没有持财务角色的账号,财务身份用 `role=FINANCE` 的自签 token。 + +### 8.1 列表角色矩阵 + +| 角色 | 结果 | +|---|---| +| SUPER_ADMIN / ADMIN / FINANCE | `200`,`status=PAID` 返回 2 条 | +| CUSTOMIZER / VEHICLE_MANAGER / ROOM_MANAGER / house_keeper_lead / GROUP_BATCH_MANAGER / OPERATOR / CUSTOMER_SERVICE / 缺角色 | **`585008`**,`data` 为 `null` | + +### 8.2 不存在的预支 ID(零写入) + +| 角色 | 审批 | 驳回 | 撤回 | +|---|---|---|---| +| CUSTOMIZER / VEHICLE_MANAGER | **585008** | **585008** | **585009** | +| FINANCE | 585000(过守卫) | 585000(过守卫) | **585009** | +| ADMIN | 585000 | 585000 | 585000 | +| GROUP_BATCH_MANAGER | 581008 | 581008 | 581008 | + +前后预支表、出纳执行单表行数不变。 + +### 8.3 真实预支的正反路径 + +在待出发订单 `HL20260918082629372` 上以定制师身份提交 4 笔预支(门票 380、餐费 260、门票 150、餐费 120): + +| 操作 | 结果 | +|---|---| +| 其他定制师 / 车务管理员 / 申请人本人审批 380 那笔 | 均 **585008**,仍待审批,出纳执行单 0 行 | +| 其他定制师驳回 | **585008**,驳回原因为空 | +| 财务、其他定制师撤回 | 均 **585009**,未删除 | +| 财务驳回 | `200`,已驳回,出纳执行单 0 行 | +| 其他定制师审批 260 那笔 | **585008**;随后财务审批 `200`,已审批,出纳执行单 1 行 | +| 申请人本人撤回 150 那笔 | `200`,已软删 | +| 管理员撤回 120 那笔(别人申请的) | `200`,已软删 | + +### 8.4 nacos 回滚开关往返 + +配置 `hl-order-service-v3-test.yml`(`tenant=test`)原本不含 `advance.*` 键,即默认 `true`。 + +| 态 | 配置 | 定制师查列表 | 定制师审批不存在的预支 | 定制师撤不存在的预支 | 团期管理员审批 | +|---|---|---|---|---|---| +| A | 无键(默认 `true`) | `585008` | `585008` | `585009` | `581008` | +| B | 追加 `role-guard: false` | `200`(5.6 秒内生效) | `585000`(回到改前) | `585000`(回到改前) | `581008`(不受开关影响) | +| C | 逐字节还原 | `585008`(9.3 秒内生效) | — | — | — | + +- 发布带 `casMd5`,还原写在 `finally` 里;还原后 md5 与原值同为 `c2206934960057f70b7173159046dc54`。 +- 服务端日志(两个实例合计):`ADVANCE_ACL_DENY ... enforced=false` 只出现在 B 态的 17:14:37~43,共 9 条,与 B 态调用次数一致;A、C 两态只有 `enforced=true`。 + +### 本地证据 + +| 项 | 读数 | +|---|---| +| 新增 4 个测试类 | 51 例全绿(守卫 29、绑定器 6、Controller 7、撤回 9) | +| 预支包 + ArchTest + 错误码门禁 + 切片清单 | 249/0/0/0 | +| order-v3 全量(有 Docker) | 14519 例,9F / 8E;红的 7 个类在基底 `48e66bd06` 上逐类、逐用例名一致,本单零新增失败 | +| 变异 | 删掉审批端点守卫 → 1 例红;开关判定上提到方法头 → 12 例红;已还原 | + +--- + +## 十、相关文档 + +- Issue `#8517`;PR `#8531` +- `docs/finance/api/API-SPEC-FINANCE-V1.0.html` §2.4.4(补判权口径) +- `docs/group/团期模块接口文档-v2.0.html` §0B.9、`docs/group/数据模型.html` §A.11.12、`docs/group/实施单/11-财务与预支.html`(错误码表补 585008 / 585009) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8517](https://git.1814.love/wx/HL/issues/8517) +- **PR**: [#8531](https://git.1814.love/wx/HL/pulls/8531) +- **Merge commit**: [e51832e8d](https://git.1814.love/wx/HL/commit/e51832e8d) + +### 联系人 + +- **后端负责人**: @jw