feat: 审批通过事件触发自动同步金蝶(AFTER_COMMIT+4域开关)

- 新增 KingdeeBillApprovedEvent(bizType复用MK处理器取值)与 KingdeeApprovalSyncListener:事务提交后同线程复用手动按钮同一Service方法推送,fallbackExecution兜底无事务场景,全量try/catch吞异常,审批结果不受推送影响
- 4域 approve 置approved成功后发布事件(重复审批被状态校验拒绝天然不重复推送);正式结算单 approve 补 @Transactional(原缺失会致AFTER_COMMIT事件被丢弃,顺带使更新与变更记录原子化)
- 域开关 thirdParty.bfi.autoSync.{settlement,payment,invoiceApply,receipt} 默认全关(Nacos显式打开),关闭即回到现状;手动同步按钮全部保留,三入口并存:自动+定时+手动
- 推送失败由上一独立事务落failed+定时重推兜底
This commit is contained in:
刘泉佳
2026-10-10 13:51:55 +08:00
parent cfbbc756eb
commit 276dd813d4
6 changed files with 198 additions and 0 deletions
@@ -0,0 +1,44 @@
/**
* 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.event;
/**
* 单据审批通过事件:审批事务提交后由 {@code KingdeeApprovalSyncListener} 消费,
* 自动推送对应单据至金蝶(星瀚),省掉页面手动点「同步金蝶」。
* <p>
* bizType 复用 MkProcessBizHandlers 既有取值;只在审批状态真正变为 approved 的那次发布,
* 重复回调被 approve 状态校验拒绝,天然不重复推送。
*
* @author Chill
*/
public record KingdeeBillApprovedEvent(String bizType, Long id) {
/** 正式结算单(应付 A102-2 / 应收 A302-2) */
public static final String BIZ_FORMAL_SETTLEMENT = "formal-settlement";
/** 付款申请(A103-1 正式 / A103-3 预付) */
public static final String BIZ_PAYMENT_APPLICATION = "payment-application";
/** 开票申请(A303-1) */
public static final String BIZ_INVOICE_APPLICATION = "invoice-application";
/** 收票登记(A303-2 + A302-6) */
public static final String BIZ_INVOICE_RECEIPT = "invoice-receipt";
}
@@ -0,0 +1,133 @@
/**
* 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.listener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IInvoiceApplicationService;
import org.springblade.transport.service.IInvoiceReceiptService;
import org.springblade.transport.service.IPaymentApplicationService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.List;
/**
* 单据审批通过自动同步金蝶监听器:审批事务提交(AFTER_COMMIT)后复用手动按钮同一 Service 方法推送,
* 与客商档案审批后自动推送同线程模式。
* <p>
* 不变式:审批结果不受推送影响——监听器整体 try/catch 吞异常,推送异常只落 failed + 日志;
* 失败单据由 {@code KingdeeAutoSyncRetryTask} 定时重推、手动按钮兜底。
* <p>
* ponytail: 同步推送占用审批请求线程,若 BFI 网关大面积变慢导致审批接口可感知变慢,
* 再把路由调用提交到守护线程池(MkProcessMessageService 已有同款裸 ScheduledExecutorService 模式)。
* <p>
* 域开关(Nacos thirdParty.bfi.autoSync.*)默认全部关闭,与既有定时任务开关风格一致;
* 关闭某域 = 该域回到现状(审批不自动推送、手动按钮照用)。
*
* @author Chill
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class KingdeeApprovalSyncListener {
private final IFormalSettlementService formalSettlementService;
private final IPaymentApplicationService paymentApplicationService;
private final IInvoiceApplicationService invoiceApplicationService;
private final IInvoiceReceiptService invoiceReceiptService;
/** 结算单审批后自动推 A102-2/A302-2 开关,默认关闭 */
@Value("${thirdParty.bfi.autoSync.settlement:false}")
private boolean settlementEnabled;
/** 付款申请审批后自动推 A103-1/A103-3 开关,默认关闭 */
@Value("${thirdParty.bfi.autoSync.payment:false}")
private boolean paymentEnabled;
/** 开票申请审批后自动推 A303-1 开关,默认关闭 */
@Value("${thirdParty.bfi.autoSync.invoiceApply:false}")
private boolean invoiceApplyEnabled;
/** 收票登记审批后自动推 A303-2+A302-6 开关,默认关闭 */
@Value("${thirdParty.bfi.autoSync.receipt:false}")
private boolean receiptEnabled;
/**
* 事务提交后同步推送。fallbackExecution=true 兜底:approve 未开启事务时事件不再被默认丢弃。
*
* @param event 审批通过事件(bizType + 单据id)
*/
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void onBillApproved(KingdeeBillApprovedEvent event) {
try {
switch (event.bizType()) {
case KingdeeBillApprovedEvent.BIZ_FORMAL_SETTLEMENT -> pushSettlement(event.id());
case KingdeeBillApprovedEvent.BIZ_PAYMENT_APPLICATION -> pushPayment(event.id());
case KingdeeBillApprovedEvent.BIZ_INVOICE_APPLICATION -> pushInvoiceApply(event.id());
case KingdeeBillApprovedEvent.BIZ_INVOICE_RECEIPT -> pushReceipt(event.id());
default -> log.warn("审批通过自动同步金蝶跳过:未知业务类型 bizType={} id={}", event.bizType(), event.id());
}
} catch (Exception exception) {
// 推送失败只记日志(失败状态已在推送方法内独立事务落库),不影响审批结果,交给重推任务与手动按钮兜底
log.error("审批通过自动同步金蝶失败 bizType={} id={}", event.bizType(), event.id(), exception);
}
}
private void pushSettlement(Long id) {
if (!settlementEnabled) {
return;
}
log.info("正式结算单[{}]审批通过自动同步金蝶开始", id);
String billNo = formalSettlementService.syncKingdee(id);
log.info("正式结算单[{}]审批通过自动同步金蝶完成:billNo={}", id, billNo);
}
private void pushPayment(Long id) {
if (!paymentEnabled) {
return;
}
log.info("付款申请[{}]审批通过自动同步金蝶开始", id);
String kingdeeBillId = paymentApplicationService.syncKingdee(id);
log.info("付款申请[{}]审批通过自动同步金蝶完成:kingdeeBillId={}", id, kingdeeBillId);
}
private void pushInvoiceApply(Long id) {
if (!invoiceApplyEnabled) {
return;
}
log.info("开票申请[{}]审批通过自动同步金蝶开始", id);
String billNo = invoiceApplicationService.syncKingdee(id);
log.info("开票申请[{}]审批通过自动同步金蝶完成:billNo={}", id, billNo);
}
private void pushReceipt(Long id) {
if (!receiptEnabled) {
return;
}
log.info("收票登记[{}]审批通过自动推送金蝶收票单开始", id);
String summary = invoiceReceiptService.pushKingdeeReceivedInvoice(List.of(id), true);
log.info("收票登记[{}]审批通过自动推送金蝶收票单完成:{}", id, summary);
}
}
@@ -92,11 +92,13 @@ import org.springblade.transport.support.KingdeeSyncLockSupport;
import org.springblade.transport.support.KingdeeAttachmentSyncSupport;
import org.springblade.transport.wrapper.PreSettlementWrapper;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.wrapper.FormalSettlementWrapper;
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
import org.springblade.system.cache.UserCache;
import org.springframework.beans.factory.annotation.Value;
import org.springblade.core.tool.utils.SpringUtil;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -167,6 +169,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private final KingdeeArBillSyncSupport kingdeeArBillSyncSupport;
private final KingdeeAttachmentSyncSupport kingdeeAttachmentSyncSupport;
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
private final ApplicationEventPublisher eventPublisher;
/**
* 金蝶应收单勾稽 single-flight 锁租约秒数,须大于最坏一轮耗时(批量远程查询 + 分批纠偏回写),
@@ -540,6 +543,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
@Override public void returnBill(FormalSettlementStatusRequest request) { changeStatus(request.getId(), TransportBusiConstant.REVIEWING, TransportBusiConstant.RETURNED, "已驳回", request.getReason()); }
@Override
@Transactional(rollbackFor = Exception.class)
public void approve(FormalSettlementStatusRequest request) {
FormalSettlement settlement = existing(request.getId());
if (!TransportBusiConstant.REVIEWING.equals(settlement.getApprovalStatus())) throw new ServiceException("仅审批中的正式结算单允许审核");
@@ -549,6 +553,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setApprovedTime(LocalDateTime.now());
updateById(settlement);
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "正式结算审批通过", request.getReason());
// 审批事务提交后自动推送金蝶(AFTER_COMMIT 监听器消费,推送失败不影响审批结果)
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_FORMAL_SETTLEMENT, settlement.getId()));
}
@Override
@@ -56,6 +56,7 @@ import org.springblade.transport.config.BfiProperties;
import org.springblade.transport.constant.KingdeeAttachmentConstant;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.CustomerContactMapper;
@@ -101,6 +102,7 @@ import org.springblade.transport.wrapper.InvoiceApplicationWrapper;
import org.springblade.transport.wrapper.WaybillWrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springblade.core.tool.utils.SpringUtil;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -156,6 +158,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
private final KingdeeArBillSyncSupport kingdeeArBillSyncSupport;
private final KingdeeAttachmentSyncSupport kingdeeAttachmentSyncSupport;
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
private final ApplicationEventPublisher eventPublisher;
/**
* 金蝶开票结果回写 single-flight 锁租约秒数,须大于最坏一轮耗时(分批远程查询 + 逐单回写),
@@ -484,6 +487,8 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
@Transactional(rollbackFor = Exception.class)
public void approve(InvoiceApplicationStatusRequest request) {
changeStatus(request.getId(), TransportBusiConstant.REVIEWING, TransportBusiConstant.APPROVED, "approve", "审批通过", null);
// 审批事务提交后自动推送金蝶(AFTER_COMMIT 监听器消费,推送失败不影响审批结果)
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_INVOICE_APPLICATION, request.getId()));
}
@Override
@@ -53,6 +53,7 @@ import org.springblade.thirdparty.bfi.service.IBfiService;
import org.springblade.transport.constant.KingdeeAttachmentConstant;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.CustomerContactMapper;
import org.springblade.transport.mapper.FormalSettlementDetailMapper;
@@ -84,6 +85,7 @@ import org.springblade.transport.support.KingdeeReceivedInvoiceSyncSupport;
import org.springblade.transport.support.KingdeeSyncLockSupport;
import org.springblade.transport.wrapper.InvoiceReceiptWrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -149,6 +151,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
private final KingdeeReceivedInvoiceSyncSupport kingdeeReceivedInvoiceSyncSupport;
private final KingdeeAttachmentSyncSupport kingdeeAttachmentSyncSupport;
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
private final ApplicationEventPublisher eventPublisher;
/** 金蝶收票单结果回写 single-flight 锁键前缀(多副本定时任务与手动触发共用) */
private static final String RECEIVED_RESULT_SYNC_LOCK_PREFIX = "transport:invoice-receipt:kingdee-received-result-sync:";
@@ -491,6 +494,8 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
}
validateStoredAllocation(entity);
changeStatus(entity, TransportBusiConstant.APPROVED, "approve", "审批通过", null);
// 审批事务提交后自动推送金蝶收票单(AFTER_COMMIT 监听器消费,推送失败不影响审批结果)
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_INVOICE_RECEIPT, entity.getId()));
}
@Override
@@ -52,6 +52,7 @@ import org.springblade.transport.config.BfiProperties;
import org.springblade.transport.constant.KingdeeAttachmentConstant;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.mapper.BillLedgerMapper;
import org.springblade.transport.mapper.BillLedgerUsageMapper;
import org.springblade.transport.mapper.ContractManageMapper;
@@ -100,6 +101,7 @@ import org.springblade.transport.support.KingdeeAttachmentSyncSupport;
import org.springblade.transport.support.KingdeeSyncLockSupport;
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -161,6 +163,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private final BfiProperties bfiProperties;
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
private final BladeRedis bladeRedis;
private final ApplicationEventPublisher eventPublisher;
/**
* 金蝶付款结果回写 single-flight 锁租约秒数,须大于最坏一轮耗时(分批远程查询 + 逐单回写),
@@ -404,6 +407,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
entity.setApprovalStatus(TransportBusiConstant.APPROVED); entity.setCurrentNode("审批通过"); entity.setCurrentProcessor(AuthUtil.getUserName());
updateById(entity);
refreshFormalSettlement(entity);
// 审批事务提交后自动推送金蝶(AFTER_COMMIT 监听器消费,推送失败不影响审批结果)
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_PAYMENT_APPLICATION, entity.getId()));
}
@Override