--- 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