refactor: 合同表移除风险留痕字段,客商风险改为列表详情实时关联客商档案组装(含变更单)

This commit is contained in:
刘泉佳
2026-10-30 14:41:41 +08:00
parent f42987e6c5
commit d5033f3b05
10 changed files with 210 additions and 22 deletions
@@ -204,12 +204,6 @@ public class ContractChange extends TenantEntity {
@Schema(description = "其他事项说明")
private String remark;
@Schema(description = "合规客商风险等级(展示用)")
private String hcsRiskLevel;
@Schema(description = "合规客商黑名单状态(展示用)")
private String kingdeeBlackStatus;
@Schema(description = "审批状态")
private String approvalStatus;
@@ -240,15 +240,6 @@ public class ContractManage extends TenantEntity {
@Schema(description = "合规履约计划ID(C103-9 返回 fdExeplanId)")
private String hcsExeplanId;
@Schema(description = "合规客商风险等级(C101-1,甲方/乙方取高)")
private String hcsRiskLevel;
@Schema(description = "合规客商黑名单状态(C101-1,甲方/乙方取是)")
private String kingdeeBlackStatus;
@Schema(description = "合规风险校验时间")
private LocalDateTime hcsRiskTime;
@Schema(description = "合规特批流程ID(C103-8 返回)")
private String hcsSpecFdId;
@@ -67,4 +67,12 @@ public class ContractChangeVO extends ContractChange {
@Schema(description = "同步状态名称")
private String syncStatusName;
@TableField(exist = false)
@Schema(description = "客商风险等级(甲乙双方关联客商档案实时组装,等级取高):0=低风险,1=中风险,2=高风险")
private String hcsRiskLevel;
@TableField(exist = false)
@Schema(description = "客商黑名单状态(甲乙双方关联客商档案实时组装,任一方金蝶黑名单命中记是):yes/no")
private String kingdeeBlackStatus;
}
@@ -83,4 +83,12 @@ public class ContractManageVO extends ContractManage {
@Schema(description = "审核状态名称")
private String approvalStatusName;
@TableField(exist = false)
@Schema(description = "客商风险等级(甲乙双方关联客商档案实时组装,等级取高):0=低风险,1=中风险,2=高风险")
private String hcsRiskLevel;
@TableField(exist = false)
@Schema(description = "客商黑名单状态(甲乙双方关联客商档案实时组装,任一方金蝶黑名单命中记是):yes/no")
private String kingdeeBlackStatus;
}
@@ -177,6 +177,16 @@ public final class HcsSyncConstant {
*/
public static final String CUSTOMER_KIND_INTERNAL = "internal";
/**
* C101-1 黑名单状态:是(客商档案/合同客商风险展示与组装用)
*/
public static final String BLACK_STATUS_YES = "yes";
/**
* C101-1 黑名单状态:否
*/
public static final String BLACK_STATUS_NO = "no";
/**
* 合同审核通过状态集合(首推/变更通过,客商风险定时刷新扫描口径与 HcsPushTask 一致)
*/
@@ -36,7 +36,9 @@ import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.transport.constant.HcsSyncConstant;
import org.springblade.transport.mapper.ContractChangeMapper;
import org.springblade.transport.support.hcs.HcsCreditCheckSupport;
import org.springblade.transport.pojo.entity.ContractChange;
import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.vo.ContractChangeVO;
import org.springblade.transport.pojo.vo.ContractManageVO;
@@ -77,12 +79,14 @@ public class ContractChangeServiceImpl extends BaseServiceImpl<ContractChangeMap
private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
private final IContractManageService contractManageService;
private final HcsCreditCheckSupport hcsCreditCheckSupport;
@Override
public IPage<ContractChangeVO> selectContractChangePage(IPage<ContractChange> page, ContractChangeVO contractChange) {
IPage<ContractChange> entityPage = page(page, buildQuery(contractChange).orderByDesc(ContractChange::getCreateTime));
IPage<ContractChangeVO> voPage = ContractChangeWrapper.build().pageVO(entityPage);
voPage.getRecords().forEach(this::fillReadonly);
this.fillContractRisk(voPage.getRecords());
return voPage;
}
@@ -90,6 +94,7 @@ public class ContractChangeServiceImpl extends BaseServiceImpl<ContractChangeMap
public ContractChangeVO detail(Long id) {
ContractChangeVO contractChangeVO = ContractChangeWrapper.build().entityVO(loadExists(id));
fillReadonly(contractChangeVO);
this.fillContractRisk(java.util.Collections.singletonList(contractChangeVO));
return contractChangeVO;
}
@@ -97,10 +102,40 @@ public class ContractChangeServiceImpl extends BaseServiceImpl<ContractChangeMap
@TenantIgnore
public ContractChangeVO publicDetail(Long id) {
ContractChangeVO contractChangeVO = ContractChangeWrapper.build().entityVO(loadExists(id));
this.fillContractRisk(java.util.Collections.singletonList(contractChangeVO));
contractChangeVO.setReadonly(Boolean.TRUE);
return contractChangeVO;
}
/**
* 组装客商风险展示数据(变更列表/详情共用,复用 HcsCreditCheckSupport 公共查询):
* 等级取甲乙双方较高值、黑名单任一方金蝶命中记是;名称关联不到或内部组织客商按无数据处理
*/
private void fillContractRisk(java.util.List<ContractChangeVO> records) {
if (Func.isEmpty(records)) {
return;
}
java.util.Set<String> partyNames = new java.util.HashSet<>();
records.forEach(change -> {
if (Func.isNotEmpty(change.getPartyA())) {
partyNames.add(change.getPartyA().trim());
}
if (Func.isNotEmpty(change.getPartyB())) {
partyNames.add(change.getPartyB().trim());
}
});
java.util.Map<String, CustomerArchive> riskMap = this.hcsCreditCheckSupport.queryArchiveRiskMap(partyNames);
for (ContractChangeVO change : records) {
CustomerArchive partyARisk = Func.isEmpty(change.getPartyA()) ? null : riskMap.get(change.getPartyA().trim());
CustomerArchive partyBRisk = Func.isEmpty(change.getPartyB()) ? null : riskMap.get(change.getPartyB().trim());
change.setHcsRiskLevel(HcsCreditCheckSupport.higherRiskLevel(
partyARisk == null ? null : partyARisk.getHcsRiskLevel(),
partyBRisk == null ? null : partyBRisk.getHcsRiskLevel()));
change.setKingdeeBlackStatus(
HcsCreditCheckSupport.isKingdeeBlackHit(partyARisk) || HcsCreditCheckSupport.isKingdeeBlackHit(partyBRisk)
? HcsSyncConstant.BLACK_STATUS_YES : HcsSyncConstant.BLACK_STATUS_NO);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ContractChangeVO createFromContract(Long contractId, String changeType) {
@@ -43,6 +43,7 @@ import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.HcsContractExeStageMapper;
import org.springblade.transport.pojo.entity.ContractChange;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.HcsContractExeStage;
import org.springblade.transport.pojo.vo.ContractManageVO;
import org.springblade.transport.service.IContractExeStageService;
@@ -109,6 +110,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
IPage<ContractManage> entityPage = page(page, buildQuery(contractManage));
IPage<ContractManageVO> voPage = ContractManageWrapper.build().pageVO(entityPage);
voPage.getRecords().forEach(this::fillReadonly);
this.fillContractRisk(voPage.getRecords());
return voPage;
}
@@ -116,6 +118,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
public ContractManageVO detail(Long id) {
ContractManageVO contractManageVO = ContractManageWrapper.build().entityVO(loadExists(id));
fillReadonly(contractManageVO);
this.fillContractRisk(java.util.Collections.singletonList(contractManageVO));
return contractManageVO;
}
@@ -128,6 +131,36 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
return contractManageVO;
}
/**
* 组装客商风险展示数据(列表/详情共用,复用 HcsCreditCheckSupport 公共查询):
* 等级取甲乙双方较高值、黑名单任一方金蝶命中记是;名称关联不到或内部组织客商按无数据处理,前端展示"-"
*/
private void fillContractRisk(List<ContractManageVO> records) {
if (Func.isEmpty(records)) {
return;
}
Set<String> partyNames = new HashSet<>();
records.forEach(contract -> {
if (Func.isNotEmpty(contract.getPartyA())) {
partyNames.add(contract.getPartyA().trim());
}
if (Func.isNotEmpty(contract.getPartyB())) {
partyNames.add(contract.getPartyB().trim());
}
});
Map<String, CustomerArchive> riskMap = this.hcsCreditCheckSupport.queryArchiveRiskMap(partyNames);
for (ContractManageVO contract : records) {
CustomerArchive partyARisk = Func.isEmpty(contract.getPartyA()) ? null : riskMap.get(contract.getPartyA().trim());
CustomerArchive partyBRisk = Func.isEmpty(contract.getPartyB()) ? null : riskMap.get(contract.getPartyB().trim());
contract.setHcsRiskLevel(HcsCreditCheckSupport.higherRiskLevel(
partyARisk == null ? null : partyARisk.getHcsRiskLevel(),
partyBRisk == null ? null : partyBRisk.getHcsRiskLevel()));
contract.setKingdeeBlackStatus(
HcsCreditCheckSupport.isKingdeeBlackHit(partyARisk) || HcsCreditCheckSupport.isKingdeeBlackHit(partyBRisk)
? HcsSyncConstant.BLACK_STATUS_YES : HcsSyncConstant.BLACK_STATUS_NO);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveDraft(ContractManage contractManage) {
@@ -282,10 +282,7 @@ public class HcsContractSyncSupport {
String riskLevel = StringUtil.isNotBlank(riskPartyA.riskLevel()) ? riskPartyA.riskLevel() : riskPartyB.riskLevel();
// 黑名单状态:任一方金蝶黑名单(A801-5)命中即为是(复用 HcsCreditCheckSupport.isKingdeeBlacklistHit 公共口径)
boolean blacklist = riskPartyA.kingdeeHit() || riskPartyB.kingdeeHit();
contractManage.setHcsRiskLevel(riskLevel);
contractManage.setKingdeeBlackStatus(blacklist ? BLACK_STATUS_YES : "no");
contractManage.setHcsRiskTime(LocalDateTime.now());
contractManageMapper.updateById(contractManage);
// 判定结果仅在内存参与推送/特批流程(risky),风险展示数据由列表/详情实时关联客商档案组装,合同表不再落库风险留痕
boolean risky = HcsSyncConstant.RISK_LEVEL_HIGH.equals(riskLevel) || blacklist;
log.info("HCS合同风险校验完成:contractNo={},甲方[金蝶黑名单={},等级={}],乙方[金蝶黑名单={},等级={}],高风险/黑名单={}",
contractManage.getContractNo(), riskPartyA.kingdeeHit(), riskPartyA.riskLevel(),
@@ -312,9 +309,8 @@ public class HcsContractSyncSupport {
// TMS 流程挂起为"特批审核",特批通过恢复展示、驳回联动合同审批驳回
contractManage.setCurrentNode(HcsSyncConstant.SPEC_REVIEW_NODE);
contractManageMapper.updateById(contractManage);
log.info("HCS合同高风险/黑名单挂起特批审核:contractNo={},riskLevel={},blackStatus={},挂起节点={}",
contractManage.getContractNo(), contractManage.getHcsRiskLevel(), contractManage.getKingdeeBlackStatus(),
contractManage.getCurrentNode());
log.info("HCS合同高风险/黑名单挂起特批审核:contractNo={},挂起节点={}",
contractManage.getContractNo(), contractManage.getCurrentNode());
}
/**
@@ -60,6 +60,7 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -526,6 +527,64 @@ public class HcsCreditCheckSupport {
return summary;
}
/**
* 按甲乙方名称批量查客商档案风险数据(合同/变更列表与详情组装用,合同表不再落库风险留痕):
* 一次 IN 查询避免 N+1,键为客商名称;仅保留已有风险数据的档案(等级或金蝶黑名单字段非空),
* 名称重名时取第一条;内部组织客商无风险数据天然不入 map
*
* @param partyNames 甲/乙方名称集合
* @return 客商名称 -> 客商档案(含 hcsRiskLevel/kingdeeBlackCategory/kingdeeBlackReason)
*/
public Map<String, CustomerArchive> queryArchiveRiskMap(Collection<String> partyNames) {
if (Func.isEmpty(partyNames)) {
return Map.of();
}
Map<String, CustomerArchive> archiveMap = new HashMap<>();
for (CustomerArchive archive : customerArchiveMapper.selectList(Wrappers.<CustomerArchive>lambdaQuery()
.in(CustomerArchive::getFullName, partyNames)
.eq(CustomerArchive::getIsDeleted, 0))) {
if (Func.isEmpty(archive.getFullName())) {
continue;
}
boolean hasRiskData = Func.isNotEmpty(archive.getHcsRiskLevel())
|| Func.isNotEmpty(archive.getKingdeeBlackCategory()) || Func.isNotEmpty(archive.getKingdeeBlackReason());
if (hasRiskData) {
archiveMap.putIfAbsent(archive.getFullName(), archive);
}
}
return archiveMap;
}
/**
* 合同客商风险等级合并(甲乙双方取较高值,2>1>0,无有效等级返回 null)
*/
public static String higherRiskLevel(String riskLevelA, String riskLevelB) {
int levelA = parseRiskLevel(riskLevelA);
int levelB = parseRiskLevel(riskLevelB);
if (levelA < 0 && levelB < 0) {
return null;
}
return String.valueOf(Math.max(levelA, levelB));
}
private static int parseRiskLevel(String riskLevel) {
if (Func.isEmpty(riskLevel)) {
return -1;
}
try {
return Integer.parseInt(riskLevel.trim());
} catch (NumberFormatException exception) {
return -1;
}
}
/**
* 客商档案金蝶黑名单命中判定(失信类别/列入事由任一非空)
*/
public static boolean isKingdeeBlackHit(CustomerArchive archive) {
return archive != null && (Func.isNotEmpty(archive.getKingdeeBlackCategory()) || Func.isNotEmpty(archive.getKingdeeBlackReason()));
}
/**
* 金蝶黑名单批量查询(A801-5,定时刷新专用,调用方按 200 客商/批切分保证入参 ≤200):
* 键为大写税号;失信类别取 awrp_combofield_title、缺失兜底 awrp_combofield(口径同提审校验 validateBlacklist);
@@ -0,0 +1,54 @@
-- 合同表移除客商风险留痕列(风险展示改为列表/详情实时关联客商档案组装,不再落库):
-- blade_contract_manage DROP hcs_risk_level / kingdee_black_status / hcs_risk_time
-- blade_contract_change DROP hcs_risk_level / kingdee_black_status
-- 幂等可重复执行:列存在才 DROP。
-- ① blade_contract_manage.hcs_risk_level
SET @ddl = (SELECT IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'blade_contract_manage' AND COLUMN_NAME = 'hcs_risk_level') > 0,
'ALTER TABLE `blade_contract_manage` DROP COLUMN `hcs_risk_level`',
'SELECT 1'));
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ② blade_contract_manage.kingdee_black_status
SET @ddl = (SELECT IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'blade_contract_manage' AND COLUMN_NAME = 'kingdee_black_status') > 0,
'ALTER TABLE `blade_contract_manage` DROP COLUMN `kingdee_black_status`',
'SELECT 1'));
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ③ blade_contract_manage.hcs_risk_time
SET @ddl = (SELECT IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'blade_contract_manage' AND COLUMN_NAME = 'hcs_risk_time') > 0,
'ALTER TABLE `blade_contract_manage` DROP COLUMN `hcs_risk_time`',
'SELECT 1'));
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ④ blade_contract_change.hcs_risk_level
SET @ddl = (SELECT IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'blade_contract_change' AND COLUMN_NAME = 'hcs_risk_level') > 0,
'ALTER TABLE `blade_contract_change` DROP COLUMN `hcs_risk_level`',
'SELECT 1'));
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ⑤ blade_contract_change.kingdee_black_status
SET @ddl = (SELECT IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'blade_contract_change' AND COLUMN_NAME = 'kingdee_black_status') > 0,
'ALTER TABLE `blade_contract_change` DROP COLUMN `kingdee_black_status`',
'SELECT 1'));
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;