@@ -0,0 +1,777 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6436"
|
||||
title: "供应商敏感字段完整回显与主类型"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "PR #6478、补充 PR #6483/#6486 已合并 dev-v3,最终提交 aa735fb8 已由 Deploy Panel 任务 7a48028c 发布 TEST;真实 TEST 身份已验证完整值、权限投影、失败零写入及测试数据清理。"
|
||||
updated_at: "2026-08-27"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 🔧 供应商敏感字段完整回显与主类型
|
||||
|
||||
供应商管理接口不再用掩码替代已授权管理员需要处理的业务原值,并补齐唯一主类型和注册账户摘要。证明附件仍受独立权限保护,不能因为本次完整值调整而绕过授权或同步读取审计。
|
||||
|
||||
## 一、背景
|
||||
|
||||
此前详情、账户和审批记录混用了原值、掩码与历史密文回退,草稿类型也缺少稳定的主类型回显,导致管理端无法可靠编辑、审核或回填表单。本次统一以下消费口径:
|
||||
|
||||
- 通过既有供应商读取权限后,税号、法人证件、电话、证照号和银行账号返回完整业务值。
|
||||
- `proofFileUrls` 继续要求 `supplier:account:proof:read`,且只有同步读取审计成功后才返回;无权限时字段不序列化。
|
||||
- `types[].isPrimary` 与根对象 `primaryTypeCode`、`primaryTypeName` 成为主类型权威回显。
|
||||
- `*Mask` 字段保留兼容但已废弃,兼容期内与对应完整值字段同值;新代码应使用无 `Mask` 的字段。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 响应修改 | 返回完整主体、联系人、资质字段及主类型 |
|
||||
| 2 | 查询供应商账户信息 | GET | `/admin/supplier/items/{supplierId}/account-info/list` | 响应修改 | 返回可读账户完整账号,按权限决定附件字段 |
|
||||
| 3 | 查询收款账户详情 | GET | `/admin/supplier/bank-accounts/{accountId}/view` | 响应修改 | 返回完整账号,按权限决定附件字段 |
|
||||
| 4 | 查询供应商审批记录 | GET | `/admin/supplier/items/{supplierId}/approval-records/page` | 请求与响应修改 | 增加目标筛选及完整前后值可用性 |
|
||||
| 5 | 创建供应商注册草稿 | POST | `/admin/supplier/items/add` | 响应修改 | `initialAccounts` 回显完整账号摘要 |
|
||||
| 6 | 更新供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | 响应修改 | 稳定回显主类型及完整初始账号摘要 |
|
||||
| 7 | 提交供应商注册 | POST | `/admin/supplier/items/{supplierId}/submit` | 响应修改 | 审批结果增加完整初始账号摘要 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
**VO**: `SupplierBasicInfoRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
供应商详情和编辑表单初始化。管理端可直接使用完整字段,并用主类型字段初始化单选控件。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `supplierId` | String | 供应商 ID |
|
||||
| `tax_no` | String | 完整主体证件号,JSON 名保持既有口径 |
|
||||
| `legalRepresentativeIdNo` | String/null | 完整法人居民身份证号 |
|
||||
| `legalRepresentativeIdCardFrontUrl` / `legalRepresentativeIdCardBackUrl` | String/null | 完整永久文件地址 |
|
||||
| `contactPhone` | String/null | 完整公司联系电话 |
|
||||
| `contacts[].contactPhone` | String | 完整联系人电话 |
|
||||
| `qualifications[].certNo` | String/null | 完整证照编号 |
|
||||
| `types[].isPrimary` | Boolean | 当前类型是否为主类型 |
|
||||
| `primaryTypeCode` / `primaryTypeName` | String/null | 主类型值和展示名称;无类型草稿为 null |
|
||||
| `legalRepresentativeIdNoMask` / `contactPhoneMask` | String/null | 废弃兼容别名,与完整值字段同值 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2092800000000000001/basic-info/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"fullName": "示例旅行服务有限公司",
|
||||
"tax_no": "91350211M000100Y46",
|
||||
"legalRepresentativeIdNo": "11010519491231002X",
|
||||
"legalRepresentativeIdNoMask": "11010519491231002X",
|
||||
"legalRepresentativeIdCardFrontUrl": "https://files.example.com/supplier/front.jpg",
|
||||
"legalRepresentativeIdCardBackUrl": "https://files.example.com/supplier/back.jpg",
|
||||
"contactPhone": "13800138000",
|
||||
"contactPhoneMask": "13800138000",
|
||||
"types": [{"typeCode": "HOTEL", "typeName": "酒店", "isPrimary": true}],
|
||||
"primaryTypeCode": "HOTEL",
|
||||
"primaryTypeName": "酒店",
|
||||
"contacts": [{"contactName": "示例联系人", "contactPhone": "13900139000", "contactPhoneMask": "13900139000"}],
|
||||
"qualifications": [{"qualType": "BUSINESS_LICENSE", "certNo": "LIC-2026-001", "certNoMask": "LIC-2026-001"}],
|
||||
"updateTime": "2026-08-27 11:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无类型草稿返回 `types: []`、`primaryTypeCode: null`、`primaryTypeName: null`。联系人或资质为空时返回空数组;历史停用类型的名称无法从字典解析时,名称回退为类型值,不阻断详情。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395001,
|
||||
"message": "供应商不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 要求既有 `supplier:view` 和数据范围校验,登录态不能替代业务权限。
|
||||
- 普通应用日志、异常和 Trace 不记录上述完整值。
|
||||
- `*Mask` 仅用于旧客户端兼容,新代码不得继续依赖掩码语义。
|
||||
|
||||
### 2. 查询供应商账户信息 `GET /admin/supplier/items/{supplierId}/account-info/list`
|
||||
|
||||
**VO**: `SupplierAccountInfoRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
在供应商结算信息区域展示已进入 PENDING、ACTIVE 或 DISABLED 的管理端可读账户。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `supplierId` | String | 供应商 ID |
|
||||
| `bankAccounts` | Array | 可读账户,默认账户优先 |
|
||||
| `bankAccounts[].accountNo` | String | 完整银行账号 |
|
||||
| `bankAccounts[].accountNoMask` | String | 废弃兼容别名,与 `accountNo` 同值 |
|
||||
| `bankAccounts[].proofFileUrls` | Array | 有独立权限且审计成功时出现;无权限时整个字段省略 |
|
||||
| `bankAccounts[].status` | String | `PENDING`、`ACTIVE` 或 `DISABLED` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2092800000000000001/account-info/list
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"bankAccounts": [{
|
||||
"accountId": "2092800000000000011",
|
||||
"accountName": "示例旅行服务有限公司",
|
||||
"bankName": "示例银行",
|
||||
"accountNo": "6222021234567890",
|
||||
"accountNoMask": "6222021234567890",
|
||||
"proofFileUrls": ["https://files.example.com/supplier/account-proof.pdf"],
|
||||
"status": "ACTIVE",
|
||||
"isDefault": "YES"
|
||||
}],
|
||||
"updateTime": "2026-08-27 11:31:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
仅有 DRAFT 账户或没有账户时返回 `bankAccounts: []`。无 `supplier:account:proof:read` 时账号仍为完整值,但每个账户均省略 `proofFileUrls`,不是返回空数组。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
证明附件同步审计不可用时失败关闭,不返回任何附件:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395039,
|
||||
"message": "暂时无法校验资源,请稍后重试",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 要求 `supplier:view`;附件另需 `supplier:account:proof:read`。
|
||||
- 每次获准的附件读取都会先同步写安全审计,失败时整个请求失败。
|
||||
- DRAFT 初始账户不会出现在该读取接口中,应使用创建/更新响应的 `initialAccounts` 展示草稿摘要。
|
||||
|
||||
### 3. 查询收款账户详情 `GET /admin/supplier/bank-accounts/{accountId}/view`
|
||||
|
||||
**VO**: `SupplierBankAccountDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
查看单个已进入可读状态的收款账户完整信息。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `accountId` | Path | String | 是 | 正整数 ID 字符串 | 账户 ID |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `accountId` | String | 账户 ID |
|
||||
| `accountName` | String | 收款户名 |
|
||||
| `accountNo` | String | 完整银行账号 |
|
||||
| `accountNoMask` | String | 废弃兼容别名,与完整账号同值 |
|
||||
| `proofFileUrls` | Array | 有独立权限且同步审计成功时出现,否则省略 |
|
||||
| `status` / `isDefault` | String | 账户状态与默认标记 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/bank-accounts/2092800000000000011/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"accountId": "2092800000000000011",
|
||||
"accountName": "示例旅行服务有限公司",
|
||||
"accountType": "CORPORATE",
|
||||
"bankName": "示例银行",
|
||||
"accountNo": "6222021234567890",
|
||||
"accountNoMask": "6222021234567890",
|
||||
"proofFileUrls": ["https://files.example.com/supplier/account-proof.pdf"],
|
||||
"settleMode": "PREPAY",
|
||||
"invoiceType": "NONE",
|
||||
"status": "ACTIVE",
|
||||
"isDefault": "YES",
|
||||
"updateTime": "2026-08-27 11:31:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无附件权限时响应省略 `proofFileUrls`;DRAFT、已删除或不存在的账户按不可读处理,不返回草稿详情。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395001,
|
||||
"message": "供应商不存在",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 完整账号沿用基础查看权限,证明附件使用独立权限和同步审计。
|
||||
- 账户必须属于未删除供应商且状态为 PENDING、ACTIVE 或 DISABLED。
|
||||
- 无附件权限时后端读取阶段即排除附件字段,不是先读取后隐藏。
|
||||
|
||||
### 4. 查询供应商审批记录 `GET /admin/supplier/items/{supplierId}/approval-records/page`
|
||||
|
||||
**VO**: `SupplierApprovalRecordPageReqVO / PageResult<SupplierApprovalRecordRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
统一查看供应商主体与收款账户的变更历史,并区分完整新记录和不可恢复的历史掩码记录。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `page` / `limit` | Query | Integer | 否 | 正整数,`limit` 不超过 200 | 分页参数 |
|
||||
| `approvalLogId` | Query | String | 否 | 正整数 ID 字符串 | 精确筛选审批 |
|
||||
| `operationType` | Query | String | 否 | `CREATE/UPDATE/ENABLE/DISABLE/DELETE` | 操作类型 |
|
||||
| `targetType` | Query | String | 否 | `SUPPLIER/ACCOUNT` | 本次增加的目标筛选 |
|
||||
| `fieldName` / `status` | Query | String | 否 | 当前接口枚举 | 字段或状态筛选 |
|
||||
| `from` / `to` | Query | String | 否 | 必须成对,`yyyy-MM-dd HH:mm:ss` | 时间范围 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `records[].targetType` | String | `SUPPLIER` 或 `ACCOUNT` |
|
||||
| `records[].targetId` | String/null | 主体记录为 null,账户记录为账户 ID |
|
||||
| `records[].oldValue` / `newValue` | String/null | 完整中文业务摘要,附件仍按独立权限投影 |
|
||||
| `records[].valueAvailability` | String | `FULL` 或 `LEGACY_MASKED_UNRECOVERABLE` |
|
||||
| `records[].oldValueMasked` / `newValueMasked` | String/null | 废弃兼容别名 |
|
||||
| `total` / `page` / `pageSize` | Integer | 分页元数据 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2092800000000000001/approval-records/page?page=1&limit=20&targetType=ACCOUNT
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"records": [{
|
||||
"supplierId": "2092800000000000001",
|
||||
"operationType": "ENABLE",
|
||||
"targetType": "ACCOUNT",
|
||||
"targetId": "2092800000000000011",
|
||||
"oldValue": "账户状态:待审批;收款账号:6222021234567890",
|
||||
"newValue": "账户状态:已生效;收款账号:6222021234567890",
|
||||
"valueAvailability": "FULL",
|
||||
"status": "已生效",
|
||||
"operatorName": "测试管理员",
|
||||
"operatorRole": "超级管理员",
|
||||
"createTime": "2026-08-27 11:32:00"
|
||||
}],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无匹配记录返回 `records: []` 和 `total: 0`。历史记录只保存掩码且无法恢复原值时,`valueAvailability` 返回 `LEGACY_MASKED_UNRECOVERABLE`,不会猜测或拼造完整值。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "目标类型仅支持SUPPLIER或ACCOUNT",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 要求 `supplier:approval:read` 及供应商数据范围权限。
|
||||
- 证明附件只有独立权限存在且同步审计成功时才进入完整摘要。
|
||||
- 分页内主体记录和账户记录按创建时间、记录 ID 稳定排序。
|
||||
|
||||
### 5. 创建供应商注册草稿 `POST /admin/supplier/items/add`
|
||||
|
||||
**VO**: `SupplierDraftSaveReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
创建供应商草稿并可同时保存一个初始收款账户;成功响应直接回显该账户的完整账号摘要。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `fullName` | Body | String | 是 | 非空白,最长 500 字符 | 供应商全称 |
|
||||
| `taxNo` | Body | String | 是 | 6 至 64 字符 | 主体证件号 |
|
||||
| `types` | Body | Array | 否 | 草稿可为空,非空不得重复 | 供应商类型,首项成为主类型 |
|
||||
| `mainCooperation` | Body | String | 是 | 非空白 | 主要合作内容 |
|
||||
| `initialAccounts` | Body | Array | 否 | 最多 1 项 | 初始收款账户完整输入 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `supplierId` | String | 新供应商 ID |
|
||||
| `status` | String | `DRAFT` |
|
||||
| `initialAccounts[].accountId` | String | 初始账户 ID |
|
||||
| `initialAccounts[].accountNo` | String | 完整银行账号 |
|
||||
| `initialAccounts[].accountNoMask` | String | 废弃兼容别名,与完整账号同值 |
|
||||
| `initialAccounts[].status` | String | 创建时为 `DRAFT` |
|
||||
| `updateTime` | String | 并发版本时间 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"fullName": "示例旅行服务有限公司",
|
||||
"taxNo": "91350211M000100Y46",
|
||||
"types": [{"typeCode": "HOTEL"}],
|
||||
"mainCooperation": "酒店资源合作",
|
||||
"initialAccounts": [{
|
||||
"accountType": "CORPORATE",
|
||||
"bankName": "示例银行",
|
||||
"accountNo": "6222021234567890",
|
||||
"proofFileUrls": ["https://files.example.com/supplier/account-proof.pdf"],
|
||||
"settleMode": "PREPAY",
|
||||
"invoiceType": "NONE"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"supplierNo": null,
|
||||
"status": "DRAFT",
|
||||
"onboardingStage": "PROFILE_DRAFT",
|
||||
"initialAccounts": [{
|
||||
"accountId": "2092800000000000011",
|
||||
"accountNo": "6222021234567890",
|
||||
"accountNoMask": "6222021234567890",
|
||||
"status": "DRAFT"
|
||||
}],
|
||||
"updateTime": "2026-08-27 11:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
省略 `initialAccounts` 或传空数组时成功创建并返回 `initialAccounts: []`。草稿可传 `types: []`,此时详情的主类型字段为 null。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395002,
|
||||
"message": "无权执行该供应商写操作",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 仅可信 `FINANCE`、`SUPER_ADMIN` 且拥有 `supplier:create` 的身份可写;`ADMIN` 明确拒绝。
|
||||
- 初始账户最多一项,完整请求原子成功或失败,失败不留下主体或子项。
|
||||
- 新写入和新审计使用完整业务值,但普通日志、异常、Trace 和跨服务消息不得携带这些值。
|
||||
|
||||
### 6. 更新供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
增量更新供应商草稿或可变字段,并获得稳定的主类型与初始账户摘要回显。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `types` | Body | Array | 否 | 省略表示不改;空数组表示清空草稿类型 | 类型完整快照 |
|
||||
| `changeReason` | Body | String | 是 | 非空白 | 变更原因 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 乐观并发版本 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `supplierId` / `status` | String | 供应商 ID 和当前状态 |
|
||||
| `initialAccounts[].accountNo` | String | 既有初始账户完整账号 |
|
||||
| `initialAccounts[].accountNoMask` | String | 废弃兼容别名 |
|
||||
| `updateTime` | String | 更新后的并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"types": [{"typeCode": "HOTEL", "isPrimary": true}],
|
||||
"changeReason": "调整主合作类型",
|
||||
"expectedUpdateTime": "2026-08-27 11:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"status": "DRAFT",
|
||||
"onboardingStage": "PROFILE_DRAFT",
|
||||
"initialAccounts": [{
|
||||
"accountId": "2092800000000000011",
|
||||
"accountNo": "6222021234567890",
|
||||
"accountNoMask": "6222021234567890",
|
||||
"status": "DRAFT"
|
||||
}],
|
||||
"updateTime": "2026-08-27 11:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
省略 `types` 保持现有类型;草稿传 `types: []` 会清空类型并在详情返回 null 主类型。响应没有初始账户时固定返回空数组。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395014,
|
||||
"message": "数据已被他人修改,请刷新后重试",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 仅可信 `FINANCE`、`SUPER_ADMIN` 且拥有 `supplier:update` 的身份可写。
|
||||
- 非空类型集合稳定保留且恰有一个主类型;提交审批时仍要求至少一个类型。
|
||||
- 并发版本、状态或权限不满足时失败且零写入。
|
||||
|
||||
### 7. 提交供应商注册 `POST /admin/supplier/items/{supplierId}/submit`
|
||||
|
||||
**VO**: `SupplierSubmitReqVO / SupplierApprovalCommandRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
提交完整注册表单并进入审批;审批响应直接携带本次注册初始账户的完整摘要。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 草稿供应商 |
|
||||
| `fullName` / `taxNo` | Body | String | 是 | 完整注册表单约束 | 主体信息 |
|
||||
| `types` | Body | Array | 是 | 至少一项且不得重复 | 第一项为主类型 |
|
||||
| `initialAccounts` | Body | Array | 否 | 最多一项;省略可保留既有草稿账户 | 初始账户完整快照 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | 必须等于当前版本 | 并发围栏 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `approvalLogId` / `requestNo` | String | 审批记录与幂等请求号 |
|
||||
| `provider` / `approvalStatus` / `syncStatus` | String | 审批通道、结果与本地应用状态 |
|
||||
| `initialAccounts[].accountId` | String | 本次注册关联账户 ID |
|
||||
| `initialAccounts[].accountNo` | String | 完整银行账号 |
|
||||
| `initialAccounts[].accountNoMask` | String | 废弃兼容别名 |
|
||||
| `initialAccounts[].status` | String | 审批通过后为 `ACTIVE` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"fullName": "示例旅行服务有限公司",
|
||||
"taxNo": "91350211M000100Y46",
|
||||
"types": [{"typeCode": "HOTEL"}],
|
||||
"mainCooperation": "酒店资源合作",
|
||||
"licenseImageUrl": "https://files.example.com/supplier/license.jpg",
|
||||
"qualifications": [{
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"certNo": "LIC-2026-001",
|
||||
"imageUrl": "https://files.example.com/supplier/license.jpg",
|
||||
"permanentValid": true
|
||||
}],
|
||||
"expectedUpdateTime": "2026-08-27 11:35:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"approvalLogId": "2092800000000000021",
|
||||
"requestNo": "SUP-REQ-EXAMPLE",
|
||||
"provider": "LOCAL_AUTO",
|
||||
"approvalStatus": "APPROVED",
|
||||
"syncStatus": "APPLIED",
|
||||
"initialAccounts": [{
|
||||
"accountId": "2092800000000000011",
|
||||
"accountNo": "6222021234567890",
|
||||
"accountNoMask": "6222021234567890",
|
||||
"status": "ACTIVE"
|
||||
}],
|
||||
"submittedAt": "2026-08-27 11:36:00",
|
||||
"finishedAt": "2026-08-27 11:36:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有初始账户时返回 `initialAccounts: []`。提交仍必须包含非空类型和当前完整注册资料,不因草稿阶段允许空类型而放宽。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395008,
|
||||
"message": "请至少选择一个类型并设置唯一主类型",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 仅可信 `FINANCE`、`SUPER_ADMIN` 且同时拥有更新和提交权限的身份可执行。
|
||||
- 审批准备、结果应用、主体状态、账户状态和完整审计保持原有事务与幂等语义。
|
||||
- 权限、状态、摘要或并发围栏失败时不允许部分写入。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
| 场景 | 正确处理 |
|
||||
|---|---|
|
||||
| 主类型绑定 | 使用 `primaryTypeCode`;类型列表使用 `types[].isPrimary`,不要自行取第一项猜测 |
|
||||
| 完整字段 | 使用 `tax_no`、`legalRepresentativeIdNo`、`contactPhone`、`certNo`、`accountNo` |
|
||||
| 废弃别名 | `legalRepresentativeIdNoMask`、`contactPhoneMask`、`certNoMask`、`accountNoMask` 仅作旧客户端兼容 |
|
||||
| 证明附件无权限 | `proofFileUrls` 字段不存在;不要把缺字段当接口异常或空附件 |
|
||||
| 草稿账户 | 从写响应 `initialAccounts` 获取;账户读取接口只返回 PENDING/ACTIVE/DISABLED |
|
||||
| 业务失败 | HTTP 状态之外必须检查 `code`、`success`、`message` 和 `data` |
|
||||
|
||||
管理端不得把业务请求体中的身份或角色作为授权依据,也不得缓存其他管理员读取到的完整敏感值供当前会话复用。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 外部动作 | 可观察结果 |
|
||||
|---|---|
|
||||
| 创建/更新供应商 | 主体、联系人、资质、类型和初始账户在同一业务事务中成功或失败 |
|
||||
| 注册提交 | 审批结果成功应用后主体和初始账户进入可读生效状态,响应返回完整账户摘要 |
|
||||
| 新增或变更敏感值 | 后续授权读取返回与提交一致的完整业务值,唯一冲突或非法值整次失败 |
|
||||
| 失败请求 | 未认证、无权、缺参、非法状态、并发冲突和审计失败均不留下部分业务写入 |
|
||||
| 历史数据 | 可恢复历史值继续读取;只剩不可逆掩码的审批历史显式标记为不可恢复 |
|
||||
|
||||
这些是接口可观察行为;调用方不依赖具体存储结构,也不应自行维护明文/密文兼容状态。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录经 Gateway 返回 `401`。
|
||||
- `ADMIN` 可在既有查看权限与数据范围内读取完整业务值,但写入返回 `395002`。
|
||||
- `FINANCE`、`SUPER_ADMIN` 仍需同时拥有对应平台权限才能写,角色名称本身不是唯一授权条件。
|
||||
- 附件独立权限不足时字段省略;同步审计失败时请求失败,不降级泄露附件。
|
||||
- 列表 `limit=201`、缺少必填字段或非法状态返回业务失败,并保持零写入。
|
||||
- Snowflake ID 继续按字符串处理;时间格式继续为 `yyyy-MM-dd HH:mm:ss`。
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### `targetType` / `valueAvailability`
|
||||
|
||||
| 字段 | 值 | 中文与说明 |
|
||||
|---|---|---|
|
||||
| `targetType` | `SUPPLIER` | 供应商主体变更,`targetId` 为 null |
|
||||
| `targetType` | `ACCOUNT` | 收款账户变更,`targetId` 为账户 ID |
|
||||
| `valueAvailability` | `FULL` | 完整业务前后值可用 |
|
||||
| `valueAvailability` | `LEGACY_MASKED_UNRECOVERABLE` | 历史只剩不可逆掩码,不推测原值 |
|
||||
|
||||
### 账户可读状态
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `PENDING` | 待审批 | 账户可在管理端读取 |
|
||||
| `ACTIVE` | 已生效 | 正常可用账户 |
|
||||
| `DISABLED` | 已停用 | 保留只读历史信息 |
|
||||
| `DRAFT` | 草稿 | 不进入账户列表和账户详情 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 税号、法人证件号、电话、证照号 | 主要返回掩码或混合口径 | 授权读取返回完整值 |
|
||||
| `accountNo` | 可能为空或仅依赖 `accountNoMask` | 返回完整账号 |
|
||||
| `*Mask` | 表示掩码 | 废弃兼容别名,暂与完整值同值 |
|
||||
| `proofFileUrls` | 权限语义分散 | 独立权限 + 同步审计;无权时字段省略 |
|
||||
| `types[].isPrimary` | 主类型回显不稳定 | 明确 Boolean 标记 |
|
||||
| `primaryTypeCode/Name` | 不存在 | 根对象直接返回,空类型草稿为 null |
|
||||
| `initialAccounts` | 写/提交响应摘要不完整 | 返回账户 ID、完整账号和状态 |
|
||||
| 审批记录 | 主体与账户口径分散 | 统一目标类型、目标 ID、完整值和可用性 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 编辑页初始化 | 可能需要用掩码字段或猜测主类型 | 可直接绑定完整值与权威主类型 |
|
||||
| 附件读取 | 容易把空值与无权混淆 | 无权省略字段,审计失败整体失败 |
|
||||
| 草稿账户展示 | 读取接口与草稿状态语义不清 | 写响应展示草稿摘要,读取接口只展示可读状态 |
|
||||
| 历史审批 | 无法区分完整值与不可逆掩码 | 通过 `valueAvailability` 明确区分 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。既有字段名保留,废弃别名在兼容窗口内继续返回。
|
||||
- **前端是否必须同步上线**: 建议尽快。应切换到无 `Mask` 字段、接入主类型字段,并正确处理 `proofFileUrls` 缺失。
|
||||
- **前端 workaround 清理点**: 删除自行猜主类型、对 `accountNoMask` 二次掩码、把附件缺字段强制转空数组等兼容逻辑。
|
||||
- **敏感展示责任**: 完整值只在已授权业务页面按最小必要范围展示,不写入前端日志、埋点、错误上报或持久缓存。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 管理后台供应商基本信息、结算账户、注册提交与审批记录消费契约。
|
||||
- **零影响**:
|
||||
- 不新增或修改 Gateway 路由。
|
||||
- 不修改角色、菜单、按钮或数据范围定义。
|
||||
- 不修改小程序、C 端、订单、产品、车队接口。
|
||||
- 不修改文件上传接口、Redis、MQ 或跨服务 DTO。
|
||||
- 不允许 DRAFT 账户通过账户查询接口提前暴露。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
最终后端提交 `aa735fb8531a3463b2b460965245e07ed8697775` 已通过 Deploy Panel 任务 `7a48028c` 发布 `hl-resource-service` 双实例,真实 TEST 身份经 Gateway 验证:
|
||||
|
||||
```text
|
||||
历史兼容:完整值与数据库一致,税号关键字搜索命中 ✓
|
||||
SUPER_ADMIN:创建草稿、详情完整值、注册审批、账号及附件审计读取 ✓
|
||||
ADMIN:详情与完整账号可读,proofFileUrls 省略,写入拒绝 395002 ✓
|
||||
未认证 401、缺参 400、limit=201 为 400、非法状态 400 ✓
|
||||
新写主体/联系人/资质/账户及新审计均为完整值,失败请求零写入 ✓
|
||||
合成供应商完成精确清理:17 张相关表检查、业务残留 0、失败标记残留 0 ✓
|
||||
验收会话主动失效,敏感读取操作审计按审计规则保留 ✓
|
||||
```
|
||||
|
||||
获批真实账号没有 `FINANCE` 角色,因此没有伪造该身份;`FINANCE` 与 `SUPER_ADMIN` 的服务端写权限同构由后端自动化测试覆盖,真实 TEST 写入使用 `SUPER_ADMIN`,并用真实 `ADMIN` 验证拒绝路径。
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|---|---|---|---|
|
||||
| #6478 | #6436 | 完整字段、主类型、权限、迁移与兼容读写 | ✅ 主交付 |
|
||||
| #6483 | #6481 | 修复 TEST 排序规则下回填精确比较 | ✅ 补充修复 |
|
||||
| #6486 | #6484 | 支持历史 Unicode 税号回填 | ✅ 补充修复 |
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [主工单 #6436](https://git.1814.love:8443/wx/HL/issues/6436)
|
||||
- [主 PR #6478](https://git.1814.love:8443/wx/HL/pulls/6478)
|
||||
- [补充工单 #6481](https://git.1814.love:8443/wx/HL/issues/6481) / [PR #6483](https://git.1814.love:8443/wx/HL/pulls/6483)
|
||||
- [补充工单 #6484](https://git.1814.love:8443/wx/HL/issues/6484) / [PR #6486](https://git.1814.love:8443/wx/HL/pulls/6486)
|
||||
- 后端详细 API 说明:`docs/supplier/API-CHANGE-6436.html`
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#6436](https://git.1814.love:8443/wx/HL/issues/6436)
|
||||
- **PR**: [#6478](https://git.1814.love:8443/wx/HL/pulls/6478)
|
||||
- **最终 TEST 提交**: [aa735fb8](https://git.1814.love:8443/wx/HL/commit/aa735fb8531a3463b2b460965245e07ed8697775)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @lc
|
||||
在新工单中引用
屏蔽一个用户