文件
hl-api-changelog/changelogs-v2/2026-09/18_7919_供应商合同改为主合同补充合同并按资源选择主合同-修改接口-管理后台.md
Mimingguang 514ab2cf1f
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): #7919 供应商合同主/补充合同前端回写 verified(mmg)
前端 hl-admin 按本条完成破坏性表单重构并验证,回写 frontmatter:
frontend_status=verified + owner mmg + ref dde41d86ffa2a6bd2cd0ae10b96e0ab1d78821fa
+ verified_at 2026-09-18 + status_note 追加实现与验证说明。
2026-09-18 12:23:13 +08:00

21 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 7919 供应商合同改为主合同/补充合同,业务线改为资源并按资源选择主合同 admin lc(GIT) 修改接口 deployed verified verified mmg dde41d86ffa2a6bd2cd0ae10b96e0ab1d78821fa 2026-09-18 PR #7924 已合并 dev-v3,合并提交 ac6b9a3b1 由部署任务 b958fc83 发布到 TEST,并经真实 Gateway 验收 30/30 通过;前端需按本条改表单与取数。[mmg 2026-09-18 已实现并验证] 供应商合同登记/编辑弹窗业务线文本块改「合同类型」可见必填(MAIN/SUPPLEMENT)+「资源」filterable 下拉(composite key module|id 同设两字段,buildBody split 拆分),relatedMainContract 文本块改 relatedMainContractId 下拉仅 SUPPLEMENT 渲染;级联走显式 @update:value 非 watch(换资源/切类型清空已选主合同并按新资源重拉 main-options,fillForm 编程回填直接 loadMainOptions 不清已选值防 fill-race);历史非新枚举类型回显归 null 强制补齐;列表列改业务线→资源 resourceName/关联主合同→relatedMainContractNo 并新增合同类型列;#6842 隐藏字段语义不变整份回传;新增 getSupplierContractMainOptions 封装,395034/395038/395052/395064 透 message;两弹窗 spec 重写/对齐定向 35/35+scoped checkpoint 全绿(全量 Vitest+生产构建)。 2026-09-18 dev-v3

供应商合同: 合同类型改为主合同/补充合同,业务线改为资源,补充合同按资源选择主合同

服务: hl-resource-service (端口 8082) PR: #7924 Issue: #7919 日期: 2026-09-18 影响范围: 管理后台供应商详情「合同信息」的新增/编辑合同表单与合同列表


⚠️ 关键变化

  • 合同新增、编辑不再接收 businessLine(业务线)和 relatedMainContract(手填的关联主合同文字),改为 resourceModule + resourceId(资源)和 relatedMainContractId(关联主合同 ID)。
  • contractType 由可选的 FRAME/SINGLE_TRIP/PURCHASE 改为必填的 MAIN(主合同)/ SUPPLEMENT(补充合同),旧三个值会被拒绝。
  • 前端不改就无法保存合同(缺合同类型、缺资源都会报 400),需要前端同步上线。

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 独立登记供应商合同 POST /admin/supplier/items/{supplierId}/contracts/add 请求体与响应体字段变更 合同类型必填主/补充;资源替代业务线;关联主合同改为 ID
2 独立更新供应商合同 PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update 请求体与响应体字段变更 同上
3 查询可选主合同 GET /admin/supplier/items/{supplierId}/contracts/main-options 新增接口 按供应商 + 资源列出补充合同可关联的主合同
4 查询供应商基本信息 GET /admin/supplier/items/{supplierId}/basic-info/view 响应体字段变更 contracts[] 去掉业务线/手填关联主合同,新增资源与关联主合同字段

三、接口详情

1. 独立登记供应商合同 POST /admin/supplier/items/{supplierId}/contracts/add

VO: SupplierContractCreateReqVO → Result<SupplierContractRespVO>

使用场景

在供应商详情「合同信息」新增一份合同。资源从该供应商「资源信息」(GET /admin/supplier/items/{supplierId}/resource-info/page 返回的 resourceModule + resourceId)中选;合同类型为补充合同时,关联主合同从接口 3 的返回里选。

