diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java index 6e7a46ed..9442ed96 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java @@ -321,4 +321,19 @@ public class HcsSyncController { public R projectLegalResult(@RequestBody(required = false) HcsProjectLegalResultDTO param) { return R.data(hcsProjectSyncSupport.handleLegalResult(param)); } + + /** + * 合同金蝶同步结果回传接收(合规系统 contractKingdee/result 推送,D903,投送人/钢厂/结算中心合同共用,原文留痕) + *

+ * 免登免授权:blade.secure skip-url + auth permitAll 放行(doc/nacos/blade.yaml); + * iPaaS 网关注册映射:/gwzh/HTHG/contractKingdee/result → /hcs/contractKingdee/result; + * 报文 {contractType?, contractCode, requestId, success, reason?, kingdeeId?, sourceSystem?}, + * 本接口不处理法务审批结论,仅按 contractCode=TMS 合同编号定位回填金蝶合同ID/记录失败原因。 + */ + @PostMapping("/contractKingdee/result") + @ApiOperationSupport(order = 19) + @Operation(summary = "合同金蝶同步结果回传接收", description = "合规系统推送合同金蝶最终同步结果(D903,投送人/钢厂/结算中心共用),按合同编号定位回填金蝶ID,不处理法务审批结论,原文留痕") + public R contractKingdeeResult(@RequestBody(required = false) HcsContractLegalResultDTO param) { + return R.data(hcsContractSyncSupport.handleKingdeeResult(param)); + } } 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 index c527c9b6..47e1adc2 100644 --- 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 @@ -25,6 +25,7 @@ */ package org.springblade.transport.support.hcs; +import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,6 +38,7 @@ import org.springblade.hcs.dto.HcsAttachmentRefDTO; import org.springblade.hcs.dto.HcsContractDraftDTO; import org.springblade.hcs.dto.HcsContractDraftVO; import org.springblade.hcs.dto.HcsContractFieldJsonDTO; +import org.springblade.hcs.dto.HcsContractLegalResultDTO; import org.springblade.hcs.dto.HcsContractVO; import org.springblade.hcs.dto.HcsPageQueryDTO; import org.springblade.hcs.dto.HcsPageVO; @@ -66,6 +68,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -97,6 +100,11 @@ public class HcsContractSyncSupport { */ private static final String BLACK_STATUS_YES = "yes"; + /** + * 合同金蝶同步结果回传来源系统标识(D903 约定传 WCCK,非必填,传值时校验) + */ + private static final String KINGDEE_CALLBACK_SRC_SYSTEM = "WCCK"; + private final IHcsClient hcsClient; private final HcsProperties hcsProperties; private final ContractManageMapper contractManageMapper; @@ -107,6 +115,123 @@ public class HcsContractSyncSupport { private final HcsExeStageSyncSupport hcsExeStageSyncSupport; private final HcsContractSpecSyncSupport hcsContractSpecSyncSupport; + /** + * 合同金蝶同步结果接收处理(合规 contractKingdee/result 推送,D903,投送人/钢厂/结算中心合同共用): + * 合规侧收到金蝶最终同步结果后通知 TMS 更新同步状态,本接口不处理法务审批结论(特批联动走 contractLegal/result + C103-4 兜底); + * contractType(deliverer/gc/sett_center)非必填仅随原文留痕;sourceSystem 非必填,传值时须为 WCCK; + * contractCode=TMS 合同编号定位(requestId 仅 X-ERP-Request-Id 回显不可定位); + * success=true 回填金蝶合同ID(kingdeeId),success=false 记录失败原因;均追加变更记录并原文留痕 blade_hcs_check_log + * + * @param param 合规推送报文 + * @return 处理结果描述 + */ + public String handleKingdeeResult(HcsContractLegalResultDTO param) { + // sourceSystem 非必填(D903),传值时仅接受合规方约定的 WCCK,防伪造回传 + String sourceSystem = param == null ? null : param.getSourceSystem(); + if (StringUtil.isNotBlank(sourceSystem) && !KINGDEE_CALLBACK_SRC_SYSTEM.equalsIgnoreCase(sourceSystem)) { + log.warn("合同金蝶同步结果推送来源系统标识错误:sourceSystem={}", sourceSystem); + throw new ServiceException("来源系统标识错误"); + } + String raw = JSONUtil.toJsonStr(param); + ContractManage contractManage = locateContractByNo(param.getContractCode()); + if (contractManage == null) { + log.warn("合同金蝶同步结果推送未定位到合同:contractCode={},报文已留痕", param.getContractCode()); + writeKingdeeCallbackLog(null, HcsSyncConstant.CHECK_RESULT_SKIP, "合同金蝶同步结果推送未定位到合同:" + raw); + return "已留痕:未定位到合同(contractCode=" + param.getContractCode() + ")"; + } + if (param.getSuccess() == null) { + log.warn("合同金蝶同步结果推送 success 字段缺失:contractNo={},报文已留痕", contractManage.getContractNo()); + writeKingdeeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP, "合同金蝶同步结果推送 success 字段缺失:" + raw); + return "已留痕:success 字段缺失(contractNo=" + contractManage.getContractNo() + ")"; + } + if (param.getSuccess()) { + // 成功:回填金蝶合同ID,同步结果信息随 hcs_sync_msg 留痕,不联动审批/特批状态 + contractManage.setKingdeeId(param.getKingdeeId()); + contractManage.setHcsSyncMsg(HcsSyncConstant.truncateMsg("合同金蝶同步结果回调:金蝶同步成功,kingdeeId=" + param.getKingdeeId())); + appendKingdeeResultRecord(contractManage, "金蝶同步成功:金蝶合同ID=" + param.getKingdeeId()); + contractManageMapper.updateById(contractManage); + writeKingdeeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_PASS, "合同金蝶同步结果推送:" + raw); + log.info("合同金蝶同步结果推送处理完成:contractNo={},结果=success,kingdeeId={}", contractManage.getContractNo(), param.getKingdeeId()); + return "金蝶同步结果已处理:success"; + } + // 失败:记录失败原因(金蝶同步在合规侧完成,TMS 不重推,失败重试由合规侧负责) + contractManage.setHcsSyncMsg(HcsSyncConstant.truncateMsg("合同金蝶同步结果回调:金蝶同步失败:" + param.getReason())); + appendKingdeeResultRecord(contractManage, "金蝶同步失败:" + param.getReason()); + contractManageMapper.updateById(contractManage); + writeKingdeeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_FAIL, "合同金蝶同步结果推送:" + raw); + log.info("合同金蝶同步结果推送处理完成:contractNo={},结果=fail,reason={}", contractManage.getContractNo(), param.getReason()); + return "金蝶同步结果已处理:fail"; + } + + /** + * 按 ERP 合同编号定位合同(合规推送 contractCode,合同编号唯一) + */ + private ContractManage locateContractByNo(String contractCode) { + if (StringUtil.isBlank(contractCode)) { + return null; + } + return contractManageMapper.selectOne(Wrappers.lambdaQuery() + .eq(ContractManage::getContractNo, contractCode.trim()) + .last("limit 1")); + } + + /** + * 合同金蝶同步结果推送留痕(blade_hcs_check_log,biz_type=contract,合同未定位到时 bizId 为空,异常不影响主流程) + */ + private void writeKingdeeCallbackLog(ContractManage contractManage, String result, String msg) { + try { + HcsContractCheckLog checkLog = new HcsContractCheckLog(); + if (contractManage != null) { + checkLog.setTenantId(contractManage.getTenantId()); + checkLog.setBizId(contractManage.getId()); + } + checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT); + checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_KINGDEE); + checkLog.setResult(result); + checkLog.setMsg(StringUtil.left(msg, HcsSyncConstant.SYNC_MSG_MAX_LENGTH)); + checkLog.setCreateTime(LocalDateTime.now()); + hcsContractCheckLogMapper.insert(checkLog); + } catch (Exception exception) { + log.error("合同金蝶同步结果推送留痕写入失败:contractNo={}", contractManage == null ? null : contractManage.getContractNo(), exception); + } + } + + /** + * 金蝶同步结果回填变更记录(changeRecordJson 追加一条;系统回调无登录用户,操作人记来源标识) + */ + private void appendKingdeeResultRecord(ContractManage contractManage, String changeContent) { + List records = StringUtil.isBlank(contractManage.getChangeRecordJson()) + ? new ArrayList<>() : new ArrayList<>(JSONUtil.toList(contractManage.getChangeRecordJson(), Object.class)); + Map record = new LinkedHashMap<>(); + record.put("changeDate", LocalDate.now().toString()); + record.put("handlerUserName", "合规系统回调"); + record.put("changeType", "金蝶同步结果"); + record.put("changeContent", changeContent); + record.put("status", contractManage.getApprovalStatus()); + record.put("statusName", approvalStatusName(contractManage.getApprovalStatus())); + records.add(record); + contractManage.setChangeRecordJson(JSONUtil.toJsonStr(records)); + } + + /** + * 审批状态中文名(变更记录展示用,未匹配回退原值) + */ + private String approvalStatusName(String approvalStatus) { + if (StringUtil.isBlank(approvalStatus)) { + return null; + } + return switch (approvalStatus) { + case "draft" -> "草稿"; + case "reviewing" -> "审批中"; + case "withdrawn" -> "已撤回"; + case "approved" -> "审批通过"; + case "rejected" -> "已驳回"; + case "change_reviewing" -> "变更审批中"; + case "change_approved" -> "变更审批通过"; + default -> approvalStatus; + }; + } + /** * 推送合同到 HCS:特批挂起门禁 → 风险校验(仅留痕,高风险不再阻断)→ 附件转传 → 判重 → 起草/更新 → 回填合规 fdId 置 synced * → 履约计划行级同步(C103-9,先于特批挂起)→ 高风险/黑名单客商合同挂起"特批审核"等待合规特批结果(报文随 fdSepcailContract=true,外部客商管控) diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index f0aab0c3..8515a4fb 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -237,13 +237,15 @@ blade: - /feign/client/businessProcess/getProcessInfo - /feign/client/customerArchive/syncProcessNode - /feign/client/mkProcess/** - # HCS 合同/付款特批结果回传 + 项目金蝶同步结果回传:合规系统经 iPaaS 网关无令牌调用,免登免授权 + # HCS 合同/付款特批结果回传 + 项目/合同金蝶同步结果回传:合规系统经 iPaaS 网关无令牌调用,免登免授权 - /blade-transport/hcs/contractLegal/result - /hcs/contractLegal/result - /blade-transport/hcs/paymentLegal/result - /hcs/paymentLegal/result - /blade-transport/hcs/projectLegal/result - /hcs/projectLegal/result + - /blade-transport/hcs/contractKingdee/result + - /hcs/contractKingdee/result #授权认证配置 auth: - method: ALL @@ -271,7 +273,7 @@ blade: - method: POST pattern: /kingdee-bfi/** expression: "permitAll()" - # HCS 合同/付款特批结果回传 + 项目金蝶同步结果回传:合规系统经 iPaaS 网关无令牌调用,免登免授权 + # HCS 合同/付款特批结果回传 + 项目/合同金蝶同步结果回传:合规系统经 iPaaS 网关无令牌调用,免登免授权 - method: POST pattern: /blade-transport/hcs/contractLegal/result expression: "permitAll()" @@ -290,6 +292,12 @@ blade: - method: POST pattern: /hcs/projectLegal/result expression: "permitAll()" + - method: POST + pattern: /blade-transport/hcs/contractKingdee/result + expression: "permitAll()" + - method: POST + pattern: /hcs/contractKingdee/result + expression: "permitAll()" #基础认证配置 basic: - method: ALL