✨ 新增金蝶客商数据定时同步任务(自动推送+使用状态停用联动)

This commit is contained in:
刘泉佳
2026-09-18 21:34:37 +08:00
parent d9f94bb161
commit 0531177ed7
4 changed files with 209 additions and 1 deletions
@@ -132,6 +132,21 @@ public interface ICustomerArchiveService extends BaseService<CustomerArchive> {
*/
CustomerArchive syncKingdee(Long id);
/**
* 定时同步金蝶客商数据:待推送客商自动推送 + 已同步客商使用状态回写
*
* @return 同步汇总说明
*/
String syncKingdeeCustomerData();
/**
* 金蝶侧停用联动:停用客商档案并记录变更(仅当前启用的客商生效)
*
* @param id 主键
* @return 是否执行停用
*/
boolean disableCustomerFromKingdee(Long id);
/**
* 基于启用评分量化表生成评分记录
*
@@ -36,6 +36,7 @@ import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.thirdparty.bfi.constant.BfiConstant;
import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerQueryDTO;
@@ -128,6 +129,10 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
private static final String KINGDEE_STATUS_SYNCED = "synced";
private static final String KINGDEE_STATUS_FAILED = "failed";
private static final String CUSTOMER_CODE_PREFIX = "KS";
/**
* A902-1 批量查询分批大小
*/
private static final int KINGDEE_CUSTOMER_QUERY_BATCH = 50;
/**
* 变更快照忽略字段:明细每次保存会重建主键/审计字段,不能作为“是否有修改”的依据
*/
@@ -391,6 +396,120 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
return text.length() > 500 ? text.substring(0, 500) : text;
}
/**
* 自代理:跨方法调用确保事务注解生效(单条推送、停用各自独立事务)
*/
private ICustomerArchiveService self() {
return SpringUtil.getBean(ICustomerArchiveService.class);
}
@Override
public String syncKingdeeCustomerData() {
// ① 待推送客商:审批通过且未推送/推送失败,复用单条推送(A902-1 复查 + A902-2 保存),单条失败不中断批次
List<CustomerArchive> pendingList = list(Wrappers.<CustomerArchive>lambdaQuery()
.eq(CustomerArchive::getApprovalStatus, APPROVAL_APPROVED)
.in(CustomerArchive::getKingdeeStatus, KINGDEE_STATUS_NONE, KINGDEE_STATUS_FAILED)
.orderByAsc(CustomerArchive::getId));
ICustomerArchiveService proxy = self();
int pushed = 0;
int existed = 0;
int pushFailed = 0;
for (CustomerArchive customer : pendingList) {
try {
CustomerArchive after = proxy.syncKingdee(customer.getId());
if (Objects.equals(after.getKingdeeStatus(), KINGDEE_STATUS_SYNCED)) {
pushed++;
} else if (Objects.equals(after.getKingdeeStatus(), KINGDEE_STATUS_EXIST)) {
existed++;
} else {
pushFailed++;
}
} catch (Exception exception) {
pushFailed++;
log.error("定时同步金蝶客商推送异常,id={},unifiedCreditCode={}",
customer.getId(), customer.getUnifiedCreditCode(), exception);
}
}
// ② 已同步客商使用状态回写:金蝶禁用(enable=0)→ TMS 自动停用;金蝶恢复可用不自动启用(TMS 人工控制)
List<CustomerArchive> enabledList = list(Wrappers.<CustomerArchive>lambdaQuery()
.eq(CustomerArchive::getApprovalStatus, APPROVAL_APPROVED)
.in(CustomerArchive::getKingdeeStatus, KINGDEE_STATUS_SYNCED, KINGDEE_STATUS_EXIST)
.eq(CustomerArchive::getStatus, STATUS_ENABLED)
.orderByAsc(CustomerArchive::getId));
int disabled = 0;
int disableFailed = 0;
if (!enabledList.isEmpty()) {
List<String> creditCodes = enabledList.stream()
.map(CustomerArchive::getUnifiedCreditCode)
.filter(Func::isNotEmpty)
.map(code -> code.trim().toUpperCase())
.distinct()
.toList();
Map<String, BfiCustomerVO> kingdeeMap = queryKingdeeCustomerMap(creditCodes);
for (CustomerArchive customer : enabledList) {
BfiCustomerVO kingdeeCustomer = kingdeeMap.get(customer.getUnifiedCreditCode().trim().toUpperCase());
if (kingdeeCustomer == null || !"0".equals(kingdeeCustomer.getEnable())) {
continue;
}
try {
if (proxy.disableCustomerFromKingdee(customer.getId())) {
disabled++;
}
} catch (Exception exception) {
disableFailed++;
log.error("定时同步金蝶客商停用异常,id={},unifiedCreditCode={}",
customer.getId(), customer.getUnifiedCreditCode(), exception);
}
}
}
return "待推送 " + pendingList.size() + " 条(推送成功 " + pushed + ",金蝶已有 " + existed
+ ",失败 " + pushFailed + "),停用联动 " + disabled + " 条(失败 " + disableFailed + ")";
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean disableCustomerFromKingdee(Long id) {
CustomerArchive customer = ensureCustomerAccessible(id);
if (!Objects.equals(customer.getStatus(), STATUS_ENABLED)) {
return false;
}
CustomerArchive after = copyCustomer(customer);
after.setStatus(STATUS_DISABLED);
addChangeRecord(id, "金蝶侧客商已停用,同步停用客商档案", customer, after);
return updateById(after);
}
/**
* 按统一社会信用代码分批查询金蝶客商,键为大写信用代码
*/
private Map<String, BfiCustomerVO> queryKingdeeCustomerMap(List<String> creditCodes) {
Map<String, BfiCustomerVO> result = new HashMap<>();
for (int start = 0; start < creditCodes.size(); start += KINGDEE_CUSTOMER_QUERY_BATCH) {
List<String> batch = creditCodes.subList(start, Math.min(start + KINGDEE_CUSTOMER_QUERY_BATCH, creditCodes.size()));
if (batch.isEmpty()) {
continue;
}
try {
BfiCustomerQueryDTO param = new BfiCustomerQueryDTO();
param.setPageNo(1);
param.setPageSize(batch.size());
param.setSocietycreditcode(batch);
BfiPageVO<BfiCustomerVO> page = bfiService.queryCustomer(param);
List<BfiCustomerVO> rows = page == null ? null : page.getRows();
if (rows != null) {
for (BfiCustomerVO vo : rows) {
if (Func.isNotEmpty(vo.getSocietycreditcode())) {
result.put(vo.getSocietycreditcode().trim().toUpperCase(), vo);
}
}
}
} catch (Exception exception) {
log.error("定时同步金蝶客商批量查询异常,batchSize={}", batch.size(), exception);
}
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean changeStatus(Long id, Integer status) {
@@ -0,0 +1,73 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.task;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.transport.service.ICustomerArchiveService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 金蝶客商数据定时同步任务
* <p>
* 对应业财融合「客商数据定时同步子流程」(图01,ERP 泳道):
* ① 待推送客商(审批通过且未推送/推送失败)自动执行 A902-1 复查 + A902-2 推送;
* ② 已同步客商回写金蝶使用状态,金蝶禁用(enable=0)联动停用客商档案。
* A901-1 供应商查询待金蝶 SDK(blade-bfi-api)提供能力后扩展。
* 业务逻辑收敛在 {@link ICustomerArchiveService#syncKingdeeCustomerData()},便于手动触发联调。
* <p>
* 调度配置(Nacos thirdParty.bfi.customer.*):syncEnabled 开关、cron 表达式。
*
* @author Chill
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CustomerKingdeeSyncTask {
private final ICustomerArchiveService customerArchiveService;
/** 客商金蝶数据同步开关,默认关闭 */
@Value("${thirdParty.bfi.customer.syncEnabled:false}")
private boolean syncEnabled;
/** 客商金蝶数据同步调度,默认每天 02:00 执行 */
@Scheduled(cron = "${thirdParty.bfi.customer.cron:0 0 2 * * ?}")
public void syncKingdeeCustomerData() {
if (!syncEnabled) {
return;
}
try {
String summary = customerArchiveService.syncKingdeeCustomerData();
log.info("定时同步金蝶客商数据:{}", summary);
} catch (Exception exception) {
log.error("定时同步金蝶客商数据异常", exception);
}
}
}