feat: 客商风险C101-1查询改按统一社会信用代码, 信用代码为空各链路跳过留痕不阻断

This commit is contained in:
刘泉佳
2026-11-09 15:54:02 +08:00
parent 2841688728
commit 923d963b29
4 changed files with 60 additions and 26 deletions
@@ -337,14 +337,14 @@ public class HcsContractSyncSupport {
} }
/** /**
* 查询单方客商风险:按名称反查客商档案(§9-U6 编码体系联调确认),反查不到记 skip 留痕不阻断; * 查询单方客商风险:按名称反查客商档案(§9-U6 编码体系联调确认),反查不到档案或信用代码为空记 skip 留痕不阻断;
* 内部组织客商默认无风险跳过(与展示/提交/付款链路口径一致,不调 C101-1) * 内部组织客商默认无风险跳过(与展示/提交/付款链路口径一致,不调 C101-1)
*/ */
private RiskResult queryRisk(ContractManage contractManage, String standing, String partyName) { private RiskResult queryRisk(ContractManage contractManage, String standing, String partyName) {
CustomerArchive customerArchive = queryCustomerArchive(partyName); CustomerArchive customerArchive = queryCustomerArchive(partyName);
if (customerArchive == null) { if (customerArchive == null) {
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP, writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
standing + "方[" + partyName + "]反查不到客商编号,跳过风险校验(§9-U6)", null); standing + "方[" + partyName + "]反查不到客商档案,跳过风险校验(§9-U6)", null);
return new RiskResult(false, null); return new RiskResult(false, null);
} }
// 内部组织类型客商默认无风险,不调 C101-1(与展示/提交/付款链路口径一致) // 内部组织类型客商默认无风险,不调 C101-1(与展示/提交/付款链路口径一致)
@@ -353,13 +353,20 @@ public class HcsContractSyncSupport {
standing + "方[" + partyName + "]为内部组织客商,默认无风险跳过校验", null); standing + "方[" + partyName + "]为内部组织客商,默认无风险跳过校验", null);
return new RiskResult(false, null); return new RiskResult(false, null);
} }
// C101-1 改按统一社会信用代码查询:信用代码为空无法查询,跳过并留痕(同反查不到口径)
String creditCode = customerArchive.getUnifiedCreditCode();
if (StringUtil.isBlank(creditCode)) {
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
standing + "方[" + partyName + "]信用代码为空,跳过风险校验", null);
return new RiskResult(false, null);
}
HcsRiskQueryDTO param = new HcsRiskQueryDTO(); HcsRiskQueryDTO param = new HcsRiskQueryDTO();
// 【必填】客商编号:按甲乙方名称反查 CustomerArchive 客商编码(§9-U6) // 【必填】统一社会信用代码:按甲乙方名称反查 CustomerArchive(C101-1 改按信用代码查询)
param.setFdNumber(customerArchive.getCustomerCode()); param.setFdNumber(creditCode);
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param); HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
if (result == null || !result.isSuccess()) { if (result == null || !result.isSuccess()) {
log.error("HCS客商风险查询失败响应:{},contractNo={},standing={},fdNumber={}", log.error("HCS客商风险查询失败响应:{},contractNo={},standing={},fdNumber={}",
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo(), standing, customerArchive.getCustomerCode()); result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo(), standing, creditCode);
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg())); throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
} }
HcsRiskVO risk = result.getData(); HcsRiskVO risk = result.getData();
@@ -123,8 +123,8 @@ public class HcsCreditCheckSupport {
CustomerArchive customerArchive = queryCustomerArchive(payment.getPayeeName()); CustomerArchive customerArchive = queryCustomerArchive(payment.getPayeeName());
if (customerArchive == null) { if (customerArchive == null) {
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP, writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP,
"收款方[" + payment.getPayeeName() + "]反查不到客商编号,跳过风险校验(§9-U6)", null); "收款方[" + payment.getPayeeName() + "]反查不到客商档案,跳过风险校验(§9-U6)", null);
log.warn("HCS付款提交风险校验跳过:paymentNo={},收款方反查不到客商编号", payment.getPaymentNo()); log.warn("HCS付款提交风险校验跳过:paymentNo={},收款方反查不到客商档案", payment.getPaymentNo());
return; return;
} }
// 内部组织类型客商默认无风险,不查 C101-1(与 queryCustomerRisk 展示口径一致) // 内部组织类型客商默认无风险,不查 C101-1(与 queryCustomerRisk 展示口径一致)
@@ -133,13 +133,21 @@ public class HcsCreditCheckSupport {
"收款方[" + payment.getPayeeName() + "]为内部组织客商,默认无风险跳过校验", null); "收款方[" + payment.getPayeeName() + "]为内部组织客商,默认无风险跳过校验", null);
return; return;
} }
// C101-1 改按统一社会信用代码查询:信用代码为空无法查询,跳过并留痕(同反查不到口径)
String creditCode = customerArchive.getUnifiedCreditCode();
if (StringUtil.isBlank(creditCode)) {
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP,
"收款方[" + payment.getPayeeName() + "]信用代码为空,跳过风险校验", null);
log.warn("HCS付款提交风险校验跳过:paymentNo={},收款方信用代码为空", payment.getPaymentNo());
return;
}
HcsRiskQueryDTO param = new HcsRiskQueryDTO(); HcsRiskQueryDTO param = new HcsRiskQueryDTO();
// 【必填】客商编号:按收款方名称反查 CustomerArchive 客商编码(用哪个编码待联调,§9-U6) // 【必填】统一社会信用代码:按收款方名称反查 CustomerArchive(C101-1 改按信用代码查询)
param.setFdNumber(customerArchive.getCustomerCode()); param.setFdNumber(creditCode);
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param); HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
if (result == null || !result.isSuccess()) { if (result == null || !result.isSuccess()) {
log.error("HCS客商风险查询失败响应:{},paymentNo={},fdNumber={}", log.error("HCS客商风险查询失败响应:{},paymentNo={},fdNumber={}",
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo(), customerArchive.getCustomerCode()); result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo(), creditCode);
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg())); throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
} }
HcsRiskVO risk = result.getData(); HcsRiskVO risk = result.getData();
@@ -204,7 +212,7 @@ public class HcsCreditCheckSupport {
/** /**
* 客商风险查询(C101-1,展示与提交校验共用,前端接入设计 §3.3): * 客商风险查询(C101-1,展示与提交校验共用,前端接入设计 §3.3):
* 内部组织类型客商默认无风险(不调 C101-1);外部客商按编号查询风险等级与黑名单状态并留痕; * 内部组织类型客商默认无风险(不调 C101-1);外部客商按信用代码查询风险等级与黑名单状态并留痕;
* 金蝶黑名单(A801-5)命中同样判定为风险客商(risky=true,C101-1 空数据不漏提示); * 金蝶黑名单(A801-5)命中同样判定为风险客商(risky=true,C101-1 空数据不漏提示);
* riskTip 经 {@link #buildRiskDetail} 统一组装(等级 + 金蝶黑名单失信类别/列入事由),与列表悬浮明细同口径 * riskTip 经 {@link #buildRiskDetail} 统一组装(等级 + 金蝶黑名单失信类别/列入事由),与列表悬浮明细同口径
* *
@@ -247,12 +255,13 @@ public class HcsCreditCheckSupport {
riskVO.setRisky(Boolean.FALSE); riskVO.setRisky(Boolean.FALSE);
return riskVO; return riskVO;
} }
RelativeRiskResult fetch = this.fetchRelativeRisk(customerCode, customerName); // 档案存在以档案信用代码为准;档案缺失(新增客商实时校验)兜底用前端传入信用代码
String archiveCreditCode = customerArchive == null ? null : customerArchive.getUnifiedCreditCode();
String effectiveCreditCode = StringUtil.isNotBlank(archiveCreditCode) ? archiveCreditCode : creditCode;
RelativeRiskResult fetch = this.fetchRelativeRisk(customerCode, effectiveCreditCode, customerName);
CustomerRiskVO riskVO = fetch.risk(); CustomerRiskVO riskVO = fetch.risk();
// 表单实时提示与列表明细统一组装口径:追加金蝶黑名单段(A801-5 按信用代码查询,失败静默缺失; // 表单实时提示与列表明细统一组装口径:追加金蝶黑名单段(A801-5 按信用代码查询,失败静默缺失;
// 档案缺失(新增客商实时校验)时兜底用入参信用代码) // 档案缺失(新增客商实时校验)时兜底用入参信用代码)
String archiveCreditCode = customerArchive == null ? null : customerArchive.getUnifiedCreditCode();
String effectiveCreditCode = StringUtil.isNotBlank(archiveCreditCode) ? archiveCreditCode : creditCode;
BfiBlacklistVO hit = StringUtil.isBlank(effectiveCreditCode) ? null BfiBlacklistVO hit = StringUtil.isBlank(effectiveCreditCode) ? null
: this.queryBlacklistByCodes(List.of(effectiveCreditCode.trim().toUpperCase())).get(effectiveCreditCode.trim().toUpperCase()); : this.queryBlacklistByCodes(List.of(effectiveCreditCode.trim().toUpperCase())).get(effectiveCreditCode.trim().toUpperCase());
String blackCategory = hit == null ? null String blackCategory = hit == null ? null
@@ -275,20 +284,31 @@ public class HcsCreditCheckSupport {
/** /**
* C101-1 单客商远程查询并组装展示信息(不查档案、不写留痕,供单查与定时批量刷新复用); * C101-1 单客商远程查询并组装展示信息(不查档案、不写留痕,供单查与定时批量刷新复用);
* riskTip 不在本方法组装,由调用方经 {@link #buildRiskDetail} 统一组装 * riskTip 不在本方法组装,由调用方经 {@link #buildRiskDetail} 统一组装;
* C101-1 改按统一社会信用代码查询,信用代码为空时不调远程、按无风险数据返回
* *
* @param customerCode 客商编号 * @param customerCode 客商编号(留痕与展示用)
* @param creditCode 统一社会信用代码(C101-1 查询键)
* @param customerName 客商名称(展示信息用) * @param customerName 客商名称(展示信息用)
* @return 风险展示信息 + 合规 traceId * @return 风险展示信息 + 合规 traceId
*/ */
private RelativeRiskResult fetchRelativeRisk(String customerCode, String customerName) { private RelativeRiskResult fetchRelativeRisk(String customerCode, String creditCode, String customerName) {
if (StringUtil.isBlank(creditCode)) {
log.warn("HCS客商风险查询跳过:customerCode={}, 信用代码为空", customerCode);
CustomerRiskVO emptyRisk = new CustomerRiskVO();
emptyRisk.setCustomerCode(customerCode);
emptyRisk.setCustomerName(customerName);
emptyRisk.setCustomerKind("external");
emptyRisk.setRisky(Boolean.FALSE);
return new RelativeRiskResult(emptyRisk, null);
}
HcsRiskQueryDTO param = new HcsRiskQueryDTO(); HcsRiskQueryDTO param = new HcsRiskQueryDTO();
// 【必填】客商编号(用哪个编码待联调,§9-U6) // 【必填】统一社会信用代码(C101-1 改按信用代码查询,字段名 fdNumber 保持合规侧定义)
param.setFdNumber(customerCode); param.setFdNumber(creditCode);
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param); HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
if (result == null || !result.isSuccess()) { if (result == null || !result.isSuccess()) {
log.error("HCS客商风险查询失败响应:{},fdNumber={}", log.error("HCS客商风险查询失败响应:{},fdNumber={}",
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), customerCode); result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), creditCode);
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg())); throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
} }
HcsRiskVO risk = result.getData(); HcsRiskVO risk = result.getData();
@@ -441,7 +461,7 @@ public class HcsCreditCheckSupport {
/** /**
* 客商风险批量查询(C101-1,客商列表页展示用,前端接入设计 §3.3): * 客商风险批量查询(C101-1,客商列表页展示用,前端接入设计 §3.3):
* 逐个复用 {@link #queryCustomerRisk}(内部客商默认无风险,外部客商查 C101-1,与付款页面展示口径一致); * 逐个复用 {@link #queryCustomerRisk}(内部客商默认无风险,外部客商查 C101-1,与付款页面展示口径一致);
* C101-1 仅支持单编号查询,逐个串行调用;展示场景单个失败跳过不阻断整页(强制校验仍在合同/付款链路) * C101-1 仅支持单信用代码查询,逐个串行调用;展示场景单个失败跳过不阻断整页(强制校验仍在合同/付款链路)
* *
* @param customerCodes 客商编号集合 * @param customerCodes 客商编号集合
* @return 风险展示信息列表(查询失败的客商不在结果中) * @return 风险展示信息列表(查询失败的客商不在结果中)
@@ -465,7 +485,7 @@ public class HcsCreditCheckSupport {
* 客商风险每日定时刷新(C101-1,客商档案列表展示与筛选数据源): * 客商风险每日定时刷新(C101-1,客商档案列表展示与筛选数据源):
* ① 取合同期限内(正式/临时期限任一覆盖当天)审核通过且未终止合同的甲乙方名称; * ① 取合同期限内(正式/临时期限任一覆盖当天)审核通过且未终止合同的甲乙方名称;
* ② 按名称反查客商档案,内部组织客商默认无风险跳过; * ② 按名称反查客商档案,内部组织客商默认无风险跳过;
* ③ 逐个查询 C101-1(接口仅支持单编号,属远程调用非数据库 N+1;复用 {@link #fetchRelativeRisk},不重复查档案), * ③ 逐个查询 C101-1(接口仅支持单信用代码,属远程调用非数据库 N+1;复用 {@link #fetchRelativeRisk},不重复查档案),
* 单个失败记 error 日志不中断(失败原因备查); * 单个失败记 error 日志不中断(失败原因备查);
* ④ 外部客商按 200/批切分,每批黑名单(A801-5)一次批量查询 + C101-1 逐个远程查询,失信类别/列入事由落原始字段 * ④ 外部客商按 200/批切分,每批黑名单(A801-5)一次批量查询 + C101-1 逐个远程查询,失信类别/列入事由落原始字段
* (明细文案由列表查询统一组装); * (明细文案由列表查询统一组装);
@@ -501,7 +521,7 @@ public class HcsCreditCheckSupport {
int internalCount = archiveList.size() - externalArchives.size(); int internalCount = archiveList.size() - externalArchives.size();
int refreshCount = 0; int refreshCount = 0;
int failCount = 0; int failCount = 0;
// 外部客商按 200/批处理:每批黑名单一次批量查询 + C101-1 逐个远程查询(接口仅支持单编号,非数据库 N+1), // 外部客商按 200/批处理:每批黑名单一次批量查询 + C101-1 逐个远程查询(接口仅支持单信用代码,非数据库 N+1),
// 批内成功结果立即批量落库与留痕,风险/黑名单批次互不影响,单批失败不中断其余批次 // 批内成功结果立即批量落库与留痕,风险/黑名单批次互不影响,单批失败不中断其余批次
for (int start = 0; start < externalArchives.size(); start += HcsSyncConstant.CUSTOMER_RISK_REFRESH_BATCH) { for (int start = 0; start < externalArchives.size(); start += HcsSyncConstant.CUSTOMER_RISK_REFRESH_BATCH) {
List<CustomerArchive> batch = externalArchives.subList(start, Math.min(start + HcsSyncConstant.CUSTOMER_RISK_REFRESH_BATCH, externalArchives.size())); List<CustomerArchive> batch = externalArchives.subList(start, Math.min(start + HcsSyncConstant.CUSTOMER_RISK_REFRESH_BATCH, externalArchives.size()));
@@ -515,7 +535,14 @@ public class HcsCreditCheckSupport {
List<HcsContractCheckLog> batchCheckLogs = new ArrayList<>(); List<HcsContractCheckLog> batchCheckLogs = new ArrayList<>();
for (CustomerArchive customerArchive : batch) { for (CustomerArchive customerArchive : batch) {
try { try {
RelativeRiskResult fetch = this.fetchRelativeRisk(customerArchive.getCustomerCode(), customerArchive.getFullName()); // C101-1 改按信用代码查询:信用代码为空的档案跳过刷新(不计入失败数)
if (Func.isEmpty(customerArchive.getUnifiedCreditCode())) {
log.warn("定时刷新客商风险跳过:customerCode={}, fullName={}, 信用代码为空",
customerArchive.getCustomerCode(), customerArchive.getFullName());
continue;
}
RelativeRiskResult fetch = this.fetchRelativeRisk(customerArchive.getCustomerCode(),
customerArchive.getUnifiedCreditCode(), customerArchive.getFullName());
CustomerRiskVO risk = fetch.risk(); CustomerRiskVO risk = fetch.risk();
// 仅提交风险列,避免整行回写覆盖刷新期间其它并发修改(审批状态/额度/金蝶推送等) // 仅提交风险列,避免整行回写覆盖刷新期间其它并发修改(审批状态/额度/金蝶推送等)
CustomerArchive riskPatch = new CustomerArchive(); CustomerArchive riskPatch = new CustomerArchive();
@@ -44,7 +44,7 @@ public class HcsRiskQueryDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
* 客商编号(必填;用客商哪个编码待联调确认,§9-U6) * 统一社会信用代码(必填;C101-1 改按信用代码查询,字段名 fdNumber 保持合规侧定义)
*/ */
private String fdNumber; private String fdNumber;
} }
@@ -139,7 +139,7 @@ public interface IHcsClient {
HcsResult<HcsPageVO<HcsProjectVO>> projectList(@RequestBody HcsPageQueryDTO param); HcsResult<HcsPageVO<HcsProjectVO>> projectList(@RequestBody HcsPageQueryDTO param);
/** /**
* C101-1 客商获取相对方风险等级 * C101-1 客商获取相对方风险等级(入参 fdNumber 传统一社会信用代码)
* *
* @param param 请求参数 * @param param 请求参数
* @return 相对方风险信息 * @return 相对方风险信息