docs: 下发供应商合同必填与去原因契约(#7290)
changelog-filename-gate / validate (push) Successful in 2s

这个提交包含在:
lc
2026-09-07 19:53:41 +08:00
父节点 0c042ba736
当前提交 0b86d2593f
@@ -0,0 +1,327 @@
---
schema: "hl-changelog/v2"
ticket: "7290"
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: ""
status_note: "后端已部署并通过 TEST Gateway 验证。前端须将合同编号文案改为“合同编号”,把十项字段设为必填,并移除新增、编辑的登记原因输入与 changeReason 传参;删除合同的原因仍必填。"
updated_at: "2026-09-07"
base: "dev-v3"
---
# 供应商合同:十项必填并移除登记原因
## 一、关键变化
- 新增、编辑合同均要求提交:合同名称、合同编号、合同状态、业务线、关联主合同、签署日期、自动续约、有效期开始、有效期结束、合同附件。
- 页面字段 `contractNo` 的文案改为“合同编号”,不再显示“补合同编号”。
- 新增、编辑不再要求登记原因,请移除原因输入框并停止发送 `changeReason`;旧调用方多传该字段仍兼容忽略。
- 新增、编辑成功后不再产生供应商变更审计。合同删除接口及其必填原因、删除审计均不变。
本文覆盖此前 #6654、#6842 中“合同业务字段可空、新增或编辑原因必填”的旧口径。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 新增供应商合同 | POST | `/admin/supplier/items/{supplierId}/contracts/add` | 请求校验与行为修改 | 十项必填,移除登记原因与新增审计 |
| 2 | 编辑供应商合同 | PUT | `/admin/supplier/items/{supplierId}/contracts/{contractId}/update` | 请求校验与行为修改 | 十项必填,移除登记原因与编辑审计 |
## 三、接口详情
### 1. 新增供应商合同 `POST /admin/supplier/items/{supplierId}/contracts/add`
**VO**: `SupplierContractCreateReqVO / SupplierContractRespVO`
#### 使用场景
在已有供应商下新增一份完整合同,不再填写登记原因。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `supplierId` | Path | String | 是 | 正整数 ID | 所属供应商 |
| `contractName` | Body | String | 是 | 非空,最长 500 | 合同名称 |
| `contractNo` | Body | String | 是 | 非空,最长 100 | 合同编号;页面不得再写“补合同编号” |
| `status` | Body | String | 是 | `DRAFT` / `ACTIVE` / `SIGNED` / `EXPIRED` | 合同状态 |
| `businessLine` | Body | String | 是 | 非空,最长 100 | 业务线 |
| `relatedMainContract` | Body | String | 是 | 非空,最长 100 | 关联主合同引用 |
| `signDate` | Body | String | 是 | `yyyy-MM-dd` | 签署日期 |
| `autoRenew` | Body | Boolean | 是 | `true` / `false` | 自动续约;`false` 是有效值 |
| `startDate` | Body | String | 是 | `yyyy-MM-dd` | 有效期开始 |
| `endDate` | Body | String | 是 | 不早于 `startDate` | 有效期结束 |
| `scanFileUrl` | Body | String | 是 | 非空,最长 1000 | 合同附件永久地址 |
| `contractType` | Body | String/null | 否 | `FRAME` / `SINGLE_TRIP` / `PURCHASE` | 合同类型 |
| `amount` | Body | String/null | 否 | 非负,最多 10 位整数和 2 位小数 | 合同金额 |
| `pricingMode` | Body | String/null | 否 | 最长 100 | 计价方式 |
| `settleCycle` | Body | String/null | 否 | 最长 32 | 结算周期 |
| `remark` | Body | String/null | 否 | 最长 500 | 备注 |
`changeReason` 已从请求模型移除,不要再由新增表单组装。
#### 出参
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 成功为 `200` / `成功` / `true` |
| `data.contractId` | String | 合同 ID |
| `data.contractName` / `contractNo` | String | 合同名称 / 合同编号 |
| `data.status` / `businessLine` | String | 合同状态 / 业务线 |
| `data.relatedMainContract` | String | 关联主合同 |
| `data.signDate` / `startDate` / `endDate` | String | `yyyy-MM-dd` |
| `data.autoRenew` | Boolean | 自动续约 |
| `data.scanFileUrl` | String | 合同附件地址 |
| `data.contractType` / `amount` / `pricingMode` / `settleCycle` / `remark` | 对应类型/null | 可选字段,响应结构不变 |
| `data.updateTime` | String | 合同版本,`yyyy-MM-dd HH:mm:ss` |
#### 请求示例
```json
{
"contractName": "年度服务合同",
"contractNo": "HT-2026-001",
"status": "SIGNED",
"businessLine": "旅行服务",
"relatedMainContract": "MAIN-2026-001",
"signDate": "2026-09-07",
"autoRenew": false,
"startDate": "2026-09-07",
"endDate": "2027-09-06",
"scanFileUrl": "https://files.example.com/contracts/demo.pdf",
"contractType": "FRAME",
"amount": "1200.50",
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"remark": null
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"contractId": "2097000000000000001",
"contractName": "年度服务合同",
"contractNo": "HT-2026-001",
"contractType": "FRAME",
"signDate": "2026-09-07",
"startDate": "2026-09-07",
"endDate": "2027-09-06",
"businessLine": "旅行服务",
"relatedMainContract": "MAIN-2026-001",
"autoRenew": false,
"amount": "1200.50",
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"status": "SIGNED",
"scanFileUrl": "https://files.example.com/contracts/demo.pdf",
"remark": null,
"updateTime": "2026-09-07 20:00:00"
},
"success": true
}
```
#### 空数据 / 降级响应
没有空成功数据。十项必填字段缺少、为 `null` 或文本为空白时均失败,`data` 为 `null`。
#### 错误响应
```json
{"code":400,"message":"合同编号不能为空","data":null,"success":false}
```
#### 业务边界
- 前端提交前应同时校验十项必填;业务错误可能仍使用 HTTP 200,必须判断响应体 `code` 和 `success`。
- `autoRenew=false` 不得被空值过滤器删除;`startDate` 不得晚于 `endDate`。
- 权限、供应商状态、审批冻结、幂等和错误码均保持原规则。
### 2. 编辑供应商合同 `PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update`
**VO**: `SupplierContractUpdateReqVO / SupplierContractRespVO`
#### 使用场景
完整编辑一份已有合同,不再填写编辑原因。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `supplierId` | Path | String | 是 | 正整数 ID | 所属供应商 |
| `contractId` | Path | String | 是 | 正整数 ID | 目标合同 |
| `contractName` | Body | String | 是 | 非空,最长 500 | 合同名称 |
| `contractNo` | Body | String | 是 | 非空,最长 100 | 合同编号 |
| `status` | Body | String | 是 | `DRAFT` / `ACTIVE` / `SIGNED` / `EXPIRED` | 合同状态 |
| `businessLine` | Body | String | 是 | 非空,最长 100 | 业务线 |
| `relatedMainContract` | Body | String | 是 | 非空,最长 100 | 关联主合同引用 |
| `signDate` | Body | String | 是 | `yyyy-MM-dd` | 签署日期 |
| `autoRenew` | Body | Boolean | 是 | `true` / `false` | 自动续约 |
| `startDate` | Body | String | 是 | `yyyy-MM-dd` | 有效期开始 |
| `endDate` | Body | String | 是 | 不早于 `startDate` | 有效期结束 |
| `scanFileUrl` | Body | String | 是 | 非空,最长 1000 | 合同附件永久地址 |
| `expectedUpdateTime` | Body | String/null | 否 | `yyyy-MM-dd HH:mm:ss` | 传入时必须匹配合同当前版本 |
| `contractType` / `amount` / `pricingMode` / `settleCycle` / `remark` | Body | 对应类型/null | 否 | 与新增接口一致 | 完整替换时原样回传需保留的可选值 |
`changeReason` 已从请求模型移除,不要再由编辑表单组装。
#### 出参
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 成功为 `200` / `成功` / `true` |
| `data.contractId` | String | 原合同 ID |
| `data.contractName` / `contractNo` / `status` / `businessLine` | String | 编辑后的必填文本字段 |
| `data.relatedMainContract` / `scanFileUrl` | String | 关联主合同 / 合同附件地址 |
| `data.signDate` / `startDate` / `endDate` | String | 编辑后的日期 |
| `data.autoRenew` | Boolean | 编辑后的自动续约值 |
| `data.contractType` / `amount` / `pricingMode` / `settleCycle` / `remark` | 对应类型/null | 编辑后的可选字段 |
| `data.updateTime` | String | 新合同版本,供下一次编辑或删除使用 |
#### 请求示例
```json
{
"contractName": "年度服务合同(续签)",
"contractNo": "HT-2026-001-A",
"status": "ACTIVE",
"businessLine": "旅行服务",
"relatedMainContract": "MAIN-2026-001",
"signDate": "2026-09-07",
"autoRenew": true,
"startDate": "2026-09-07",
"endDate": "2028-09-06",
"scanFileUrl": "https://files.example.com/contracts/demo-a.pdf",
"contractType": "FRAME",
"amount": "1500.00",
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"remark": "续签",
"expectedUpdateTime": "2026-09-07 20:00:00"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"contractId": "2097000000000000001",
"contractName": "年度服务合同(续签)",
"contractNo": "HT-2026-001-A",
"contractType": "FRAME",
"signDate": "2026-09-07",
"startDate": "2026-09-07",
"endDate": "2028-09-06",
"businessLine": "旅行服务",
"relatedMainContract": "MAIN-2026-001",
"autoRenew": true,
"amount": "1500.00",
"pricingMode": "按团结算",
"settleCycle": "MONTHLY",
"status": "ACTIVE",
"scanFileUrl": "https://files.example.com/contracts/demo-a.pdf",
"remark": "续签",
"updateTime": "2026-09-07 20:00:01"
},
"success": true
}
```
#### 空数据 / 降级响应
没有空成功数据。历史合同仍可读取;编辑历史合同前须先补齐十项必填字段。
#### 错误响应
```json
{"code":400,"message":"合同附件不能为空","data":null,"success":false}
```
#### 业务边界
- 编辑仍是完整替换;十项必填字段必须全部提交,可选字段需按是否保留正确回传。
- `expectedUpdateTime` 可省略,提交时应使用详情返回的合同版本;版本过期返回 `395014`。
- 删除接口不在本次变化内,删除仍必须提交 `changeReason` 并产生删除审计。
## 四、契约约束与正确调用方式
1. 新增、编辑表单将十项字段全部标为必填,尤其不得把 `autoRenew=false` 当空值。
2. 将 `contractNo` 的页面标签统一改为“合同编号”。
3. 移除新增、编辑的登记原因输入与校验,请求体不再组装 `changeReason`。
4. 编辑时从详情的 `contracts[]` 完整回填并提交;隐藏的可选字段继续原样保留。
5. 删除合同继续沿用原因弹框和原 DELETE 请求体,不要一并移除。
## 五、数据库行为
新增、编辑成功后保存完整合同值,但不新增供应商变更审计;校验失败时合同与审计均不变化。删除仍为软删除并保留原删除审计。本次无表结构或历史数据迁移。
## 六、边界行为
- 缺少任一必填字段:`code=400`、`success=false`,不会写入或修改合同。
- 未登录:`code=401`;无写权限:`395002`;合同不存在或归属错误:`395051`;日期倒置:`395054`;无实际变化:`395057`。
- 历史合同可能含空字段,读取不受影响;再次编辑时须补齐十项。
- 旧请求额外携带 `changeReason` 不会失败,但后端不保存该值,也不产生新增、编辑审计。
## 六.6、修改前后对比
| 字段 / 行为 | 修改前 | 修改后 |
|---|---|---|
| 十项业务字段 | 均可省略或清空 | 新增、编辑全部必填 |
| `contractNo` 页面文案 | 补合同编号 | 合同编号 |
| 新增、编辑 `changeReason` | 必填 | 不需要;旧请求多传兼容忽略 |
| 新增、编辑供应商变更审计 | 产生审计 | 不产生审计 |
| 删除原因与审计 | 原因必填并产生审计 | 不变 |
## 六.7、影响评估
- **是否破坏向后兼容**:对缺少十项中任一字段的旧新增、编辑请求是收紧变化;旧请求多传 `changeReason` 仍兼容。
- **前端是否必须同步上线**:是。必须调整必填、文案和新增/编辑原因交互。
- **前端 workaround 清理点**:删除新增、编辑原因弹框及 `changeReason` 组装;保留删除原因流程。
## 七、不影响范围
- 仅影响管理后台供应商合同新增、编辑。
- 不修改合同删除、供应商主体、收款账户、小程序接口、响应字段、数据库结构及历史合同读取。
## 八、测试环境已验证
- POST 与 PUT 各对十项字段逐项省略,共 20 个请求均返回 `code=400`,失败路径零写入。
- 不带 `changeReason` 的新增、编辑均成功;`autoRenew=false` 正常保存,旧请求多传 `changeReason` 仍兼容。
- 新增、编辑对应的供应商变更审计均为 0;临时合同已删除,原删除审计保持存在。
**当前状态:后端已部署并验证;待前端处理。**
## 十、相关文档
- [Issue #7290](https://git.1814.love:8443/wx/HL/issues/7290)
- [PR #7298](https://git.1814.love:8443/wx/HL/pulls/7298)
- [被本文覆盖的合同旧口径 #6842](https://git.1814.love:8443/wx/HL/issues/6842)
## 关联 / 联系人
### 链接
- **Issue**: [#7290](https://git.1814.love:8443/wx/HL/issues/7290)
- **PR**: [#7298](https://git.1814.love:8443/wx/HL/pulls/7298)
- **Merge commit**: [29ee134f330ca9524d6e74090a80aea3594b334e](https://git.1814.love:8443/wx/HL/commit/29ee134f330ca9524d6e74090a80aea3594b334e)
### 联系人
- **后端负责人**: @lc