18 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 | 6476 | 供应商详情补齐地址备注并统一表单校验 | admin | lc(GIT) | 修改接口 | deployed | verified | verified | mmg | 6635a2ae | v2.1 | 2026-08-27 | PR #6517 已合并 dev-v3(合并提交 a952a04c);hl-resource-service 已随 dev-v3 精确提交 b269e5fd 由 Deploy Panel 任务 938ca09e 发布 TEST。真实 TEST 身份已验证 address/remark 创建、详情、更新回显,创建/更新同口径校验、权限失败零写入及测试数据清理。 | 2026-08-27 | dev-v3 |
供应商管理:详情补齐地址备注并统一表单校验
供应商详情接口现在完整返回主体的 address 和 remark,管理端可用同一份详情快照初始化查看页与编辑表单。创建、提交和更新原本已有的业务字段与校验未增加新的必填项;本工单用契约测试和真实 TEST 验收固定三条写链路的一致口径。
本次没有新增接口路径、权限点或业务错误码。
一、背景
创建和更新请求已经接受地址、备注等主体字段,但基础信息详情此前没有回传 address、remark,导致管理端打开编辑页时无法完整还原已保存表单。同时,创建、提交和更新分属不同请求模型,消费方需要一个明确、可验证的共同校验口径。
本次处理后:
- 详情响应补齐主体地址与内部备注。
- 创建、提交、更新对共同主体字段、类型、联系人、资质和合同继续执行同一业务规则。
- 更新只额外要求变更原因、主体并发版本,以及被修改子项的 ID/版本。
- #6436/#6499 已交付的授权完整值语义保持不变,不重新引入脱敏值或“留空表示保留旧敏感值”的旧约定。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询供应商基本信息 | GET | /admin/supplier/items/{supplierId}/basic-info/view |
响应新增字段 | 根对象新增 address、remark,用于完整初始化查看与编辑表单 |
创建草稿、提交注册和更新资料的路径、请求字段及错误码没有结构性变化,因此不作为新增接口列入上表;其稳定调用规则在第四节集中说明。
三、接口详情
1. 查询供应商基本信息 GET /admin/supplier/items/{supplierId}/basic-info/view
VO: SupplierBasicInfoRespVO
使用场景
管理端进入供应商详情或编辑页时调用。响应是主体、类型、联系人、资质、合同及并发版本的完整快照;编辑页应直接使用其中的 address、remark 和 updateTime,不得用本地空值覆盖。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商;不得转为 JavaScript Number |
出参 Result<SupplierBasicInfoRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
supplierId |
String | 供应商 ID |
fullName / shortName |
String/null | 供应商全称与简称 |
tax_no |
String | 完整主体证件号;JSON 字段名保持 tax_no |
legalRepresentative |
String/null | 法定代表人姓名 |
legalRepresentativeIdNo |
String/null | 完整法人居民身份证号 |
legalRepresentativeIdCardFrontUrl / legalRepresentativeIdCardBackUrl |
String/null | 法人证件正反面永久文件地址 |
contactPhone |
String/null | 完整公司联系电话 |
establishDate |
String/null | 成立日期,格式 yyyy-MM-dd |
registeredCapital / businessScope |
String/null | 注册资本与经营范围 |
address |
String/null | 本次新增回显:注册地址或经营地址 |
staffScale |
String/null | 人员规模字典值 |
mainCooperation |
String | 主要合作内容 |
remark |
String/null | 本次新增回显:供应商主体内部备注 |
types |
Array | 类型完整集合;每项包含 isPrimary |
primaryTypeCode / primaryTypeName |
String/null | 当前主类型 |
contacts / qualifications / contracts |
Array | 联系人、资质和合同完整集合;现有项包含字符串 ID 与 updateTime |
status |
String | 当前供应商状态 |
updateTime |
String | 主体并发版本,格式 yyyy-MM-dd HH:mm:ss |
请求示例
GET /admin/supplier/items/2092800000000000001/basic-info/view
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092800000000000001",
"fullName": "示例旅行服务有限公司",
"shortName": "示例旅行",
"tax_no": "91350211M000100Y46",
"legalRepresentative": "示例法人",
"legalRepresentativeIdNo": "11010519491231002X",
"legalRepresentativeIdNoMask": "11010519491231002X",
"legalRepresentativeIdCardFrontUrl": "https://files.example.com/supplier/id-front.jpg",
"legalRepresentativeIdCardBackUrl": "https://files.example.com/supplier/id-back.jpg",
"contactPhone": "13800138000",
"contactPhoneMask": "13800138000",
"establishDate": "2020-01-01",
"registeredCapital": "100万元",
"businessScope": "境内旅游服务",
"address": "福建省厦门市示例路 1 号",
"staffScale": "LT50",
"mainCooperation": "酒店与景区资源合作",
"remark": "重点合作供应商",
"types": [{"typeCode": "HOTEL", "typeName": "酒店", "isPrimary": true}],
"primaryTypeCode": "HOTEL",
"primaryTypeName": "酒店",
"contacts": [],
"qualifications": [],
"contracts": [],
"status": "DRAFT",
"updateTime": "2026-08-27 15:30:00"
}
}
空数据 / 降级响应
历史供应商未填写地址或备注时,字段明确返回 null;无类型、联系人、资质或合同时,相应集合返回空数组,不把缺失数据当成接口异常:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092800000000000001",
"address": null,
"remark": null,
"types": [],
"primaryTypeCode": null,
"primaryTypeName": null,
"contacts": [],
"qualifications": [],
"contracts": [],
"updateTime": "2026-08-27 15:30:00"
}
}
错误响应
供应商不存在、已删除或超出数据范围时不返回空详情:
{
"code": 395001,
"message": "供应商不存在",
"success": false,
"data": null
}
业务边界
- 要求可信管理身份、
supplier:view平台权限和供应商数据范围;登录态不能代替业务权限。 address、remark属于响应的向后兼容增加;旧客户端忽略未知字段即可继续运行。legalRepresentativeIdNoMask、contactPhoneMask等废弃兼容别名继续与完整值字段同值;新代码使用无Mask字段。- Long ID 一律按字符串保存、比较和传输。
- 本接口只读,不修改主体版本、缓存或审批状态。
四、契约约束与正确调用方式
以下三个既有写接口没有新增路径或字段,但共同业务口径由本工单固定:
- 创建草稿:
POST /admin/supplier/items/add - 提交注册:
POST /admin/supplier/items/{supplierId}/submit - 更新资料:
PUT /admin/supplier/items/{supplierId}/update
共同主体字段规则
| 字段 | 创建/提交 | 更新 | 统一约束 |
|---|---|---|---|
fullName |
必填 | 可选 | 最长 500 字符 |
shortName |
可选 | 可选 | 最长 300 字符 |
taxNo |
必填 | 可选,且仅允许在状态规则内修改 | 6 至 64 位字母、数字或展示分隔符;身份证/统一社会信用代码还校验日期或校验位 |
legalRepresentative |
可选 | 可选 | 最长 500 字符 |
legalRepresentativeIdNo |
可选 | 可选 | 18 位合法居民身份证号;与正反面地址按完整三字段组合校验 |
legalRepresentativeIdCardFrontUrl / legalRepresentativeIdCardBackUrl |
可选 | 可选 | 公网 HTTPS 永久文件地址,单项最长 1000 字符 |
contactPhone |
可选 | 可选 | 7 至 20 位合法电话字符 |
establishDate |
可选 | 可选 | yyyy-MM-dd,不得晚于当前日期 |
registeredCapital |
可选 | 可选 | 最长 50 字符 |
businessScope |
可选 | 可选 | 最长 500 字符 |
address |
可选 | 可选 | 最长 500 字符;创建和更新同口径 |
staffScale |
可选 | 可选 | LT50、R50_200、R200_500、GT500 |
mainCooperation |
必填 | 可选 | 创建/提交不能为空白;更新传值时按同一业务含义保存 |
licenseImageUrl |
可选 | 可选 | 最长 500 字符,并与营业执照资质归一化 |
remark |
可选 | 可选 | 供应商主体内部备注,不等同于审批意见或 changeReason |
集合上限与语义保持不变:types 最多 15 项;contacts、qualifications、contracts 各最多 100 项;initialAccounts 最多 1 项。非空类型必须来自生效字典且不得重复,非空联系人集合必须形成唯一默认联系人,资质和合同继续执行类型、日期、金额、归属和完整快照校验。
更新场景额外要求:
changeReason必填、非空白、最长 500 字符,并进入业务审计。expectedUpdateTime必须等于详情最新updateTime。- 已有联系人、资质、合同随完整快照更新时,必须带回对应字符串 ID 和子项
updateTime。
✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload / 结果 |
|---|---|
| ✅ 更新地址和备注 | {"address":"福建省厦门市示例路 2 号","remark":"地址已确认","changeReason":"更新地址和备注","expectedUpdateTime":"2026-08-27 15:30:00"} |
| ✅ 只更新备注 | {"remark":"新的内部备注","changeReason":"补充内部备注","expectedUpdateTime":"2026-08-27 15:30:00"} |
| ❌ 地址超过 500 字符 | 创建、更新均返回 400,零写入 |
| ❌ 成立日期晚于当前日期 | 创建、更新均返回 400,零写入 |
❌ 更新缺少 changeReason |
返回 400,零写入 |
❌ 使用旧 expectedUpdateTime |
返回 395014,零写入;必须刷新详情后由用户确认 |
正确更新顺序
- 先查询详情,保存字符串 ID、子项版本和根对象
updateTime。 - 用户提交时只发送实际变更字段,并附非空
changeReason、最新expectedUpdateTime。 - 更新成功后采用响应里的新版本;需要完整表单时重新查询详情。
- 遇到
395014先刷新,不得自动用旧表单覆盖他人修改。
五、数据库行为
- 成功创建、提交或更新时,主体与本次携带的类型、联系人、资质、合同及业务审计按聚合事务一起成功。
- 地址和备注保存后,详情读取与变更审计使用同一业务值;
remark不会被提升为审批意见或变更原因。 - 请求校验、权限、状态、归属或并发版本失败时零写入,主体
updateTime不变化。 - 本次没有数据库结构变更、Flyway migration 或历史数据回填;旧记录的地址/备注原值保持不变。
- 不新增 Redis、MQ、Feign 或跨服务副作用。
六、边界行为
- 未登录或 Token 失效:
401,不把无认证响应当作正向验收。 - ADMIN 等无写权限角色调用创建、提交或更新:
395002,零写入;已有详情仍按读取权限返回。 - 供应商不存在或已删除:
395001。 - 并发版本过期:
395014,调用方刷新详情后重提。 - 地址超过 500 字符、未来成立日期、必填字段缺失或组合不完整:
400,零写入。 - 历史
address、remark为空:详情返回null,不报错、不猜测默认值。 ARCHIVED等不可修改状态继续由既有状态门禁拒绝更新。- 业务失败可能仍使用 HTTP 200,调用方必须同时判断响应体
code、success、data。
六.5、枚举 / 数据字典
staffScale(供应商人员规模稳定值)
所属字段: SupplierDraftSaveReqVO.staffScale / SupplierUpdateReqVO.staffScale / SupplierBasicInfoRespVO.staffScale | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
LT50 |
50 人以下 | 小于 50 人 |
R50_200 |
50 至 200 人 | 50 至 200 人档 |
R200_500 |
200 至 500 人 | 200 至 500 人档 |
GT500 |
500 人以上 | 大于 500 人 |
types[].typeCode、contacts[].contactRole、qualifications[].qualType 继续从对应生效数据字典读取,不在客户端硬编码展示名。
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
详情根对象 address |
未返回,已保存值无法用于表单回填 | 返回保存的地址;未填写为 null |
详情根对象 remark |
未返回,已保存值无法用于表单回填 | 返回主体内部备注;未填写为 null |
| 创建/提交/更新请求字段 | 已存在 | 无新增字段、无新增必填项 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 打开编辑页 | 详情缺少地址和备注,表单初始化不完整 | 一次详情调用可完整初始化主体字段 |
| 共同字段校验 | 实现存在,但缺少跨创建/更新契约锁定 | 自动化和 TEST 验收固定创建/更新同口径 |
| 完整敏感值回显 | 按 #6436/#6499 返回授权完整值 | 保持不变 |
六.7、影响评估
- 是否破坏向后兼容: 否。响应新增两个可空字段,旧客户端可忽略;请求无新增必填项。
- 前端是否必须同步上线: 为闭合编辑体验需要接入
address、remark,但不构成旧客户端继续运行的硬门禁。 - 前端 workaround 清理点: 删除编辑页对地址、备注的本地空值兜底,改为使用详情真实值。
- QA 重点: 新建后打开详情、更新后刷新详情、地址长度 500/501、今天/未来成立日期、ADMIN 越权、旧版本并发冲突。
七、不影响范围
- 仅影响: 管理后台供应商详情/编辑表单的主体地址、内部备注回填,以及已有写接口校验口径的契约固定。
- 零影响:
- Gateway 路由和认证级别。
- 供应商生命周期状态机、审批、收款账户、证明附件、资源关系及归档语义。
- 数据库结构、历史数据、Nacos 配置、Redis、MQ、Feign 和其他服务。
- 小程序、C 端、Web、H5、桌面端接口。
- #6436/#6499 的授权完整值和废弃
*Mask兼容语义。
八、测试环境已验证
- 本地自动化:供应商定向测试 49 项通过;最新
dev-v3上hl-resource-serviceReactor 全量 2,153 项,0 失败、0 错误、38 项条件跳过。 - 部署:Deploy Panel API 任务
938ca09e终态success;目标与实际提交均为b269e5fdca2e1d29e0336314a909885e9d1f9d16,包含本工单合并提交a952a04c。 - 采样:部署任务期 8 个有效采样;每个采样至少 2 个运行进程、2 个健康启用 Nacos 实例,零观测不可用采样。该证据只证明采样点,不代表采样间绝对连续。
- 真实 Gateway:SUPER_ADMIN 创建完整表单后,详情精确回显
address、remark、完整授权字段、主类型及联系人/资质/合同 ID 和版本;更新后 API、数据库主体与更新审计一致。 - 失败路径:创建/更新的地址超长均返回
400;创建/更新的未来成立日期均返回400;ADMIN 更新返回395002;所有失败均验证零写入、版本不变。 - 清理:临时供应商先经业务删除接口软删除,确认主体已删除且活动子项为 0;随后按本次唯一标记精确清理 8 行测试数据,17 张关联表回读,剩余供应商 0 行;测试会话已注销并验证失效。
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #6478 / #6483 / #6486 | #6436 | 供应商授权完整值、主类型及同日修正 | ✅ 有效,本次保持 |
| #6517 | #6476 | 详情补齐地址备注并固定表单校验契约 | ✅ 最新 |
十、相关文档
- 关联 Issue:#6476
- 关联 PR:#6517
- 相关完整值契约:
changelogs-v2/2026-08/27_6436_供应商敏感字段完整回显与主类型-修改接口-管理后台.md - 管理端接入:读取详情
address、remark,更新时保留最新expectedUpdateTime;前端引用待回填。
撤回
-
从最新
dev-v3创建独立回退分支,执行:git revert -m 1 --no-edit a952a04cf80f24d6c50cbec4b992c82f8580beba -
运行供应商定向测试、
hl-resource-service全量测试和差异检查,经独立 PR 合入。 -
使用同一 Deploy Panel 两阶段客户端,仅滚动部署
hl-resource-service,并绑定批准回退分支的精确提交。 -
管理端停止依赖详情响应中的
address、remark,再撤回本 Changelog;既有请求字段和完整值契约保持不变。 -
无数据库、配置、Redis 或 MQ 恢复步骤;已保存的地址和备注数据保留,不做破坏性回填或删除。
-
撤回后经 Gateway 复测详情、创建、更新、未认证、越权、并发冲突和失败零写入,并确认 Resource 双实例与 Nacos 健康。
关联 / 联系人
链接
联系人
- 后端负责人: @lc
- 管理端负责人: @mmg(
frontend_status: pending)