23 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 | 6474 | 供应商详情分离变更记录与审批流水 | admin | lc(GIT) | 新增接口 | deployed | verified | verified | mmg | 199147de | v2.1 | 2026-08-27 | PR #6519 已合并 dev-v3;补充缺陷 PR #6534 已恢复旧兼容查询。hl-resource-service 已由 Deploy Panel 任务 9e69fd2c 精确发布提交 aec1de2d 至 TEST,并以真实 SUPER_ADMIN、ADMIN 和受限 CUSTOMIZER 身份完成 Gateway 只读验收。 | 2026-08-27 | dev-v3 |
供应商管理:详情分离变更记录与审批流水
供应商详情新增两个相互独立的只读分页接口:“变更记录”只返回供应商主体发生过的业务变化,“审批记录”只返回供应商主体审批事实。两个列表独立计数、独立筛选、独立排序,收款账户记录不会混入。
旧 /admin/supplier/items/{supplierId}/approval-records/page 继续保留,用于仍需主体与账户变更混合列表的兼容场景;本次不删除、不改名,也不要求现有调用方同步切换。
一、背景
旧审批记录接口承载的是主体与收款账户变更的兼容混合列表,不能同时满足详情页“业务变更”和“审批过程”两种独立展示语义。若管理端在本地拆分或二次计数,会出现分页总数不准确、账户记录混入主体历史、审批技术字段误展示等问题。
本次由后端直接提供两个稳定的业务白名单视图:
- “变更记录”展示操作类型、变更前后业务摘要、原因、生命周期状态、操作人、历史角色和发生时间。
- “审批记录”展示审批业务、申请人、状态、动作、审批人安全展示值、意见、提交时间和完成时间。
- 两个接口都在查询前校验可信管理身份、角色和
supplier:approval:read权限。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询供应商主体变更记录 | GET | /admin/supplier/items/{supplierId}/change-records/page |
新增只读接口 | 独立分页返回主体变更业务摘要 |
| 2 | 查询供应商主体审批流水 | GET | /admin/supplier/items/{supplierId}/approval-history/page |
新增只读接口 | 独立分页返回主体审批过程与结果 |
三、接口详情
1. 查询供应商主体变更记录 GET /admin/supplier/items/{supplierId}/change-records/page
VO: SupplierChangeRecordPageReqVO / SupplierChangeRecordRespVO
使用场景
管理端进入供应商详情的“变更记录”页签时调用。服务端完成主体记录筛选、分页和业务摘要投影;前端不要从旧混合列表中再次筛选主体记录,也不要自行拼接前后值摘要。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商;不得转为 JavaScript Number |
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 | 否 | 生命周期编码 | 按变更后的供应商状态筛选 |
from |
Query | String | 条件必填 | yyyy-MM-dd HH:mm:ss |
与 to 成对传入,按发生时间筛选,含边界 |
to |
Query | String | 条件必填 | yyyy-MM-dd HH:mm:ss,不得早于 from |
与 from 成对传入,含边界 |
sortBy |
Query | String | 否 | occurredAt 或 changeLogId,默认 occurredAt |
服务端白名单排序字段 |
sortDirection |
Query | String | 否 | ASC 或 DESC,不区分大小写,默认 DESC |
排序方向 |
出参 Result<PageResult<SupplierChangeRecordRespVO>>
分页对象固定包含 records、total、page、pageSize。
| 字段 | 类型 | 说明 |
|---|---|---|
records |
Array | 当前页主体变更记录;无数据时为 [] |
total |
Integer | 符合筛选条件的主体变更总数,不包含账户记录 |
page |
Integer | 当前页码 |
pageSize |
Integer | 当前页容量 |
records[].operationType |
String | 操作类型编码 |
records[].oldValue |
String | 变更前中文业务摘要;空值使用明确占位 |
records[].newValue |
String | 变更后中文业务摘要;空值使用明确占位 |
records[].valueAvailability |
String | FULL 或 LEGACY_MASKED_UNRECOVERABLE |
records[].changeReason |
String | 业务变更原因或稳定占位 |
records[].status |
String/null | 变更后的供应商生命周期中文名 |
records[].operatorName |
String | 操作人展示名;系统操作为“系统”,依赖降级为“未知管理员” |
records[].operatorRole |
String | 操作发生时的角色快照中文名 |
records[].occurredAt |
String | 变更发生时间,格式 yyyy-MM-dd HH:mm:ss |
响应不会返回变更日志 ID、管理员内部 ID、原始审计 JSON、TraceId、账户 ID、证明附件或其他主体敏感快照字段。
请求示例
GET /admin/supplier/items/2092800000000000001/change-records/page?page=1&pageSize=20&operationType=UPDATE&sortBy=occurredAt&sortDirection=DESC
Authorization: Bearer <admin-token>
GET 请求无请求体。
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [
{
"operationType": "UPDATE",
"oldValue": "供应商全称:示例旅行服务公司",
"newValue": "供应商全称:示例旅行服务有限公司",
"valueAvailability": "FULL",
"changeReason": "修正供应商主体名称",
"status": "合作中",
"operatorName": "示例管理员",
"operatorRole": "超级管理员",
"occurredAt": "2026-08-27 15:20:00"
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
空数据 / 降级响应
筛选结果为空仍返回成功分页,不回退到旧混合列表:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [],
"total": 0,
"page": 1,
"pageSize": 20
}
}
历史记录无法恢复完整原值时,仍返回已有安全摘要,并明确标记:
{
"operationType": "UPDATE",
"oldValue": "138****8000",
"newValue": "139****9000",
"valueAvailability": "LEGACY_MASKED_UNRECOVERABLE",
"changeReason": "历史记录",
"status": "合作中",
"operatorName": "未知管理员",
"operatorRole": "管理员",
"occurredAt": "2026-07-01 10:00:00"
}
错误响应
非法筛选、分页、排序或时间范围返回参数错误,例如只传 from:
{
"code": 400,
"message": "from和to必须同时传入",
"success": false,
"data": null
}
供应商不存在或已删除:
{
"code": 395001,
"message": "供应商不存在",
"success": false,
"data": null
}
业务边界
- 要求 Gateway 登录态、可信管理员身份、允许的读角色以及
supplier:approval:read平台权限;任一门禁失败时不查询历史数据。 - 代码角色矩阵沿用
ADMIN、FINANCE、SUPER_ADMIN;角色允许不等于拥有平台权限,两项必须同时满足。 - 只读取供应商主体变更,不包含收款账户变更或审批流水。
oldValue、newValue是后端形成的中文业务摘要,不是可回填编辑表单的结构化快照。- User 姓名依赖异常只影响
operatorName,分页仍成功且不会以管理员内部 ID 降级。 - 接口只读,成功、空数据和失败场景均不修改供应商、审批、缓存或消息状态。
2. 查询供应商主体审批流水 GET /admin/supplier/items/{supplierId}/approval-history/page
VO: SupplierApprovalHistoryPageReqVO / SupplierApprovalHistoryRespVO
使用场景
管理端进入供应商详情的“审批记录”页签时调用。该列表只表达主体审批申请、过程和结果,不包含主体字段变更摘要,也不包含收款账户审批。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商;不得转为 JavaScript Number |
page |
Query | Integer | 否 | 默认 1,最小 1 |
当前页;兼容别名 pageNo |
pageSize |
Query | Integer | 否 | 默认 20,范围 1..100 |
每页条数 |
bizType |
Query | String | 否 | 大写字母开头,仅大写字母、数字、下划线,最长 32 字符 | 审批业务类型精确筛选 |
approvalStatus |
Query | String | 否 | 大写字母开头,仅大写字母、数字、下划线,最长 20 字符 | 审批状态精确筛选 |
action |
Query | String | 否 | 大写字母开头,仅大写字母、数字、下划线,最长 32 字符 | 审批动作或结果精确筛选 |
from |
Query | String | 条件必填 | yyyy-MM-dd HH:mm:ss |
与 to 成对传入,按提交时间筛选,含边界 |
to |
Query | String | 条件必填 | yyyy-MM-dd HH:mm:ss,不得早于 from |
与 from 成对传入,含边界 |
sortBy |
Query | String | 否 | submittedAt 或 finishedAt,默认 submittedAt |
服务端白名单排序字段 |
sortDirection |
Query | String | 否 | ASC 或 DESC,不区分大小写,默认 DESC |
排序方向 |
出参 Result<PageResult<SupplierApprovalHistoryRespVO>>
分页对象固定包含 records、total、page、pageSize。
| 字段 | 类型 | 说明 |
|---|---|---|
records |
Array | 当前页主体审批记录;无数据时为 [] |
total |
Integer | 符合筛选条件的主体审批总数,不包含账户审批 |
page |
Integer | 当前页码 |
pageSize |
Integer | 当前页容量 |
records[].bizType |
String | 审批业务类型编码 |
records[].bizTypeName |
String | 审批业务类型中文名;未知编码显示“其他供应商审批” |
records[].applicantName |
String | 申请人展示名;系统申请为“系统”,依赖降级为“未知申请人” |
records[].approvalStatus |
String | 审批状态编码 |
records[].approvalStatusName |
String | 审批状态中文名;未知编码显示“未知状态” |
records[].action |
String/null | 审批动作或结果编码 |
records[].actionName |
String | 审批动作中文名;未知编码显示“其他动作” |
records[].approverName |
String | 安全展示值:“系统”“待审批”或“外部审批人” |
records[].opinion |
String | 审批意见;无意见时为 — |
records[].submittedAt |
String | 提交时间;历史缺失时使用该审批事实的创建时间,格式 yyyy-MM-dd HH:mm:ss |
records[].finishedAt |
String/null | 完成时间;审批中为 null,格式 yyyy-MM-dd HH:mm:ss |
响应不会返回审批日志 ID、申请人内部 ID、requestNo、spNo、审批模板 ID、企微用户 ID、候选或详情摘要、候选或详情 JSON/密文、apply/sync 技术状态。
请求示例
GET /admin/supplier/items/2092800000000000001/approval-history/page?page=1&pageSize=20&approvalStatus=APPROVED&sortBy=submittedAt&sortDirection=DESC
Authorization: Bearer <admin-token>
GET 请求无请求体。
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [
{
"bizType": "PROFILE_CREATE",
"bizTypeName": "供应商建档审批",
"applicantName": "示例管理员",
"approvalStatus": "APPROVED",
"approvalStatusName": "已通过",
"action": "SYSTEM_AUTO_APPROVE",
"actionName": "系统自动通过",
"approverName": "系统",
"opinion": "—",
"submittedAt": "2026-08-27 14:00:00",
"finishedAt": "2026-08-27 14:00:01"
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
空数据 / 降级响应
不存在符合条件的审批事实时返回成功空分页:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [],
"total": 0,
"page": 1,
"pageSize": 20
}
}
申请人姓名依赖不可用时,该记录仍正常返回,applicantName 为 未知申请人;不会回退为内部管理员 ID。
错误响应
非法枚举、分页、排序或反向时间范围返回参数错误,例如:
{
"code": 400,
"message": "from不能晚于to",
"success": false,
"data": null
}
无业务读取权限时返回拒绝结果,且不查询审批历史:
{
"code": 403,
"message": "无权访问供应商数据",
"success": false,
"data": null
}
业务边界
- 权限条件与变更记录接口相同:可信管理身份、
ADMIN/FINANCE/SUPER_ADMIN读角色和supplier:approval:read平台权限缺一不可。 - 只读取供应商主体审批,不包含收款账户审批或主体字段变更记录。
- 时间筛选基于提交时间;历史提交时间为空时使用该审批事实的创建时间。
- 申请人名称每页最多批量补全一次;User 服务异常、空响应或缺失用户时安全降级,整页不返回 500。
- 审批人只返回业务安全展示值,不暴露企微或外部审批系统标识。
- 接口只读,不推进审批状态,不触发审批回调,也不产生缓存、消息或配置副作用。
四、契约约束与正确调用方式
详情页接入映射
| 页面区域 | 正确接口 | 数据范围 |
|---|---|---|
| 变更记录 | GET /admin/supplier/items/{supplierId}/change-records/page |
仅主体变更 |
| 审批记录 | GET /admin/supplier/items/{supplierId}/approval-history/page |
仅主体审批 |
| 旧兼容混合列表 | GET /admin/supplier/items/{supplierId}/approval-records/page |
主体与账户变更,契约不变 |
✅ 正确 / ❌ 错误调用对照
| 场景 | 调用 / 结果 |
|---|---|
| ✅ 分别加载两个页签 | 两条新接口分别维护自己的 page、pageSize、筛选和 total |
| ✅ 查询完整时间区间 | 同时传 from=2026-08-01 00:00:00 与 to=2026-08-31 23:59:59 |
| ✅ 兼容旧页面 | 继续调用旧 approval-records/page,无需因本次新增接口修改 |
| ❌ 在前端拆旧混合列表 | 分页后再过滤会得到错误总数,也不能生成审批流水 |
| ❌ 只传一侧时间 | 返回 400,不执行历史查询 |
❌ 把 supplierId 转为 Number |
可能丢失精度;ID 必须始终按 String 传输和比较 |
调用方必须同时检查 code、message、success 和 data,不能只用 HTTP 状态判断业务成功。
六、边界行为
- 未登录或登录态失效:统一结果业务码
401,不进入供应商查询。 - 已登录但角色或平台权限不满足:业务码
403,不读取历史。 supplierId<=0、page<1、pageSize不在1..100、非法筛选或排序:业务码400。- 供应商不存在或已软删除:业务码
395001。 from、to必须同时传入,且from<=to;时间边界包含起止时刻。- 请求超过最后一页:返回原请求页码、空
records和真实total,不自动改页。 - 两个新接口的
total分别统计自己的数据集,互不借用;账户变更和账户审批都不进入。 - 旧
approval-records/page的路径、请求、响应及主体/账户混合语义保持兼容。
六.5、枚举 / 数据字典
operationType(变更记录操作类型)
所属字段: SupplierChangeRecordPageReqVO.operationType / SupplierChangeRecordRespVO.operationType | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
CREATE |
创建 | 创建供应商主体 |
UPDATE |
更新 | 更新主体业务字段 |
ENABLE |
启用 | 恢复可用状态 |
DISABLE |
停用 | 暂停或禁用合作 |
DELETE |
删除 | 软删除业务事实 |
status(变更后的供应商生命周期)
所属字段: SupplierChangeRecordPageReqVO.status | 类型: String
| 值 | 中文展示 |
|---|---|
DRAFT |
草稿 |
VETTING |
注册审核中 |
ACTIVE |
合作中 |
SUSPENDED |
暂停合作 |
FROZEN |
已冻结 |
BLACKLIST |
黑名单 |
ARCHIVED |
已归档 |
valueAvailability(变更摘要可用性)
所属字段: SupplierChangeRecordRespVO.valueAvailability | 类型: String
| 值 | 中文 | 调用方处理 |
|---|---|---|
FULL |
完整业务摘要可用 | 正常展示 oldValue / newValue |
LEGACY_MASKED_UNRECOVERABLE |
历史原值不可恢复 | 展示现有摘要,并标注其为历史脱敏值;不要提示用户重试 |
bizType(主体审批业务类型)
所属字段: SupplierApprovalHistoryPageReqVO.bizType / SupplierApprovalHistoryRespVO.bizType | 类型: String
| 值 | 中文展示 | 说明 |
|---|---|---|
PROFILE_CREATE |
供应商建档审批 | 注册或建档审批 |
STATUS_CHANGE |
供应商状态变更审批 | 生命周期状态变更审批 |
| 其他合法大写编码 | 其他供应商审批 | 为历史及后续业务保留兼容 |
approvalStatus(主体审批状态)
所属字段: SupplierApprovalHistoryPageReqVO.approvalStatus / SupplierApprovalHistoryRespVO.approvalStatus | 类型: String
| 值 | 中文展示 |
|---|---|
PENDING |
审批中 |
APPROVED |
已通过 |
REJECTED |
已驳回 |
CANCELED / CANCELLED |
已撤销 |
FAILED |
失败 |
| 其他合法大写编码 | 未知状态 |
action(主体审批动作或结果)
所属字段: SupplierApprovalHistoryPageReqVO.action / SupplierApprovalHistoryRespVO.action | 类型: String
| 值 | 中文展示 |
|---|---|
SUBMIT |
已提交 |
SYSTEM_AUTO_APPROVE |
系统自动通过 |
APPLY_FAIL |
结果应用失败 |
APPROVE |
通过 |
REJECT |
驳回 |
REVOKE |
撤销 |
CANCEL |
取消 |
| 其他合法大写编码 | 其他动作 |
七、不影响范围
- 仅新增:供应商详情的两个管理端只读分页契约。
- 保持兼容:旧
approval-records/page继续返回主体与账户变更混合列表。 - 零影响:供应商详情、创建、更新、提交、归档、暂停合作、拉黑、删除及收款账户管理接口。
- 零影响:供应商写权限、数据范围、状态机、审批提交/结果应用、事务、锁、幂等、审计与软删除语义。
- 零影响:数据库结构与历史数据;本次无 migration、数据回填或破坏性数据操作。
- 零影响:Gateway 顶级路由、Nacos 配置、Redis、MQ 和跨服务写链路。
- 后端仓库未修改任何管理端前端源码;管理端接入状态保持
pending。
八、测试环境已验证
- 本地自动化:主功能定向测试 79 项零失败;补充空上下文兼容测试套件 51 项零失败;最终
hl-resource-service全量 2158 项零失败、零错误(38 项条件跳过);GatewayRouteAuditTest4 项零失败。 - 合并:主 PR #6519 合并提交
3dc80ad69630127d91fa973a682051b2ef5c41d8;验收发现旧兼容接口空上下文缺陷后,补充工单 #6532 / PR #6534 以最小修复合入,最新合并提交为aec1de2db2b4ed07d78877c1ccd4e532919bdaf3。 - TEST 精确部署:Deploy Panel 任务
9e69fd2c成功发布aec1de2db2b4ed07d78877c1ccd4e532919bdaf3;构建退出码 0,任务期 10 次有效采样均至少 2 个运行进程、2 个健康启用 Nacos 实例,零不可用采样,部署窗日志无失败标记。 - 真实 Gateway:使用现有真实
SUPER_ADMIN与同账号可切换的ADMIN身份,两条新接口的成功分页、字段白名单、独立总数、筛选、排序、边界参数和旧接口兼容均通过;CUSTOMIZER返回403,未认证返回401。 - TEST 数据覆盖:目标供应商存在 18 条主体变更事实和 1 条主体审批事实;对应账户变更、账户审批均为 0,验证两个新接口没有跨域混页。审批样本为
APPROVED/SYSTEM_AUTO_APPROVE;环境没有外部审批人样本,未伪造业务数据。 - 身份说明:代码角色矩阵仍包含
ADMIN、FINANCE、SUPER_ADMIN。TEST 角色目录存在FINANCE,但当前获批真实账号不能切换到该角色;按工单确认使用现有SUPER_ADMIN(并覆盖ADMIN)替代 FINANCE 实测,不创建、不修改、不伪造财务账号。 - 清理:验收全程只读,供应商数据指纹前后一致,业务测试数据创建数为 0;真实会话均已失效处理,无数据库、Redis、MQ 或临时配置需要清理。
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #6486 | #6436 | 冻结供应商授权完整值与历史摘要语义 | ✅ 有效,本次沿用 |
| #6519 | #6474 | 新增主体变更记录与主体审批流水独立分页 | ✅ 本次主功能 |
| #6534 | #6532 | 修复旧兼容接口投影空上下文时的空指针 | ✅ 有效,保障历史兼容 |
十、相关文档
撤回
- 管理端先停止请求两个新路径,并恢复使用旧
approval-records/page,避免代码回退窗口产生请求失败。 - 从最新
dev-v3创建独立回退分支,对主功能合并执行git revert -m 1 --no-edit 3dc80ad69630127d91fa973a682051b2ef5c41d8,验证后经独立 PR 合入。 - #6532 的空上下文修复可独立保留,它修复的是旧兼容接口且不依赖两个新路径。若明确要求连同该修复一起撤回,再按从新到旧顺序执行
git revert -m 1 --no-edit aec1de2db2b4ed07d78877c1ccd4e532919bdaf3;这样会重新引入旧兼容接口在特定记录上的 500 风险,不作为推荐方案。 - 使用 Deploy Panel 两阶段客户端,仅滚动部署
hl-resource-service;本次无数据库、配置、Redis 或 MQ 恢复步骤,也无不可逆数据影响。 - 撤回后经 Gateway 验证两个新路径不可用、旧兼容接口按选定回退范围正常,复测未认证和越权,并确认 Resource 双实例、Nacos、日志及零写入。
- 同步发布本 Changelog 的撤回说明;不得仅改文件名表达状态。
关联 / 联系人
链接
联系人
- 后端负责人: @lc