--- schema: "hl-changelog/v2" ticket: "7265" title: "供应商账户原地修改与变更明细" consumer: "admin" author: "lc(GIT)" change_type: "新增接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "626bc9e5" target_release: "" verified_at: "2026-09-08" status_note: "前端已交付并验证(commit 626bc9e5):推翻 #7182 新记录式,PUT /update 改原地修改返回原 accountId(审批期原值 ACTIVE、通过同行应用新值、不新增待审替换行),EditModal/ManageModal/accounts.js 全文去「新记录式」注释与提示,在途审批围栏 pendingApprovalIds 保留;新增 getSupplierBankAccountChangeRecords 封装+SupplierAccountChangeRecordsModal 分页弹窗(锁 occurredAt DESC、operationType/fieldName/status/时间范围筛选、from/to 成对双校验防 395045/395046、valueAvailability 三态),操作列加「变更明细」入口(不显隐,395002 后端兜底);字段维持 #7291 口径不加回三结算字段(#7265 入参表系陈旧表述);supplier 域 181 例绿,对抗 review PASS,checkpoint 全绿。" updated_at: "2026-09-08" base: "dev-v3" --- # 供应商账户:原地修改与变更明细 ## 一、关键变化 - `PUT /admin/supplier/bank-accounts/{accountId}/update` 现在返回原 `accountId`,账户列表不再新增待审替换行。审批期间正式账户保持原值和 `ACTIVE`,通过后在同一行应用新值,驳回或撤销则保持不变。 - 新增账户变更明细分页接口,展示该账户的创建、修改、启用、停用和删除历史。 - 本文覆盖 #7182 中“修改会新建账户、返回新 accountId”的旧说明。前端不得把修改响应追加为第二条账户数据。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | 提交账户修改审批 | PUT | `/admin/supplier/bank-accounts/{accountId}/update` | 行为修改 | 改为原 accountId 原地审批 | | 2 | 查询账户变更明细 | GET | `/admin/supplier/bank-accounts/{accountId}/change-records/page` | 新增接口 | 按单个账户分页查询变更事实 | ## 三、接口详情 ### 1. 提交账户修改审批 `PUT /admin/supplier/bank-accounts/{accountId}/update` **VO**: `SupplierAccountUpdateReqVO / BankAccountSubmitResultRespVO` #### 使用场景 对 `ACTIVE` 账户提交完整新资料并发起企微审批。提交成功后继续使用原账户行和原 `accountId`。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `accountId` | Path | String | 是 | 正整数 | 当前账户 ID | | `accountType` | Body | String | 是 | `CORPORATE` / `PERSONAL` | 对公 / 对私 | | `bankName` | Body | String | 是 | 非空,最长 500 字符 | 开户银行 | | `bankBranch` | Body | String/null | 否 | 最长 500 字符 | 开户支行 | | `accountNo` | Body | String | 是 | 8~128 位,仅数字、空格和 `-` | 除当前账户外须全局唯一 | | `proofFileUrls` | Body | Array/null | 否 | 最多 20 项 | 完整快照;`[]` 表示清空 | | `settleMode` | Body | String/null | 否 | `PREPAY` / `MONTHLY` / `SINGLE` | 结算方式 | | `accountPeriod` | Body | String/null | 条件必填 | `MONTHLY` 时必填,其他方式须为空 | 月结账期 | | `invoiceType` | Body | String/null | 否 | `SPECIAL` / `NORMAL` / `NONE` | 发票类型 | | `taxRate` | Body | String/null | 条件必填 | 可开票时必填;`NONE` 时须为空 | 税率 | | `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 账户详情或列表的最新 `updateTime` | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `accountId` / `approvalLogId` | String | 原账户 ID / 本次审批记录 ID | | `requestNo` / `provider` / `spNo` | String | 幂等号 / 审批提供方 / 企微单号 | | `approvalStatus` / `approvalStatusName` | String | 审批状态 / 中文名 | | `syncStatus` | String | 审批结果同步状态 | | `accountStatus` / `isDefault` | String | 正式账户当前状态 / 默认标记 | | `submittedAt` / `finishedAt` | String/null | 提交时间 / 终态时间 | #### 请求示例 ```http PUT /admin/supplier/bank-accounts/726500000000000001/update Authorization: Bearer <当前有效凭证> Content-Type: application/json {"accountType":"CORPORATE","bankName":"示例银行","bankBranch":"示例支行","accountNo":"6222000012345678","proofFileUrls":[],"settleMode":"PREPAY","accountPeriod":null,"invoiceType":"NONE","taxRate":null,"expectedUpdateTime":"2026-09-07 15:00:00"} ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "accountId": "726500000000000001", "approvalLogId": "726500000000000002", "requestNo": "SUP-ACC-UPDATE-example", "provider": "WECOM", "approvalStatus": "PENDING", "approvalStatusName": "审核中", "spNo": "202609070001", "spStatus": null, "syncStatus": "REQUESTING", "accountStatus": "ACTIVE", "isDefault": "NO", "submittedAt": "2026-09-07 15:00:01", "finishedAt": null }, "success": true } ``` #### 空数据 / 降级响应 无空成功数据。企微异常时可能保留原审批事实并返回 `APPLY_FAILED` 或 `RESULT_UNCERTAIN`;正式账户不会提前改变,前端不得自动创建新申请。 #### 错误响应 ```json {"code":395028,"message":"该账户正在变更审批中,请勿重复提交","data":null,"success":false} ``` #### 业务边界 - 仅 `ACTIVE` 供应商的 `ACTIVE` 账户可修改;需要账户管理与审批提交权限。 - `code=200` 仅表示审批已提交,不表示新值已生效;响应 `accountId` 与 Path 中原值相同。 - 同一账户已有在途审批、版本过期或新账号被其他账户占用时失败且不产生第二条账户。 ### 2. 查询账户变更明细 `GET /admin/supplier/bank-accounts/{accountId}/change-records/page` **VO**: `SupplierAccountChangeRecordPageReqVO / PageResult` #### 使用场景 在账户管理页查看指定账户的完整变更时间线,并按操作、状态、字段或时间范围筛选。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `accountId` | Path | String | 是 | 正整数 | 账户 ID | | `page` | Query | Integer | 否 | 默认 1,最小 1 | 页码;兼容 `pageNo` | | `pageSize` | Query | Integer | 否 | 默认 20,范围 1~100 | 每页数量 | | `operationType` | Query | String | 否 | `CREATE` / `UPDATE` / `ENABLE` / `DISABLE` / `DELETE` | 操作类型 | | `fieldName` | Query | String | 否 | 最长 64,不能全空白 | 精确筛选变更字段 | | `status` | Query | String | 否 | `DRAFT` / `PENDING` / `REJECTED` / `ACTIVE` / `DISABLED` | 事实中的账户状态 | | `from` / `to` | Query | String | 否 | `yyyy-MM-dd HH:mm:ss` | 必须成对传入,且 `from <= to` | | `sortBy` | Query | String | 否 | `occurredAt` / `changeLogId`,默认前者 | 排序字段 | | `sortDirection` | Query | String | 否 | `ASC` / `DESC`,默认 `DESC` | 排序方向 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `records[].operationType` | String | 操作类型 | | `records[].oldValue` / `newValue` | String | 变更前后中文业务摘要 | | `records[].valueAvailability` | String | `FULL` 或 `LEGACY_MASKED_UNRECOVERABLE` | | `records[].changeReason` | String | 变更原因 | | `records[].status` | String | 账户状态中文名 | | `records[].operatorName` / `operatorRole` | String | 操作人展示名 / 操作时角色中文名 | | `records[].occurredAt` | String | 发生时间,格式 `yyyy-MM-dd HH:mm:ss` | | `total` / `page` / `pageSize` | Integer | 总数 / 当前页 / 每页数量 | #### 请求示例 ```http GET /admin/supplier/bank-accounts/726500000000000001/change-records/page?page=1&pageSize=20&operationType=UPDATE&sortBy=occurredAt&sortDirection=DESC Authorization: Bearer <当前有效凭证> ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "records": [{ "operationType": "UPDATE", "oldValue": "开户支行:旧示例支行", "newValue": "开户支行:新示例支行", "valueAvailability": "FULL", "changeReason": "提交收款账户修改审批,终审前保留原账户原值生效", "status": "已生效", "operatorName": "示例管理员", "operatorRole": "超级管理员", "occurredAt": "2026-09-07 15:00:01" }], "total": 1, "page": 1, "pageSize": 20 }, "success": true } ``` #### 空数据 / 降级响应 无记录时返回 `records: []`、`total: 0`。操作人服务降级时返回不含内部 ID 的稳定展示名;历史完整值不可恢复时返回 `valueAvailability=LEGACY_MASKED_UNRECOVERABLE`。 #### 错误响应 ```json {"code":395045,"message":"历史查询起始与结束时间必须同时传入","data":null,"success":false} ``` #### 业务边界 - 需要 `supplier:approval:read`;账户证明附件继续受 `supplier:account:proof:read` 独立控制,无权限时摘要显示“无权查看”。 - 查询只返回 Path 指定账户的事实,不混入同供应商其他账户或主体变更。 - `oldValue`、`newValue` 可能含完整业务值,前端须按敏感信息展示规范处理。 ## 四、契约约束与正确调用方式 1. 编辑前读取账户详情,完整回填当前资料和 `updateTime`。 2. 修改成功后保留原列表行,以响应中的同一 `accountId` 刷新列表;不得追加第二条待审账户。 3. 在账户操作区新增“变更明细”,调用新增 GET 接口并支持分页,默认按 `occurredAt DESC` 展示。 4. `approvalStatus=PENDING` 时展示“审核中”,正式账户仍使用列表返回的 `ACTIVE` 原值;终态后重新拉取列表和明细。 5. 移除 #7182 的“新 accountId / 新待审行替换原行”处理。 ## 五、数据库行为 账户修改提交、审批通过、驳回或撤销均只保留原账户列表行和原 `accountId`;提交审批只追加可查询的审批与变更事实,不新增替换账户。校验失败不改变账户或新增事实。 ## 六、边界行为 - `401`:未登录或凭证失效。 - `400`:分页、账户字段或结算组合不合法。 - `395001`:账户或所属供应商不存在;`395002`:无读取或写入权限。 - `395005` / `395010`:状态不允许或供应商未生效。 - `395011` / `395028`:账户已有在途审批或变更;`395014`:版本过期;`395027`:新账号被其他账户占用。 - `395045` / `395046`:时间范围缺一端或起止倒置。 - `395019`~`395022`:企微配置、绑定、提交或对账异常,不自动新建申请。 - 业务错误可能仍使用 HTTP 200,必须同时判断响应体 `code` 和 `success`。 ## 七、不影响范围 - 不修改账户新增、启用、停用、删除和默认账户切换接口。 - 不改变现有请求字段、枚举或审批状态字段;不影响小程序接口。 - 旧替换式账户历史继续可读,不合并或删除历史记录。 ## 八、测试环境已验证 - 修改请求返回原 `accountId`;提交前后账户列表与有效账户数量均为 1,正式账户内容未提前变化。 - 新接口返回新增的 `UPDATE` 明细,分页和 `operationType` 筛选有效。 - 未登录返回 `401`;非法账号返回 `400`;时间范围只传一端返回 `395045`,失败路径未产生写入。 **当前状态:后端已部署并通过 TEST Gateway 验证;待前端接入。** ## 十、相关文档 - [Issue #7265](https://git.1814.love:8443/wx/HL/issues/7265) - [PR #7271](https://git.1814.love:8443/wx/HL/pulls/7271) - [被本文纠正的 #7182 说明](https://git.1814.love:8443/wx/HL/issues/7182) ## 关联 / 联系人 ### 链接 - **Issue**: [#7265](https://git.1814.love:8443/wx/HL/issues/7265) - **PR**: [#7271](https://git.1814.love:8443/wx/HL/pulls/7271) - **Merge commit**: [859a62ddb512074b33103be9bb9e4e579bdbb3ad](https://git.1814.love:8443/wx/HL/commit/859a62ddb512074b33103be9bb9e4e579bdbb3ad) ### 联系人 - **后端负责人**: @lc