--- schema: "hl-changelog/v2" ticket: "7087" title: "供应商新建修改必填资料校验" consumer: "admin" author: "lc(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "72d63c8e" target_release: "" verified_at: "2026-09-04" status_note: "后端已部署并通过 TEST;前端需为供应商新建和编辑表单补齐主体资料、联系人及结算账户必填校验。" updated_at: "2026-09-04" base: "dev-v3" --- # 供应商模块:新建与修改补齐必填资料校验 `POST /admin/supplier/items/add` 新建时必须一次提交完整资料;`PUT /admin/supplier/items/{supplierId}/update` 仍是增量接口,但保存后的供应商聚合必须完整。前端需同步补齐表单必填标识和提交前校验。 ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | 新建供应商草稿 | POST | `/admin/supplier/items/add` | 必填校验收紧 | 主体资料完整,且至少一名联系人、一个初始账户 | | 2 | 修改供应商资料 | PUT | `/admin/supplier/items/{supplierId}/update` | 聚合完整性校验 | 省略字段保留现值;显式空类型、联系人或账户拒绝 | ## 三、接口详情 ### 1. 新建供应商草稿 `POST /admin/supplier/items/add` **VO**: `SupplierDraftSaveReqVO → SupplierWriteRespVO` #### 使用场景 管理端完成供应商新建表单后保存草稿。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `fullName` | Body | String | 是 | 非空,最长 500 | 供应商全称 | | `taxNo` | Body | String | 是 | 合法主体证件号 | 统一社会信用代码 | | `types` | Body | Array | 是 | 至少 1 项,最多 15 项 | `typeCode` 取 `supplier_type` 生效字典值 | | `legalRepresentative` | Body | String | 是 | 非空 | 法定代表人 | | `legalRepresentativeIdNo` | Body | String | 是 | 合法 18 位居民身份证号 | 法人身份证号 | | `legalRepresentativeIdCardFrontUrl` | Body | String | 是 | 公网 HTTPS 永久地址 | 身份证人像面 | | `legalRepresentativeIdCardBackUrl` | Body | String | 是 | 公网 HTTPS 永久地址 | 身份证国徽面 | | `contactPhone` | Body | String | 是 | 合法手机号、座机或 400/800 号码 | 法人联系电话 | | `establishDate` | Body | String | 是 | `yyyy-MM-dd`,不得晚于当天 | 成立日期 | | `balance` | Body | Number | 是 | 最多 16 位整数、2 位小数 | 余额,可为正数、0 或负数 | | `paymentType` | Body | String | 是 | `supplier_payment_type` 生效字典值 | 支付类型 | | `mainCooperation` | Body | String | 是 | 非空 | 主要合作内容 | | `licenseImageUrl` | Body | String | 是 | 非空 | 营业执照影像地址 | | `address` | Body | String | 是 | 非空,最长 500 | 注册地址 | | `contacts` | Body | Array | 是 | 至少 1 项,最多 100 项 | 每项填写姓名、电话及 `sup_content_role` 角色 | | `initialAccounts` | Body | Array | 是 | 当前必须且只能 1 项 | 每项填写账户类型、银行和账号 | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `data.supplierId` | String | 供应商 ID | | `data.supplierNo` | String | 供应商编号 | | `data.status` | String | 新建成功为 `DRAFT` | | `data.initialAccounts` | Array | 初始账户摘要 | | `data.updateTime` | String | 后续修改使用的并发版本 | #### 请求示例 ```json { "fullName": "示例供应商有限公司", "taxNo": "91350211M000100Y46", "types": [{"typeCode": "HOTEL"}], "legalRepresentative": "张三", "legalRepresentativeIdNo": "11010519491231002X", "legalRepresentativeIdCardFrontUrl": "https://example.com/supplier/id-front.jpg", "legalRepresentativeIdCardBackUrl": "https://example.com/supplier/id-back.jpg", "contactPhone": "13800138000", "establishDate": "2020-01-02", "balance": 0, "paymentType": "1", "mainCooperation": "酒店资源合作", "licenseImageUrl": "https://example.com/supplier/license.jpg", "address": "厦门市思明区示例路 1 号", "contacts": [{"contactName": "李四", "contactPhone": "13800138001", "contactRole": "contentBus", "isPrimary": true}], "initialAccounts": [{"accountType": "CORPORATE", "bankName": "示例银行", "accountNo": "6222000012345678"}] } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "supplierId": "2095000000000000001", "supplierNo": "SUP2095000000000000001", "status": "DRAFT", "initialAccounts": [{"accountId": "2095000000000000002", "status": "DRAFT"}], "updateTime": "2026-09-04 16:48:49" } } ``` #### 空数据 / 降级响应 写接口没有空数据成功或降级成功;任一必填项缺失时返回失败响应。 #### 错误响应 ```json {"code":400,"message":"联系人不能为空","data":null,"success":false} ``` #### 业务边界 - 未登录返回业务码 `401`;写入仍要求 `FINANCE` 或 `SUPER_ADMIN` 及 `supplier:create` 权限。 - `types`、`contacts`、`initialAccounts` 传 `null`、省略或空数组均视为缺失。 - 校验失败不创建供应商或子项;响应可能使用 HTTP 200,必须同时检查 `code` 与 `success`。 ### 2. 修改供应商资料 `PUT /admin/supplier/items/{supplierId}/update` **VO**: `SupplierUpdateReqVO → SupplierWriteRespVO` #### 使用场景 管理端编辑既有供应商资料并按最新版本保存。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | `supplierId` | Path | String | 是 | 正整数 ID | 目标供应商 | | `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 取详情最新 `updateTime` | | 主体必填资料 | Body | 原类型 | 条件必填 | 与新建接口相同 | 省略表示保留现值;保存后的有效值必须完整 | | `types` | Body | Array | 条件必填 | 显式提交时至少 1 项 | 省略保留存量,`[]` 拒绝 | | `contacts` | Body | Array | 条件必填 | 显式提交时至少 1 项 | 省略保留存量,`[]` 拒绝 | | `initialAccounts` | Body | Array | 条件必填 | 显式提交时当前必须且只能 1 项 | 省略保留存量,`[]` 拒绝 | | `changeReason` | Body | String | 条件必填 | 最长 500 | `DRAFT` 可省略,其他可修改状态沿用既有规则 | 主体必填资料指:`fullName`、`taxNo`、`legalRepresentative`、`legalRepresentativeIdNo`、`legalRepresentativeIdCardFrontUrl`、`legalRepresentativeIdCardBackUrl`、`contactPhone`、`establishDate`、`balance`、`paymentType`、`mainCooperation`、`licenseImageUrl`、`address`。 #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | `data.supplierId` | String | 供应商 ID | | `data.status` | String | 保存后的状态 | | `data.initialAccounts` | Array | 当前初始账户摘要 | | `data.updateTime` | String | 保存后的新并发版本 | #### 请求示例 ```json { "shortName": "示例供应商", "expectedUpdateTime": "2026-09-04 16:48:49" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "supplierId": "2095000000000000001", "status": "DRAFT", "initialAccounts": [{"accountId": "2095000000000000002", "status": "DRAFT"}], "updateTime": "2026-09-04 16:49:16" } } ``` #### 空数据 / 降级响应 省略未修改字段会保留已存值;接口没有空数据成功或降级成功。 #### 错误响应 ```json {"code":400,"message":"供应商类型不能为空","data":null,"success":false} ``` #### 业务边界 - 写入仍要求 `FINANCE` 或 `SUPER_ADMIN` 及 `supplier:update` 权限。 - 保存前按“请求值 + 已存值”校验完整聚合;存量资料缺项时,必须补齐后才能保存其他修改。 - 非草稿账户继续走独立账户审批;本次不改变状态、并发、审批、幂等或审计规则。 - `expectedUpdateTime` 过期返回既有业务码 `395014`,失败不更新任何聚合数据。 ## 四、契约约束与正确调用方式(接口类必写) | 场景 | 正确调用 | |---|---| | 新建 | 一次提交全部主体必填资料、非空 `types`、非空 `contacts` 和一个 `initialAccounts` | | 修改普通字段 | 先查询详情;已存聚合完整时,只提交变化字段和最新 `expectedUpdateTime` | | 修改关系快照 | 提交完整非空快照;不修改关系时省略对应字段 | | 字典字段 | 提交字典接口返回的 `dictValue`,不要提交中文标签或前端硬编码默认值 | 支付类型当前字典值为 `1`(现付)、`2`(签单)、`3`(月付);供应商类型和联系人角色分别从 `supplier_type`、`sup_content_role` 动态字典读取。 ## 五、数据库行为 - 校验失败不新增或更新供应商聚合数据。 - 本次没有表结构、字段非空约束、数据迁移或历史数据回填。 ## 六、边界行为 - 业务失败可能仍为 HTTP 200;前端必须检查 `success=false` 和业务 `code/message`。 - 必填文本为空白、必填值为 `null`、必填集合为空时均拒绝。 - 更新省略字段表示保留,不等于清空;完整性按保存后的有效聚合判断。 - 超过列表上限、字典值失效、身份证/电话/URL 格式不合法时继续使用既有校验错误。 ## 六.6、修改前后对比 | 行为 | 改前 | 改后 | |---|---|---| | 新建缺主体资料 | 部分字段可缺失并保存草稿 | 任一约定主体必填资料缺失即拒绝 | | 新建缺关系资料 | 类型、联系人或账户可能为空 | 三类均必须非空 | | 修改显式空关系快照 | 可清空部分关系 | `types: []`、`contacts: []`、`initialAccounts: []` 均拒绝 | | 修改省略未变化字段 | 保留现值 | 仍保留现值,并校验最终聚合完整性 | ## 六.7、影响评估 - **是否破坏向后兼容**: 是。依赖不完整草稿或显式清空类型、联系人、初始账户的旧请求会被拒绝。 - **前端是否必须同步上线**: 是。新建和编辑表单均需补齐必填标识、校验提示和提交数据。 - **前端 workaround 清理点**: 不再允许以空数组清空供应商类型、联系人或初始账户。 ## 七、不影响范围 - 响应结构和字段名不变。 - 不改变供应商注册提交接口、权限矩阵、状态机、审批流、并发版本、幂等与审计语义。 - 不改变非草稿账户独立审批入口,也不修改数据库约束。 ## 八、测试环境已验证 - 新建缺注册地址、联系人或账户分别返回业务码 `400`,且无业务数据写入;完整资料创建成功。 - 修改显式空类型、联系人、账户或清空营业执照分别返回业务码 `400`,资料与版本不变;完整存量上的增量修改成功。 - 匿名新建返回业务码 `401`;验收草稿已通过应用删除接口清理,有效聚合数据为零。 ## 十、相关文档 - [后端 Issue #7087](https://git.1814.love:8443/wx/HL/issues/7087) - [后端 PR #7093](https://git.1814.love:8443/wx/HL/pulls/7093) - [历史契约 #6343](https://git.1814.love:8443/wx/HL/issues/6343):其中“新建可空类型、修改可显式清空类型”已被本工单取代。 ## 关联 / 联系人 ### 链接 - **Issue**: [#7087](https://git.1814.love:8443/wx/HL/issues/7087) - **PR**: [#7093](https://git.1814.love:8443/wx/HL/pulls/7093) - **Merge commit**: [e8b96e9a5](https://git.1814.love:8443/wx/HL/commit/e8b96e9a5dd0c75e65157bf889336ef740533da4) ### 联系人 - **后端负责人**: @lc