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