feat: 付款申请纳入金蝶单据锁定与解锁流程(推送后锁单-删单回调还原-作废守卫-锁状态展示)

This commit is contained in:
刘泉佳
2026-10-10 16:00:32 +08:00
parent 15d6f736e5
commit 5d1f977abd
4 changed files with 103 additions and 11 deletions
@@ -24,12 +24,15 @@ import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.InvoiceApplicationMapper;
import org.springblade.transport.mapper.KingdeeBillLockMapper;
import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.InvoiceApplication;
import org.springblade.transport.pojo.entity.KingdeeBillLock;
import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IInvoiceApplicationService;
import org.springblade.transport.service.IKingdeeBillLockService;
import org.springblade.transport.service.IPaymentApplicationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -66,6 +69,7 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
private final BfiProperties bfiProperties;
private final FormalSettlementMapper formalSettlementMapper;
private final InvoiceApplicationMapper invoiceApplicationMapper;
private final PaymentApplicationMapper paymentApplicationMapper;
@Override
public String lockAfterPush(String sourceBillType, Long sourceBillId, String sourceBillNo, String targetBillType, List<String> targetBillNos) {
@@ -210,6 +214,20 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
}
SpringUtil.getBean(IInvoiceApplicationService.class).voidByKingdee(application.getId(), operator);
}
case KingdeeLockConstant.SOURCE_PAY_APPLY -> {
PaymentApplication application = paymentApplicationMapper.selectById(lockRecord.getSourceBillId());
if (application == null || Objects.equals(application.getIsDeleted(), 1)) {
log.warn("金蝶删单回调源付款申请不存在:id={}", lockRecord.getSourceBillId());
return;
}
if (TransportBusiConstant.VOIDED.equals(application.getApprovalStatus())) {
log.info("金蝶删单回调源付款申请[{}]已作废(幂等跳过)", application.getPaymentNo());
return;
}
// 复用 ADR-0005 解锁口径:不作废源单,kingdeeStatus→生成失败、清编号内码(原值留档),
// 不自动重推,人工核实后重新推送(A103-2 勾稽预检防重复推单)
SpringUtil.getBean(IPaymentApplicationService.class).unlockBill(application.getId(), "金蝶删单回调");
}
default -> log.warn("金蝶删单回调暂不支持的单据类型:targetBillType={}(映射预留)", lockRecord.getTargetBillType());
}
}
@@ -51,6 +51,7 @@ import org.springblade.thirdparty.bfi.util.BfiUtil;
import org.springblade.transport.config.BfiProperties;
import org.springblade.transport.constant.KingdeeAttachmentConstant;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.constant.KingdeeLockConstant;
import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.mapper.BillLedgerMapper;
import org.springblade.transport.mapper.BillLedgerUsageMapper;
@@ -82,6 +83,7 @@ import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.KingdeeBillLock;
import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.entity.PaymentApplicationInvoice;
import org.springblade.transport.pojo.entity.PaymentApplicationRecord;
@@ -93,6 +95,7 @@ import org.springblade.transport.pojo.entity.TemporaryCreditLimit;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IKingdeeBillLockService;
import org.springblade.transport.service.IPaymentApplicationService;
import org.springblade.transport.service.KingdeeSyncContext;
import org.springblade.transport.support.KingdeeApBillSyncSupport;
@@ -155,6 +158,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private final BillLedgerUsageMapper billLedgerUsageMapper;
private final TemporaryCreditLimitMapper temporaryCreditLimitMapper;
private final IFormalSettlementService formalSettlementService;
private final IKingdeeBillLockService kingdeeBillLockService;
private final IBfiService bfiService;
private final KingdeeApBillSyncSupport kingdeeApBillSyncSupport;
private final KingdeeAttachmentSyncSupport kingdeeAttachmentSyncSupport;
@@ -193,6 +197,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
return vo;
});
fillAttachmentSyncStatus(KingdeeAttachmentConstant.BIZ_PAY_APPLICATION, voPage.getRecords());
fillKingdeeLockStatus(voPage.getRecords());
return voPage;
}
@@ -218,10 +223,35 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
}
}
/**
* 填充金蝶锁状态标识(locked-锁定中/failed-锁定失败;unlocked 终态不返回):
* 锁表按本页源单一次批量查询,避免 N+1;locked 时前端可禁用编辑/作废,
* failed 时展示失败原因(tryCount 达上限 5 提示重推同步金蝶)
*
* @param rows 本页 VO 列表
*/
private void fillKingdeeLockStatus(List<PaymentApplicationVO> rows) {
if (Func.isEmpty(rows)) {
return;
}
Map<Long, KingdeeBillLock> lockMap = kingdeeBillLockService.mapActiveLocks(KingdeeLockConstant.SOURCE_PAY_APPLY,
rows.stream().map(PaymentApplicationVO::getId).toList());
for (PaymentApplicationVO vo : rows) {
KingdeeBillLock lockRecord = lockMap.get(vo.getId());
if (lockRecord == null) {
continue;
}
vo.setKingdeeLockStatus(lockRecord.getLockStatus());
vo.setKingdeeLockFailReason(lockRecord.getFailReason());
vo.setKingdeeTryCount(lockRecord.getTryCount());
}
}
@Override
public PaymentApplicationVO detail(Long id) {
PaymentApplication entity = existing(id);
PaymentApplicationVO vo = PaymentApplicationWrapper.build().entityVO(entity);
fillKingdeeLockStatus(List.of(vo));
vo.setInvoices(invoiceMapper.selectList(Wrappers.<PaymentApplicationInvoice>lambdaQuery()
.eq(PaymentApplicationInvoice::getPaymentApplicationId, id).orderByAsc(PaymentApplicationInvoice::getLineNo)));
vo.setPaymentRecords(recordMapper.selectList(Wrappers.<org.springblade.transport.pojo.entity.PaymentApplicationRecord>lambdaQuery()
@@ -386,6 +416,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
public void voidBill(PaymentApplicationStatusRequest request) {
PaymentApplication entity = lockedPayment(request.getId());
if (!TransportBusiConstant.APPROVED.equals(entity.getApprovalStatus())) throw new ServiceException("仅审批通过的付款申请允许作废");
// 金蝶锁定中禁止作废(lockBusiData 已锁单,金蝶侧未解锁前单据不可改动)
kingdeeBillLockService.checkEditable(KingdeeLockConstant.SOURCE_PAY_APPLY, request.getId());
if (isBillPayment(entity.getPaymentMethod())) releaseBillBalance(entity);
entity.setApprovalStatus(TransportBusiConstant.VOIDED);
entity.setCurrentNode("已作废");
@@ -427,9 +459,34 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
self().markKingdeeSyncFailed(id);
throw new ServiceException("推送金蝶付款申请失败:" + exception.getMessage());
}
// 主单事务提交后锁定金蝶付款申请单(lockBusiData):一张付款申请对一张金蝶付款申请单,接口1对1;
// 失败只留 failed 锁记录由补偿任务重锁,不污染主单同步状态;锁定失败摘要拼入返回值,页面直接可见
String lockFailSummary = lockKingdeeBillAfterPush(id);
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
syncAttachmentsAfterPush(id);
return kingdeeBillId;
return Func.isBlank(lockFailSummary) ? kingdeeBillId
: kingdeeBillId + ";注意:金蝶单据锁定未成功(" + lockFailSummary + "),系统将自动补偿重锁,重推同步金蝶可立即补锁";
}
/**
* 金蝶付款申请单锁定(lockBusiData):A103-1/A103-3 推送成功事务提交后调用,
* 一张付款申请对一张金蝶付款申请单;锁定异常独立捕获,不影响主单同步结果
*
* @param id 付款申请id
* @return 锁定失败的金蝶单号摘要,全部锁定成功返回 null
*/
private String lockKingdeeBillAfterPush(Long id) {
try {
PaymentApplication entity = getById(id);
if (entity == null || Func.isBlank(entity.getKingdeeBillNo())) {
return null;
}
return kingdeeBillLockService.lockAfterPush(KingdeeLockConstant.SOURCE_PAY_APPLY, id, entity.getPaymentNo(),
KingdeeLockConstant.TARGET_PAY_APPLY, Func.toStrList(entity.getKingdeeBillNo()));
} catch (Exception exception) {
log.error("付款申请[{}]金蝶单据锁定联带动作异常(锁记录已落库待补偿)", id, exception);
return "锁定动作异常,已留档待补偿";
}
}
/**