入参

字段 位置 类型 必填 约束 说明
supplierId Path String(Long) ✅ 正整数 供应商 ID
contractType Body String ✅ MAIN / SUPPLEMENT 合同类型:主合同 / 补充合同(新)
resourceModule Body String ✅ 资源信息的资源模块编码,如 HOTEL 资源模块(新,替代 businessLine)
resourceId Body String(Long) ✅ 正整数;须为该供应商当前绑定的资源 资源 ID(新,替代 businessLine)
relatedMainContractId Body String(Long) 补充合同✅ / 主合同不传 正整数;须为同一供应商、同一资源下的主合同 关联主合同 ID(新,替代 relatedMainContract);主合同传了也不保存
amount Body Number ❌ 0~9999999999.99,最多两位小数 签约金额(字段不变,名称改为签约金额)
contractName / contractNo / signDate / startDate / endDate / autoRenew / status / scanFileUrl Body - ✅ 与原来相同 其余必填项不变
pricingMode / settleCycle / remark Body String ❌ 与原来相同 不变
businessLine / relatedMainContract Body - - 已移除 传了会被忽略

出参 Result<SupplierContractRespVO>

字段 类型 说明
contractType String MAIN / SUPPLEMENT
resourceModule String 资源模块
resourceId String 资源 ID
resourceName String 资源当前名称;资源已删除或暂时读不到时为 null
relatedMainContractId String 关联主合同 ID;主合同为 null
relatedMainContractNo String 关联主合同的合同编号;主合同为 null
amount String 签约金额
其余字段 - 与原来相同;businessLine、relatedMainContract 不再返回

请求示例

{
  "contractName": "满洲里饭店补充协议",
  "contractNo": "HT-2026-009",
  "contractType": "SUPPLEMENT",
  "resourceModule": "HOTEL",
  "resourceId": "3001000000000000010",
  "relatedMainContractId": "2100783918801174530",
  "amount": 20000.00,
  "signDate": "2026-09-18",
  "startDate": "2026-09-18",
  "endDate": "2027-09-17",
  "autoRenew": false,
  "status": "SIGNED",
  "scanFileUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/other/xxx.pdf"
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "amount": null,
    "autoRenew": false,
    "contractId": "2100784143032860674",
    "contractName": "HL7919-TEST-S1",
    "contractNo": "HL7919-TEST-S1",
    "contractType": "SUPPLEMENT",
    "endDate": "2027-09-17",
    "pricingMode": null,
    "relatedMainContractId": "2100783918801174530",
    "relatedMainContractNo": "HL7919-TEST-M1",
    "remark": null,
    "resourceId": "3001000000000000010",
    "resourceModule": "HOTEL",
    "resourceName": "满洲里饭店(百年俄式)",
    "scanFileUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/other/xxx.pdf",
    "settleCycle": null,
    "signDate": "2026-09-18",
    "startDate": "2026-09-18",
    "status": "SIGNED",
    "updateTime": "2026-09-18 11:09:05"
  },
  "success": true
}

空数据 / 降级响应

资源名称读取失败(如车队服务暂不可用)时合同照常保存,resourceName 为 null。

错误响应

{
  "code": 395064,
  "message": "关联主合同须为同一供应商、同一资源下的主合同",
  "success": false,
  "data": null
}

业务边界

  • 权限与原来相同(supplier:update,FINANCE / SUPER_ADMIN),供应商暂停合作、黑名单、归档或企微审批中时仍拒绝写合同。
  • 资源不是该供应商当前绑定的资源 → 395038 供应商资源关联不存在;资源模块非法 → 395034。
  • 补充合同未传 relatedMainContractId → 395052 关联主合同不能为空;关联的不是同一供应商、同一资源下的主合同(含关联补充合同、其他资源或其他供应商的合同、自己)→ 395064 关联主合同须为同一供应商、同一资源下的主合同。
  • 失败请求不写库。

2. 独立更新供应商合同 PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update

