docs(api): 发布供应商资质有效期契约
这个提交包含在:
@@ -0,0 +1,419 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6304"
|
||||
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 #6342 与补充修复 PR #6349 均已合并 dev-v3,TEST 最终部署提交为 af3a7bae5。真实 SUPER_ADMIN 经 Gateway 已验证显式永久有效、395042 冲突零写入、历史省略兼容和数据清理;未认证请求仍返回标准 401 业务响应。管理端尚待适配,frontend_status 保持 pending。"
|
||||
updated_at: "2026-08-25"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商资质增加永久有效与到期剩余天数
|
||||
|
||||
供应商资质的有效期输入改为显式的“永久有效”契约;非永久有效资质必须提交到期日期。详情按上海时区自然日返回有符号剩余天数,便于页面直接展示“剩余 N 天”“今日到期”或“已过期 N 天”。
|
||||
|
||||
> **服务**: `hl-resource-service`
|
||||
> **PR**: #6342、补充修复 #6349
|
||||
> **Issue**: #6304、补充修复 #6347
|
||||
> **日期**: 2026-08-25
|
||||
> **影响范围**: 管理端供应商新建、编辑、提交审批和基本信息详情的资质证照区域
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 资质写入字段新增 `permanentValid`;`true` 时不得提交 `expiryDate`,`false` 时必须提交 `expiryDate`。
|
||||
- 详情资质项新增 `daysUntilExpiry`;永久有效时返回 `null`,有限期按 `Asia/Shanghai` 自然日返回正数、零或负数。
|
||||
- 既有只读字段 `isRequired` 仍表示“该供应商类型是否要求此资质”,没有改义,也不能作为永久有效标识。
|
||||
|
||||
## 一、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变化 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 创建供应商注册草稿 | POST | `/admin/supplier/items/add` | `qualifications[].permanentValid` 可写 |
|
||||
| 2 | 更新供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | `qualifications[].permanentValid` 可写,并支持增量保留语义 |
|
||||
| 3 | 提交供应商注册审批 | POST | `/admin/supplier/items/{supplierId}/submit` | `qualifications[].permanentValid` 可写 |
|
||||
| 4 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | `qualifications[]` 新增 `daysUntilExpiry` |
|
||||
|
||||
统一响应仍为 `Result<T>`。业务校验失败可能仍返回 HTTP 200,调用方必须同时判断 `code`、`success` 和 `data`。
|
||||
|
||||
## 二、字段契约
|
||||
|
||||
### 2.1 写入字段
|
||||
|
||||
| 字段 | 位置 | JSON 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `permanentValid` | `qualifications[]` | Boolean | 条件必填 | `true` 表示永久有效,`false` 表示存在到期日;历史调用方可省略并触发兼容推导 |
|
||||
| `expiryDate` | `qualifications[]` | String / null | 条件必填 | 格式 `yyyy-MM-dd`;仅 `permanentValid=false` 时必须有值 |
|
||||
|
||||
合法组合:
|
||||
|
||||
| 场景 | `permanentValid` | `expiryDate` | 结果 |
|
||||
|---|---:|---|---|
|
||||
| 永久有效 | `true` | 省略或 `null` | 接受,到期日为空 |
|
||||
| 有到期日 | `false` | `yyyy-MM-dd` | 接受,保存该到期日 |
|
||||
| 历史永久请求 | 省略 | 省略或 `null` | 接受,兼容推导为 `true` |
|
||||
| 历史有限期请求 | 省略 | `yyyy-MM-dd` | 接受,兼容推导为 `false` |
|
||||
| 矛盾组合 | `true` | `yyyy-MM-dd` | 拒绝,业务码 `395042` |
|
||||
| 缺少到期日 | `false` | 省略或 `null` | 拒绝,业务码 `395042` |
|
||||
|
||||
更新既有资质时还有以下增量规则:
|
||||
|
||||
- `permanentValid` 与 `expiryDate` 均省略:保留当前有效期,不做修改。
|
||||
- 仅提交非空 `expiryDate`:兼容推导 `permanentValid=false`。
|
||||
- 显式提交 `permanentValid=true` 且不提交到期日:切换为永久有效并清空原到期日。
|
||||
- 新增资质且两字段均省略:按历史兼容口径创建为永久有效。
|
||||
|
||||
### 2.2 详情响应字段
|
||||
|
||||
| 字段 | JSON 类型 | 空值 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `permanentValid` | Boolean | 不为空 | 由 `expiryDate` 是否为空动态推导 |
|
||||
| `daysUntilExpiry` | Integer / null | 永久有效时为 `null` | 到期日相对上海业务当日的有符号自然日差 |
|
||||
| `expired` | Boolean | 不为空 | 仅当 `daysUntilExpiry < 0` 时为 `true` |
|
||||
| `isRequired` | Boolean | 不为空 | 历史只读兼容字段;继续表示供应商类型规则是否要求该资质 |
|
||||
|
||||
以 2026-08-25(上海日期)为例:
|
||||
|
||||
| `expiryDate` | `permanentValid` | `daysUntilExpiry` | `expired` |
|
||||
|---|---:|---:|---:|
|
||||
| `null` | `true` | `null` | `false` |
|
||||
| `2026-08-26` | `false` | `1` | `false` |
|
||||
| `2026-08-25` | `false` | `0` | `false` |
|
||||
| `2026-08-24` | `false` | `-1` | `true` |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 3.1 创建供应商注册草稿 `POST /admin/supplier/items/add`
|
||||
|
||||
使用场景:新建供应商草稿时保存资质证照。`permanentValid` 位于每个 `qualifications[]` 项内。
|
||||
|
||||
典型请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"fullName": "示例酒店管理有限公司",
|
||||
"shortName": "示例酒店",
|
||||
"taxNo": "91110108MA00000001",
|
||||
"types": [
|
||||
{
|
||||
"typeCode": "HOTEL"
|
||||
}
|
||||
],
|
||||
"mainCooperation": "酒店住宿服务",
|
||||
"qualifications": [
|
||||
{
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"certNo": "LIC-2026-0001",
|
||||
"imageUrl": "https://files.example.test/supplier/license-1.jpg",
|
||||
"permanentValid": false,
|
||||
"expiryDate": "2027-08-25"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092152294748352514",
|
||||
"supplierNo": null,
|
||||
"status": "DRAFT",
|
||||
"onboardingStage": "PROFILE_DRAFT",
|
||||
"initialAccounts": [],
|
||||
"updateTime": "2026-08-25 15:21:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
永久有效请求只需将资质项改为:
|
||||
|
||||
```json
|
||||
{
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"certNo": "LIC-2026-PERM",
|
||||
"imageUrl": "https://files.example.test/supplier/license-perm.jpg",
|
||||
"permanentValid": true,
|
||||
"expiryDate": null
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 更新供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
使用场景:编辑既有资质或切换永久有效状态。`qualifications` 一旦传入,仍代表资质集合的完整当前快照;既有项必须携带 `qualificationId` 和该项的 `expectedUpdateTime`。
|
||||
|
||||
将有限期资质切换为永久有效:
|
||||
|
||||
```json
|
||||
{
|
||||
"qualifications": [
|
||||
{
|
||||
"qualificationId": "2092152300000000001",
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"certNo": "LIC-2026-0001",
|
||||
"imageUrl": "https://files.example.test/supplier/license-1.jpg",
|
||||
"permanentValid": true,
|
||||
"expiryDate": null,
|
||||
"expectedUpdateTime": "2026-08-25 15:21:00"
|
||||
}
|
||||
],
|
||||
"changeReason": "营业执照变更为永久有效",
|
||||
"expectedUpdateTime": "2026-08-25 15:21:00"
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092152294748352514",
|
||||
"supplierNo": null,
|
||||
"status": "DRAFT",
|
||||
"onboardingStage": "PROFILE_DRAFT",
|
||||
"initialAccounts": [],
|
||||
"updateTime": "2026-08-25 15:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
若本次不修改既有资质的有效期,可同时省略该项的 `permanentValid` 和 `expiryDate`;不要回传未经确认的默认值。
|
||||
|
||||
### 3.3 提交供应商注册审批 `POST /admin/supplier/items/{supplierId}/submit`
|
||||
|
||||
使用场景:提交完整供应商表单。资质有效期组合规则与创建草稿完全一致。
|
||||
|
||||
典型请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"fullName": "示例酒店管理有限公司",
|
||||
"shortName": "示例酒店",
|
||||
"taxNo": "91110108MA00000001",
|
||||
"types": [
|
||||
{
|
||||
"typeCode": "HOTEL"
|
||||
}
|
||||
],
|
||||
"mainCooperation": "酒店住宿服务",
|
||||
"licenseImageUrl": "https://files.example.test/supplier/license-1.jpg",
|
||||
"qualifications": [
|
||||
{
|
||||
"qualificationId": "2092152300000000001",
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"certNo": "LIC-2026-0001",
|
||||
"imageUrl": "https://files.example.test/supplier/license-1.jpg",
|
||||
"permanentValid": false,
|
||||
"expiryDate": "2027-08-25"
|
||||
}
|
||||
],
|
||||
"expectedUpdateTime": "2026-08-25 15:35:00"
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"approvalLogId": "2092152400000000001",
|
||||
"requestNo": "SUP-PROFILE-20260825-0001",
|
||||
"provider": "LOCAL_AUTO",
|
||||
"approvalStatus": "APPROVED",
|
||||
"spNo": null,
|
||||
"spStatus": null,
|
||||
"syncStatus": "APPLIED",
|
||||
"submittedAt": "2026-08-25 15:36:00",
|
||||
"finishedAt": "2026-08-25 15:36:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
使用场景:展示供应商详情的资质证照。请求需要有效的管理端 `Authorization`,无请求体。
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2092152294748352514/basic-info/view
|
||||
Authorization: Bearer <有效管理端令牌>
|
||||
```
|
||||
|
||||
有限期资质成功响应(假设上海日期为 2026-08-25):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092152294748352514",
|
||||
"supplierNo": null,
|
||||
"fullName": "示例酒店管理有限公司",
|
||||
"shortName": "示例酒店",
|
||||
"tax_no": "9111********0001",
|
||||
"legalRepresentative": null,
|
||||
"contactPhoneMask": null,
|
||||
"establishDate": null,
|
||||
"registeredCapital": null,
|
||||
"businessScope": null,
|
||||
"staffScale": null,
|
||||
"mainCooperation": "酒店住宿服务",
|
||||
"status": "DRAFT",
|
||||
"creditLevel": "B",
|
||||
"totalScore": null,
|
||||
"types": [
|
||||
{
|
||||
"typeCode": "HOTEL",
|
||||
"typeName": "酒店"
|
||||
}
|
||||
],
|
||||
"contacts": [],
|
||||
"qualifications": [
|
||||
{
|
||||
"qualificationId": "2092152300000000001",
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"qualTypeName": "营业执照",
|
||||
"certNoMask": "LIC****0001",
|
||||
"imageUrl": "https://files.example.test/supplier/license-1.jpg",
|
||||
"expiryDate": "2026-08-26",
|
||||
"permanentValid": false,
|
||||
"daysUntilExpiry": 1,
|
||||
"validityStatus": "VALID",
|
||||
"validityStatusName": "有效",
|
||||
"isRequired": true,
|
||||
"expired": false,
|
||||
"updateTime": "2026-08-25 15:21:00"
|
||||
}
|
||||
],
|
||||
"updateTime": "2026-08-25 15:21:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
永久有效资质的差异字段为:
|
||||
|
||||
```json
|
||||
{
|
||||
"expiryDate": null,
|
||||
"permanentValid": true,
|
||||
"daysUntilExpiry": null,
|
||||
"validityStatus": "VALID",
|
||||
"validityStatusName": "有效",
|
||||
"expired": false
|
||||
}
|
||||
```
|
||||
|
||||
## 四、错误响应
|
||||
|
||||
新增业务错误码:
|
||||
|
||||
| 业务码 | 消息 | 触发条件 | 写入结果 |
|
||||
|---:|---|---|---|
|
||||
| `395042` | 永久有效标记与到期日期不一致 | `permanentValid=true` 且到期日非空,或 `permanentValid=false` 且到期日为空 | 整笔请求失败,零写入 |
|
||||
|
||||
异常请求片段:
|
||||
|
||||
```json
|
||||
{
|
||||
"qualifications": [
|
||||
{
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"permanentValid": true,
|
||||
"expiryDate": "2027-08-25"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
错误响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395042,
|
||||
"message": "永久有效标记与到期日期不一致",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
既有错误码继续有效,包括资质类型无效、证照编号无效、必备资质缺失、资质过期和并发版本冲突。
|
||||
|
||||
## 五、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 写入 `qualifications[].permanentValid` | 无显式写入契约,只能由到期日是否为空隐式表达 | 可显式提交;省略时仍兼容历史请求 |
|
||||
| 响应 `qualifications[].daysUntilExpiry` | 无 | 有限期返回有符号整数,永久有效返回 `null` |
|
||||
| 响应 `qualifications[].isRequired` | 供应商类型是否要求此资质 | 语义不变,继续只读返回 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 永久有效输入 | 仅通过不传到期日表达 | 优先显式提交 `permanentValid=true`;旧请求仍兼容 |
|
||||
| 非永久有效输入 | 提交到期日 | 提交 `permanentValid=false` 与到期日 |
|
||||
| 到期提示 | 调用方自行按日期计算 | 直接读取 `daysUntilExpiry`,无需自行处理时区和跨日 |
|
||||
|
||||
## 六、管理端接入事项
|
||||
|
||||
1. 将资质证照区域的“是否必备”编辑控件改为“永久有效”;提交字段必须使用 `permanentValid`,不得复用或改写 `isRequired`。
|
||||
2. `permanentValid=true` 时清空并禁用 `expiryDate`;`false` 时要求用户选择到期日。
|
||||
3. 非永久有效时在到期日期附近显示后端返回的 `daysUntilExpiry`:正数表示剩余天数,零表示今日到期,负数表示已过期天数。
|
||||
4. 永久有效时不显示到期天数。
|
||||
5. 编辑既有数据以详情返回的 `permanentValid` 回显;不要从 `isRequired` 推导。
|
||||
|
||||
## 七、兼容性与影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。旧客户端省略 `permanentValid` 时仍按 `expiryDate` 兼容推导。
|
||||
- **前端是否必须同步上线**: 是。当前界面尚未提交新字段,也未展示剩余天数。
|
||||
- **前端 workaround 清理点**: 若页面存在本地到期天数计算,改为直接消费 `daysUntilExpiry`。
|
||||
- 不新增接口、Gateway 路由、权限点、数据库迁移、配置、Redis 或 MQ 契约。
|
||||
- 不改变供应商状态机、审批语义、资质必备规则、证照编号脱敏、软删除和数据范围。
|
||||
|
||||
## 八、TEST 验证证据
|
||||
|
||||
- 原实现 PR #6342 与补充修复 PR #6349 均已合并;远端精确部署分支 `deploy/6347-supplier-qualification-null-expiry` 的 HEAD 与最终 `dev-v3` 合并提交 `af3a7bae5f3d19b97a7ad52daf31bb8fd18d5676` 完全一致,TEST 双实例健康。
|
||||
- 真实 `SUPER_ADMIN` 经 Gateway 将既有有限期资质切换为永久有效后,更新成功;重新查询返回 `expiryDate=null`、`permanentValid=true`、`daysUntilExpiry=null`、`expired=false`、`validityStatus=VALID`。
|
||||
- 同一资质提交 `permanentValid=true` 与非空 `expiryDate` 时返回业务码 `395042`、消息“永久有效标记与到期日期不一致”;失败前后供应商和资质的 `updateTime` 均保持 `2026-08-25 16:19:20`,证明零写入。
|
||||
- 历史客户端省略 `permanentValid`、仅提交 `expiryDate=2026-08-26` 时更新成功;详情兼容返回 `permanentValid=false`、`daysUntilExpiry=1`、`expired=false`。
|
||||
- 本工单创建的 DRAFT 测试供应商已通过业务删除接口清理;按精确测试名称分页查询 `total=0`,按原 ID 查询返回 `395001 供应商不存在`。
|
||||
- 未认证访问供应商入口返回业务码 `401`、`success=false`、`data=null`,证明请求仍经 Gateway 认证门禁。
|
||||
- 原实现定向 80 项零失败、Resource 全量 2058 项零失败(38 项条件跳过);补充修复定向 46 项零失败、Resource 全量 2059 项零失败(38 项条件跳过)。离线 HTML 自检通过,文档仓库 `npm test` 46 项零失败。
|
||||
- 管理端适配不属于本后端工单,`frontend_status` 保持 `pending`,不把后端契约验证误记为前端已完成。
|
||||
|
||||
## 九、撤回
|
||||
|
||||
1. 从最新 `dev-v3` 创建回退分支,先执行 `git revert -m 1 --no-edit af3a7bae5f3d19b97a7ad52daf31bb8fd18d5676` 撤回补充修复,再执行 `git revert -m 1 --no-edit c5b1a84aea6b1c7beb7a626131376355008e662c` 撤回原实现;分别审查并经独立 PR 合入。
|
||||
2. 重新构建并滚动部署 `hl-resource-service`;本次无数据库、配置、Redis 或 MQ 变更,无需执行 DDL、DML 或数据恢复。
|
||||
3. 回退前确认管理端不再提交 `permanentValid` 或读取 `daysUntilExpiry`;旧字段 `expiryDate`、`expired` 和 `isRequired` 以及存量数据无需迁移。
|
||||
4. 回退后经 Gateway 复测草稿创建、更新、提交、详情、未认证和永久/有限期边界,并确认资质必备规则与证照脱敏保持原行为。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **Issue**: [#6304](https://git.1814.love:8443/wx/HL/issues/6304)
|
||||
- **PR**: [#6342](https://git.1814.love:8443/wx/HL/pulls/6342)
|
||||
- **合并提交**: [c5b1a84ae](https://git.1814.love:8443/wx/HL/commit/c5b1a84aea6b1c7beb7a626131376355008e662c)
|
||||
- **补充 Issue**: [#6347](https://git.1814.love:8443/wx/HL/issues/6347)
|
||||
- **补充 PR**: [#6349](https://git.1814.love:8443/wx/HL/pulls/6349)
|
||||
- **最终部署提交**: [af3a7bae5](https://git.1814.love:8443/wx/HL/commit/af3a7bae5f3d19b97a7ad52daf31bb8fd18d5676)
|
||||
- **后端负责人**: @lc
|
||||
在新工单中引用
屏蔽一个用户