16 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 | 6654 | 供应商合同字段可空与编辑信息入口 | admin | lc(GIT) | 修改接口 | deployed | verified | verified | mmg | 5fe4e6fa | v2.1 | 2026-08-29 | PR #6665 已合并 dev-v3;hl-resource-service 已通过 Deploy Panel 任务 3dde08b9 部署提交 65ac86e9312b22dd2fc7313049a693fdb0ccad59。真实 TEST Gateway 已验证合同业务字段全空登记、补全、清空、删除,changeReason 必填失败零写入,expectedUpdateTime 省略成功及过期值拒绝;测试草稿已清理。合同与结算 table 的页面顺序和消费映射待前端处理。 | 2026-08-29 | dev-v3 |
供应商合同字段可空与编辑信息入口
供应商合同继续走独立登记接口,不并入供应商档案聚合。合同的 12 个业务字段现在全部可空,changeReason 继续必填;合同更新、删除的 expectedUpdateTime 改为可选,但一旦提供,过期版本仍会被拒绝。
管理端新建供应商时可完全不登记合同,取得 supplierId 后再补录;编辑页通过基本信息详情读取合同,通过既有账户接口读取和维护结算信息。页面按“资质证照 → 合同信息 → 结算信息”排列属于前端消费工作,当前状态为待前端处理。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 独立登记供应商合同 | POST | /admin/supplier/items/{supplierId}/contracts/add |
修改请求 | 12 个合同业务字段全部可空,changeReason 仍必填 |
| 2 | 独立更新供应商合同 | PUT | /admin/supplier/items/{supplierId}/contracts/{contractId}/update |
修改请求 | 合同业务字段与新增一致且可清空,expectedUpdateTime 可选 |
| 3 | 独立删除供应商合同 | DELETE | /admin/supplier/items/{supplierId}/contracts/{contractId}/del |
修改请求 | expectedUpdateTime 可选,changeReason 仍必填 |
| 4 | 查询供应商基本信息 | GET | /admin/supplier/items/{supplierId}/basic-info/view |
修改响应语义 | contracts[] 的全部业务字段允许返回 null |
三、接口详情
1. 独立登记供应商合同 POST /admin/supplier/items/{supplierId}/contracts/add
VO: SupplierContractCreateReqVO / SupplierContractRespVO
使用场景
供应商已经创建但合同资料尚未齐全时,可先登记一条纯合同记录,之后再补充。若暂时不需要合同记录,供应商新建请求直接省略废弃的 contracts 字段即可。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
contractName |
Body | String | 否 | 最长 500 字符 | 合同名称 |
contractNo |
Body | String | 否 | 最长 100 字符 | 合同编号 |
contractType |
Body | String | 否 | FRAME / SINGLE_TRIP / PURCHASE |
合同类型 |
signDate |
Body | String | 否 | yyyy-MM-dd |
签署日期 |
startDate |
Body | String | 否 | yyyy-MM-dd |
有效期开始;仅与同时提供的结束日期做区间校验 |
endDate |
Body | String | 否 | yyyy-MM-dd |
有效期结束;两端都提供时不得早于开始日期 |
amount |
Body | String | 否 | 非负,最多 10 位整数和 2 位小数 | 合同金额 |
pricingMode |
Body | String | 否 | 最长 100 字符 | 计价方式 |
settleCycle |
Body | String | 否 | 最长 32 字符 | 结算周期说明 |
status |
Body | String | 否 | DRAFT / ACTIVE / EXPIRED |
合同状态 |
scanFileUrl |
Body | String | 否 | 最长 1000 字符 | 扫描件永久地址 |
remark |
Body | String | 否 | 最长 500 字符 | 合同备注 |
changeReason |
Body | String | 是 | 去空白后非空,最长 500 字符 | 审计原因,不属于可空业务字段 |
出参 Result<SupplierContractRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
data.contractId |
String | 新合同雪花 ID |
data.contractName 等 12 个业务字段 |
对应类型/null | 未填写的字段返回 null |
data.updateTime |
String | 服务端合同版本,格式 yyyy-MM-dd HH:mm:ss |
请求示例
{
"changeReason": "合同资料暂缺,先登记记录"
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"contractId": "2094000000000000001",
"contractName": null,
"contractNo": null,
"contractType": null,
"signDate": null,
"startDate": null,
"endDate": null,
"amount": null,
"pricingMode": null,
"settleCycle": null,
"status": null,
"scanFileUrl": null,
"remark": null,
"updateTime": "2026-08-29 16:10:00"
}
}
空数据 / 降级响应
请求体除 changeReason 外可以不包含任何字段;成功后返回合同对象,业务字段全部为 null。不要把 null 自动替换为默认合同类型、状态、日期或金额。
错误响应
缺少审计原因时失败且不新增合同:
{ "code": 400, "message": "变更原因不能为空", "success": false, "data": null }
业务边界
- 要求可信管理员、
FINANCE/SUPER_ADMIN写角色和supplier:update平台权限。 - 合同登记不进入供应商审批,不推进供应商主体
updateTime。 POST /admin/supplier/items/add和供应商资料更新中的废弃contracts字段仍被忽略;前端必须先获得supplierId,再按需调用本接口。
2. 独立更新供应商合同 PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update
VO: SupplierContractUpdateReqVO / SupplierContractRespVO
使用场景
补齐、修改或清空一条已登记合同。该接口执行完整替换:未传或传 null 的业务字段会保存为 null,不是“保持原值”。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
contractId |
Path | String | 是 | 正整数 ID 字符串 | 目标合同 |
| 新增接口的 12 个业务字段 | Body | 对应类型 | 否 | 与新增一致 | 完整替换;省略即清空对应字段 |
changeReason |
Body | String | 是 | 去空白后非空,最长 500 字符 | 审计原因 |
expectedUpdateTime |
Body | String | 否 | yyyy-MM-dd HH:mm:ss |
提供时必须等于当前合同版本;省略时由服务端锁串行更新 |
出参 Result<SupplierContractRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
data.contractId |
String | 合同雪花 ID |
data.contractName 等 12 个业务字段 |
对应类型/null | 完整替换后的值,允许为 null |
data.updateTime |
String | 更新后的合同版本 |
请求示例
{
"contractName": "2026 年度框架合同",
"contractType": "FRAME",
"startDate": "2026-09-01",
"endDate": "2027-08-31",
"status": "ACTIVE",
"changeReason": "补齐已签署合同"
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"contractId": "2094000000000000001",
"contractName": "2026 年度框架合同",
"contractType": "FRAME",
"startDate": "2026-09-01",
"endDate": "2027-08-31",
"status": "ACTIVE",
"updateTime": "2026-08-29 16:10:01"
}
}
空数据 / 降级响应
仅发送 changeReason 可把 12 个业务字段全部清空。若替换后的业务载荷与当前记录完全相同,返回“未检测到合同实际变化”,不会伪造新版本。
错误响应
提供过期版本时继续失败且零写入:
{ "code": 395014, "message": "数据已被修改,请刷新后重试", "success": false, "data": null }
业务边界
expectedUpdateTime省略不等于关闭并发保护;分布式合同锁和数据库行锁仍串行化同一合同写入。- 客户端若选择发送版本,必须使用最近一次详情或写响应中的
updateTime。 changeReason始终必填,失败时合同和审计均不写入。
3. 独立删除供应商合同 DELETE /admin/supplier/items/{supplierId}/contracts/{contractId}/del
VO: SupplierContractDeleteReqVO / Void
使用场景
删除不再保留的合同登记。删除为软删除,并记录完整审计原因。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
contractId |
Path | String | 是 | 正整数 ID 字符串 | 目标合同 |
expectedUpdateTime |
Body | String | 否 | yyyy-MM-dd HH:mm:ss |
提供时执行版本匹配 |
changeReason |
Body | String | 是 | 去空白后非空,最长 500 字符 | 删除审计原因 |
出参 Result<Void>
| 字段 | 类型 | 说明 |
|---|---|---|
code |
Integer | 成功时为 200 |
success |
Boolean | 成功时为 true |
data |
null | 删除成功不返回业务对象 |
请求示例
{
"changeReason": "合同登记作废"
}
响应示例
{ "code": 200, "message": "成功", "success": true, "data": null }
空数据 / 降级响应
expectedUpdateTime 可省略,但请求体不能省略,且必须包含非空 changeReason。
错误响应
{ "code": 400, "message": "变更原因不能为空", "success": false, "data": null }
业务边界
- 提供的过期
expectedUpdateTime仍返回395014,不删除、不写审计。 - 删除成功后基本信息详情的
contracts[]不再返回该记录。 - 权限、锁、幂等、审计和软删除边界均保持原有实现。
4. 查询供应商基本信息 GET /admin/supplier/items/{supplierId}/basic-info/view
VO: SupplierBasicInfoRespVO
使用场景
进入供应商编辑页时读取主体、资质和合同。前端将 data.contracts 绑定到“合同信息”table。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
无 Query 参数,无请求体。
出参 Result<SupplierBasicInfoRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
data.contracts |
Array | 当前未软删除合同,按 contractId 升序 |
data.contracts[].contractId |
String | 合同雪花 ID |
data.contracts[] 的 12 个业务字段 |
对应类型/null | 合同登记未填写时返回 null |
data.contracts[].updateTime |
String | 合同当前版本 |
请求示例
GET /admin/supplier/items/2094000000000000000/basic-info/view
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2094000000000000000",
"contracts": [
{
"contractId": "2094000000000000001",
"contractName": null,
"contractType": null,
"startDate": null,
"endDate": null,
"status": null,
"updateTime": "2026-08-29 16:10:00"
}
]
}
}
空数据 / 降级响应
没有合同登记时 data.contracts 返回空数组;单份合同没有填写的业务字段返回 null,二者含义不同。
错误响应
{ "code": 395001, "message": "供应商不存在", "success": false, "data": null }
业务边界
- 只读接口要求可信管理员、可读角色和
supplier:view平台权限。 - 查询不推进主体或合同版本,不触发审批和写副作用。
- 合同 table 的新增、编辑、删除分别调用前三个独立写接口,不把
contracts回传给供应商聚合更新接口。
四、契约约束与正确调用方式
- 新建供应商可完全省略合同;若需登记,先调用
POST /admin/supplier/items/add获取字符串supplierId,再调用合同新增接口。 - 编辑页合同读取使用
GET /admin/supplier/items/{supplierId}/basic-info/view的data.contracts。 - 编辑页结算读取继续使用
GET /admin/supplier/items/{supplierId}/account-info/list;新增账户继续使用POST /admin/supplier/items/{supplierId}/bank-accounts/add,其审批和主体状态门禁不变。 - 新建的
initialAccounts[]与独立账户新增的accounts[]继续复用同一SupplierBankAccountReqVO,可填写项一致:accountType、bankName、bankBranch、accountNo、proofFileUrls、settleMode、accountPeriod、invoiceType、taxRate。新建供应商最多携带 1 项初始账户。 supplierId、contractId、accountId和amount按字符串处理,禁止转为 JavaScript Number。
五、数据库行为
- Flyway migration
V20260829_002将supplier_contract.contract_name、contract_type、start_date、end_date、status从NOT NULL调整为可空。 - 合同创建、更新、删除继续在本服务 schema 内完成;审计与业务写同事务提交或回滚。
- 没有跨 schema 写入,没有新增 Redis、MQ、配置或路由变更。
六、边界行为
- 业务字段为空不等于审计字段可空:合同新增、更新、删除均必须有
changeReason。 expectedUpdateTime仅在合同更新和删除中可省略;提供时仍执行秒级版本比较。- 合同日期只在
startDate与endDate同时存在时校验先后顺序。 - 合同枚举字段为空时不校验;非空时仍只接受既有枚举值。
- 账户查询和新增的既有状态、审批、权限及敏感附件读取规则未改变。
六.6、修改前后对比
| 项目 | 修改前 | 修改后 |
|---|---|---|
| 合同名称、类型、开始日、结束日、状态 | 必填 | 可空 |
| 其余 7 个合同业务字段 | 可空 | 仍可空 |
changeReason |
新增、更新、删除均必填 | 继续必填 |
expectedUpdateTime |
更新、删除必填 | 更新、删除可选;提供过期值仍拒绝 |
| 结算信息字段模型 | 新建与独立账户新增复用同一 VO | 不变,编辑页继续复用既有账户接口 |
六.7、影响评估
- 是否破坏向后兼容: 否;原先完整载荷继续有效,旧客户端继续发送版本也有效。
- 前端是否必须同步上线: 是;需要增加合同/结算 table、允许合同业务控件为空,并保留
changeReason必填。 - 前端 workaround 清理点: 删除合同业务字段的前端强制必填;不要删除
changeReason校验。
七、不影响范围
- 不修改供应商新建、更新、提交请求中的废弃
contracts聚合字段语义。 - 不修改结算账户字段、审批、状态机、默认账户、权限或接口路径。
- 不修改供应商主体的
changeReason、expectedUpdateTime必填规则。 - 不新增业务错误码、Gateway 路由、Redis、MQ 或 Nacos 配置。
八、测试环境已验证
- 合同信息整体省略不影响供应商草稿创建;空业务字段合同可登记并由详情读回。
- 合同可在不传
expectedUpdateTime时补全、清空和删除;传入过期版本返回并发失败且零写入。 - 新增、更新、删除缺少
changeReason均失败且零写入。 - 编辑供应商后合同和初始结算账户摘要保持;结算读取入口可正常访问。
- TEST 验收产生的供应商草稿、合同和初始账户已软删除,并通过详情与结算入口读回确认不可见。
十、相关文档
关联 / 联系人
- 后端负责人: @lc
- 前端状态: 已消费(
frontend_status: verified)。页面「资质→合同→结算」排列现状已符(资质/合同在基本信息区、结算独立账户 Tab),DetailModal 合同表格对全 null 已 EMPTY/renderStatusTag 兜底安全;实质改动在 SupplierContractEditModal——12 业务字段全可空(去 contractName/contractType/status/startDate 必填、status 不再默认 DRAFT)、日期仅两端同填校验、expectedUpdateTime 编辑仅拿到版本才携带,changeReason 仍必填。