VO: SupplierContractUpdateReqVO → Result<SupplierContractRespVO>

使用场景

编辑一份合同。整份替换,入参与接口 1 相同,另可带 expectedUpdateTime(取详情里该合同的 updateTime)。历史合同(类型为空或框架/单团/采购、没有资源)编辑保存时必须按新规则补齐合同类型与资源。

入参

字段 位置 类型 必填 约束 说明
supplierId / contractId Path String(Long) ✅ 正整数 供应商 ID / 合同 ID
contractType Body String ✅ MAIN / SUPPLEMENT 同接口 1
resourceModule / resourceId Body String ✅ 同接口 1 同接口 1
relatedMainContractId Body String(Long) 补充合同✅ 同接口 1;不能是本合同自己 同接口 1
expectedUpdateTime Body String ❌ yyyy-MM-dd HH:mm:ss 乐观版本,不变
其余字段 Body - - 与接口 1 相同 -

出参 Result<SupplierContractRespVO>

字段 类型 说明
同接口 1 - 同接口 1

请求示例

{
  "contractName": "满洲里饭店主合同",
  "contractNo": "HT-2026-008",
  "contractType": "MAIN",
  "resourceModule": "HOTEL",
  "resourceId": "3001000000000000010",
  "amount": 20000.00,
  "signDate": "2026-09-18",
  "startDate": "2026-09-18",
  "endDate": "2027-09-17",
  "autoRenew": false,
  "status": "SIGNED",
  "scanFileUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/other/xxx.pdf",
  "expectedUpdateTime": "2026-09-18 15:00:00"
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "amount": "20000.00",
    "autoRenew": false,
    "contractId": "2100783918801174530",
    "contractName": "HL7919-TEST-M1",
    "contractNo": "HL7919-TEST-M1",
    "contractType": "MAIN",
    "endDate": "2027-09-17",
    "pricingMode": null,
    "relatedMainContractId": null,
    "relatedMainContractNo": null,
    "remark": "主合同带关联主合同ID",
    "resourceId": "3001000000000000010",
    "resourceModule": "HOTEL",
    "resourceName": "满洲里饭店(百年俄式)",
    "scanFileUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/other/xxx.pdf",
    "settleCycle": null,
    "signDate": "2026-09-18",
    "startDate": "2026-09-18",
    "status": "SIGNED",
    "updateTime": "2026-09-18 11:08:50"
  },
  "success": true
}

空数据 / 降级响应

同接口 1,资源名称读取失败时 resourceName 为 null。

错误响应

{
  "code": 395038,
  "message": "供应商资源关联不存在",
  "success": false,
  "data": null
}

业务边界

  • 错误码与接口 1 相同;另有原有的 395014 并发修改、395051 合同不存在、395057 无变化。
  • 主合同改动时不检查是否已被补充合同关联。

3. 查询可选主合同 GET /admin/supplier/items/{supplierId}/contracts/main-options

VO: SupplierMainContractOptionReqVO → Result<List<SupplierMainContractOptionRespVO>>

使用场景

新增或编辑补充合同、选定资源后,取该供应商在该资源下的主合同,作为「关联主合同」的可选项。

入参

字段 位置 类型 必填 约束 说明
supplierId Path String(Long) ✅ 正整数 供应商 ID
resourceModule Query String ✅ 资源模块编码 补充合同所选资源的模块
resourceId Query String(Long) ✅ 正整数 补充合同所选资源的 ID

出参 Result<List<SupplierMainContractOptionRespVO>>

字段 类型 说明
contractId String 主合同 ID,保存补充合同时作为 relatedMainContractId 传回
contractNo String 合同编号
contractName String 合同名称

请求示例

GET /admin/supplier/items/2091381643661983746/contracts/main-options?resourceModule=HOTEL&resourceId=3001000000000000010

响应示例

{
  "code": 200,
  "data": [
    {
      "contractId": "2100783918801174530",
      "contractName": "HL7919-TEST-M1",
      "contractNo": "HL7919-TEST-M1"
    }
  ],
  "message": "成功",
  "success": true,
  "traceId": null
}

