:feat: 新增合同金蝶同步结果回调(D903)

- 新增 /hcs/contractKingdee/result 免登回调端点,投送人/钢厂/结算中心合同共用
- 按合同编号定位回填金蝶合同ID,失败记录原因;本接口不处理法务审批结论
- contractType/sourceSystem 均按非必填处理,传值时校验来源标识为 WCCK
- 回调成功/失败追加变更记录并原文留痕 blade_hcs_check_log(check_type=kingdee)
This commit is contained in:
刘泉佳
2026-10-20 16:45:52 +08:00
parent fac4c112aa
commit 7a48057019
3 changed files with 150 additions and 2 deletions
@@ -321,4 +321,19 @@ public class HcsSyncController {
public R<String> projectLegalResult(@RequestBody(required = false) HcsProjectLegalResultDTO param) {
return R.data(hcsProjectSyncSupport.handleLegalResult(param));
}
/**
* 合同金蝶同步结果回传接收(合规系统 contractKingdee/result 推送,D903,投送人/钢厂/结算中心合同共用,原文留痕)
* <p>
* 免登免授权: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<String> contractKingdeeResult(@RequestBody(required = false) HcsContractLegalResultDTO param) {
return R.data(hcsContractSyncSupport.handleKingdeeResult(param));
}
}
@@ -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.<ContractManage>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<Object> records = StringUtil.isBlank(contractManage.getChangeRecordJson())
? new ArrayList<>() : new ArrayList<>(JSONUtil.toList(contractManage.getChangeRecordJson(), Object.class));
Map<String, Object> 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,外部客商管控)
+10 -2
View File
@@ -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