✨ 新增金蝶客商数据定时同步任务(自动推送+使用状态停用联动)
This commit is contained in:
+15
@@ -132,6 +132,21 @@ public interface ICustomerArchiveService extends BaseService<CustomerArchive> {
|
||||
*/
|
||||
CustomerArchive syncKingdee(Long id);
|
||||
|
||||
/**
|
||||
* 定时同步金蝶客商数据:待推送客商自动推送 + 已同步客商使用状态回写
|
||||
*
|
||||
* @return 同步汇总说明
|
||||
*/
|
||||
String syncKingdeeCustomerData();
|
||||
|
||||
/**
|
||||
* 金蝶侧停用联动:停用客商档案并记录变更(仅当前启用的客商生效)
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 是否执行停用
|
||||
*/
|
||||
boolean disableCustomerFromKingdee(Long id);
|
||||
|
||||
/**
|
||||
* 基于启用评分量化表生成评分记录
|
||||
*
|
||||
|
||||
+119
@@ -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) {
|
||||
|
||||
+73
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -287,7 +287,7 @@ VALUES (2075431800000000001, {客商档案菜单id}, 'customer_archive_sync', '
|
||||
| 推送时复查到金蝶暂存/已提交 | 置 `failed`,`kingdee_fail_reason='金蝶侧该客商审核中,请稍后重推'` |
|
||||
| A902-2 返回 `billStatus=false` / `failCount>0` | 置 `failed`,`errors` 拼接写入 `kingdee_fail_reason`(截断 500 字),日志 `@Slf4j` 占位符输出含客商 id 与信用代码 |
|
||||
| A902-2 网关异常(超时/5xx/token 失效) | `BfiException` 统一上抛,同上置 `failed`;token 由 `BfiServiceImpl` 自动续期 |
|
||||
| 重推仍失败 | 保持 `failed`,可继续人工重推;连续失败转人工核对金蝶侧该信用代码客商状态 |
|
||||
| 重推仍失败 | 保持 `failed`;定时任务(`CustomerKingdeeSyncTask`,开关 `thirdParty.bfi.customer.syncEnabled`,默认关闭)每轮自动重推,也可列表「同步金蝶」人工重推;连续失败转人工核对金蝶侧该信用代码客商状态 |
|
||||
|
||||
---
|
||||
|
||||
@@ -309,3 +309,4 @@ VALUES (2075431800000000001, {客商档案菜单id}, 'customer_archive_sync', '
|
||||
- **取消 `kingdee_number` 字段**:A902-2 `number` 直接传 TMS 客商编号,金蝶客户编码与客商编号保持一致;DDL 缩减为 3 字段,前端移除「金蝶客户编码」列及相关引用。
|
||||
- **客商数据定时同步子流程本期不做**:经确认 ERP/TMS 无此既有逻辑,且金蝶 SDK(blade-bfi-api)暂未提供 A901-1 供应商查询;该子流程不阻塞核心链路,待 A901-1 能力补齐后按 §2.3 步骤9(ERP 定时调 A902-1/A901-1 更新客商数据与使用状态)另行立项。推送失败(`failed`)由列表「同步金蝶」按钮人工重推。
|
||||
- 待人工执行:`doc/sql/transport/blade_customer_archive_kingdee_20260917.sql`(含「同步金蝶」按钮菜单 INSERT);与财务核对客商性质映射键与 `createorg_number` 取值。
|
||||
- 2026-09-17(第三轮新增定时任务):新增 `task/CustomerKingdeeSyncTask`(仿 `PaymentKingdeeResultTask`,开关 `thirdParty.bfi.customer.syncEnabled` 默认关闭、cron `thirdParty.bfi.customer.cron` 默认每天 02:00)与 `ICustomerArchiveService.syncKingdeeCustomerData()`:① 待推送客商(approved 且 none/failed)复用 `syncKingdee` 自动推送(自代理逐条独立事务,单条失败不中断);② 已同步客商(synced/exist)按信用代码分批(50/批)A902-1 查询,金蝶禁用(enable=0)联动停用客商档案并记变更,金蝶恢复可用不自动启用。A901-1 供应商查询仍待 SDK 提供能力后扩展。`mvn clean compile` 通过。
|
||||
|
||||
Reference in New Issue
Block a user