--- schema: "hl-changelog/v2" ticket: "6842" title: "供应商合同修改与删除接口" consumer: "admin" author: "lc(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "汇总现有合同修改、删除契约;前端按完整替换、必填原因及可选合同版本接入。既有 TEST 业务实测及本次只读复核范围见正文。" updated_at: "2026-09-06" base: "dev-v3" --- # 供应商合同:修改与删除接口 > **影响范围**:管理后台供应商合同编辑、删除。当前状态:后端已部署;前端待核对接入。 更新为整份替换,15 个业务字段均可空;修改、删除都必须提交 `changeReason`。`expectedUpdateTime` 可省略,传入时必须使用合同自身版本。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | 修改供应商合同 | PUT | `/admin/supplier/items/{supplierId}/contracts/{contractId}/update` | 现有契约说明 | 完整替换一份合同 | | 2 | 删除供应商合同 | DELETE | `/admin/supplier/items/{supplierId}/contracts/{contractId}/del` | 现有契约说明 | 携带 JSON 原因软删除一份合同 | ## 三、接口详情 ### 1. 修改供应商合同 `PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update` **VO**: `SupplierContractUpdateReqVO / SupplierContractRespVO` #### 使用场景 编辑供应商下已登记的单份合同。提交应保留的全部业务字段;未提交字段按空值覆盖。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `supplierId` | Path | String | 是 | 正整数 ID | 所属供应商 | | `contractId` | Path | String | 是 | 正整数 ID | 目标合同 | | `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 本次修改原因 | | `expectedUpdateTime` | Body | String/null | 否 | `yyyy-MM-dd HH:mm:ss` | 合同 `updateTime`,传入即校验 | | `contractName` | Body | String/null | 否 | 最长 500 字符 | 合同名称 | | `contractNo` | Body | String/null | 否 | 最长 100 字符 | 合同编号 | | `contractType` | Body | String/null | 否 | `FRAME` / `SINGLE_TRIP` / `PURCHASE` | 合同类型 | | `signDate` | Body | String/null | 否 | `yyyy-MM-dd` | 签署日期 | | `startDate` | Body | String/null | 否 | `yyyy-MM-dd` | 有效期开始 | | `endDate` | Body | String/null | 否 | 两端有值时不得早于 `startDate` | 有效期结束 | | `businessLine` | Body | String/null | 否 | 最长 100 字符 | 业务线自由文本 | | `relatedMainContract` | Body | String/null | 否 | 最长 100 字符 | 关联主合同引用 | | `autoRenew` | Body | Boolean/null | 否 | `true` / `false` / `null` | 自动续约 / 不续约 / 未登记 | | `amount` | Body | String/null | 否 | 非负,最多 10 位整数、2 位小数 | 金额,如 `"1200.50"` | | `pricingMode` | Body | String/null | 否 | 最长 100 字符 | 计价方式 | | `settleCycle` | Body | String/null | 否 | 最长 32 字符 | 结算周期自由文本 | | `status` | Body | String/null | 否 | `DRAFT` / `ACTIVE` / `SIGNED` / `EXPIRED` | 合同状态 | | `scanFileUrl` | Body | String/null | 否 | 最长 1000 字符 | 一个合同附件的永久地址 | | `remark` | Body | String/null | 否 | 最长 500 字符 | 合同备注 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `code` / `message` / `success` | Integer / String / Boolean | 成功为 `200`、`成功`、`true` | | `data.contractId` | String | 合同 ID | | `data.contractName` | String/null | 合同名称 | | `data.contractNo` | String/null | 合同编号 | | `data.contractType` | String/null | 合同类型 | | `data.signDate` | String/null | 签署日期 | | `data.startDate` / `data.endDate` | String/null | 有效期,`yyyy-MM-dd` | | `data.businessLine` | String/null | 业务线 | | `data.relatedMainContract` | String/null | 关联主合同 | | `data.autoRenew` | Boolean/null | 自动续约标记,`false` 与 `null` 含义不同 | | `data.amount` | String/null | 金额;无金额为 `null` | | `data.pricingMode` | String/null | 计价方式 | | `data.settleCycle` | String/null | 结算周期 | | `data.status` | String/null | 合同状态;历史读取可能出现 `TERMINATED` | | `data.scanFileUrl` | String/null | 合同附件地址 | | `data.remark` | String/null | 合同备注 | | `data.updateTime` | String | 合同新版本,`yyyy-MM-dd HH:mm:ss` | #### 请求示例 以下 ID、附件地址和时间均为示例值。 ```http PUT /admin/supplier/items/2095000000000000001/contracts/2095000000000000010/update Authorization: Bearer <当前有效凭证> Content-Type: application/json { "changeReason": "调整合同有效期", "expectedUpdateTime": "2026-09-06 10:00:00", "contractName": "年度服务合同", "contractNo": "HT-2026-001", "contractType": "FRAME", "signDate": "2026-09-01", "startDate": "2026-09-01", "endDate": "2027-09-30", "businessLine": "旅行服务", "relatedMainContract": null, "autoRenew": false, "amount": "1200.50", "pricingMode": "按团结算", "settleCycle": "月结", "status": "SIGNED", "scanFileUrl": "https://files.example.com/contracts/demo.pdf", "remark": null } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "contractId": "2095000000000000010", "contractName": "年度服务合同", "contractNo": "HT-2026-001", "contractType": "FRAME", "signDate": "2026-09-01", "startDate": "2026-09-01", "endDate": "2027-09-30", "businessLine": "旅行服务", "relatedMainContract": null, "autoRenew": false, "amount": "1200.50", "pricingMode": "按团结算", "settleCycle": "月结", "status": "SIGNED", "scanFileUrl": "https://files.example.com/contracts/demo.pdf", "remark": null, "updateTime": "2026-09-06 10:00:01" } } ``` #### 空数据 / 降级响应 可空业务字段省略或传 `null` 均清空;可选文本空白也规范化为空。只传 `changeReason` 会尝试清空全部业务字段,不表示仅改原因。成功返回合同对象,无变化时返回业务错误。 #### 错误响应 合同版本过期: ```json {"code":395014,"message":"数据已被他人修改,请刷新后重试","success":false,"data":null} ``` `400`:缺少原因或格式错误;`395002`:无写权限;`395051`:合同不存在、已删除或归属不符;`395054`:日期倒置;`395057`:无实际变化;`395031`:供应商已归档;`395005`:主体企微审批未结束或状态不允许。 #### 业务边界 - 要求 `FINANCE` 或 `SUPER_ADMIN` 且具有 `supplier:update`;`ADMIN` 被拒绝。 - 合同必须属于路径供应商,供应商不能已归档,主体企微审批必须已结束。 - 完整替换包含页面隐藏字段;需保留的字段应原样提交。版本取合同自身,不使用供应商主体版本。 ### 2. 删除供应商合同 `DELETE /admin/supplier/items/{supplierId}/contracts/{contractId}/del` **VO**: `SupplierContractDeleteReqVO / Result` #### 使用场景 删除一份已登记合同。DELETE 请求须携带 JSON 请求体。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `supplierId` | Path | String | 是 | 正整数 ID | 所属供应商 | | `contractId` | Path | String | 是 | 正整数 ID | 目标合同 | | `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 本次删除原因 | | `expectedUpdateTime` | Body | String/null | 否 | `yyyy-MM-dd HH:mm:ss` | 合同当前版本,提供时必须匹配 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `code` | Integer | 成功为 `200` | | `message` | String | 成功为 `成功` | | `success` | Boolean | 成功为 `true` | | `data` | null | 无合同对象 | #### 请求示例 ```http DELETE /admin/supplier/items/2095000000000000001/contracts/2095000000000000010/del Authorization: Bearer <当前有效凭证> Content-Type: application/json { "changeReason": "合同重复登记", "expectedUpdateTime": "2026-09-06 10:00:01" } ``` #### 响应示例 ```json {"code":200,"message":"成功","success":true,"data":null} ``` #### 空数据 / 降级响应 成功的 `data: null` 为正常结果,删除后从详情合同列表中移除该项。 #### 错误响应 缺少删除原因: ```json {"code":400,"message":"变更原因不能为空","success":false,"data":null} ``` `395002`:无写权限;`395051`:合同不存在、已删除或归属不符;`395014`:版本过期;`395031`:供应商已归档;`395005`:主体企微审批未结束或状态不允许。 #### 业务边界 - 要求 `FINANCE` 或 `SUPER_ADMIN` 且具有 `supplier:update`;供应商已归档或主体企微审批未结束时拒绝。 - 删除为软删除,不删除供应商或账户;没有合同恢复接口。 - 前端请求库需将原因放进 DELETE 的 JSON body;不能只拼 Query 参数或只传 ID。 ## 四、契约约束与正确调用方式 从 `GET /admin/supplier/items/{supplierId}/basic-info/view` 的 `data.contracts[]` 读取合同、`contractId` 和 `updateTime`。修改时提交应保留的全部字段;修改成功用新版本继续操作,删除成功刷新列表。版本冲突重新读取后再编辑。日期按 GMT+8 日历日期传 `yyyy-MM-dd`。 ## 五、数据库行为 更新只替换目标合同并刷新合同版本;删除后有效详情不再返回该合同,历史记录保留。供应商主体版本不因合同写入改变。参数、权限、归属或版本校验失败时不产生合同变更。 ## 六、边界行为 未登录或登录失效按认证失败处理;供应商不存在返回 `395001`。业务错误可能随 HTTP 200 返回,应检查 `code` 和 `success`。金额 `1200.5` 与 `1200.50` 视为相同值,仅改变金额格式不会构成实际变更。 ## 六.5、枚举 / 数据字典 ### `contractType` | 值 | 中文 | 说明 | |---|---|---| | `FRAME` | 框架合同 | 可写 | | `SINGLE_TRIP` | 单团单合同 | 可写 | | `PURCHASE` | 采购合同 | 可写 | | `null` | 未登记 | 可写 | ### `status` | 值 | 中文 | 说明 | |---|---|---| | `DRAFT` | 草稿 | 可写 | | `ACTIVE` | 生效中 | 可写 | | `SIGNED` | 已签约 | 可写 | | `EXPIRED` | 已过期 | 可写 | | `TERMINATED` | 已终止 | 仅历史回显,不可提交 | | `null` | 未登记 | 可写 | ## 六.6、修改前后对比 本次为既有契约汇总,后端无新增变更。 | 字段 / 行为 | 早期 #6544 说明 | 当前契约 | |---|---|---| | 合同业务字段 | 部分必填 | 全部可空;含业务线、关联主合同、自动续约 | | `expectedUpdateTime` | 必填 | 可选,提供时校验合同版本 | | `status` | 未列出 `SIGNED` | 支持已签约 `SIGNED` | | 修改 / 删除 | 独立接口 | 路径保持不变;原因仍必填 | ## 六.7、影响评估 本次没有新增兼容性变化,无需与后端同步上线。前端接入现有修改、删除按钮时使用上述请求体,避免只传变化字段导致清空、丢弃 DELETE body 或误用主体版本。 ## 七、不影响范围 本次仅说明合同维护入口;供应商主体保存和账户维护继续使用各自接口。 ## 八、测试环境已验证 - 既有业务实测:#6654 已覆盖合同补全、清空、删除、缺原因及版本失败零写入;#6842 已覆盖扩展字段保存回读、`SIGNED`、`null`、`false` 和日期错误零写入。 - 本次于 2026-09-06 通过 TEST Gateway 只读核对 Resource Swagger:合同 PUT、DELETE 路径存在,更新的 15 个业务字段、可选版本与必填原因均已发布;同步核对最新 `dev-v3`。此次未重复执行共享环境业务写操作。 ## 十、相关文档 - [合同修改、删除实测 #6654](https://git.1814.love:8443/wx/HL/issues/6654#issuecomment-43820) - [补充字段与实测 #6842](https://git.1814.love:8443/wx/HL/issues/6842#issuecomment-44703) ## 关联 / 联系人 - **Issue**: [#6842](https://git.1814.love:8443/wx/HL/issues/6842) - **PR**: [#6858](https://git.1814.love:8443/wx/HL/pulls/6858)、[#6665](https://git.1814.love:8443/wx/HL/pulls/6665) - **后端负责人**: @lc