空数据 / 降级响应

{ "code": 200, "message": "成功", "data": [], "success": true }

错误响应

{ "code": 395034, "message": "不支持的资源模块", "success": false, "data": null }

业务边界

  • 权限同供应商详情(supplier:view,ADMIN / FINANCE / SUPER_ADMIN)。
  • 只返回未删除、类型为主合同、资源完全相同的合同,按合同 ID 升序;补充合同不会出现在结果中。

4. 查询供应商基本信息 GET /admin/supplier/items/{supplierId}/basic-info/view

VO: SupplierBasicInfoRespVO.contracts[] → SupplierContractRespVO

使用场景

供应商详情「合同信息」列表与编辑回显。

入参

字段 位置 类型 必填 约束 说明
supplierId Path String(Long) ✅ 正整数 不变

出参 Result<SupplierBasicInfoRespVO>

字段 类型 说明
contracts[].contractType String MAIN / SUPPLEMENT;历史合同可能为 null 或 FRAME/SINGLE_TRIP/PURCHASE
contracts[].resourceModule / resourceId / resourceName String 资源;历史合同为 null
contracts[].relatedMainContractId / relatedMainContractNo String 关联主合同;主合同与历史合同为 null,主合同被删除后编号为 null
contracts[].amount String 签约金额
contracts[].businessLine / relatedMainContract - 不再返回

请求示例

GET /admin/supplier/items/2091381643661983746/basic-info/view

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "supplierId": "2091381643661983746",
    "...": "其余字段不变",
    "contracts": [
      {
        "amount": "20000.00",
        "autoRenew": false,
        "contractId": "2100783918801174530",
        "contractName": "HL7919-TEST-M1",
        "contractNo": "HL7919-TEST-M1",
        "contractType": "MAIN",
        "endDate": "2027-09-17",
        "pricingMode": null,
        "relatedMainContractId": null,
        "relatedMainContractNo": null,
        "remark": "主合同带关联主合同ID",
        "resourceId": "3001000000000000010",
        "resourceModule": "HOTEL",
        "resourceName": "满洲里饭店(百年俄式)",
        "scanFileUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/other/xxx.pdf",
        "settleCycle": null,
        "signDate": "2026-09-18",
        "startDate": "2026-09-18",
        "status": "SIGNED",
        "updateTime": "2026-09-18 11:08:50"
      },
      {
        "amount": null,
        "autoRenew": false,
        "contractId": "2100784143032860674",
        "contractName": "HL7919-TEST-S1",
        "contractNo": "HL7919-TEST-S1",
        "contractType": "SUPPLEMENT",
        "endDate": "2027-09-17",
        "pricingMode": null,
        "relatedMainContractId": "2100783918801174530",
        "relatedMainContractNo": "HL7919-TEST-M1",
        "remark": null,
        "resourceId": "3001000000000000010",
        "resourceModule": "HOTEL",
        "resourceName": "满洲里饭店(百年俄式)",
        "scanFileUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/test/other/xxx.pdf",
        "settleCycle": null,
        "signDate": "2026-09-18",
        "startDate": "2026-09-18",
        "status": "SIGNED",
        "updateTime": "2026-09-18 11:09:05"
      }
    ]
  },
  "success": true
}

空数据 / 降级响应

没有合同时 contracts 为 [];资源名称读取失败时对应合同的 resourceName 为 null,详情其余部分正常返回。

错误响应

{ "code": 395001, "message": "供应商不存在", "success": false, "data": null }

业务边界

  • 其余字段不变。历史合同原有的业务线、手填关联主合同文字仍留在库中,但接口不再返回。

四、契约约束与正确调用方式

