diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java index c443b22b..af719922 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java @@ -262,4 +262,7 @@ public class ContractManage extends TenantEntity { @Schema(description = "特批申请材料+内部审批附件JSON(含C103-2 合规附件ID)") private String hcsSpecFilesJson; + @Schema(description = "金蝶合同ID(contractLegal/result 合同特批金蝶同步结果回调回填)") + private String kingdeeId; + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java index 0b6bdffa..46ede04c 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java @@ -215,4 +215,10 @@ public class ProjectApply extends TenantEntity { @TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS) private String hcsSyncMsg; + @Schema(description = "金蝶项目ID(D902 项目金蝶同步结果回调回填)") + private String kingdeeId; + + @Schema(description = "金蝶项目编号(D902 项目金蝶同步结果回调回填)") + private String kingdeeNumber; + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java index fc429ffe..5f76a439 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ContractManageVO.java @@ -55,6 +55,14 @@ public class ContractManageVO extends ContractManage { @Schema(description = "到期快捷筛选") private String expireScope; + @TableField(exist = false) + @Schema(description = "是否已同步金蝶(金蝶合同ID是否已回填)") + private Boolean kingdeeSynced; + + @TableField(exist = false) + @Schema(description = "是否已同步合规(合规同步状态是否为 synced)") + private Boolean hcsSynced; + @TableField(exist = false) @Schema(description = "生效类型筛选") private String effectiveType; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java index aed01d69..c4cd0fa6 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java @@ -86,6 +86,14 @@ public class ProjectApplyVO extends ProjectApply { @Schema(description = "是否仅查询可用于合同选择的项目(含临时项目与正式审批通过项目)") private Boolean contractSelectable; + @TableField(exist = false) + @Schema(description = "是否已同步金蝶(金蝶项目ID是否已回填)") + private Boolean kingdeeSynced; + + @TableField(exist = false) + @Schema(description = "是否已同步合规(合规同步状态是否为 synced)") + private Boolean hcsSynced; + @TableField(exist = false) @Schema(description = "资金使用风险等级:high、medium、none") private String fundUseRisk; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java index 3927006e..e82d304a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java @@ -89,6 +89,11 @@ public final class HcsSyncConstant { */ public static final String CHECK_TYPE_SPEC = "spec"; + /** + * 校验类型:项目金蝶同步结果回调(D902,projectLegal/result 推送留痕) + */ + public static final String CHECK_TYPE_KINGDEE = "kingdee"; + /** * 校验结果:通过 */ 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 70fbbb66..6e7a46ed 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 @@ -36,11 +36,13 @@ import lombok.extern.slf4j.Slf4j; import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; import org.springblade.hcs.dto.HcsAttachmentRefDTO; import org.springblade.hcs.dto.HcsContractLegalResultDTO; import org.springblade.hcs.dto.HcsCurrencyVO; import org.springblade.hcs.dto.HcsLegalResultDTO; +import org.springblade.hcs.dto.HcsProjectLegalResultDTO; import org.springblade.hcs.dto.HcsTemplateVO; import org.springblade.transport.constant.HcsSyncConstant; import org.springblade.transport.pojo.vo.CustomerRiskVO; @@ -223,6 +225,17 @@ public class HcsSyncController { return R.data(hcsCreditCheckSupport.queryCustomerRisk(customerCode)); } + /** + * 客商风险批量查询(C101-1,客商列表页展示用,前端接入设计 §3.3): + * 按客商编号集合逐个查询(口径同 /risk),展示用不作为安全边界,单个失败跳过不阻断整页 + */ + @GetMapping("/risk/batch") + @ApiOperationSupport(order = 13) + @Operation(summary = "客商风险批量查询", description = "C101-1 按客商编号集合批量查询风险等级与黑名单状态(展示用),口径同单个查询,单个失败跳过不阻断") + public R> riskBatch(@Parameter(description = "客商编号集合,逗号分隔", required = true) @RequestParam String customerCodes) { + return R.data(hcsCreditCheckSupport.queryCustomerRiskBatch(Func.toStrList(customerCodes))); + } + /** * 附件上传到合同系统(C103-2 代理,前端接入设计 §3.2),返回合规附件ID */ @@ -293,4 +306,19 @@ public class HcsSyncController { public R paymentLegalResult(@RequestBody(required = false) HcsLegalResultDTO param) { return R.data(hcsPaymentSyncSupport.handleLegalResult(param)); } + + /** + * 项目金蝶同步结果回传接收(合规系统 projectLegal/result 推送,D902,原文留痕) + *

+ * 免登免授权:blade.secure skip-url + auth permitAll 放行(doc/nacos/blade.yaml); + * iPaaS 网关注册映射:/gwzh/HTHG/projectLegal/result → /hcs/projectLegal/result; + * 报文 {sourceSystem, projectCode, requestId, success, kingdeeId, projectNumber, reason}, + * 按 projectCode=TMS 项目编号定位,success=true 回填金蝶项目ID/编号,success=false 记录失败原因。 + */ + @PostMapping("/projectLegal/result") + @ApiOperationSupport(order = 18) + @Operation(summary = "项目金蝶同步结果回传接收", description = "合规系统推送项目金蝶同步结果(D902),按项目编号定位并回填金蝶ID,原文留痕") + public R projectLegalResult(@RequestBody(required = false) HcsProjectLegalResultDTO param) { + return R.data(hcsProjectSyncSupport.handleLegalResult(param)); + } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java index 2ef52bdb..03581c51 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java @@ -482,9 +482,29 @@ public class ContractManageServiceImpl extends BaseServiceImpl queryWrapper, Boolean kingdeeSynced, Boolean hcsSynced) { + if (Boolean.TRUE.equals(kingdeeSynced)) { + queryWrapper.isNotNull(ContractManage::getKingdeeId).ne(ContractManage::getKingdeeId, ""); + } else if (Boolean.FALSE.equals(kingdeeSynced)) { + queryWrapper.and(wrapper -> wrapper.isNull(ContractManage::getKingdeeId) + .or().eq(ContractManage::getKingdeeId, "")); + } + if (Boolean.TRUE.equals(hcsSynced)) { + queryWrapper.eq(ContractManage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED); + } else if (Boolean.FALSE.equals(hcsSynced)) { + queryWrapper.and(wrapper -> wrapper.isNull(ContractManage::getHcsSyncStatus) + .or().ne(ContractManage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED)); + } + } + private void applyEffectiveType(LambdaQueryWrapper queryWrapper, String effectiveType) { if (Func.isEmpty(effectiveType)) { return; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java index f6295f43..46f9b3fe 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java @@ -406,9 +406,29 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl queryWrapper, Boolean kingdeeSynced, Boolean hcsSynced) { + if (Boolean.TRUE.equals(kingdeeSynced)) { + queryWrapper.isNotNull(ProjectApply::getKingdeeId).ne(ProjectApply::getKingdeeId, ""); + } else if (Boolean.FALSE.equals(kingdeeSynced)) { + queryWrapper.and(wrapper -> wrapper.isNull(ProjectApply::getKingdeeId) + .or().eq(ProjectApply::getKingdeeId, "")); + } + if (Boolean.TRUE.equals(hcsSynced)) { + queryWrapper.eq(ProjectApply::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED); + } else if (Boolean.FALSE.equals(hcsSynced)) { + queryWrapper.and(wrapper -> wrapper.isNull(ProjectApply::getHcsSyncStatus) + .or().ne(ProjectApply::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED)); + } + } + private void fillFundUseRisk(List records) { if (records == null || records.isEmpty()) { return; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSpecSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSpecSyncSupport.java index e5fec114..dad84844 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSpecSyncSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsContractSpecSyncSupport.java @@ -45,8 +45,12 @@ import org.springblade.transport.pojo.entity.HcsContractCheckLog; import org.springframework.stereotype.Component; import java.time.LocalDateTime; +import java.time.LocalDate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * HCS 合同特批同步支撑组件(合规特批结果接收:contractLegal/result 推送 + C103-4 定时查询兜底 + 人工确认)。 @@ -169,7 +173,7 @@ public class HcsContractSpecSyncSupport { * 合同特批结果接收处理(合规 contractLegal/result 推送,2026-10-13 按合规方最新字段表修正报文结构): * 特批完成后合规侧同步金蝶并回调,报文 {contractType, contractCode, requestId, success, reason, kingdeeId, sourceSystem=WCCK}; * sourceSystem 须为 WCCK(不符抛"来源系统标识错误"异常);contractCode=TMS 合同编号定位(requestId 仅请求头回显不可定位); - * success=true 联动恢复"审批通过"(kingdeeId 随留痕保存);success=false 联动合同审批驳回走重新提交闭环(reason 随原文留痕) + * success=true 联动恢复"审批通过"(金蝶合同ID回填 kingdee_id);success=false 联动合同审批驳回走重新提交闭环(reason 随原文留痕) * * @param param 合规推送报文 * @return 处理结果描述 @@ -193,9 +197,11 @@ public class HcsContractSpecSyncSupport { return "已留痕:success 字段缺失(contractNo=" + contractManage.getContractNo() + ")"; } boolean success = param.getSuccess(); - // 成功:特批完成且金蝶同步成功,恢复"审批通过";kingdeeId 随 hcs_spec_msg/留痕保存 + // 成功:特批完成且金蝶同步成功,恢复"审批通过";金蝶合同ID回填 kingdee_id 留痕保存 if (success) { syncContractOnSpecResult(contractManage, true); + contractManage.setKingdeeId(param.getKingdeeId()); + appendKingdeeResultRecord(contractManage, "金蝶同步成功:金蝶合同ID=" + param.getKingdeeId()); markSpec(contractManage, HcsSyncConstant.SPEC_STATUS_APPROVED, "合同特批结果推送:金蝶同步成功,kingdeeId=" + param.getKingdeeId()); writeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_PASS, "合同特批结果推送:" + raw); @@ -204,12 +210,49 @@ public class HcsContractSpecSyncSupport { } // 失败:特批流程已完成但金蝶同步失败,联动合同审批驳回,走重新编辑→提交→审批→重新特批闭环(失败单独重试机制待联调确认) syncContractOnSpecResult(contractManage, false); + appendKingdeeResultRecord(contractManage, "金蝶同步失败:" + param.getReason()); markSpec(contractManage, HcsSyncConstant.SPEC_STATUS_REJECTED, "合同特批结果推送:金蝶同步失败:" + param.getReason()); writeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_FAIL, "合同特批结果推送:" + raw); log.info("合同特批结果推送处理完成:contractNo={},结果=rejected,reason={}", contractManage.getContractNo(), param.getReason()); return "特批结果已处理:rejected"; } + /** + * 金蝶同步结果回填变更记录(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; + }; + } + /** * 按 ERP 合同编号定位合同(合规推送 contractCode,合同编号唯一) */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsProjectSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsProjectSyncSupport.java index 45f8b7de..db8c5106 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsProjectSyncSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsProjectSyncSupport.java @@ -25,6 +25,8 @@ */ 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; import org.springblade.core.log.exception.ServiceException; @@ -36,6 +38,7 @@ import org.springblade.hcs.dto.HcsPageQueryDTO; import org.springblade.hcs.dto.HcsPageVO; import org.springblade.hcs.dto.HcsProjectAddDTO; import org.springblade.hcs.dto.HcsProjectFormDataDTO; +import org.springblade.hcs.dto.HcsProjectLegalResultDTO; import org.springblade.hcs.dto.HcsProjectPropsDTO; import org.springblade.hcs.dto.HcsProjectUpdateDTO; import org.springblade.hcs.dto.HcsProjectVO; @@ -47,12 +50,17 @@ import org.springblade.hcs.exception.HcsException; import org.springblade.hcs.feign.IHcsClient; import org.springblade.transport.constant.HcsFieldConstant; import org.springblade.transport.constant.HcsSyncConstant; +import org.springblade.transport.mapper.HcsContractCheckLogMapper; import org.springblade.transport.mapper.ProjectApplyMapper; +import org.springblade.transport.pojo.entity.HcsContractCheckLog; import org.springblade.transport.pojo.entity.ProjectApply; import org.springframework.stereotype.Component; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -76,9 +84,15 @@ public class HcsProjectSyncSupport { */ private static final BigDecimal WAN_TO_YUAN = BigDecimal.valueOf(10000); + /** + * 项目金蝶同步结果回传来源系统标识(合规方接口文档约定固定传 WCCK) + */ + private static final String LEGAL_CALLBACK_SRC_SYSTEM = "WCCK"; + private final IHcsClient hcsClient; private final HcsProperties hcsProperties; private final ProjectApplyMapper projectApplyMapper; + private final HcsContractCheckLogMapper hcsContractCheckLogMapper; /** * 推送项目立项到 HCS:判重 → 新建(C102-1)/ 更新(C102-2)→ 回填合规 fdId 置 synced @@ -257,6 +271,125 @@ public class HcsProjectSyncSupport { return page.getContent().get(0).getFdId(); } + /** + * 项目金蝶同步结果接收处理(合规 projectLegal/result 推送,D902): + * sourceSystem 须为 WCCK(不符抛"来源系统标识错误"异常);projectCode=TMS 项目编号定位(requestId 仅 X-ERP-Request-Id 回显不可定位); + * success=true 回填金蝶项目ID/编号(kingdeeId/kingdeeNumber);success=false 记录失败原因;原文留痕 blade_hcs_check_log + * + * @param param 合规推送报文 + * @return 处理结果描述 + */ + public String handleLegalResult(HcsProjectLegalResultDTO param) { + // 来源系统校验:仅接受合规方约定的 WCCK,防伪造回传 + if (!LEGAL_CALLBACK_SRC_SYSTEM.equalsIgnoreCase(param == null ? null : param.getSourceSystem())) { + log.warn("项目金蝶同步结果推送来源系统标识错误:sourceSystem={}", param == null ? null : param.getSourceSystem()); + throw new ServiceException("来源系统标识错误"); + } + String raw = JSONUtil.toJsonStr(param); + ProjectApply projectApply = locateProjectByCode(param.getProjectCode()); + if (projectApply == null) { + log.warn("项目金蝶同步结果推送未定位到项目:projectCode={},报文已留痕", param.getProjectCode()); + writeCallbackLog(null, HcsSyncConstant.CHECK_RESULT_SKIP, "项目金蝶同步结果推送未定位到项目:" + raw); + return "已留痕:未定位到项目(projectCode=" + param.getProjectCode() + ")"; + } + if (param.getSuccess() == null) { + log.warn("项目金蝶同步结果推送 success 字段缺失:projectCode={},报文已留痕", projectApply.getProjectCode()); + writeCallbackLog(projectApply, HcsSyncConstant.CHECK_RESULT_SKIP, "项目金蝶同步结果推送 success 字段缺失:" + raw); + return "已留痕:success 字段缺失(projectCode=" + projectApply.getProjectCode() + ")"; + } + if (param.getSuccess()) { + // 成功:回填金蝶项目ID/编号,同步结果信息随 hcs_sync_msg 留痕 + projectApply.setKingdeeId(param.getKingdeeId()); + projectApply.setKingdeeNumber(param.getProjectNumber()); + projectApply.setHcsSyncMsg(HcsSyncConstant.truncateMsg("项目金蝶同步结果回调:金蝶同步成功,kingdeeId=" + param.getKingdeeId() + + ",projectNumber=" + param.getProjectNumber())); + appendKingdeeResultRecord(projectApply, "金蝶同步成功:金蝶项目ID=" + param.getKingdeeId() + + ",金蝶项目编号=" + param.getProjectNumber()); + projectApplyMapper.updateById(projectApply); + writeCallbackLog(projectApply, HcsSyncConstant.CHECK_RESULT_PASS, "项目金蝶同步结果推送:" + raw); + log.info("项目金蝶同步结果推送处理完成:projectCode={},结果=success,kingdeeId={}", projectApply.getProjectCode(), param.getKingdeeId()); + return "金蝶同步结果已处理:success"; + } + // 失败:记录失败原因(金蝶同步在合规侧完成,TMS 不重推,失败重试由合规侧负责) + projectApply.setHcsSyncMsg(HcsSyncConstant.truncateMsg("项目金蝶同步结果回调:金蝶同步失败:" + param.getReason())); + appendKingdeeResultRecord(projectApply, "金蝶同步失败:" + param.getReason()); + projectApplyMapper.updateById(projectApply); + writeCallbackLog(projectApply, HcsSyncConstant.CHECK_RESULT_FAIL, "项目金蝶同步结果推送:" + raw); + log.info("项目金蝶同步结果推送处理完成:projectCode={},结果=fail,reason={}", projectApply.getProjectCode(), param.getReason()); + return "金蝶同步结果已处理:fail"; + } + + /** + * 金蝶同步结果回填变更记录(changeRecordJson 追加一条;系统回调无登录用户,操作人记来源标识) + */ + private void appendKingdeeResultRecord(ProjectApply projectApply, String changeContent) { + List records = StringUtil.isBlank(projectApply.getChangeRecordJson()) + ? new ArrayList<>() : new ArrayList<>(JSONUtil.toList(projectApply.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", projectApply.getApprovalStatus()); + record.put("statusName", approvalStatusName(projectApply.getApprovalStatus())); + records.add(record); + projectApply.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_rejected" -> "变更驳回"; + case "change_approved" -> "变更审批通过"; + case "voided" -> "已作废"; + default -> approvalStatus; + }; + } + + /** + * 按 ERP 项目编号定位项目(合规推送 projectCode,对应 C102 fd_num) + */ + private ProjectApply locateProjectByCode(String projectCode) { + if (StringUtil.isBlank(projectCode)) { + return null; + } + return projectApplyMapper.selectOne(Wrappers.lambdaQuery() + .eq(ProjectApply::getProjectCode, projectCode.trim()) + .last("limit 1")); + } + + /** + * 项目金蝶同步结果推送留痕(blade_hcs_check_log,biz_type=project,项目未定位到时 bizId 为空,异常不影响主流程) + */ + private void writeCallbackLog(ProjectApply projectApply, String result, String msg) { + try { + HcsContractCheckLog checkLog = new HcsContractCheckLog(); + if (projectApply != null) { + checkLog.setTenantId(projectApply.getTenantId()); + checkLog.setBizId(projectApply.getId()); + } + checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_PROJECT); + 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("项目金蝶同步结果推送留痕写入失败:projectCode={}", projectApply == null ? null : projectApply.getProjectCode(), exception); + } + } + /** * 单条手动重推:重置为待同步并清零重试计数后立即推送(页面"推送合规/重推"按钮入口) * diff --git a/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsProjectLegalResultDTO.java b/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsProjectLegalResultDTO.java new file mode 100644 index 00000000..727c0a4b --- /dev/null +++ b/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsProjectLegalResultDTO.java @@ -0,0 +1,78 @@ +/** + * 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.hcs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 项目金蝶同步完成后合规系统回传 TMS 的结果报文(D902,POST /hcs/projectLegal/result,iPaaS 网关 /gwzh/HTHG/projectLegal/result) + *

+ * 定位项目用 projectCode(ERP 项目编号,对应 C102 请求中的 fd_num), + * requestId 仅是 C102 请求头 X-ERP-Request-Id 原样回传、不能用于定位; + * 未知或过期的 requestId 合规侧返回失败。 + * + * @author Chill + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class HcsProjectLegalResultDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * ERP 项目编号(TMS blade_project_apply.project_code,对应 C102 请求中的 fd_num,定位项目用) + */ + private String projectCode; + /** + * C102 请求头 X-ERP-Request-Id 的值,原样返回(不用于定位) + */ + private String requestId; + /** + * 金蝶同步成功传 true,失败传 false + */ + private Boolean success; + /** + * 金蝶项目 ID;成功时用于回填保存 + */ + private String kingdeeId; + /** + * 金蝶项目编号;成功时用于回填保存 + */ + private String projectNumber; + /** + * 金蝶同步失败原因;失败时建议填写 + */ + private String reason; + /** + * 来源系统标识,约定传 WCCK(最长 64 字符) + */ + private String sourceSystem; +} diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index 0b182866..f0aab0c3 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -237,11 +237,13 @@ 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 #授权认证配置 auth: - method: ALL @@ -269,7 +271,7 @@ blade: - method: POST pattern: /kingdee-bfi/** expression: "permitAll()" - # HCS 合同/付款特批结果回传:合规系统经 iPaaS 网关无令牌调用,免登免授权 + # HCS 合同/付款特批结果回传 + 项目金蝶同步结果回传:合规系统经 iPaaS 网关无令牌调用,免登免授权 - method: POST pattern: /blade-transport/hcs/contractLegal/result expression: "permitAll()" @@ -282,6 +284,12 @@ blade: - method: POST pattern: /hcs/paymentLegal/result expression: "permitAll()" + - method: POST + pattern: /blade-transport/hcs/projectLegal/result + expression: "permitAll()" + - method: POST + pattern: /hcs/projectLegal/result + expression: "permitAll()" #基础认证配置 basic: - method: ALL diff --git a/doc/sql/transport/blade_hcs_kingdee_id_20261020.sql b/doc/sql/transport/blade_hcs_kingdee_id_20261020.sql new file mode 100644 index 00000000..92a8fd4e --- /dev/null +++ b/doc/sql/transport/blade_hcs_kingdee_id_20261020.sql @@ -0,0 +1,30 @@ +-- HCS 金蝶同步结果回调回填字段: +-- blade_project_apply 追加金蝶项目ID/编号(D902 项目金蝶同步结果回调 projectLegal/result 回填); +-- blade_contract_manage 追加金蝶合同ID(合同特批金蝶同步结果回调 contractLegal/result 回填)。 +-- 通过 information_schema 判断字段是否存在,可重复执行。 +SET @db_name = DATABASE(); + +-- ==================== blade_project_apply(D902 项目金蝶同步结果回填) ==================== + +SET @sql = IF( + EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = @db_name AND table_name = 'blade_project_apply' AND column_name = 'kingdee_id'), + 'SELECT 1', + 'ALTER TABLE blade_project_apply ADD COLUMN kingdee_id varchar(64) DEFAULT NULL COMMENT ''金蝶项目ID(D902 回调回填)''' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +SET @sql = IF( + EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = @db_name AND table_name = 'blade_project_apply' AND column_name = 'kingdee_number'), + 'SELECT 1', + 'ALTER TABLE blade_project_apply ADD COLUMN kingdee_number varchar(64) DEFAULT NULL COMMENT ''金蝶项目编号(D902 回调回填)''' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +-- ==================== blade_contract_manage(contractLegal/result 金蝶同步结果回填) ==================== + +SET @sql = IF( + EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = @db_name AND table_name = 'blade_contract_manage' AND column_name = 'kingdee_id'), + 'SELECT 1', + 'ALTER TABLE blade_contract_manage ADD COLUMN kingdee_id varchar(64) DEFAULT NULL COMMENT ''金蝶合同ID(contractLegal/result 回调回填)''' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;