feat: 付款申请新增金蝶单据消失解锁兜底(A103-2连续两轮查询消失)
金蝶侧反审核删除付款申请单后,本地停留已生成/付款中且状态门禁禁止重推,单据卡死。 按 ADR-0005 新增 unlockBill():置生成失败、清空金蝶单据内码/编号(日志留档)、不自动重推; A103-2 定时回写对已推送单据连续两轮未查得(Redis 计数,TTL 覆盖三个回写周期)触发解锁; failed 单据不参与计数,Redis 异常放弃判定,重推链 A103-2 勾稽预检兜底防重复单据; B901 UNLOCK 回调后续接入复用同一 unlockBill()
This commit is contained in:
+12
@@ -73,6 +73,18 @@ public interface IPaymentApplicationService extends BaseService<PaymentApplicati
|
||||
*/
|
||||
void markKingdeeSyncFailed(Long id);
|
||||
|
||||
/**
|
||||
* 金蝶单据解锁(ADR-0005):金蝶侧单据已被反审核删除时解除本地锁定——
|
||||
* 金蝶状态置生成失败、清空金蝶单据内码与编号(原值日志留档),不自动重推,等待人工核实后重新推送。
|
||||
* 触发来源:① B901 UNLOCK 回调(待建设);② A103-2 定时回写"已同步单据连续两轮查询消失"兜底。
|
||||
* 仅对已生成/付款中单据生效,终态(已付款/已关闭)与生成失败单据自动跳过;
|
||||
* 重推时 A103-2 勾稽预检兜底,误判场景(金蝶单据实际存在)自动回填不产生重复单据
|
||||
*
|
||||
* @param id 付款申请id
|
||||
* @param source 解锁来源标识(B901 / A103-2,用于日志追溯)
|
||||
*/
|
||||
void unlockBill(Long id, String source);
|
||||
|
||||
/**
|
||||
* 定时回写金蝶付款结果(A103-2 付款申请单查询 / A103-4 付款处理查询)
|
||||
*
|
||||
|
||||
+89
-2
@@ -26,6 +26,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
@@ -113,6 +114,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -130,6 +132,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private static final String MOCK_PAYMENT_RECORD_PREFIX = "MOCK-PAY-";
|
||||
/** 金蝶付款结果回写 single-flight 锁键前缀(多副本定时任务与手动触发共用) */
|
||||
private static final String PAY_RESULT_SYNC_LOCK_PREFIX = "transport:payment-application:kingdee-pay-result-sync:";
|
||||
/** A103-2 消失轮次 Redis 计数键前缀(金蝶侧疑似反审核删除的兜底判定,ADR-0005) */
|
||||
private static final String PAY_RESULT_MISS_KEY_PREFIX = "transport:payment-application:kingdee-pay-miss:";
|
||||
/** A103-2 消失解锁轮次阈值:连续两轮未查得即解锁置失败态 */
|
||||
private static final int PAY_RESULT_MISS_UNLOCK_ROUNDS = 2;
|
||||
/** A103-2 消失轮次计数键过期秒数:覆盖三个回写周期(10分钟/轮),超时未续计则重新累计(保守方向) */
|
||||
private static final long PAY_RESULT_MISS_KEY_TTL_SECONDS = 2100L;
|
||||
private final PaymentApplicationInvoiceMapper invoiceMapper;
|
||||
private final PaymentApplicationRecordMapper recordMapper;
|
||||
private final PaymentApplicationSettlementMapper settlementRelationMapper;
|
||||
@@ -152,6 +160,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private final KingdeeAttachmentSyncSupport kingdeeAttachmentSyncSupport;
|
||||
private final BfiProperties bfiProperties;
|
||||
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
/**
|
||||
* 金蝶付款结果回写 single-flight 锁租约秒数,须大于最坏一轮耗时(分批远程查询 + 逐单回写),
|
||||
@@ -526,6 +535,80 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
.set(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void unlockBill(Long id, String source) {
|
||||
if (id == null) return;
|
||||
PaymentApplication entity = getById(id);
|
||||
if (entity == null) return;
|
||||
clearPayApplyMissKey(id);
|
||||
if (!KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(entity.getKingdeeStatus())
|
||||
&& !KingdeeBfiConstant.KINGDEE_STATUS_PAYING.equals(entity.getKingdeeStatus())) {
|
||||
// 仅已生成/付款中单据处于"金蝶已确认收单"的锁定态;生成失败/终态无需解锁
|
||||
log.info("付款申请[{}]金蝶单据解锁跳过(来源[{}]):kingdeeStatus=[{}]非锁定态",
|
||||
entity.getPaymentNo(), source, entity.getKingdeeStatus());
|
||||
return;
|
||||
}
|
||||
// 原金蝶单据编号/内码日志留档后清空:金蝶侧已反审核删除,原值不再有效,
|
||||
// 附件上传、A103-4 勾稽均依赖单据内码,重推生成新单后自动重新关联
|
||||
log.warn("付款申请[{}]金蝶单据解锁(来源[{}]):kingdeeStatus[{}]->[生成失败],原单据编号[{}]/内码[{}]已留档并清空,不自动重推,等待人工核实后重新推送",
|
||||
entity.getPaymentNo(), source, entity.getKingdeeStatus(), entity.getKingdeeBillNo(), entity.getKingdeeBillId());
|
||||
baseMapper.update(null, Wrappers.<PaymentApplication>lambdaUpdate()
|
||||
.eq(PaymentApplication::getId, id)
|
||||
.in(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED, KingdeeBfiConstant.KINGDEE_STATUS_PAYING)
|
||||
.set(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED)
|
||||
.set(PaymentApplication::getKingdeeBillId, null)
|
||||
.set(PaymentApplication::getKingdeeBillNo, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* A103-2 未查得已推送单据的兜底判定(ADR-0005"连续两轮查询消失"):
|
||||
* 仅对已生成/付款中单据经 Redis 计数累计消失轮次(生成失败单据金蝶查不到属正常态,等待人工重推,不参与计数),
|
||||
* 连续达到阈值即解锁置失败态;重推链 A103-2 勾稽预检兜底,误判场景(金蝶单据实际存在)自动回填不产生重复单据
|
||||
*
|
||||
* @param application 本轮未查得的付款申请
|
||||
*/
|
||||
private void handlePayApplyMissed(PaymentApplication application) {
|
||||
if (Func.isBlank(application.getPaymentNo())
|
||||
|| (!KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(application.getKingdeeStatus())
|
||||
&& !KingdeeBfiConstant.KINGDEE_STATUS_PAYING.equals(application.getKingdeeStatus()))) {
|
||||
return;
|
||||
}
|
||||
String missKey = payApplyMissKey(application.getId());
|
||||
Long rounds;
|
||||
try {
|
||||
rounds = bladeRedis.getStringRedisTemplate().opsForValue().increment(missKey);
|
||||
bladeRedis.getStringRedisTemplate().expire(missKey, PAY_RESULT_MISS_KEY_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (Exception exception) {
|
||||
// Redis 异常时放弃本轮判定:解锁是破坏性动作(清空金蝶单号),宁可延迟不可误判
|
||||
log.warn("付款申请[{}]A103-2 消失轮次累计失败,本轮跳过解锁判定", application.getPaymentNo(), exception);
|
||||
return;
|
||||
}
|
||||
if (rounds != null && rounds >= PAY_RESULT_MISS_UNLOCK_ROUNDS) {
|
||||
log.warn("付款申请[{}]A103-2 连续[{}]轮未查得金蝶付款申请单,判定金蝶侧已反审核删除,执行解锁",
|
||||
application.getPaymentNo(), rounds);
|
||||
self().unlockBill(application.getId(), "A103-2 连续" + rounds + "轮查询消失");
|
||||
} else {
|
||||
log.warn("付款申请[{}]A103-2 未查得金蝶付款申请单(第[{}]轮),金蝶侧可能已反审核删除,下一轮复核",
|
||||
application.getPaymentNo(), rounds);
|
||||
}
|
||||
}
|
||||
|
||||
/** A103-2 消失轮次计数键 */
|
||||
private String payApplyMissKey(Long id) {
|
||||
return PAY_RESULT_MISS_KEY_PREFIX + id;
|
||||
}
|
||||
|
||||
/** 清除 A103-2 消失轮次计数(金蝶重新查得或单据解锁后调用) */
|
||||
private void clearPayApplyMissKey(Long id) {
|
||||
if (id == null) return;
|
||||
try {
|
||||
bladeRedis.getStringRedisTemplate().delete(payApplyMissKey(id));
|
||||
} catch (Exception exception) {
|
||||
log.warn("付款申请[{}]清除 A103-2 消失轮次计数失败", id, exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> syncKingdeeBatch(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) throw new ServiceException("请至少选择一条审批通过的付款申请");
|
||||
@@ -588,8 +671,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
for (PaymentApplication application : applications) {
|
||||
BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo());
|
||||
if (payApply == null) {
|
||||
// 金蝶侧未查得已推送单据(查询批次其余单据正常返回,排除服务整体故障):
|
||||
// 累计消失轮次,连续两轮视为金蝶已反审核删除,解锁置失败交人工核实重推
|
||||
this.handlePayApplyMissed(application);
|
||||
continue;
|
||||
}
|
||||
clearPayApplyMissKey(application.getId());
|
||||
try {
|
||||
if (self.applyPayApplyResult(application, payApply)) paidCount++;
|
||||
updated++;
|
||||
@@ -957,8 +1044,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 往来户.编码:合同乙方关联客商档案客商编码
|
||||
entry.setEAsstactNumber(kingdeeApBillSyncSupport.bfiAsstactNumber(contract, context));
|
||||
entry.setEAssacct(entity.getBankAccount());
|
||||
// 往来银行.编码:与 A103-1 统一口径,取付款申请所选客商收款账户联行号(未维护即拦截)
|
||||
entry.setEBebankNumber(this.bfiBebankNumber(entity, context));
|
||||
// 往来银行.编码:与 A103-1 统一口径,取付款申请所选客商收款账户联行号(未维护即拦截)
|
||||
entry.setEBebankNumber(this.bfiBebankNumber(entity, context));
|
||||
// 应付金额
|
||||
// entry.setEPayamount(applyAmount);
|
||||
// 申请金额
|
||||
|
||||
Reference in New Issue
Block a user