场景 payload
✅ 主合同 { "contractType": "MAIN", "resourceModule": "HOTEL", "resourceId": "3001000000000000010", ... }(不传 relatedMainContractId)
✅ 补充合同 { "contractType": "SUPPLEMENT", "resourceModule": "HOTEL", "resourceId": "3001000000000000010", "relatedMainContractId": "<接口 3 返回的 contractId>", ... }
❌ 旧合同类型 { "contractType": "FRAME", ... } → 400 合同类型不合法
❌ 补充合同不带主合同 { "contractType": "SUPPLEMENT", ... } → 395052
❌ 选了另一家酒店的主合同 relatedMainContractId 属于其他资源 → 395064
❌ 资源不属于该供应商 resourceId 未绑定在该供应商下 → 395038

五、数据库行为

前端提交 合同类型 资源 关联主合同
主合同,带或不带 relatedMainContractId MAIN 按提交保存 保存为空
补充合同 + 合法主合同 SUPPLEMENT 按提交保存 保存所选主合同 ID
任一校验失败 - - 不写库

六、边界行为

  • 未登录 → 401(网关拦截)。
  • 资源名称读取失败 → resourceName 为 null,不影响保存和详情。
  • 历史合同 → 类型保持原值、资源与关联主合同为 null,不报错;编辑保存时须补齐新必填项。

六.5、枚举 / 数据字典

contractType

所属字段: contractType | 类型: String

值 中文 说明
MAIN 主合同 不关联其他合同
SUPPLEMENT 补充合同 必须关联同一供应商、同一资源下的主合同

resourceModule

所属字段: resourceModule | 类型: String

取值与供应商「资源信息」resourceModule 相同:SCENIC 景区、RESTAURANT 餐厅、SUPPLIES 备品、SUPPLIES_COMBO 组合配品、ACTIVITY 游玩项目、HOTEL 酒店、SERVICE 服务、COST_ITEM 额外成本、STAFF 服务人员、VEHICLE 车辆。

六.6、修改前后对比

字段级对比

字段 改前 改后
contractType 可选,FRAME/SINGLE_TRIP/PURCHASE 必填,MAIN/SUPPLEMENT
businessLine 必填文字 移除,改为 resourceModule + resourceId(必填)
relatedMainContract 必填文字 移除,改为 relatedMainContractId(补充合同必填)
amount 合同金额,可选 签约金额,可选,规则不变
响应 返回 businessLine、relatedMainContract 返回 resourceModule、resourceId、resourceName、relatedMainContractId、relatedMainContractNo

行为级对比

行为 改前 改后
关联主合同 所有合同都要填一段文字 主合同不填;补充合同从同一供应商、同一资源的主合同中选
可选主合同 无 新增接口 3

六.7、影响评估

  • 是否破坏向后兼容: 是(请求体必填字段变化)
  • 前端是否必须同步上线: 是
  • 前端 workaround 清理点: 业务线、关联主合同两个文本输入框及其必填校验

七、不影响范围

  • 仅影响: 供应商详情「合同信息」的新增、编辑与列表显示
  • 零影响: 合同删除接口、供应商档案/审批/资源绑定等其他接口

八、测试环境已验证

部署:dev-v3@ac6b9a3b1(合并 PR #7924)经部署任务 b958fc83 发布到 TEST,hl-resource-service 双实例滚动完成。验证供应商:HL6195-TEST-A-20260823122514(2091381643661983746)。

  • 后端:hl-resource-service 全模块回归 2997 个测试,0 失败,0 错误;数据库迁移 V20260918_002 已在 TEST 执行成功。
  • TEST 网关(部署提交 ac6b9a3b1):主合同新增/改签约金额回读一致;资源名称回读「满洲里饭店(百年俄式)」;主合同传关联主合同 ID 后回读为空;可选主合同按资源只返回对应主合同;补充合同关联主合同后回读编号;未绑定资源 395038、补充合同缺主合同 395052、关联其他资源/补充合同/其他供应商合同 395064、旧类型 FRAME 400,失败请求均未写库;历史 FRAME 合同照常返回。

十、相关文档

  • 关联 Issue: wx/HL#7919
  • 关联 PR: wx/HL#7924
  • 资源选择来源:GET /admin/supplier/items/{supplierId}/resource-info/page(已有接口,不变)

关联 / 联系人

链接

联系人

  • 后端负责人: @lc