feat: 金蝶冲销状态回写拆分独立定时任务并支持分批配置
- 新增KingdeeWriteOffResultTask独立调度(Nacos thirdParty.bfi.writeOffResult.*开关/cron),从付款结果任务第④步拆出 - BfiProperties新增writeOffResult配置:syncEnabled开关、queryBatchSize分批大小默认1000,冲销批量查询与批量落库共用 - 回写仅扫描已同步且未冲销的结算单,已冲销为终态不再复查 - kingdee_write_off_status列默认值改为unwrittenoff,存量已同步NULL数据回填未冲销 - 补充扫描数/防重键数/金蝶返回数/落库数关键日志,summary文案修正为待查未冲销结算单
This commit is contained in:
+23
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶单据锁定补偿配置项
|
||||
*/
|
||||
|
||||
+15
-5
@@ -1172,14 +1172,17 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
*/
|
||||
private String executeWriteOffSync() {
|
||||
long startMillis = System.currentTimeMillis();
|
||||
// 已同步金蝶的结算单才在金蝶存在旧应付/应收单,未同步/生成失败不查询(前端展示 '-')
|
||||
// 已同步金蝶的结算单才在金蝶存在旧应付/应收单,未同步/生成失败不查询(前端展示 '-');
|
||||
// 已冲销为终态不再复查,仅扫未冲销单据(存量 NULL 由 06 脚本回填默认未冲销)
|
||||
List<FormalSettlement> settlements = list(Wrappers.<FormalSettlement>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<Long> settlementIds = settlements.stream().map(FormalSettlement::getId).toList();
|
||||
Map<Long, List<FormalSettlementDetail>> detailsBySettlement = new HashMap<>();
|
||||
@@ -1209,17 +1212,24 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
}
|
||||
}
|
||||
// A102-4/A302-4 整批一次查询,冲销状态有变化的才落库(updateBatchById 批量更新,避免逐单 update)
|
||||
log.info("金蝶冲销状态回写:防重键组装完成,应付{}个、应收{}个,按批次大小{}分批查询",
|
||||
apSettlementBySrcCode.size(), arSettlementBySrcCode.size(), bfiProperties.getWriteOffResult().getQueryBatchSize());
|
||||
Map<String, Boolean> apWriteOffFlags = kingdeeApBillSyncSupport.queryApBillWriteOffFlags(new ArrayList<>(apSettlementBySrcCode.keySet()));
|
||||
Map<String, Boolean> arWriteOffFlags = kingdeeArBillSyncSupport.queryArBillWriteOffFlags(new ArrayList<>(arSettlementBySrcCode.keySet()));
|
||||
// 金蝶未返回的键按未冲销处理,返回数与组装数差异即金蝶侧查不到单据的规模,留痕便于对账
|
||||
log.info("金蝶冲销状态批量查询完成:应付键返回{}个、应收键返回{}个,未返回键按未冲销处理",
|
||||
apWriteOffFlags.size(), arWriteOffFlags.size());
|
||||
List<FormalSettlement> 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;
|
||||
|
||||
+1
-1
@@ -225,7 +225,7 @@ public class KingdeeApBillSyncSupport {
|
||||
*/
|
||||
public Map<String, Boolean> queryApBillWriteOffFlags(List<String> awrpSrcCodes) {
|
||||
Map<String, Boolean> 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());
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ public class KingdeeArBillSyncSupport {
|
||||
*/
|
||||
public Map<String, Boolean> queryArBillWriteOffFlags(List<String> arBillSrcCodes) {
|
||||
Map<String, Boolean> 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());
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.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;
|
||||
|
||||
/**
|
||||
* 金蝶冲销状态定时回写任务
|
||||
* <p>
|
||||
* A102-4/A302-4 批量查询已同步结算单对应金蝶旧应付/应收单的 hadwrittenoff(部分冲销视同已冲销),
|
||||
* 回写结算单冲销状态,用于结算单列表展示金蝶旧单是否已冲销。
|
||||
* 业务逻辑收敛在服务方法(分布式锁 + 分批查询 + 批量落库),便于手动触发联调。
|
||||
* <p>
|
||||
* 调度配置(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-7
@@ -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分钟执行一次 */
|
||||
|
||||
+10
-3
@@ -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 := (
|
||||
|
||||
Reference in New Issue
Block a user