diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java index 5fe089fe..3d7b078d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java @@ -115,6 +115,11 @@ public class BfiProperties { */ private ArResult arResult = new ArResult(); + /** + * 金蝶冲销状态回写配置(A102-4/A302-4) + */ + private WriteOffResult writeOffResult = new WriteOffResult(); + /** * 金蝶单据锁定补偿配置 */ @@ -374,6 +379,24 @@ public class BfiProperties { private long lockLeaseSeconds = 1800; } + /** + * 金蝶冲销状态回写配置项(A102-4/A302-4):定时回写开关、防重键批量查询与结算单批量更新的分批大小, + * 防止单批过大超出金蝶查询接口与批量落库承载能力 + */ + @Data + public static class WriteOffResult { + + /** + * 冲销状态回写任务开关,默认关闭(安全兜底),由 Nacos 按环境显式开启 + */ + private boolean syncEnabled = false; + + /** + * 冲销状态批量查询/批量更新分批大小,默认 1000 + */ + private int queryBatchSize = 1000; + } + /** * 金蝶单据锁定补偿配置项 */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java index c1751638..05276ac2 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java @@ -1172,14 +1172,17 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl settlements = list(Wrappers.lambdaQuery() .eq(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED) + .eq(FormalSettlement::getKingdeeWriteOffStatus, KingdeeBfiConstant.KINGDEE_WRITEOFF_UNWRITTEN) .orderByAsc(FormalSettlement::getId)); if (Func.isEmpty(settlements)) { - log.info("金蝶冲销状态回写:无已同步结算单"); - return "金蝶冲销状态回写:无已同步结算单"; + log.info("金蝶冲销状态回写:无待查未冲销结算单"); + return "金蝶冲销状态回写:无待查未冲销结算单"; } + log.info("金蝶冲销状态回写:本轮扫描待查未冲销结算单{}笔", settlements.size()); // 结算明细分批一次查全(消除逐单查询 N+1),按结算单 id 分组后按结算类型组装防重键 List settlementIds = settlements.stream().map(FormalSettlement::getId).toList(); Map> detailsBySettlement = new HashMap<>(); @@ -1209,17 +1212,24 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl apWriteOffFlags = kingdeeApBillSyncSupport.queryApBillWriteOffFlags(new ArrayList<>(apSettlementBySrcCode.keySet())); Map arWriteOffFlags = kingdeeArBillSyncSupport.queryArBillWriteOffFlags(new ArrayList<>(arSettlementBySrcCode.keySet())); + // 金蝶未返回的键按未冲销处理,返回数与组装数差异即金蝶侧查不到单据的规模,留痕便于对账 + log.info("金蝶冲销状态批量查询完成:应付键返回{}个、应收键返回{}个,未返回键按未冲销处理", + apWriteOffFlags.size(), arWriteOffFlags.size()); List toUpdateSettlements = new ArrayList<>(); apSettlementBySrcCode.forEach((srcCode, settlement) -> this.applyWriteOffFlag(settlement, apWriteOffFlags.get(srcCode), toUpdateSettlements)); arSettlementBySrcCode.forEach((srcCode, settlement) -> this.applyWriteOffFlag(settlement, arWriteOffFlags.get(srcCode), toUpdateSettlements)); if (Func.isNotEmpty(toUpdateSettlements)) { - updateBatchById(toUpdateSettlements); + // 批量落库按同一分批大小拆分,避免单批过大 + updateBatchById(toUpdateSettlements, bfiProperties.getWriteOffResult().getQueryBatchSize()); + log.info("金蝶冲销状态回写:批量落库完成,本批更新{}笔", toUpdateSettlements.size()); } - String summary = "金蝶冲销状态回写完成:待查已同步结算单" + (apSettlementBySrcCode.size() + arSettlementBySrcCode.size()) + String summary = "金蝶冲销状态回写完成:待查未冲销结算单" + (apSettlementBySrcCode.size() + arSettlementBySrcCode.size()) + "笔,回写" + toUpdateSettlements.size() + "笔"; log.info("{},耗时{}ms", summary, System.currentTimeMillis() - startMillis); return summary; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java index a5d74fa9..11f97ce1 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java @@ -225,7 +225,7 @@ public class KingdeeApBillSyncSupport { */ public Map queryApBillWriteOffFlags(List awrpSrcCodes) { Map writeOffFlags = new LinkedHashMap<>(); - batchForeach(awrpSrcCodes, PAY_RESULT_QUERY_BATCH, batch -> { + batchForeach(awrpSrcCodes, bfiProperties.getWriteOffResult().getQueryBatchSize(), batch -> { BfiApBillQueryDTO param = new BfiApBillQueryDTO(); param.setAwrpSrcCode(batch); param.setAwrpSrcSys(bfiProperties.getSrcSys()); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeArBillSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeArBillSyncSupport.java index 845fc575..763a3371 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeArBillSyncSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeArBillSyncSupport.java @@ -163,7 +163,7 @@ public class KingdeeArBillSyncSupport { */ public Map queryArBillWriteOffFlags(List arBillSrcCodes) { Map writeOffFlags = new LinkedHashMap<>(); - kingdeeApBillSyncSupport.batchForeach(arBillSrcCodes, AR_RESULT_QUERY_BATCH, batch -> { + kingdeeApBillSyncSupport.batchForeach(arBillSrcCodes, bfiProperties.getWriteOffResult().getQueryBatchSize(), batch -> { BfiArBillQueryDTO param = new BfiArBillQueryDTO(); param.setAwrpSrcCode(batch); param.setAwrpSrcSys(bfiProperties.getSrcSys()); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeWriteOffResultTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeWriteOffResultTask.java new file mode 100644 index 00000000..dd4a0e22 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeWriteOffResultTask.java @@ -0,0 +1,63 @@ +/** + * 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.config.BfiProperties; +import org.springblade.transport.service.IFormalSettlementService; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 金蝶冲销状态定时回写任务 + *

+ * A102-4/A302-4 批量查询已同步结算单对应金蝶旧应付/应收单的 hadwrittenoff(部分冲销视同已冲销), + * 回写结算单冲销状态,用于结算单列表展示金蝶旧单是否已冲销。 + * 业务逻辑收敛在服务方法(分布式锁 + 分批查询 + 批量落库),便于手动触发联调。 + *

+ * 调度配置(Nacos thirdParty.bfi.writeOffResult.*):syncEnabled 开关、cron 表达式、queryBatchSize 分批大小。 + * + * @author Chill + */ +@Slf4j +@Component +@RefreshScope +@RequiredArgsConstructor +public class KingdeeWriteOffResultTask { + + private final IFormalSettlementService formalSettlementService; + private final BfiProperties bfiProperties; + + /** 金蝶冲销状态回写调度,默认每10分钟执行一次(分钟位与付款结果回写错峰) */ + @Scheduled(cron = "${thirdParty.bfi.writeOffResult.cron:0 4/10 * * * ?}") + public void syncKingdeeWriteOffResult() { + if (!bfiProperties.getWriteOffResult().isSyncEnabled()) { + return; + } + try { + String summary = formalSettlementService.syncKingdeeWriteOffResult(); + log.info("定时回写金蝶冲销状态:{}", summary); + } catch (Exception exception) { + log.error("定时回写金蝶冲销状态异常", exception); + } + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/PaymentKingdeeResultTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/PaymentKingdeeResultTask.java index e5160721..e5e4fb87 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/PaymentKingdeeResultTask.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/PaymentKingdeeResultTask.java @@ -86,13 +86,6 @@ public class PaymentKingdeeResultTask { } catch (Exception exception) { log.error("定时回写金蝶开票结果异常", exception); } - // ④ A102-4/A302-4 冲销状态回写(结算单列表展示金蝶旧单是否已冲销) - try { - String summary = formalSettlementService.syncKingdeeWriteOffResult(); - log.info("定时回写金蝶冲销状态:{}", summary); - } catch (Exception exception) { - log.error("定时回写金蝶冲销状态异常", exception); - } } /** 金蝶收票单结果回写调度(异常回收/指定补偿/单号补登),默认每10分钟执行一次 */ diff --git a/doc/sql/transport/lqj/temp/06_blade_formal_settlement_kingdee_writeoff_20261030.sql b/doc/sql/transport/lqj/temp/06_blade_formal_settlement_kingdee_writeoff_20261030.sql index 34bb320a..a5fc952a 100644 --- a/doc/sql/transport/lqj/temp/06_blade_formal_settlement_kingdee_writeoff_20261030.sql +++ b/doc/sql/transport/lqj/temp/06_blade_formal_settlement_kingdee_writeoff_20261030.sql @@ -2,7 +2,7 @@ -- 列名遵循 MyBatis-Plus 驼峰转下划线默认映射(kingdeeWriteOffStatus -> kingdee_write_off_status,kingdeeWriteOffTime -> kingdee_write_off_time); -- 若存在早期脚本误建的旧列名 kingdee_writeoff_status / kingdee_writeoff_time 则先重命名,否则按缺失新增 -- 结算单列表展示金蝶侧旧应付/应收单冲销状态:定时任务按 A102-4/A302-4 hadwrittenoff 批量回写, --- 选单预检/重推闸门实时查询后顺带回写;NULL=未推送金蝶或尚未校验,unwrittenoff-未冲销,writtenoff-已冲销(部分冲销视同已冲销) +-- 选单预检/重推闸门实时查询后顺带回写;默认 unwrittenoff-未冲销(金蝶单据在未冲销前即视为未冲销,避免首跑全量初始化回写),writtenoff-已冲销(部分冲销视同已冲销) -- ==================== kingdee_write_off_status ==================== -- 旧列名重命名(早期脚本版本,不存在则跳过) @@ -12,7 +12,7 @@ SET @old_col := ( AND COLUMN_NAME = 'kingdee_writeoff_status' ); SET @ddl_sql = IF(@old_col = 1, - 'ALTER TABLE `blade_formal_settlement` CHANGE COLUMN `kingdee_writeoff_status` `kingdee_write_off_status` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''金蝶冲销状态:unwrittenoff-未冲销 writtenoff-已冲销(NULL=未推送金蝶或未校验)''', + 'ALTER TABLE `blade_formal_settlement` CHANGE COLUMN `kingdee_writeoff_status` `kingdee_write_off_status` varchar(20) COLLATE utf8mb4_general_ci DEFAULT ''unwrittenoff'' COMMENT ''金蝶冲销状态:unwrittenoff-未冲销(默认) writtenoff-已冲销''', 'SELECT 1'); PREPARE ddl_stmt FROM @ddl_sql; EXECUTE ddl_stmt; @@ -21,7 +21,7 @@ DEALLOCATE PREPARE ddl_stmt; -- 缺失则新增(支持重复执行) SET @ddl_sql = ( SELECT IF(COUNT(*) = 0, - 'ALTER TABLE `blade_formal_settlement` ADD COLUMN `kingdee_write_off_status` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''金蝶冲销状态:unwrittenoff-未冲销 writtenoff-已冲销(NULL=未推送金蝶或未校验)'' AFTER `kingdee_fail_reason`', + 'ALTER TABLE `blade_formal_settlement` ADD COLUMN `kingdee_write_off_status` varchar(20) COLLATE utf8mb4_general_ci DEFAULT ''unwrittenoff'' COMMENT ''金蝶冲销状态:unwrittenoff-未冲销(默认) writtenoff-已冲销'' AFTER `kingdee_fail_reason`', 'SELECT 1') FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() @@ -32,6 +32,13 @@ PREPARE ddl_stmt FROM @ddl_sql; EXECUTE ddl_stmt; DEALLOCATE PREPARE ddl_stmt; +-- 存量已同步结算单回填默认未冲销(可重复执行:仅补 NULL 行;未同步单据保持默认值即可,前端按同步状态展示'-') +UPDATE `blade_formal_settlement` +SET `kingdee_write_off_status` = 'unwrittenoff' +WHERE `kingdee_write_off_status` IS NULL + AND `kingdee_sync_status` = 'synced' + AND `is_deleted` = 0; + -- ==================== kingdee_write_off_time ==================== -- 旧列名重命名(早期脚本版本,不存在则跳过) SET @old_col := (