From 75bbd829c7068450e475163c2b6aafaee53fee69 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 22:54:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20HCS=E5=90=88=E5=90=8C=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=94=AF=E6=92=91=EF=BC=88C101-1=E9=A3=8E=E9=99=A9=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E7=95=99=E7=97=95/C103-3=E5=88=A4=E9=87=8D/C103-2?= =?UTF-8?q?=E9=99=84=E4=BB=B6=E8=BD=AC=E4=BC=A0/C103-1=E5=8F=91=E8=B5=B7?= =?UTF-8?q?=E4=B8=8EC103-11=E6=9B=B4=E6=96=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/hcs/HcsContractSyncSupport.java | 487 ++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSyncSupport.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSyncSupport.java new file mode 100644 index 00000000..734f0e3d --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSyncSupport.java @@ -0,0 +1,487 @@ +/** + * 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.tool.utils.StringUtil; +import org.springblade.hcs.config.HcsProperties; +import org.springblade.hcs.dto.HcsContractDraftDTO; +import org.springblade.hcs.dto.HcsContractDraftVO; +import org.springblade.hcs.dto.HcsContractFieldJsonDTO; +import org.springblade.hcs.dto.HcsContractVO; +import org.springblade.hcs.dto.HcsPageQueryDTO; +import org.springblade.hcs.dto.HcsPageVO; +import org.springblade.hcs.dto.HcsResult; +import org.springblade.hcs.dto.HcsRiskQueryDTO; +import org.springblade.hcs.dto.HcsRiskVO; +import org.springblade.hcs.dto.HcsSignerDTO; +import org.springblade.hcs.dto.HcsUserRefVO; +import org.springblade.hcs.dto.HcsXformFieldDTO; +import org.springblade.hcs.exception.HcsException; +import org.springblade.hcs.feign.IHcsClient; +import org.springblade.system.cache.UserCache; +import org.springblade.system.pojo.entity.User; +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.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * HCS 合同同步支撑组件(C103-1 发起 / C103-11 更新,设计文档 §5.2) + *

+ * 扫描任务按序执行:① 客商风险校验(可配开关,黑名单阻断)→ ② 附件转传(挂载点待 §9-U8 确认,先转传缓存)→ + * ③ C103-3 判重(§9-U4 查询能力联调确认)→ ④ C103-1 起草 / 变更单 C103-11 更新(§9-U5 定位方式待联调)→ + * ⑤ 履约规则 C103-9(P2 接线)。 + * 结果留痕:风险校验写 blade_hcs_check_log 与合同 hcs_risk_* 字段;推送结果回填 hcs_fd_id/hcs_sync_status。 + * + * @author Chill + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class HcsContractSyncSupport { + + /** + * 合同金额等 xformFields 值一律字符串传值 + */ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 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; + private final HcsAttachmentSupport hcsAttachmentSupport; + + /** + * 推送合同到 HCS:风险校验 → 附件转传 → 判重 → 起草/更新 → 回填合规 fdId 置 synced + * + * @param contractManage 合同单(approvalStatus 须为 approved/change_approved) + * @return 推送结果描述(供任务汇总与 hcs_sync_msg 落库) + */ + public String pushContract(ContractManage contractManage) { + // ① 客商风险校验(可配开关),黑名单且 block-blacklist=true 时阻断推送 + if (hcsProperties.getContract().isRequireRiskCheck()) { + checkRelativeRisk(contractManage); + } + // ② 附件转传(先转传并缓存合规附件ID,C103-1 挂载方式待 §9-U8 确认后接线) + transferAttachmentsIfEnabled(contractManage); + // ③④ 首推判重后起草(C103-1);已有合规ID(变更单)走更新(C103-11) + if (StringUtil.isBlank(contractManage.getHcsFdId())) { + String existFdId = findExistFdId(contractManage); + if (StringUtil.isNotBlank(existFdId)) { + markSynced(contractManage, existFdId, "判重命中,回填已有合规合同ID"); + return "判重命中,回填已有合规合同ID"; + } + String fdContractId = draftContract(contractManage); + // TODO 待联调(P2):履约规则 C103-9 行级同步随合同链路执行(§5.3,依赖 blade_contract_exe_stage 表) + markSynced(contractManage, fdContractId, "C103-1 合同发起成功"); + return "C103-1 合同发起成功"; + } + updateContract(contractManage); + markSynced(contractManage, contractManage.getHcsFdId(), "C103-11 合同更新成功"); + return "C103-11 合同更新成功"; + } + + /** + * C101-1 客商风险校验:甲乙双方各查一次,留痕 check_log 与合同 hcs_risk_* 字段,黑名单阻断 + */ + private void checkRelativeRisk(ContractManage contractManage) { + RiskResult riskPartyA = queryRisk(contractManage, "甲方", contractManage.getPartyA()); + RiskResult riskPartyB = queryRisk(contractManage, "乙方", contractManage.getPartyB()); + // 风险等级:甲乙双方取先查到的非空值(无法量化比较,留痕双方原始值,联调后细化) + String riskLevel = StringUtil.isNotBlank(riskPartyA.riskLevel()) ? riskPartyA.riskLevel() : riskPartyB.riskLevel(); + // 黑名单状态:任一方黑名单即为是 + boolean blacklist = riskPartyA.blacklist() || riskPartyB.blacklist(); + contractManage.setHcsRiskLevel(riskLevel); + contractManage.setHcsBlackStatus(blacklist ? BLACK_STATUS_YES : "no"); + contractManage.setHcsRiskTime(LocalDateTime.now()); + contractManageMapper.updateById(contractManage); + log.info("HCS合同风险校验完成:contractNo={},甲方[{}={}/{}],乙方[{}={}/{}]", + contractManage.getContractNo(), contractManage.getPartyA(), riskPartyA.blackStatus(), riskPartyA.riskLevel(), + contractManage.getPartyB(), riskPartyB.blackStatus(), riskPartyB.riskLevel()); + if (blacklist && hcsProperties.getRisk().isBlockBlacklist()) { + String message = "客商黑名单,禁止推送(甲方[" + riskPartyA.blackStatus() + "]乙方[" + riskPartyB.blackStatus() + "])"; + log.error("HCS合同推送被风险校验阻断:contractNo={},{}", contractManage.getContractNo(), message); + throw new HcsException(message); + } + } + + /** + * 查询单方客商风险:按名称反查客商编号(§9-U6 编码体系联调确认),反查不到记 skip 留痕不阻断 + */ + private RiskResult queryRisk(ContractManage contractManage, String standing, String partyName) { + String customerCode = queryCustomerCode(partyName); + if (StringUtil.isBlank(customerCode)) { + writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP, + standing + "方[" + partyName + "]反查不到客商编号,跳过风险校验(§9-U6)", null); + return new RiskResult(null, null, false); + } + HcsRiskQueryDTO param = new HcsRiskQueryDTO(); + // 【必填】客商编号:按甲乙方名称反查 CustomerArchive 客商编码(§9-U6) + param.setFdNumber(customerCode); + HcsResult result = hcsClient.getRelativeRisk(param); + if (result == null || !result.isSuccess()) { + log.error("HCS客商风险查询失败响应:{},contractNo={},standing={},fdNumber={}", + result == null ? "无响应" : result.getMsg(), contractManage.getContractNo(), standing, customerCode); + throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg())); + } + HcsRiskVO risk = result.getData(); + String blackStatus = risk == null ? null : risk.getFdBlackStatus(); + String riskLevel = risk == null ? null : risk.getFdRiskLevel(); + boolean blacklist = BLACK_STATUS_YES.equalsIgnoreCase(blackStatus); + writeCheckLog(contractManage, blacklist ? HcsSyncConstant.CHECK_RESULT_FAIL : HcsSyncConstant.CHECK_RESULT_PASS, + standing + "方[" + partyName + "]黑名单=" + blackStatus + ",风险等级=" + riskLevel, result.getTraceId()); + return new RiskResult(blackStatus, riskLevel, blacklist); + } + + /** + * C103-3 合同列表判重(conditions={"fdNumber": contractNo},§9-U4 查询能力联调确认) + */ + private String findExistFdId(ContractManage contractManage) { + HcsPageQueryDTO query = new HcsPageQueryDTO(); + query.setColumns(List.of("fdId", "fdNumber")); + // 判重条件:合同编号精确匹配 + query.setConditions(Map.of("fdNumber", Objects.toString(contractManage.getContractNo(), ""))); + query.setOffset(0); + query.setPageSize(10); + HcsResult> result = hcsClient.contractList(query); + if (result == null || !result.isSuccess()) { + log.error("HCS合同判重查询失败响应:{},contractNo={}", result == null ? "无响应" : result.getMsg(), contractManage.getContractNo()); + throw new HcsException("C103-3 合同判重查询失败:" + (result == null ? "响应为空" : result.getMsg())); + } + HcsPageVO page = result.getData(); + if (page == null || page.getContent() == null || page.getContent().isEmpty()) { + return null; + } + return page.getContent().get(0).getFdId(); + } + + /** + * C103-1 合同发起,返回合规合同 fdContractId + */ + private String draftContract(ContractManage contractManage) { + HcsContractDraftDTO param = buildDraftParam(contractManage); + log.info("HCS合同推送开始:{}(C103-1 发起)", contractManage.getContractNo()); + HcsResult result = hcsClient.contractDraft(param); + return validateDraftResult(contractManage, result); + } + + /** + * C103-11 合同更新(入参同发起;TODO 待联调(§9-U5):HCS 侧如何定位已有合同 v4 未说明) + */ + private void updateContract(ContractManage contractManage) { + HcsContractDraftDTO param = buildDraftParam(contractManage); + log.info("HCS合同推送开始:{}(C103-11 更新,§9-U5 定位方式待联调)", contractManage.getContractNo()); + HcsResult result = hcsClient.contractUpdate(param); + if (result == null || !result.isSuccess()) { + log.error("HCS合同更新失败响应:{},contractNo={}", result == null ? "无响应" : result.getMsg(), contractManage.getContractNo()); + throw new HcsException("C103-11 合同更新失败:" + (result == null ? "响应为空" : result.getMsg())); + } + } + + /** + * 组装 C103-1/C103-11 入参(字段标注照 §3.4 规范,映射表见设计文档 §5.2) + */ + private HcsContractDraftDTO buildDraftParam(ContractManage contractManage) { + HcsContractDraftDTO param = new HcsContractDraftDTO(); + // 【必填】合同模板ID:template-mapping[contractCategory](§9-U7 待合规方提供模板ID清单) + param.setFdTemplateId(requireTemplateId(contractManage)); + // 【必填】外部系统标识:fd_sources,配置 thirdParty.hcs.src-system(D14) + param.setFdOuterSystem(hcsProperties.getSrcSystem()); + // 【必填】操作人登录名:合同承办人登录名(§9-U2) + param.setFdLoginName(requireLoginName(contractManage)); + // 【必填】保存类型:submit(提交) + param.setSaveType("submit"); + HcsContractFieldJsonDTO fieldJson = new HcsContractFieldJsonDTO(); + fieldJson.setXformFields(buildXformFields(contractManage)); + // 相对方(签署方):甲方/乙方,编号按名称反查 CustomerArchive(§9-U6) + fieldJson.setSigner(buildSigners(contractManage)); + param.setFdFieldJson(fieldJson); + return param; + } + + /** + * xformFields 模板字段(字段名/中文名取自《桂物合同集成接口逻辑梳理》§5.1 合同字段说明) + */ + private List buildXformFields(ContractManage contractManage) { + List fields = new ArrayList<>(); + // 【必填】合同编号:ContractManage.contractNo + fields.add(field("fdNumber", "合同编号", contractManage.getContractNo())); + fields.add(field("CONTRACT_NO", "合同编号", contractManage.getContractNo())); + // 【必填】合同标题:ContractManage.contractName + fields.add(field("fdSubject", "合同标题", contractManage.getContractName())); + fields.add(field("CONTRACT_NAME", "合同名称", contractManage.getContractName())); + // 【必填】合同金额:contractAmount(字符串传值) + fields.add(field("fdAmount", "合同金额", toPlainString(contractManage.getContractAmount()))); + fields.add(field("AMOUNT", "合同金额", toPlainString(contractManage.getContractAmount()))); + // 币种:TMS 结算币种经 currency-name-mapping 映射(默认 RMB→人民币,照金蝶惯例) + fields.add(field("fdCurrencyId", "币种", mapCurrencyName(contractManage.getSettlementCurrency()))); + fields.add(field("CURRENCY", "币种", mapCurrencyName(contractManage.getSettlementCurrency()))); + // 合同结算类型:settlement-type-mapping[settlementMode](映射到"付款/收款"待联调,§9-U3,未配置不传) + String settlementType = hcsProperties.getContract().getSettlementTypeMapping().get(contractManage.getSettlementMode()); + if (StringUtil.isNotBlank(settlementType)) { + fields.add(field("fdSettlementType", "合同结算类型", settlementType)); + } else { + log.warn("HCS合同结算类型映射缺失:settlementMode=[{}] 不在 contract.settlement-type-mapping,fdSettlementType 不传(§9-U3)", contractManage.getSettlementMode()); + } + // 特批流程:fdSepcailContract 固定 false(原文拼写笔误必须沿用,D15,配置项预留) + fields.add(field("fdSepcailContract", "特批流程", hcsProperties.getContract().isSpecialContractEnabled() ? "true" : "false")); + // 合同类型:ContractManage.contractCategory + fields.add(field("CONTRACT_TYPE", "合同类型", contractManage.getContractCategory())); + // 甲方名称/乙方名称:TMS 存名称字符串 + fields.add(field("PARTY_A", "甲方名称", contractManage.getPartyA())); + fields.add(field("PARTY_B", "乙方名称", contractManage.getPartyB())); + // 【必填】签订日期:signDate(fdType=timestamp) + fields.add(timestampField("SIGN_DATE", "签订日期", contractManage.getSignDate())); + // 预计开始/结束时间:startDate/endDate(fdType=timestamp) + fields.add(timestampField("fdEstimateStartTime", "预计开始时间", contractManage.getStartDate())); + fields.add(timestampField("fdEstimateEndTime", "预计结束时间", contractManage.getEndDate())); + // 创建时间:createTime(fdType=timestamp) + fields.add(timestampField("CREATE_TIME", "创建时间", contractManage.getCreateTime())); + fields.add(timestampField("fdCreateTime", "创建时间", contractManage.getCreateTime())); + // 同步时间:推送时刻(fdType=timestamp) + fields.add(timestampField("fd_syn_time", "同步时间", LocalDateTime.now())); + // 承办人名称:handlerUserName + fields.add(field("CREATE_USER_NAME", "承办人名称", contractManage.getHandlerUserName())); + // TODO 待确认:合同状态 CONTRACT_STATUS,TMS 无直接对应,是否必传联调确认(§9-U3),一期不传 + return fields; + } + + /** + * signer 相对方(签署方):甲/乙双方,fdType(inner/外部)判定规则待联调(§9-U6)暂不传 + */ + private List buildSigners(ContractManage contractManage) { + List signers = new ArrayList<>(); + // 甲方相对方:fdStanding=甲,编号按名称反查(反查不到留空,§9-U6) + signers.add(signer("甲", contractManage.getPartyA())); + // 乙方相对方:fdStanding=乙 + signers.add(signer("乙", contractManage.getPartyB())); + return signers; + } + + /** + * 单个相对方 + */ + private HcsSignerDTO signer(String fdStanding, String partyName) { + HcsSignerDTO signer = new HcsSignerDTO(); + // 签署地位:甲 / 乙 + signer.setFdStanding(fdStanding); + // 相对方名称:TMS 甲乙方名称 + signer.setFdName(partyName); + // 相对方编号:按名称反查 CustomerArchive 客商编码,反查不到留空(§9-U6) + signer.setFdNumber(queryCustomerCode(partyName)); + return signer; + } + + /** + * 附件转传(可配开关):合同主文件 approved=true + 其它附件,合规附件ID 走映射缓存 + */ + private void transferAttachmentsIfEnabled(ContractManage contractManage) { + if (!hcsProperties.getAttachment().isTransferEnabled()) { + return; + } + List attachmentFdIds = hcsAttachmentSupport.transferContractAttachments( + contractManage.getContractFileJson(), contractManage.getAttachmentsJson(), requireLoginName(contractManage)); + // TODO 待联调(§9-U8):C103-1 未定义附件字段,转传后的合规附件ID 暂存映射缓存,挂载方式确认后接线 + log.info("HCS合同附件转传完成:contractNo={},共 {} 个附件(挂载点待 §9-U8 确认)", contractManage.getContractNo(), attachmentFdIds.size()); + } + + /** + * 校验 C103-1 返回并取合同 fdContractId(必须持久化) + */ + private String validateDraftResult(ContractManage contractManage, HcsResult result) { + if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdContractId())) { + log.error("HCS合同发起失败响应:{},contractNo={}", result == null ? "无响应" : result.getMsg(), contractManage.getContractNo()); + throw new HcsException("C103-1 合同发起失败:" + (result == null ? "响应为空" : result.getMsg())); + } + return result.getData().getFdContractId(); + } + + /** + * 回填合规 fdId 并置 synced + */ + public void markSynced(ContractManage contractManage, String fdId, String message) { + contractManage.setHcsFdId(fdId); + contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED); + contractManage.setHcsRetryCount(0); + contractManage.setHcsSyncTime(LocalDateTime.now()); + contractManage.setHcsSyncMsg(message); + contractManageMapper.updateById(contractManage); + log.info("HCS合同推送成功:contractNo={},fdContractId={},{}", contractManage.getContractNo(), fdId, message); + } + + /** + * 推送失败回写:failed + 重试次数+1 + 失败原因 + */ + public void markFailed(ContractManage contractManage, String message) { + contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED); + contractManage.setHcsRetryCount(contractManage.getHcsRetryCount() == null ? 1 : contractManage.getHcsRetryCount() + 1); + contractManage.setHcsSyncTime(LocalDateTime.now()); + contractManage.setHcsSyncMsg(message); + contractManageMapper.updateById(contractManage); + log.error("HCS合同推送失败:contractNo={},retryCount={},{}", contractManage.getContractNo(), contractManage.getHcsRetryCount(), message); + } + + /** + * 模板映射(template-mapping[contractCategory],未配置/无映射直接失败提示,§9-U7) + */ + private String requireTemplateId(ContractManage contractManage) { + Map mapping = hcsProperties.getContract().getTemplateMapping(); + String templateId = mapping.get(contractManage.getContractCategory()); + if (StringUtil.isBlank(templateId)) { + throw new HcsException("HCS合同推送失败,合同类别 [" + contractManage.getContractCategory() + + "] 未配置合规模板ID(thirdParty.hcs.contract.template-mapping,§9-U7)"); + } + return templateId; + } + + /** + * 合同承办人登录名(fdLoginName 必填) + */ + private String requireLoginName(ContractManage contractManage) { + if (contractManage.getHandlerUserId() == null) { + throw new HcsException("HCS合同推送失败,合同承办人未维护(contractNo=" + contractManage.getContractNo() + ")"); + } + User user = UserCache.getUser(contractManage.getHandlerUserId()); + if (user == null || StringUtil.isBlank(user.getAccount())) { + throw new HcsException("HCS合同推送失败,承办人登录名解析不到(handlerUserId=" + contractManage.getHandlerUserId() + ",§9-U2)"); + } + return user.getAccount(); + } + + /** + * 按名称反查客商编码(照 KingdeeApBillSyncSupport#queryCustomerCode 惯例,查不到返回 null) + */ + private String queryCustomerCode(String partyName) { + if (StringUtil.isBlank(partyName)) { + return null; + } + CustomerArchive customerArchive = customerArchiveMapper.selectOne(Wrappers.lambdaQuery() + .and(wrapper -> wrapper.eq(CustomerArchive::getFullName, partyName).or().eq(CustomerArchive::getShortName, partyName)) + .eq(CustomerArchive::getIsDeleted, 0) + .last("limit 1")); + return customerArchive == null ? null : customerArchive.getCustomerCode(); + } + + /** + * 币种名称映射:currency-name-mapping 命中取映射值,未命中传原值并告警(§9-U3) + */ + private String mapCurrencyName(String currency) { + if (StringUtil.isBlank(currency)) { + return null; + } + return hcsProperties.getContract().getCurrencyNameMapping().getOrDefault(currency, currency); + } + + /** + * 普通模板字段(字符串传值) + */ + private HcsXformFieldDTO field(String name, String label, String value) { + HcsXformFieldDTO field = new HcsXformFieldDTO(); + field.setName(name); + field.setLabel(label); + field.setValue(value); + return field; + } + + /** + * 时间模板字段(fdType=timestamp,日期传 yyyy-MM-dd,时间传 yyyy-MM-dd HH:mm:ss) + */ + private HcsXformFieldDTO timestampField(String name, String label, Object value) { + if (value == null) { + return field(name, label, null); + } + String text; + if (value instanceof LocalDate localDate) { + text = localDate.format(DATE_FORMATTER); + } else if (value instanceof LocalDateTime localDateTime) { + text = localDateTime.format(DATETIME_FORMATTER); + } else { + // BaseEntity.createTime 为 Date 类型 + text = DATETIME_FORMATTER.format(((java.util.Date) value).toInstant() + .atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()); + } + HcsXformFieldDTO field = field(name, label, text); + // 时间字段需带 fdType=timestamp + field.setFdType("timestamp"); + return field; + } + + /** + * 金额字符串(空值安全) + */ + private String toPlainString(BigDecimal amount) { + return amount == null ? null : amount.toPlainString(); + } + + /** + * 风险校验留痕(blade_hcs_check_log,异常不影响主流程) + */ + private void writeCheckLog(ContractManage contractManage, String result, String msg, String traceId) { + try { + HcsContractCheckLog checkLog = new HcsContractCheckLog(); + checkLog.setTenantId(contractManage.getTenantId()); + checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT); + checkLog.setBizId(contractManage.getId()); + checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_RISK); + checkLog.setResult(result); + checkLog.setMsg(msg); + checkLog.setTraceId(traceId); + checkLog.setCreateTime(LocalDateTime.now()); + hcsContractCheckLogMapper.insert(checkLog); + } catch (Exception exception) { + log.error("HCS风险校验留痕写入失败:contractNo={}", contractManage.getContractNo(), exception); + } + } + + /** + * 单方风险查询结果 + */ + private record RiskResult(String blackStatus, String riskLevel, boolean blacklist) { + } +}