feat: 金蝶锁单失败摘要拼入同步返回供页面提示,重推重置补偿计数并新增手动补偿端点

This commit is contained in:
刘泉佳
2026-10-10 12:21:41 +08:00
parent 6bff852760
commit d109e0dc9b
5 changed files with 48 additions and 21 deletions
@@ -36,6 +36,7 @@ import org.springblade.transport.excel.FormalSettlementExcel;
import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IInvoiceReceiptService;
import org.springblade.transport.service.IKingdeeBillLockService;
import org.springblade.transport.service.IPreSettlementService;
import org.springblade.transport.service.IReceiptFlowService;
import org.springframework.web.bind.annotation.GetMapping;
@@ -62,6 +63,7 @@ import java.math.RoundingMode;
@Tag(name = "正式结算单", description = "正式结算单管理")
public class FormalSettlementController extends BladeController {
private final IFormalSettlementService formalSettlementService;
private final IKingdeeBillLockService kingdeeBillLockService;
private final IPreSettlementService preSettlementService;
private final IReceiptFlowService receiptFlowService;
private final IInvoiceReceiptService invoiceReceiptService;
@@ -216,6 +218,13 @@ public class FormalSettlementController extends BladeController {
@Operation(summary = "手动勾稽金蝶应收单(A302-4 回写纠偏)")
public R<String> syncKingdeeArResult() { return R.data(formalSettlementService.syncKingdeeArResult()); }
@PostMapping("/retry-kingdee-bill-lock")
@ApiOperationSupport(order = 17)
@Operation(summary = "手动补偿金蝶单据锁定(扫锁定失败锁记录重调lockBusiData,应付/应收/开票通用)")
public R<String> retryKingdeeBillLock() {
return R.data(kingdeeBillLockService.retryFailedLocks());
}
@GetMapping("/detail-fees")
@ApiOperationSupport(order = 16)
@Operation(summary = "正式结算货物费用快照")
@@ -24,7 +24,7 @@ public interface IKingdeeBillLockService extends IService<KingdeeBillLock> {
/**
* 推送金蝶成功后锁单(由各推送链路在主单事务提交后调用):
* 每张金蝶目标单一行锁记录(先落 failed 再调 lockBusiData,成功翻 locked);
* 同源单的目标单合并为一次调用(编号逗号拼接批量传参);已 locked 幂等跳过,failed 原地重锁;
* 同源单的目标单合并为一次调用(编号逗号拼接批量传参);已 locked 幂等跳过,failed 原地重锁并重置补偿计数;
* 异常独立捕获,不影响主单同步状态
*
* @param sourceBillType WLTMS 单据类型(SETTLEMENT/INVOICE_APPLY)
@@ -32,8 +32,9 @@ public interface IKingdeeBillLockService extends IService<KingdeeBillLock> {
* @param sourceBillNo WLTMS 单号
* @param targetBillType 金蝶目标单据类型
* @param targetBillNos 金蝶单据编号集合(开票申请为多张)
* @return 锁定失败的金蝶单号摘要(逗号拼接),全部锁定成功返回 null;调用方可拼入页面提示
*/
void lockAfterPush(String sourceBillType, Long sourceBillId, String sourceBillNo, String targetBillType, List<String> targetBillNos);
String lockAfterPush(String sourceBillType, Long sourceBillId, String sourceBillNo, String targetBillType, List<String> targetBillNos);
/**
* 金蝶解锁回调(unLockNotifyBusiData):校验来源/目标系统与 lockAction,
@@ -600,11 +600,13 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
public String syncKingdee(Long id) {
// 单据级事务经自代理调用:单条失败只回滚自身
String billNo = SpringUtil.getBean(IFormalSettlementService.class).doSyncKingdee(id);
// 主单事务提交后锁定金蝶单据(lockBusiData):失败只留 failed 锁记录由补偿任务重锁,不污染主单同步状态
lockKingdeeBillAfterPush(id, billNo);
// 主单事务提交后锁定金蝶单据(lockBusiData):失败只留 failed 锁记录由补偿任务重锁,不污染主单同步状态;
// 锁定失败摘要拼入返回值,页面直接可见
String lockFailSummary = lockKingdeeBillAfterPush(id, billNo);
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
syncAttachmentsAfterPush(id);
return billNo;
return Func.isBlank(lockFailSummary) ? billNo
: billNo + ";注意:金蝶单据锁定未成功(" + lockFailSummary + "),系统将自动补偿重锁,重推同步金蝶可立即补锁";
}
/**
@@ -613,22 +615,24 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
*
* @param id 正式结算单id
* @param billNo 金蝶单据编号
* @return 锁定失败的金蝶单号摘要,全部锁定成功返回 null
*/
private void lockKingdeeBillAfterPush(Long id, String billNo) {
private String lockKingdeeBillAfterPush(Long id, String billNo) {
try {
if (Func.isBlank(billNo)) {
return;
return null;
}
FormalSettlement settlement = getById(id);
if (settlement == null) {
return;
return null;
}
kingdeeBillLockService.lockAfterPush(KingdeeLockConstant.SOURCE_SETTLEMENT, id, settlement.getFormalSettlementNo(),
return kingdeeBillLockService.lockAfterPush(KingdeeLockConstant.SOURCE_SETTLEMENT, id, settlement.getFormalSettlementNo(),
"receivable".equals(settlement.getSettlementType())
? KingdeeLockConstant.TARGET_AR_BILL : KingdeeLockConstant.TARGET_AP_BILL,
Func.toStrList(billNo));
} catch (Exception exception) {
log.error("正式结算单[{}]金蝶单据锁定联带动作异常(锁记录已落库待补偿)", id, exception);
return "锁定动作异常,已留档待补偿";
}
}
@@ -547,11 +547,12 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
// 单据级事务经自代理调用:单条失败只回滚自身
String billNo = SpringUtil.getBean(IInvoiceApplicationService.class).doSyncKingdee(id);
// 主单事务提交后锁定金蝶开票单(lockBusiData):每张发票张次一张金蝶单,多张逗号拼接批量锁定;
// 失败只留 failed 锁记录由补偿任务重锁,不污染主单同步状态
lockKingdeeBillAfterPush(id, billNo);
// 失败只留 failed 锁记录由补偿任务重锁,不污染主单同步状态;锁定失败摘要拼入返回值,页面直接可见
String lockFailSummary = lockKingdeeBillAfterPush(id, billNo);
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
syncAttachmentsAfterPush(id);
return billNo;
return Func.isBlank(lockFailSummary) ? billNo
: billNo + ";注意:金蝶单据锁定未成功(" + lockFailSummary + "),系统将自动补偿重锁,重推同步金蝶可立即补锁";
}
/**
@@ -560,20 +561,22 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
*
* @param id 开票申请id
* @param billNo 金蝶开票单编号(多张逗号拼接)
* @return 锁定失败的金蝶单号摘要,全部锁定成功返回 null
*/
private void lockKingdeeBillAfterPush(Long id, String billNo) {
private String lockKingdeeBillAfterPush(Long id, String billNo) {
try {
if (Func.isBlank(billNo)) {
return;
return null;
}
InvoiceApplication entity = getById(id);
if (entity == null) {
return;
return null;
}
kingdeeBillLockService.lockAfterPush(KingdeeLockConstant.SOURCE_INVOICE_APPLY, id, entity.getApplicationNo(),
return kingdeeBillLockService.lockAfterPush(KingdeeLockConstant.SOURCE_INVOICE_APPLY, id, entity.getApplicationNo(),
KingdeeLockConstant.TARGET_INVOICE_BILL, Func.toStrList(billNo));
} catch (Exception exception) {
log.error("开票申请[{}]金蝶单据锁定联带动作异常(锁记录已落库待补偿)", id, exception);
return "锁定动作异常,已留档待补偿";
}
}
@@ -67,9 +67,9 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
private final InvoiceApplicationMapper invoiceApplicationMapper;
@Override
public void lockAfterPush(String sourceBillType, Long sourceBillId, String sourceBillNo, String targetBillType, List<String> targetBillNos) {
public String lockAfterPush(String sourceBillType, Long sourceBillId, String sourceBillNo, String targetBillType, List<String> targetBillNos) {
if (Func.isEmpty(targetBillNos)) {
return;
return null;
}
String operator = operatorName();
// 待锁目标单:已 locked 幂等跳过(人工重推「同步金蝶」幂等命中路径不会重复锁);
@@ -80,7 +80,7 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
if (lockRecord != null && KingdeeLockConstant.STATUS_LOCKED.equals(lockRecord.getLockStatus())) {
continue;
}
if (lockRecord == null || KingdeeLockConstant.STATUS_UNLOCKED.equals(lockRecord.getLockStatus())) {
if (lockRecord == null) {
lockRecord = new KingdeeBillLock();
lockRecord.setSourceBillType(sourceBillType);
lockRecord.setSourceBillId(sourceBillId);
@@ -92,11 +92,18 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
lockRecord.setTryCount(0);
lockRecord.setLockOperator(operator);
this.save(lockRecord);
} else {
// 人工重推触发的补锁:重置补偿计数重新起算(定时补偿烧完上限后,人工动作可重新接管自动补偿)
KingdeeBillLock resetRecord = new KingdeeBillLock();
resetRecord.setId(lockRecord.getId());
resetRecord.setTryCount(0);
this.updateById(resetRecord);
lockRecord.setTryCount(0);
}
pendingRecords.add(lockRecord);
}
if (pendingRecords.isEmpty()) {
return;
return null;
}
// 同源单目标单合并一次调用:编号逗号拼接批量传参,整批同成败(应答仅聚合 updateCount)
try {
@@ -111,6 +118,7 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
}
log.info("金蝶单据批量锁定成功:源单[{}:{}],金蝶单{}张[{}]", sourceBillType, sourceBillNo,
pendingRecords.size(), String.join(",", pendingRecords.stream().map(KingdeeBillLock::getTargetBillNo).toList()));
return null;
} catch (Exception exception) {
String failReason = limitText(exception.getMessage(), 500);
for (KingdeeBillLock lockRecord : pendingRecords) {
@@ -118,7 +126,9 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
lockRecord.setFailReason(failReason);
this.updateById(lockRecord);
}
log.error("金蝶单据批量锁定失败(待补偿重锁):源单[{}:{}],金蝶单{}张", sourceBillType, sourceBillNo, pendingRecords.size(), exception);
String failedNos = pendingRecords.stream().map(KingdeeBillLock::getTargetBillNo).collect(Collectors.joining(","));
log.error("金蝶单据批量锁定失败(待补偿重锁):源单[{}:{}],金蝶单{}张[{}]", sourceBillType, sourceBillNo, pendingRecords.size(), failedNos, exception);
return failedNos;
}
}