18 KiB
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 | 6397 | 供应商注册合同聚合信息 | admin | lc(GIT) | 修改接口 | deployed | verified | pending | mmg | f2a4200d | 2026-08-26 | PR #6405 已合并 dev-v3;Deploy Panel 任务 af3f205b 成功发布 Resource 双实例,合同接口已完成 23 项真实 Gateway 验收。TEST 工作副本存在服务器本地提交导致精确提交回读仍被阻塞,后端工单保持开启;本条只交接已经实测存在的接口契约。2026-08-26 晚 PR #6450 修正:响应 contracts[].amount 由 Number 改为 String(金额全站统一字符串输出),前端 f2a4200d 按 Number 集成处需改为按字符串解析,改完请回填 frontend_status。 | 2026-08-26 | dev-v3 |
🔧 供应商注册合同聚合信息
供应商创建草稿、提交注册和基础信息详情现统一支持合同完整快照。管理端应把“合同信息”放在“资质证照”之后、现有账户区域之前,并把原“初始账户”展示标题改为“结算信息”。
展示名调整不改变接口字段:原请求字段仍为 initialAccounts,不得改成 settlementInfo 或其他名称。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变化 |
|---|---|---|---|---|
| 1 | 创建供应商注册草稿 | POST | /admin/supplier/items/add |
请求可选增加 contracts 完整集合 |
| 2 | 提交供应商注册 | POST | /admin/supplier/items/{supplierId}/submit |
请求可选增加 contracts 完整快照 |
| 3 | 查询供应商基本信息 | GET | /admin/supplier/items/{supplierId}/basic-info/view |
响应增加 contracts 列表 |
统一响应均为 Result<T>。业务失败可能仍为 HTTP 200,调用方必须同时判断 code、success、message 和 data。
⚠️ 关键变化(2026-08-26 晚,PR #6450 同日修正)
- 响应
contracts[].amount由 Number 改为 String:此前(f2a4200d 验收时)响应示例为"amount": 1200.50,现实际输出"amount": "1200.50",与全站金额字段(contractId之外的金额一律字符串)对齐,防 JavaScript 浮点精度丢失。 - 请求侧不变:
contracts[].amount请求仍按 Number 传(字符串同值也可被兼容解析),无需改表单提交。 - 前端处理:详情/列表展示处把 amount 当字符串渲染即可,参与运算前
Number(...)转换。
三、接口详情
三个接口的合同字段完全一致,统一在「公共合同字段」约定;各接口的使用场景、请求/响应示例与错误码分节详述。
公共合同字段
请求字段 contracts[]
| 字段 | 类型 | 创建必填 | 提交既有项必填 | 约束与说明 |
|---|---|---|---|---|
contractId |
String | 否,且禁止传入 | 是 | 正整数 ID 字符串;必须属于当前供应商,同一快照不得重复 |
contractName |
String | 是 | 是 | 非空白,最长 500 字符 |
contractNo |
String | 否 | 否 | 最长 100 字符 |
contractType |
String | 是 | 是 | FRAME、SINGLE_TRIP、PURCHASE |
signDate |
String | 否 | 否 | yyyy-MM-dd |
startDate |
String | 是 | 是 | yyyy-MM-dd |
endDate |
String | 是 | 是 | yyyy-MM-dd,不得早于 startDate |
amount |
Number | 否 | 否 | 大于等于 0,最多 10 位整数和 2 位小数 |
pricingMode |
String | 否 | 否 | 计价方式说明,最长 100 字符 |
settleCycle |
String | 否 | 否 | 结算周期说明,最长 32 字符 |
status |
String | 是 | 是 | 写接口允许 DRAFT、ACTIVE、EXPIRED |
scanFileUrl |
String | 否 | 否 | 合同扫描件永久地址,最长 1000 字符 |
remark |
String | 否 | 否 | 最长 500 字符 |
响应字段 contracts[]
详情返回上述全部业务字段,并额外返回:
| 字段 | 类型 | 说明 |
|---|---|---|
contractId |
String | 合同 ID,始终按字符串处理,不得转 JavaScript Number |
updateTime |
String | 合同当前版本,格式 yyyy-MM-dd HH:mm:ss |
响应中的 amount 为 String(如 "1200.50",PR #6450 起),请求中的 amount 仍按 Number 传。
历史数据可能返回只读状态 TERMINATED;创建和提交请求不得发送该状态。
1. 创建供应商注册草稿 POST /admin/supplier/items/add
POST /admin/supplier/items/add
使用场景与边界
contracts可省略、为null或空数组,旧客户端行为不变。- 非空时最多 100 项,合同与供应商主体、资质和
initialAccounts一起成功或一起失败。 - 创建请求中的每个合同都是新合同,禁止携带
contractId。 - 写入仍要求现有供应商创建权限;仅可信
FINANCE、SUPER_ADMIN且拥有对应平台权限的身份可执行。
典型请求
POST /admin/supplier/items/add
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"fullName": "示例供应商有限公司",
"shortName": "示例供应商",
"taxNo": "91350211M000100Y46",
"types": [
{
"typeCode": "SCENIC"
}
],
"mainCooperation": "景区资源合作",
"licenseImageUrl": "https://files.example.com/license.png",
"qualifications": [
{
"qualType": "BUSINESS_LICENSE",
"certNo": "LIC-2026-001",
"imageUrl": "https://files.example.com/license.png",
"permanentValid": true
}
],
"contracts": [
{
"contractName": "2026 年度框架合同",
"contractNo": "HT-2026-001",
"contractType": "FRAME",
"signDate": "2026-08-26",
"startDate": "2026-09-01",
"endDate": "2027-08-31",
"amount": 1200.50,
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"status": "DRAFT",
"scanFileUrl": "https://files.example.com/contracts/HT-2026-001.pdf",
"remark": "年度合作"
}
],
"initialAccounts": []
}
成功响应
{
"code": 200,
"message": "成功",
"data": {
"supplierId": "2090300000000063970",
"supplierNo": null,
"status": "DRAFT",
"onboardingStage": "PROFILE_DRAFT",
"initialAccounts": [],
"updateTime": "2026-08-26 11:10:00"
},
"success": true
}
失败响应:创建携带合同 ID
{
"code": 400,
"message": "创建草稿不能携带合同ID",
"data": null,
"success": false
}
失败时不会留下供应商主体或部分合同。
2. 提交供应商注册 POST /admin/supplier/items/{supplierId}/submit
POST /admin/supplier/items/{supplierId}/submit
使用场景与快照语义
contracts省略或为null:本次不处理合同,保留草稿当前合同。contracts: []:明确清空当前全部合同。- 非空数组:作为完整快照;带
contractId的项覆盖当前合同,不带 ID 的项新增,当前已有但数组中遗漏的合同删除。 - 带 ID 的合同必须属于路径中的供应商;不属于当前供应商、重复 ID、非法枚举、负金额或日期逆序均失败。
expectedUpdateTime仍是供应商聚合并发版本;发生并发修改时调用方应刷新详情后重新组装完整表单。- 合同快照会进入本次审批资料,但提交注册不会自动改写合同自身的
status。
典型请求
POST /admin/supplier/items/2090300000000063970/submit
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"fullName": "示例供应商有限公司",
"shortName": "示例供应商",
"taxNo": "91350211M000100Y46",
"types": [
{
"typeCode": "SCENIC"
}
],
"mainCooperation": "景区资源合作",
"licenseImageUrl": "https://files.example.com/license.png",
"qualifications": [
{
"qualType": "BUSINESS_LICENSE",
"certNo": "LIC-2026-001",
"imageUrl": "https://files.example.com/license.png",
"permanentValid": true
}
],
"contracts": [
{
"contractId": "2090300000000063971",
"contractName": "2026 年度框架合同",
"contractNo": "HT-2026-001",
"contractType": "FRAME",
"signDate": "2026-08-26",
"startDate": "2026-09-01",
"endDate": "2027-08-31",
"amount": "1200.50",
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"status": "ACTIVE",
"scanFileUrl": "https://files.example.com/contracts/HT-2026-001.pdf",
"remark": "提交审批"
}
],
"initialAccounts": [],
"expectedUpdateTime": "2026-08-26 11:10:00"
}
成功响应
{
"code": 200,
"message": "成功",
"data": {
"approvalLogId": "2090300000000063972",
"requestNo": "SUP-REQ-7b8c9d00112233445566778899aabbccddeeff00112233445566778899aabbcc",
"provider": "LOCAL_AUTO",
"approvalStatus": "APPROVED",
"spNo": null,
"spStatus": null,
"syncStatus": "APPLIED",
"submittedAt": "2026-08-26 11:11:00",
"finishedAt": "2026-08-26 11:11:00"
},
"success": true
}
失败响应:合同不属于当前供应商
{
"code": 400,
"message": "合同不属于当前供应商",
"data": null,
"success": false
}
该失败会回滚本次提交表单中的主体、资质、合同和审计变化,供应商仍保持原状态和原版本。
3. 查询供应商基本信息 GET /admin/supplier/items/{supplierId}/basic-info/view
GET /admin/supplier/items/{supplierId}/basic-info/view
请求
GET /admin/supplier/items/2090300000000063970/basic-info/view
Authorization: Bearer <admin-token>
无请求体。读取继续要求可信读角色和 supplier:view 平台权限。
成功响应
{
"code": 200,
"message": "成功",
"data": {
"supplierId": "2090300000000063970",
"supplierNo": null,
"fullName": "示例供应商有限公司",
"shortName": "示例供应商",
"tax_no": "9135**********0Y46",
"legalRepresentative": null,
"legalRepresentativeIdNoMask": null,
"legalRepresentativeIdCardFrontUrl": null,
"legalRepresentativeIdCardBackUrl": null,
"contactPhoneMask": null,
"establishDate": null,
"registeredCapital": null,
"businessScope": null,
"staffScale": null,
"mainCooperation": "景区资源合作",
"status": "DRAFT",
"creditLevel": "B",
"totalScore": null,
"types": [
{
"typeCode": "SCENIC",
"typeName": "景区"
}
],
"contacts": [],
"qualifications": [
{
"qualificationId": "2090300000000063973",
"qualType": "BUSINESS_LICENSE",
"qualTypeName": "营业执照",
"certNoMask": "LI*********01",
"imageUrl": "https://files.example.com/license.png",
"expiryDate": null,
"permanentValid": true,
"daysUntilExpiry": null,
"validityStatus": "VALID",
"validityStatusName": "有效",
"isRequired": false,
"expired": false,
"updateTime": "2026-08-26 11:10:00"
}
],
"contracts": [
{
"contractId": "2090300000000063971",
"contractName": "2026 年度框架合同",
"contractNo": "HT-2026-001",
"contractType": "FRAME",
"signDate": "2026-08-26",
"startDate": "2026-09-01",
"endDate": "2027-08-31",
"amount": "1200.50",
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"status": "DRAFT",
"scanFileUrl": "https://files.example.com/contracts/HT-2026-001.pdf",
"remark": "年度合作",
"updateTime": "2026-08-26 11:10:00"
}
],
"updateTime": "2026-08-26 11:10:00"
},
"success": true
}
合同按 contractId 升序返回,只包含当前有效合同。没有合同时返回空数组 [],不返回 null。
常见失败
| 场景 | code |
前端处理 |
|---|---|---|
| 未登录或 Token 失效 | 401 |
跳转登录,不展示空详情 |
可信角色或 supplier:view 平台权限不足 |
403 |
展示无权限状态 |
| 供应商不存在或已删除 | 395001 |
返回列表并刷新 |
六、边界行为
contracts省略、null与空数组语义不同:省略/传null= 不处理合同(旧客户端兼容);[]= 明确清空全部合同。- 合同非空时单请求最多 100 项;合同与供应商主体、资质、
initialAccounts同事务,一起成功或一起失败。 - 创建草稿禁止携带
contractId(每个合同都是新项);提交既有合同必须原样带回字符串contractId,外部/他人合同 ID 触发整体回滚零写入。 endDate早于startDate直接校验失败,零写入。amount边界:大于等于 0,最多 10 位整数和 2 位小数;响应按字符串输出(PR #6450 起)。- 历史只读状态
TERMINATED仅可返回,创建/提交发送该状态会被拒绝。 - 越权:仅可信
FINANCE、SUPER_ADMIN且拥有对应平台权限可写;普通 ADMIN 调用写接口返回越权错误。
六.6、修改前后对比
| 场景 | 修改前 | 修改后 |
|---|---|---|
| 创建草稿 | 请求不能携带合同 | 可选携带完整 contracts,与草稿一起成功或失败 |
| 提交注册 | 提交表单不能维护合同 | 可省略保留、空数组清空或提交完整合同快照 |
| 基础信息 | 不返回合同列表 | 返回完整 contracts[] 及字符串 ID、版本 |
| 账户区域标题 | 页面显示“初始账户” | 页面应显示“结算信息”,接口字段仍为 initialAccounts |
| 页面区块顺序 | 资质后直接进入账户区域 | 资质证照 → 合同信息 → 结算信息 |
六.7、影响评估
- 管理端(admin):供应商注册/编辑页需新增「合同信息」表格并调整区块顺序;既有合同必须缓存并原样回传字符串
contractId,否则提交会整单回滚。 - 同日修正(PR #6450):响应
contracts[].amount由 Number 改为 String,前端 f2a4200d 按 Number 集成的解析处需改为字符串处理(展示直接渲染、运算前Number(...)),影响面限于合同金额展示/计算处,请求提交不受影响。 - C 端(mp):不涉及,无影响。
- QA 排查面:问题定位优先看「契约约束与正确调用方式」第 3、4 条(快照回传与空数组语义)与「关键变化」(amount 字符串化)。
四、契约约束与正确调用方式
- 在“资质证照”区域之后新增“合同信息”表格,在合同之后显示原账户表格。
- 原账户表格标题改为“结算信息”;所有请求和响应继续使用
initialAccounts,不要改字段名。 - 创建草稿时合同为完整新项,不发送
contractId;编辑后提交时,既有合同必须原样带回字符串contractId。 - 提交表单是完整快照。用户明确删除全部合同时发送
contracts: [];未加载合同或不处理合同时省略字段,不要误发空数组。 - 所有 ID 均作为字符串保存、比较和回传,不经过 Number 转换。
- 本次不新增接口路径、权限点或业务错误码;旧客户端省略
contracts时继续可用。 - 管理端源码不在本后端工单中修改,前端状态保持
pending,直至完成页签、标题和表格接入并提供前端引用。
七、不影响范围
- 不新增接口路径、权限点或业务错误码;旧客户端省略
contracts时行为完全不变。 initialAccounts字段名、类型与语义不变(仅页面展示标题由「初始账户」改「结算信息」,属前端文案)。- 请求侧
contracts[].amount仍按 Number 传,PR #6450 只改响应输出,不改请求解析。 - 供应商其余模块(资源信息、审批记录、账户证明)的接口与字段不受影响。
- 数据库结构无变更(复用既有快照列),无 Redis/MQ 行为变化。
八、测试环境已验证
- 自动化:供应商定向测试 115 项通过;
hl-resource-service全量 2,111 项,0 失败、0 错误,38 项条件跳过;hl-verify与差异检查通过。 - 部署:Deploy Panel API 任务
af3f205b终态success、退出码 0、has_build_error=false,未发现 Maven、编译或滚动发布错误。 - 健康:
hl-resource-service的 8082、8182 双实例运行;Nacostest命名空间两实例均healthy=true、enabled=true。 - 真实 Gateway:23 项断言通过,覆盖合同随草稿创建、详情完整回显、字符串 ID、创建携带 ID 失败、普通 ADMIN 越权、提交外部合同 ID 完整回滚、日期逆序零写入和旧客户端省略
contracts。 - 清理:两个临时 DRAFT 均通过业务删除接口软删除并回读为不存在;仅保留不可逆的 CREATE/DELETE 操作审计。
- 环境限制:部署前锁定
origin/dev-v3=1a16a5aec7d0b95ec87e6fb222581060a3135984,但面板 Git API 回读到服务器本地短提交6f7d3ca78,Gitea 无法解析该对象。接口行为已真实验证,后端工单仍等待测试环境恢复精确远端提交后复验,不能据此宣称最终交付完成。
撤回
- 从最新
dev-v3创建回退分支,执行git revert -m 1 --no-edit 1a16a5aec7d0b95ec87e6fb222581060a3135984,经独立 PR 合入。 - 重新滚动部署
hl-resource-service;无需执行数据库结构、配置、Redis 或 MQ 恢复。 - 管理端停止发送和读取
contracts,恢复原页面结构;initialAccounts契约始终不变。 - 已保存的合同资料保留,不做破坏性批量清理;回退后旧客户端继续按省略
contracts的路径工作。 - 经 Gateway 复测创建、提交、基础信息、未认证、越权、非法合同 ID、失败零写入和旧客户端兼容,并确认双实例与 Nacos 健康。
十、相关文档
- 设计/API 说明:仓库
docs/supplier/API-CHANGE-6397.html - 同日修正 PR:#6450(响应
contracts[].amountNumber→String,每日审查红线修复) - 供应商暂停/拉黑原因必填(同属供应商状态域):
changelogs-v2/2026-08/26_6392_供应商暂停合作与拉黑原因必填接口-新增接口-管理后台.md