--- schema: "hl-changelog/v2" ticket: "6842" title: "供应商补充合同字段与附件" consumer: "admin" author: "lc(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "hl-admin" frontend_ref: "f5414b12" target_release: "v2.1" verified_at: "2026-09-01" status_note: "PR #6858 已合并 dev-v3,hl-resource-service 已部署提交 5546d7907c6eda9b372b4639094f84e568de6496。TEST Gateway 已验证完整字段与全空业务字段合同的新增、更新、详情回读,以及单个 PDF 上传、预览、下载和清理。前端 hl-admin v2.1 提交 f5414b12 已交付:ContractEditModal 增 businessLine/relatedMainContract/autoRenew(三态,false 不按空过滤)+SIGNED 选项,scanFileUrl 改 FileUpload 文件中心单附件(upload/token→confirm 写 ossUrl),隐藏 contractType/amount/pricingMode/settleCycle/remark 但编辑原样回传防整份替换误清;ManageModal 列重排契约固定集、SIGNED→已签约、有效期直拼上海日历日、附件预览带登录态 preview-by-url/下载直开永久地址;api/file.js 加 getContractPreviewBlobUrl。spec 编辑 16 例+管理 6 例绿。当前状态:后端已就绪,前端已交付。" updated_at: "2026-09-01" base: "dev-v3" --- # 供应商补充合同字段与附件 供应商补充合同新增 `businessLine`、`relatedMainContract`、`autoRenew`,并允许写入 `SIGNED`(已签约)。所有合同业务字段均可为空,只有 `changeReason` 继续必填。 管理端登记页仅展示补合同编号、状态、合同名称、业务线、有效期、签署日期、关联主合同、自动续约和单个 Word/PDF 附件;其他兼容字段隐藏。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---:|---|---|---|---|---| | 1 | 独立登记供应商合同 | POST | `/admin/supplier/items/{supplierId}/contracts/add` | 扩展请求与响应 | 增加三个字段,状态支持 `SIGNED` | | 2 | 独立更新供应商合同 | PUT | `/admin/supplier/items/{supplierId}/contracts/{contractId}/update` | 扩展请求与响应 | 新字段支持补录、清空和 `false` | | 3 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 扩展响应 | `contracts[]` 完整回读补充合同字段 | ## 三、接口详情 ### 1. 独立登记供应商合同 `POST /admin/supplier/items/{supplierId}/contracts/add` **VO**: `SupplierContractCreateReqVO / SupplierContractRespVO` #### 使用场景 供应商已创建后登记一份补充合同;业务资料未齐时也可只提交审计原因,稍后再补录。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---:|---|---| | `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 | | `contractNo` | Body | String/null | 否 | 最长 100 | 补合同编号 | | `status` | Body | String/null | 否 | `DRAFT` / `ACTIVE` / `SIGNED` / `EXPIRED` | `SIGNED` 显示为“已签约” | | `contractName` | Body | String/null | 否 | 最长 500 | 合同名称 | | `businessLine` | Body | String/null | 否 | 最长 100 | 业务线,自由文本 | | `startDate` | Body | String/null | 否 | `yyyy-MM-dd` | 有效期开始日 | | `endDate` | Body | String/null | 否 | `yyyy-MM-dd` | 有效期结束日;两端都有值时不得早于开始日 | | `signDate` | Body | String/null | 否 | `yyyy-MM-dd` | 合同签署日期 | | `relatedMainContract` | Body | String/null | 否 | 最长 100 | 关联主合同外部引用 | | `autoRenew` | Body | Boolean/null | 否 | `true` / `false` / `null` | 是 / 否 / 未登记 | | `scanFileUrl` | Body | String/null | 否 | 最长 1000 | 文件中心确认上传后返回的永久 `ossUrl` | | `changeReason` | Body | String | 是 | 去空白后非空,最长 500 | 登记原因,写入审计 | `contractType`、`amount`、`pricingMode`、`settleCycle`、`remark` 仍兼容但本页面隐藏,也均可为空。 #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `data.contractId` | String | 合同 ID | | `data.contractNo` | String/null | 补合同编号 | | `data.status` | String/null | 包含新增可写值 `SIGNED` | | `data.contractName` | String/null | 合同名称 | | `data.businessLine` | String/null | 业务线 | | `data.startDate` / `data.endDate` | String/null | 有效期日期 | | `data.signDate` | String/null | 签署日期 | | `data.relatedMainContract` | String/null | 关联主合同 | | `data.autoRenew` | Boolean/null | 自动续约三态值 | | `data.scanFileUrl` | String/null | 合同附件永久地址 | | `data.updateTime` | String | 当前合同版本,格式 `yyyy-MM-dd HH:mm:ss` | #### 请求示例 ```json { "contractNo": "SHCT2025070102599446", "status": "SIGNED", "contractName": "内蒙古牵手草原国际旅行社有限公司额外后返", "businessLine": null, "startDate": "2010-01-01", "endDate": "2099-12-31", "signDate": "2025-07-01", "relatedMainContract": "2185933", "autoRenew": true, "scanFileUrl": "https://files.example.com/supplier-contract/example.pdf", "changeReason": "补录已签约合同" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "contractId": "2095000000000000001", "contractNo": "SHCT2025070102599446", "status": "SIGNED", "contractName": "内蒙古牵手草原国际旅行社有限公司额外后返", "businessLine": null, "startDate": "2010-01-01", "endDate": "2099-12-31", "signDate": "2025-07-01", "relatedMainContract": "2185933", "autoRenew": true, "scanFileUrl": "https://files.example.com/supplier-contract/example.pdf", "updateTime": "2026-08-31 12:00:00" } } ``` #### 空数据 / 降级响应 仅提交 `changeReason` 也可成功;所有合同业务字段返回 `null`,前端不得补默认状态或日期。 #### 错误响应 ```json { "code": 400, "message": "变更原因不能为空", "success": false, "data": null } ``` #### 业务边界 - 所有合同业务字段可空;`changeReason` 不属于业务展示字段,继续必填。 - `startDate`、`endDate` 同时有值时才校验日期先后。 - `scanFileUrl` 只保存一个附件永久地址;上传、预览、下载复用文件中心。 ### 2. 独立更新供应商合同 `PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update` **VO**: `SupplierContractUpdateReqVO / SupplierContractRespVO` #### 使用场景 补齐、修改或清空已登记合同。该接口为完整替换,省略或传 `null` 会清空对应字段。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---:|---|---| | `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 | | `contractId` | Path | String | 是 | 正整数 ID 字符串 | 目标合同 | | 新增接口全部业务字段 | Body | 对应类型/null | 否 | 与新增一致 | 完整替换;`autoRenew=false` 会保留为否 | | `expectedUpdateTime` | Body | String/null | 否 | `yyyy-MM-dd HH:mm:ss` | 提供时必须匹配当前版本 | | `changeReason` | Body | String | 是 | 去空白后非空,最长 500 | 更新原因 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `data.contractId` | String | 原合同 ID | | `data.businessLine` | String/null | 更新后的业务线,空白规范为 `null` | | `data.relatedMainContract` | String/null | 更新后的关联主合同 | | `data.autoRenew` | Boolean/null | 精确保留 `true`、`false` 或 `null` | | `data.status` | String/null | 可返回 `SIGNED` | | 其余合同业务字段 | 对应类型/null | 完整替换后的值 | | `data.updateTime` | String | 更新后的合同版本 | #### 请求示例 ```json { "contractNo": "SHCT2025070102599446", "status": "SIGNED", "contractName": "内蒙古牵手草原国际旅行社有限公司额外后返", "businessLine": null, "startDate": "2010-01-01", "endDate": "2099-12-31", "signDate": "2025-07-01", "relatedMainContract": null, "autoRenew": false, "scanFileUrl": "https://files.example.com/supplier-contract/example.pdf", "changeReason": "调整自动续约信息" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "contractId": "2095000000000000001", "contractNo": "SHCT2025070102599446", "status": "SIGNED", "businessLine": null, "relatedMainContract": null, "autoRenew": false, "updateTime": "2026-08-31 12:00:01" } } ``` #### 空数据 / 降级响应 仅提交 `changeReason` 可把全部业务字段清空;与当前内容完全相同时拒绝空更新,不生成新版本。 #### 错误响应 ```json { "code": 395054, "message": "合同有效期开始日期不能晚于结束日期", "success": false, "data": null } ``` #### 业务边界 - 页面隐藏兼容字段时,编辑既有合同应从详情原样回传其 `contractType`、`amount`、`pricingMode`、`settleCycle`、`remark`,避免完整替换误清历史值。 - `autoRenew=false` 是有效业务值,不能按空值过滤。 - `expectedUpdateTime` 可省略;提供过期值时返回 `395014`,合同保持不变。 ### 3. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view` **VO**: `SupplierBasicInfoRespVO / SupplierContractRespVO` #### 使用场景 进入供应商编辑或详情页时,从 `data.contracts[]` 回显合同表格和附件操作。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---:|---|---| | `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `data.contracts` | Array | 当前有效合同,按合同 ID 升序 | | `data.contracts[].businessLine` | String/null | 业务线 | | `data.contracts[].relatedMainContract` | String/null | 关联主合同 | | `data.contracts[].autoRenew` | Boolean/null | 自动续约三态值 | | `data.contracts[].status` | String/null | `SIGNED` 显示为“已签约” | | `data.contracts[].scanFileUrl` | String/null | 单个合同附件永久地址 | | 其余合同字段 | 对应类型/null | 与新增、更新响应一致 | #### 请求示例 ```http GET /admin/supplier/items/2095000000000000000/basic-info/view Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "supplierId": "2095000000000000000", "contracts": [ { "contractId": "2095000000000000001", "contractNo": "SHCT2025070102599446", "status": "SIGNED", "contractName": "内蒙古牵手草原国际旅行社有限公司额外后返", "businessLine": null, "startDate": "2010-01-01", "endDate": "2099-12-31", "signDate": "2025-07-01", "relatedMainContract": "2185933", "autoRenew": true, "scanFileUrl": "https://files.example.com/supplier-contract/example.pdf" } ] } } ``` #### 空数据 / 降级响应 没有合同记录时 `contracts=[]`;合同存在但某项未登记时,对应字段为 `null`。 #### 错误响应 ```json { "code": 395001, "message": "供应商不存在", "success": false, "data": null } ``` #### 业务边界 - 日期均为上海业务日字符串,前端按 `yyyy-MM-dd` 原样展示,不做 UTC 日期转换。 - 查询只返回未删除合同;附件为空时隐藏预览、下载操作。 - 未登录请求由 Gateway 返回业务码 `401`。 ## 四、契约约束与正确调用方式 1. 表格列固定映射为 `contractNo`、`status`、`contractName`、`businessLine`、`startDate/endDate`、`signDate`、`relatedMainContract`、`autoRenew`、`scanFileUrl`;`SIGNED` 展示“已签约”。 2. `startDate/endDate` 直接按上海日历日拼成有效期;`autoRenew=true` 可显示“到期自动续约”,`false` 显示“否”,`null` 显示未登记占位。 3. 附件复用文件中心:申请上传凭证 `POST /admin/file/upload/token`,按返回模式上传后调用 `POST /admin/file/upload/confirm`,把确认响应的 `data.ossUrl` 写入 `scanFileUrl`。组件仅保留一个 `.doc`、`.docx` 或 `.pdf` 文件。 4. 预览使用带登录态的 `GET /admin/file/preview-by-url?url={encodeURIComponent(scanFileUrl)}`;下载直接访问 `scanFileUrl`。 5. 本页面隐藏 `contractType`、`amount`、`pricingMode`、`settleCycle`、`remark`;编辑历史合同时仍从详情原样回传,避免完整替换清空旧值。 ## 五、数据库行为 这里只约定外部可观察结果:新增或更新成功后,再次请求基本信息详情可读到相同字段;未填写值返回 `null`,`autoRenew=false` 不会丢失。失败响应不产生部分合同变更。 ## 六、边界行为 - 所有合同业务字段可空,只有新增、更新、删除操作的 `changeReason` 必填。 - `SIGNED` 仅是新增可写状态,不改变既有 `DRAFT`、`ACTIVE`、`EXPIRED` 和历史 `TERMINATED` 的兼容读取。 - 日期两端都有值且开始日晚于结束日时返回 `395054`,整次更新零写入。 - 附件字段为单值;前端负责限制为一个 Word/PDF,并在替换附件后提交新的永久地址。 - 写接口沿用既有角色、权限、幂等、锁和审计规则,无新增错误码。 ## 六.6、修改前后对比 | 项目 | 修改前 | 修改后 | |---|---|---| | 业务线、关联主合同、自动续约 | 无字段 | 新增 `businessLine`、`relatedMainContract`、`autoRenew`,均可空 | | 合同状态 | 可写 `DRAFT` / `ACTIVE` / `EXPIRED` | 追加可写 `SIGNED`(已签约) | | 详情回读 | 无新增三字段 | `contracts[]` 完整回读新增三字段 | | 其他合同业务字段 | 可空 | 继续可空,语义不变 | ## 六.7、影响评估 - **是否破坏向后兼容**:否,均为可选字段和状态枚举扩展。 - **前端是否必须同步上线**:是,需要新增列、附件控件和 `SIGNED` 中文映射,并隐藏非目标字段。 - **前端 workaround 清理点**:不要在本地拼装缺失字段;以详情 `contracts[]` 为回显真相。 ## 七、不影响范围 - 不修改供应商主体、联系人、资质、结算账户、审批和归档接口。 - 不把合同并回供应商注册聚合;仍先取得 `supplierId`,再调用独立合同接口。 - 不新增 Gateway 路由、权限点、配置、Redis 或 MQ 契约。 ## 八、测试环境已验证 - 已在 TEST Gateway 验证完整字段和仅含 `changeReason` 的合同均可新增并跨请求回读;`SIGNED`、`null`、`false`、日期错误零写入均符合本契约。 - 已验证一个 PDF 经文件中心上传后可预览、下载,验收产生的合同、附件和会话均已清理。 ## 当前状态 - 后端:已部署并已验证。 - 前端:待处理。 ## 十、相关文档 - Issue:[#6842](https://git.1814.love:8443/wx/HL/issues/6842) - 后端 PR:[#6858](https://git.1814.love:8443/wx/HL/pulls/6858) - 合并提交:[5546d790](https://git.1814.love:8443/wx/HL/commit/5546d7907c6eda9b372b4639094f84e568de6496) ## 关联 / 联系人 - **Issue**: [#6842](https://git.1814.love:8443/wx/HL/issues/6842) - **PR**: [#6858](https://git.1814.love:8443/wx/HL/pulls/6858) - **后端负责人**: @lc