16 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 | 6304 | 供应商资质增加永久有效与到期剩余天数 | admin | lc(GIT) | 修改接口 | deployed | verified | pending | PR #6342 与补充修复 PR #6349 均已合并 dev-v3,TEST 最终部署提交为 af3a7bae5。真实 SUPER_ADMIN 经 Gateway 已验证显式永久有效、395042 冲突零写入、历史省略兼容和数据清理;未认证请求仍返回标准 401 业务响应。管理端尚待适配,frontend_status 保持 pending。 | 2026-08-25 | dev-v3 |
供应商资质增加永久有效与到期剩余天数
供应商资质的有效期输入改为显式的“永久有效”契约;非永久有效资质必须提交到期日期。详情按上海时区自然日返回有符号剩余天数,便于页面直接展示“剩余 N 天”“今日到期”或“已过期 N 天”。
服务:
hl-resource-servicePR: #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[] 项内。
典型请求:
{
"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"
}
]
}
成功响应:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092152294748352514",
"supplierNo": null,
"status": "DRAFT",
"onboardingStage": "PROFILE_DRAFT",
"initialAccounts": [],
"updateTime": "2026-08-25 15:21:00"
}
}
永久有效请求只需将资质项改为:
{
"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。
将有限期资质切换为永久有效:
{
"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"
}
成功响应:
{
"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
使用场景:提交完整供应商表单。资质有效期组合规则与创建草稿完全一致。
典型请求:
{
"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"
}
成功响应:
{
"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,无请求体。
GET /admin/supplier/items/2092152294748352514/basic-info/view
Authorization: Bearer <有效管理端令牌>
有限期资质成功响应(假设上海日期为 2026-08-25):
{
"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"
}
}
永久有效资质的差异字段为:
{
"expiryDate": null,
"permanentValid": true,
"daysUntilExpiry": null,
"validityStatus": "VALID",
"validityStatusName": "有效",
"expired": false
}
四、错误响应
新增业务错误码:
| 业务码 | 消息 | 触发条件 | 写入结果 |
|---|---|---|---|
395042 |
永久有效标记与到期日期不一致 | permanentValid=true 且到期日非空,或 permanentValid=false 且到期日为空 |
整笔请求失败,零写入 |
异常请求片段:
{
"qualifications": [
{
"qualType": "BUSINESS_LICENSE",
"permanentValid": true,
"expiryDate": "2027-08-25"
}
]
}
错误响应:
{
"code": 395042,
"message": "永久有效标记与到期日期不一致",
"success": false,
"data": null
}
既有错误码继续有效,包括资质类型无效、证照编号无效、必备资质缺失、资质过期和并发版本冲突。
五、修改前后对比
字段级对比
| 字段 | 修改前 | 修改后 |
|---|---|---|
写入 qualifications[].permanentValid |
无显式写入契约,只能由到期日是否为空隐式表达 | 可显式提交;省略时仍兼容历史请求 |
响应 qualifications[].daysUntilExpiry |
无 | 有限期返回有符号整数,永久有效返回 null |
响应 qualifications[].isRequired |
供应商类型是否要求此资质 | 语义不变,继续只读返回 |
行为级对比
| 行为 | 修改前 | 修改后 |
|---|---|---|
| 永久有效输入 | 仅通过不传到期日表达 | 优先显式提交 permanentValid=true;旧请求仍兼容 |
| 非永久有效输入 | 提交到期日 | 提交 permanentValid=false 与到期日 |
| ���期提示 | 调用方自行按日期计算 | 直接读取 daysUntilExpiry,无需自行处理时区和跨日 |
六、管理端接入事项
- 将资质证照区域的“是否必备”编辑控件改为“永久有效”;提交字段必须使用
permanentValid,不得复用或改写isRequired。 permanentValid=true时清空并禁用expiryDate;false时要求用户选择到期日。- 非永久有效时在到期日期附近显示后端返回的
daysUntilExpiry:正数表示剩余天数,零表示今日到期,负数表示已过期天数。 - 永久有效时不显示到期天数。
- 编辑既有数据以详情返回的
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 test46 项零失败。 - 管理端适配不属于本后端工单,
frontend_status保持pending,不把后端契约验证误记为前端已完成。
九、撤回
- 从最新
dev-v3创建回退分支,先执行git revert -m 1 --no-edit af3a7bae5f3d19b97a7ad52daf31bb8fd18d5676撤回补充修复,再执行git revert -m 1 --no-edit c5b1a84aea6b1c7beb7a626131376355008e662c撤回原实现;分别审查并经独立 PR 合入。 - 重新构建并滚动部署
hl-resource-service;本次无数据库、配置、Redis 或 MQ 变更,无需执行 DDL、DML 或数据恢复。 - 回退前确认管理端不再提交
permanentValid或读取daysUntilExpiry;旧字段expiryDate、expired和isRequired以及存量数据无需迁移。 - 回退后经 Gateway 复测草稿创建、更新、提交、详情、未认证和永久/有限期边界,并确认资质必备规则与证照脱敏保持原行为。