From 0531177ed71e05fea6e03126d5db099935c06600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Fri, 18 Sep 2026 21:34:37 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E9=87=91?= =?UTF-8?q?=E8=9D=B6=E5=AE=A2=E5=95=86=E6=95=B0=E6=8D=AE=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E4=BB=BB=E5=8A=A1=EF=BC=88=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8E=A8=E9=80=81+=E4=BD=BF=E7=94=A8=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=81=9C=E7=94=A8=E8=81=94=E5=8A=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ICustomerArchiveService.java | 15 +++ .../impl/CustomerArchiveServiceImpl.java | 119 ++++++++++++++++++ .../task/CustomerKingdeeSyncTask.java | 73 +++++++++++ ...Œ步金蝶(A902-1查询回填+A902-2审批推送)技术文档.md | 3 +- 4 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/task/CustomerKingdeeSyncTask.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java index 09ea4d92..1e5af2fd 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java @@ -132,6 +132,21 @@ public interface ICustomerArchiveService extends BaseService { */ CustomerArchive syncKingdee(Long id); + /** + * 定时同步金蝶客商数据:待推送客商自动推送 + 已同步客商使用状态回写 + * + * @return 同步汇总说明 + */ + String syncKingdeeCustomerData(); + + /** + * 金蝶侧停用联动:停用客商档案并记录变更(仅当前启用的客商生效) + * + * @param id 主键 + * @return 是否执行停用 + */ + boolean disableCustomerFromKingdee(Long id); + /** * 基于启用评分量化表生成评分记录 * diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index 7ce32739..96eb5cdd 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -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 500 ? text.substring(0, 500) : text; } + /** + * 自代理:跨方法调用确保事务注解生效(单条推送、停用各自独立事务) + */ + private ICustomerArchiveService self() { + return SpringUtil.getBean(ICustomerArchiveService.class); + } + + @Override + public String syncKingdeeCustomerData() { + // ① 待推送客商:审批通过且未推送/推送失败,复用单条推送(A902-1 复查 + A902-2 保存),单条失败不中断批次 + List pendingList = list(Wrappers.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 enabledList = list(Wrappers.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 creditCodes = enabledList.stream() + .map(CustomerArchive::getUnifiedCreditCode) + .filter(Func::isNotEmpty) + .map(code -> code.trim().toUpperCase()) + .distinct() + .toList(); + Map 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 queryKingdeeCustomerMap(List creditCodes) { + Map result = new HashMap<>(); + for (int start = 0; start < creditCodes.size(); start += KINGDEE_CUSTOMER_QUERY_BATCH) { + List 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 page = bfiService.queryCustomer(param); + List 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) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/CustomerKingdeeSyncTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/CustomerKingdeeSyncTask.java new file mode 100644 index 00000000..a0f0a2ec --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/CustomerKingdeeSyncTask.java @@ -0,0 +1,73 @@ +/** + * 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.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; + +/** + * 金蝶客商数据定时同步任务 + *

+ * 对应业财融合「客商数据定时同步子流程」(图01,ERP 泳道): + * ① 待推送客商(审批通过且未推送/推送失败)自动执行 A902-1 复查 + A902-2 推送; + * ② 已同步客商回写金蝶使用状态,金蝶禁用(enable=0)联动停用客商档案。 + * A901-1 供应商查询待金蝶 SDK(blade-bfi-api)提供能力后扩展。 + * 业务逻辑收敛在 {@link ICustomerArchiveService#syncKingdeeCustomerData()},便于手动触发联调。 + *

+ * 调度配置(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); + } + } + +} diff --git a/doc/docs/业务流程/2026-09-17-客商档案同步金蝶(A902-1查询回填+A902-2审批推送)技术文档.md b/doc/docs/业务流程/2026-09-17-客商档案同步金蝶(A902-1查询回填+A902-2审批推送)技术文档.md index 46a2093a..f0e0cd5e 100644 --- a/doc/docs/业务流程/2026-09-17-客商档案同步金蝶(A902-1查询回填+A902-2审批推送)技术文档.md +++ b/doc/docs/业务流程/2026-09-17-客商档案同步金蝶(A902-1查询回填+A902-2审批推送)技术文档.md @@ -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` 通过。