:fix: 统一内部组织客商判定口径、精简高风险提示文案并移除mock控制器

This commit is contained in:
刘泉佳
2026-10-22 11:35:21 +08:00
parent 86b8babcfc
commit 2b581b7305
3 changed files with 32 additions and 150 deletions
@@ -1,123 +0,0 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.controller;
import cn.hutool.core.util.StrUtil;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tenant.annotation.TenantIgnore;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.Objects;
/**
* HCS 合规回调 Mock 端点
* <p>
* 合同法务审批结果回调(D103)、付款法务审批结果回调(D104)、项目金蝶同步结果回调(D902)、
* 合同金蝶同步结果回传接收(D903)四个入站回调的纯 mock 实现:仅校验来源系统标识后返回固定结果,
* 不读写业务数据、不联动状态。真实处理逻辑保留在对应 Support 中,联调时把端点切回
* {@link } 并调用 Support 方法即可。
*
* @author Chill
*/
@Slf4j
@RestController
@TenantIgnore
@RequestMapping("hcs")
@Tag(name = "HCS合规回调Mock", description = "HCS合规回调Mock端点(仅自测,返回固定 mock 数据)")
public class HcsMockController {
/**
* 来源系统标识(合规方约定值,与 third-party.hcs.src-system 默认值一致)
*/
private static final String SOURCE_SYSTEM = "WLTMS";
/**
* 合同法务审批结果回调(D103)
*/
@PostMapping("/contractLegal/result")
@ApiOperationSupport(order = 15)
@Operation(summary = "合同法务审批结果回调Mock", description = "D103 合规推送合同特批(金蝶同步)结果,仅校验来源系统并返回 mock 数据")
public R<String> contractLegalResult(@RequestBody(required = false) Map<String, Object> param) {
this.checkSourceSystem("合同法务审批结果", param);
return R.data("mock特批结果已处理:approved");
}
/**
* 付款法务审批结果回调(D104)
*/
@PostMapping("/paymentLegal/result")
@ApiOperationSupport(order = 16)
@Operation(summary = "付款法务审批结果回调Mock", description = "D104 合规推送付款特批结果,仅校验来源系统并返回 mock 数据")
public R<String> paymentLegalResult(@RequestBody(required = false) Map<String, Object> param) {
this.checkSourceSystem("付款法务审批结果", param);
return R.data("mock付款特批结果接收已处理:approved");
}
/**
* 项目金蝶同步结果回调(D902)
*/
@PostMapping("/projectLegal/result")
@ApiOperationSupport(order = 18)
@Operation(summary = "项目金蝶同步结果回调Mock", description = "D902 合规推送项目金蝶同步结果,仅校验来源系统并返回 mock 数据")
public R<String> projectLegalResult(@RequestBody(required = false) Map<String, Object> param) {
this.checkSourceSystem("项目金蝶同步结果", param);
return R.data("mock项目金蝶同步结果接收已处理:approved");
}
/**
* 合同金蝶同步结果回传接收(D903)
*/
@PostMapping("/contractKingdee/result")
@ApiOperationSupport(order = 19)
@Operation(summary = "合同金蝶同步结果回传接收Mock", description = "D903 合规推送合同金蝶最终同步结果,仅校验来源系统并返回 mock 数据")
public R<String> contractKingdeeResult(@RequestBody(required = false) Map<String, Object> param) {
this.checkSourceSystem("合同金蝶同步结果", param);
return R.data("mock合同金蝶同步结果接收处理:approved");
}
/**
* 来源系统校验:仅接受合规方约定的来源标识,防伪造回传
*
* @param bizName 业务名称(日志用)
* @param param 回调报文
*/
private void checkSourceSystem(String bizName, Map<String, Object> param) {
String sourceSystem = param == null ? null : Objects.toString(param.get("sourceSystem"), null);
if (!StrUtil.equalsIgnoreCase(sourceSystem, SOURCE_SYSTEM)) {
log.warn("{}推送来源系统标识错误:sourceSystem={}", bizName, sourceSystem);
throw new ServiceException("来源系统标识错误");
}
}
}
@@ -337,22 +337,29 @@ public class HcsContractSyncSupport {
}
/**
* 查询单方客商风险:按名称反查客商编号(§9-U6 编码体系联调确认),反查不到记 skip 留痕不阻断
* 查询单方客商风险:按名称反查客商档案(§9-U6 编码体系联调确认),反查不到记 skip 留痕不阻断;
* 内部组织客商默认无风险跳过(与展示/提交/付款链路口径一致,不调 C101-1)
*/
private RiskResult queryRisk(ContractManage contractManage, String standing, String partyName) {
String customerCode = queryCustomerCode(partyName);
if (StringUtil.isBlank(customerCode)) {
CustomerArchive customerArchive = queryCustomerArchive(partyName);
if (customerArchive == null) {
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
standing + "方[" + partyName + "]反查不到客商编号,跳过风险校验(§9-U6)", null);
return new RiskResult(null, null, false);
}
// 内部组织类型客商默认无风险,不调 C101-1(与展示/提交/付款链路口径一致)
if (HcsCreditCheckSupport.isInternalCustomer(customerArchive)) {
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
standing + "方[" + partyName + "]为内部组织客商,默认无风险跳过校验", null);
return new RiskResult(null, null, false);
}
HcsRiskQueryDTO param = new HcsRiskQueryDTO();
// 【必填】客商编号:按甲乙方名称反查 CustomerArchive 客商编码(§9-U6)
param.setFdNumber(customerCode);
param.setFdNumber(customerArchive.getCustomerCode());
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
if (result == null || !result.isSuccess()) {
log.error("HCS客商风险查询失败响应:{},contractNo={},standing={},fdNumber={}",
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo(), standing, customerCode);
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo(), standing, customerArchive.getCustomerCode());
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
}
HcsRiskVO risk = result.getData();
@@ -534,8 +541,8 @@ public class HcsContractSyncSupport {
// 甲乙方各反查一次客商档案,编号与内部组织判定共用查询结果
CustomerArchive partyAArchive = queryCustomerArchive(contractManage.getPartyA());
CustomerArchive partyBArchive = queryCustomerArchive(contractManage.getPartyB());
boolean partyAInternal = isInternalCustomer(partyAArchive);
boolean partyBInternal = isInternalCustomer(partyBArchive);
boolean partyAInternal = HcsCreditCheckSupport.isInternalCustomer(partyAArchive);
boolean partyBInternal = HcsCreditCheckSupport.isInternalCustomer(partyBArchive);
String partyAType;
String partyBType;
if (partyAInternal || partyBInternal) {
@@ -672,14 +679,6 @@ public class HcsContractSyncSupport {
return templateId;
}
/**
* 按名称反查客商编码(照 KingdeeApBillSyncSupport#queryCustomerCode 惯例,查不到返回 null)
*/
private String queryCustomerCode(String partyName) {
CustomerArchive customerArchive = queryCustomerArchive(partyName);
return customerArchive == null ? null : customerArchive.getCustomerCode();
}
/**
* 按名称反查客商档案(全称或简称精确匹配,查不到返回 null)
*/
@@ -693,15 +692,6 @@ public class HcsContractSyncSupport {
.last("limit 1"));
}
/**
* 内部组织客商判定(集团内我方签约主体,档案登记 customerKind=internal/内部组织)
*/
private boolean isInternalCustomer(CustomerArchive customerArchive) {
return customerArchive != null
&& (HcsSyncConstant.CUSTOMER_KIND_INTERNAL.equals(customerArchive.getCustomerKind())
|| "内部组织".equals(customerArchive.getCustomerKind()));
}
/**
* 关联项目取值:合同所选项目对应合同系统项目ID(blade_project_apply.hcs_fd_id,C102-1 立项同步回填),
* 未选择项目、立项单不存在或未同步合规时返回 null 不传(fdProject 选填)
@@ -118,7 +118,7 @@ public class HcsCreditCheckSupport {
return;
}
// 内部组织类型客商默认无风险,不查 C101-1(与 queryCustomerRisk 展示口径一致)
if (HcsSyncConstant.CUSTOMER_KIND_INTERNAL.equals(customerArchive.getCustomerKind())) {
if (isInternalCustomer(customerArchive)) {
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP,
"收款方[" + payment.getPayeeName() + "]为内部组织客商,默认无风险跳过校验", null);
return;
@@ -212,7 +212,7 @@ public class HcsCreditCheckSupport {
riskVO.setCustomerCode(customerCode);
riskVO.setCustomerName(customerArchive == null ? customerCode : customerArchive.getFullName());
// 内部组织类型客商默认无风险;档案不存在按外部客商查询(不静默放行)
boolean internal = customerArchive != null && HcsSyncConstant.CUSTOMER_KIND_INTERNAL.equals(customerArchive.getCustomerKind());
boolean internal = isInternalCustomer(customerArchive);
riskVO.setCustomerKind(internal ? HcsSyncConstant.CUSTOMER_KIND_INTERNAL : "external");
if (internal) {
riskVO.setRisky(Boolean.FALSE);
@@ -236,7 +236,9 @@ public class HcsCreditCheckSupport {
riskVO.setRiskLevelName(riskLevelName(blackStatus, riskLevel));
riskVO.setRisky(risky);
if (risky) {
riskVO.setRiskTip("【" + riskVO.getCustomerName() + "】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷");
// 风险提示固定短文案(C101-1 未返回风险项明细,不拼接枚举清单):黑名单与高风险区分表述
riskVO.setRiskTip("【" + riskVO.getCustomerName() + "】:"
+ (BLACK_STATUS_YES.equalsIgnoreCase(blackStatus) ? "被列入外部黑名单" : "为高风险客商"));
}
try {
HcsContractCheckLog checkLog = new HcsContractCheckLog();
@@ -271,6 +273,19 @@ public class HcsCreditCheckSupport {
return queryCustomerRisk(customerCode);
}
/**
* 内部组织客商判定(集团内我方签约主体,档案登记 customerKind=internal/内部组织);
* 展示、提交校验、付款校验、合同推送、signer 单位类型各链路统一复用本判定
*
* @param customerArchive 客商档案
* @return 是否内部组织客商
*/
public static boolean isInternalCustomer(CustomerArchive customerArchive) {
return customerArchive != null
&& (HcsSyncConstant.CUSTOMER_KIND_INTERNAL.equals(customerArchive.getCustomerKind())
|| "内部组织".equals(customerArchive.getCustomerKind()));
}
/**
* 风险等级转义(C101-1 枚举 0=低风险/1=中风险/2=高风险):黑名单状态优先展示,无等级返回空
*/