diff --git a/changelogs-v2/2026-08/26_6397_供应商注册合同聚合信息-修改接口-管理后台.md b/changelogs-v2/2026-08/26_6397_供应商注册合同聚合信息-修改接口-管理后台.md new file mode 100644 index 00000000..ab23cb56 --- /dev/null +++ b/changelogs-v2/2026-08/26_6397_供应商注册合同聚合信息-修改接口-管理后台.md @@ -0,0 +1,392 @@ +--- +schema: "hl-changelog/v2" +ticket: "6397" +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: "2026-08-26" +status_note: "PR #6405 已合并 dev-v3;Deploy Panel 任务 af3f205b 成功发布 Resource 双实例,合同接口已完成 23 项真实 Gateway 验收。TEST 工作副本存在服务器本地提交导致精确提交回读仍被阻塞,后端工单保持开启;本条只交接已经实测存在的接口契约。" +updated_at: "2026-08-26" +base: "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`。业务失败可能仍为 HTTP 200,调用方必须同时判断 `code`、`success`、`message` 和 `data`。 + +## 公共合同字段 + +### 请求字段 `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` | + +历史数据可能返回只读状态 `TERMINATED`;创建和提交请求不得发送该状态。 + +## 1. 创建供应商注册草稿 + +`POST /admin/supplier/items/add` + +### 使用场景与边界 + +- `contracts` 可省略、为 `null` 或空数组,旧客户端行为不变。 +- 非空时最多 100 项,合同与供应商主体、资质和 `initialAccounts` 一起成功或一起失败。 +- 创建请求中的每个合同都是新合同,禁止携带 `contractId`。 +- 写入仍要求现有供应商创建权限;仅可信 `FINANCE`、`SUPER_ADMIN` 且拥有对应平台权限的身份可执行。 + +### 典型请求 + +```http +POST /admin/supplier/items/add +Authorization: Bearer +Content-Type: application/json +``` + +```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": [] +} +``` + +### 成功响应 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "supplierId": "2090300000000063970", + "supplierNo": null, + "status": "DRAFT", + "onboardingStage": "PROFILE_DRAFT", + "initialAccounts": [], + "updateTime": "2026-08-26 11:10:00" + }, + "success": true +} +``` + +### 失败响应:创建携带合同 ID + +```json +{ + "code": 400, + "message": "创建草稿不能携带合同ID", + "data": null, + "success": false +} +``` + +失败时不会留下供应商主体或部分合同。 + +## 2. 提交供应商注册 + +`POST /admin/supplier/items/{supplierId}/submit` + +### 使用场景与快照语义 + +- `contracts` 省略或为 `null`:本次不处理合同,保留草稿当前合同。 +- `contracts: []`:明确清空当前全部合同。 +- 非空数组:作为完整快照;带 `contractId` 的项覆盖当前合同,不带 ID 的项新增,当前已有但数组中遗漏的合同删除。 +- 带 ID 的合同必须属于路径中的供应商;不属于当前供应商、重复 ID、非法枚举、负金额或日期逆序均失败。 +- `expectedUpdateTime` 仍是供应商聚合并发版本;发生并发修改时调用方应刷新详情后重新组装完整表单。 +- 合同快照会进入本次审批资料,但提交注册不会自动改写合同自身的 `status`。 + +### 典型请求 + +```http +POST /admin/supplier/items/2090300000000063970/submit +Authorization: Bearer +Content-Type: application/json +``` + +```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" +} +``` + +### 成功响应 + +```json +{ + "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 +} +``` + +### 失败响应:合同不属于当前供应商 + +```json +{ + "code": 400, + "message": "合同不属于当前供应商", + "data": null, + "success": false +} +``` + +该失败会回滚本次提交表单中的主体、资质、合同和审计变化,供应商仍保持原状态和原版本。 + +## 3. 查询供应商基本信息 + +`GET /admin/supplier/items/{supplierId}/basic-info/view` + +### 请求 + +```http +GET /admin/supplier/items/2090300000000063970/basic-info/view +Authorization: Bearer +``` + +无请求体。读取继续要求可信读角色和 `supplier:view` 平台权限。 + +### 成功响应 + +```json +{ + "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`,与草稿一起成功或失败 | +| 提交注册 | 提交表单不能维护合同 | 可省略保留、空数组清空或提交完整合同快照 | +| 基础信息 | 不返回合同列表 | 返回完整 `contracts[]` 及字符串 ID、版本 | +| 账户区域标题 | 页面显示“初始账户” | 页面应显示“结算信息”,接口字段仍为 `initialAccounts` | +| 页面区块顺序 | 资质后直接进入账户区域 | 资质证照 → 合同信息 → 结算信息 | + +## 兼容性与管理端接入事项 + +1. 在“资质证照”区域之后新增“合同信息”表格,在合同之后显示原账户表格。 +2. 原账户表格标题改为“结算信息”;所有请求和响应继续使用 `initialAccounts`,不要改字段名。 +3. 创建草稿时合同为完整新项,不发送 `contractId`;编辑后提交时,既有合同必须原样带回字符串 `contractId`。 +4. 提交表单是完整快照。用户明确删除全部合同时发送 `contracts: []`;未加载合同或不处理合同时省略字段,不要误发空数组。 +5. 所有 ID 均作为字符串保存、比较和回传,不经过 Number 转换。 +6. 本次不新增接口路径、权限点或业务错误码;旧客户端省略 `contracts` 时继续可用。 +7. 管理端源码不在本后端工单中修改,前端状态保持 `pending`,直至完成页签、标题和表格接入并提供前端引用。 + +## TEST 验证证据 + +- 自动化:供应商定向测试 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 双实例运行;Nacos `test` 命名空间两实例均 `healthy=true`、`enabled=true`。 +- 真实 Gateway:23 项断言通过,覆盖合同随草稿创建、详情完整回显、字符串 ID、创建携带 ID 失败、普通 ADMIN 越权、提交外部合同 ID 完整回滚、日期逆序零写入和旧客户端省略 `contracts`。 +- 清理:两个临时 DRAFT 均通过业务删除接口软删除并回读为不存在;仅保留不可逆的 CREATE/DELETE 操作审计。 +- 环境限制:部署前锁定 `origin/dev-v3=1a16a5aec7d0b95ec87e6fb222581060a3135984`,但面板 Git API 回读到服务器本地短提交 `6f7d3ca78`,Gitea 无法解析该对象。接口行为已真实验证,后端工单仍等待测试环境恢复精确远端提交后复验,不能据此宣称最终交付完成。 + +## 撤回 + +1. 从最新 `dev-v3` 创建回退分支,执行 `git revert -m 1 --no-edit 1a16a5aec7d0b95ec87e6fb222581060a3135984`,经独立 PR 合入。 +2. 重新滚动部署 `hl-resource-service`;无需执行数据库结构、配置、Redis 或 MQ 恢复。 +3. 管理端停止发送和读取 `contracts`,恢复原页面结构;`initialAccounts` 契约始终不变。 +4. 已保存的合同资料保留,不做破坏性批量清理;回退后旧客户端继续按省略 `contracts` 的路径工作。 +5. 经 Gateway 复测创建、提交、基础信息、未认证、越权、非法合同 ID、失败零写入和旧客户端兼容,并确认双实例与 Nacos 健康。 + +## 关联 / 联系人 + +- **Issue**: [#6397](https://git.1814.love:8443/wx/HL/issues/6397) +- **PR**: [#6405](https://git.1814.love:8443/wx/HL/pulls/6405) +- **合并提交**: [1a16a5aec](https://git.1814.love:8443/wx/HL/commit/1a16a5aec7d0b95ec87e6fb222581060a3135984) +- **后端负责人**: @lc