From cb54b569cd2a34ad8ef56fc442334e001d8d1472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sun, 27 Sep 2026 23:41:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20HCS=E4=BB=98=E6=AC=BE=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=A0=A1=E9=AA=8C=EF=BC=88C101-1=E5=AE=A2?= =?UTF-8?q?=E5=95=86=E9=A3=8E=E9=99=A9=E9=BB=91=E5=90=8D=E5=8D=95=E9=98=BB?= =?UTF-8?q?=E6=96=AD/C106-1=E9=A2=9D=E5=BA=A6=E6=A0=A1=E9=AA=8C=E8=B6=85?= =?UTF-8?q?=E9=A2=9D=E8=BD=AC=E7=89=B9=E6=89=B9/=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E7=95=99=E7=97=95check=5Flog=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/PaymentApplicationServiceImpl.java | 4 + .../support/hcs/HcsCreditCheckSupport.java | 203 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsCreditCheckSupport.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java index 0ca877da..a52fcc28 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java @@ -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 selectPage(IPage 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()); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsCreditCheckSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsCreditCheckSupport.java new file mode 100644 index 00000000..42605ab7 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsCreditCheckSupport.java @@ -0,0 +1,203 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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) + *

+ * 付款申请提交(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 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 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.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); + } + } +}