feat: HCS付款提交同步校验(C101-1客商风险黑名单阻断/C106-1额度校验超额转特批/校验留痕check_log)
This commit is contained in:
+4
@@ -95,6 +95,7 @@ import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springblade.transport.service.KingdeeSyncContext;
|
||||
import org.springblade.transport.support.KingdeeApBillSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsCreditCheckSupport;
|
||||
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -145,6 +146,7 @@ private final CustomerReceiptAccountMapper receiptAccountMapper;
|
||||
private final IBfiService bfiService;
|
||||
private final KingdeeApBillSyncSupport kingdeeApBillSyncSupport;
|
||||
private final BfiProperties bfiProperties;
|
||||
private final HcsCreditCheckSupport hcsCreditCheckSupport;
|
||||
|
||||
@Override
|
||||
public IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query) {
|
||||
@@ -323,6 +325,8 @@ private final CustomerReceiptAccountMapper receiptAccountMapper;
|
||||
validateProjectAdvanceContract(entity);
|
||||
validateSelectedBill(entity, false);
|
||||
validateQuota(entity);
|
||||
// HCS 提交同步校验(事务内查询类同步,D15/R2-Q3):C101-1 风险(黑名单阻断)+ C106-1 额度(超额转特批)
|
||||
hcsCreditCheckSupport.checkOnSubmit(entity);
|
||||
entity.setApprovalStatus(TransportBusiConstant.REVIEWING);
|
||||
entity.setCurrentNode("财务审核");
|
||||
entity.setCurrentProcessor(AuthUtil.getUserName());
|
||||
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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.support.hcs;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsCreditCheckDTO;
|
||||
import org.springblade.hcs.dto.HcsCreditCheckVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsRiskQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsRiskVO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.CustomerArchiveMapper;
|
||||
import org.springblade.transport.mapper.HcsContractCheckLogMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.CustomerArchive;
|
||||
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 付款提交校验支撑组件(C101-1 客商风险 + C106-1 额度校验,设计文档 §5.4,D15/R2-Q3)
|
||||
* <p>
|
||||
* 付款申请提交(submit)事务内同步调用(查询类同步):
|
||||
* ① C101-1 按收款方客商编号查风险,黑名单且 block-blacklist=true 时阻断提交;
|
||||
* ② C106-1 校验申请金额是否超出合同额度,超额不拦截提交,置 hcs_credit_status=over 并标记
|
||||
* hcs_spec_status=need 转特批链路(D15/R2-Q2)。
|
||||
* 校验依赖合同已推送(hcs_fd_id 存在),未推送时按 credit.require-contract-synced 配置阻断或跳过。
|
||||
* 结果留痕 blade_hcs_check_log(biz_type=payment)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsCreditCheckSupport {
|
||||
|
||||
/**
|
||||
* C101-1 黑名单状态取值(示例 no 表示非黑名单,§9-U3 字典值联调确认)
|
||||
*/
|
||||
private static final String BLACK_STATUS_YES = "yes";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final CustomerArchiveMapper customerArchiveMapper;
|
||||
private final HcsContractCheckLogMapper hcsContractCheckLogMapper;
|
||||
|
||||
/**
|
||||
* 付款提交同步校验入口(submit 事务内调用,校验结果字段随提交一并落库)
|
||||
*
|
||||
* @param payment 付款申请单(须含合同与收款方信息)
|
||||
*/
|
||||
public void checkOnSubmit(PaymentApplication payment) {
|
||||
// ① 客商风险校验(可配开关),黑名单阻断提交
|
||||
if (hcsProperties.getRisk().isCheckOnPayment()) {
|
||||
checkRelativeRisk(payment);
|
||||
}
|
||||
// ② 额度校验(超额不拦截,转特批)
|
||||
checkCredit(payment);
|
||||
}
|
||||
|
||||
/**
|
||||
* C101-1 收款方客商风险校验:黑名单且 block-blacklist=true 时阻断提交
|
||||
*/
|
||||
private void checkRelativeRisk(PaymentApplication payment) {
|
||||
String customerCode = queryCustomerCode(payment.getPayeeName());
|
||||
if (StringUtil.isBlank(customerCode)) {
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP,
|
||||
"收款方[" + payment.getPayeeName() + "]反查不到客商编号,跳过风险校验(§9-U6)", null);
|
||||
log.warn("HCS付款提交风险校验跳过:paymentNo={},收款方反查不到客商编号", payment.getPaymentNo());
|
||||
return;
|
||||
}
|
||||
HcsRiskQueryDTO param = new HcsRiskQueryDTO();
|
||||
// 【必填】客商编号:按收款方名称反查 CustomerArchive 客商编码(用哪个编码待联调,§9-U6)
|
||||
param.setFdNumber(customerCode);
|
||||
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS客商风险查询失败响应:{},paymentNo={},fdNumber={}",
|
||||
result == null ? "无响应" : result.getMsg(), payment.getPaymentNo(), customerCode);
|
||||
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsRiskVO risk = result.getData();
|
||||
String blackStatus = risk == null ? null : risk.getFdBlackStatus();
|
||||
boolean blacklist = BLACK_STATUS_YES.equalsIgnoreCase(blackStatus);
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, blacklist ? HcsSyncConstant.CHECK_RESULT_FAIL : HcsSyncConstant.CHECK_RESULT_PASS,
|
||||
"收款方[" + payment.getPayeeName() + "]黑名单=" + blackStatus + ",风险等级=" + (risk == null ? null : risk.getFdRiskLevel()),
|
||||
result.getTraceId());
|
||||
if (blacklist && hcsProperties.getRisk().isBlockBlacklist()) {
|
||||
log.error("HCS付款提交被风险校验阻断:paymentNo={},收款方[{}]黑名单", payment.getPaymentNo(), payment.getPayeeName());
|
||||
throw new ServiceException("收款方客商黑名单,禁止提交付款申请");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C106-1 额度校验:通过置 pass;超额置 over 并标记待特批(不拦截提交,D15/R2-Q2)
|
||||
*/
|
||||
private void checkCredit(PaymentApplication payment) {
|
||||
ContractManage contractManage = payment.getContractId() == null ? null : contractManageMapper.selectById(payment.getContractId());
|
||||
if (contractManage == null || StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
if (hcsProperties.getCredit().isRequireContractSynced()) {
|
||||
throw new ServiceException("关联合同尚未推送合规,无法校验额度(paymentNo=" + payment.getPaymentNo() + ")");
|
||||
}
|
||||
log.warn("HCS付款提交额度校验跳过:paymentNo={},合同未推送合规(require-contract-synced=false)", payment.getPaymentNo());
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_CREDIT, HcsSyncConstant.CHECK_RESULT_SKIP, "合同未推送合规,跳过额度校验", null);
|
||||
return;
|
||||
}
|
||||
HcsCreditCheckDTO param = new HcsCreditCheckDTO();
|
||||
// 【必填】合同ID:HCS 合同ID(blade_contract_manage.hcs_fd_id)
|
||||
param.setFdContractId(contractManage.getHcsFdId());
|
||||
// 【必填】申请金额:PaymentApplication.appliedAmount(元)
|
||||
param.setFdApplyAmount(payment.getAppliedAmount() == null ? BigDecimal.ZERO : payment.getAppliedAmount());
|
||||
HcsResult<HcsCreditCheckVO> result = hcsClient.contractCreditCheck(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS额度校验失败响应:{},paymentNo={}", result == null ? "无响应" : result.getMsg(), payment.getPaymentNo());
|
||||
throw new HcsException("C106-1 额度校验失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsCreditCheckVO credit = result.getData();
|
||||
payment.setHcsCreditTime(LocalDateTime.now());
|
||||
if (credit != null && Boolean.TRUE.equals(credit.getFdIsCredit())) {
|
||||
// 额度通过:正常提交
|
||||
payment.setHcsCreditStatus(HcsSyncConstant.CREDIT_STATUS_PASS);
|
||||
payment.setHcsCreditMsg(null);
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_CREDIT, HcsSyncConstant.CHECK_RESULT_PASS, "额度校验通过", result.getTraceId());
|
||||
return;
|
||||
}
|
||||
// 超额:success 仍为 true,必须以 fdIsCredit 为准;不拦截提交,转入特批链路
|
||||
String message = credit == null ? "响应数据为空" : credit.getFdMessage();
|
||||
payment.setHcsCreditStatus(HcsSyncConstant.CREDIT_STATUS_OVER);
|
||||
payment.setHcsCreditMsg(message);
|
||||
payment.setHcsSpecStatus(HcsSyncConstant.SPEC_STATUS_NEED);
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_CREDIT, HcsSyncConstant.CHECK_RESULT_FAIL, "额度超额:" + message, result.getTraceId());
|
||||
log.warn("HCS付款提交额度超额(转特批):paymentNo={},{}", payment.getPaymentNo(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按名称反查客商编码(照 KingdeeApBillSyncSupport#queryCustomerCode 惯例,查不到返回 null)
|
||||
*/
|
||||
private String queryCustomerCode(String customerName) {
|
||||
if (StringUtil.isBlank(customerName)) {
|
||||
return null;
|
||||
}
|
||||
CustomerArchive customerArchive = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
|
||||
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, customerName).or().eq(CustomerArchive::getShortName, customerName))
|
||||
.eq(CustomerArchive::getIsDeleted, 0)
|
||||
.last("limit 1"));
|
||||
return customerArchive == null ? null : customerArchive.getCustomerCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验留痕(blade_hcs_check_log,异常不影响主流程)
|
||||
*/
|
||||
private void writeCheckLog(PaymentApplication payment, String checkType, String result, String msg, String traceId) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
checkLog.setTenantId(payment.getTenantId());
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_PAYMENT);
|
||||
checkLog.setBizId(payment.getId());
|
||||
checkLog.setCheckType(checkType);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(msg);
|
||||
checkLog.setTraceId(traceId);
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS付款校验留痕写入失败:paymentNo={}", payment.getPaymentNo(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user