fix: 移除付款申请A103-2连续两轮查询消失自动解锁兜底

- 回写未查得不再Redis计数解锁,保持现状等待下一轮;unlockBill 仅保留金蝶删单回调触发,避免金蝶单据实际存在时误解锁清空本地金蝶单据编号/内码
- 附三笔误解锁单据(FKSQ-00000287/88/89)还原SQL:doc/sql/transport/lqj/temp/restore_3_payment_application_kingdee_unlock_20261106.sql
This commit is contained in:
刘泉佳
2026-11-06 14:41:19 +08:00
parent 643e7f88ad
commit 3acd1afd66
3 changed files with 8 additions and 70 deletions
@@ -89,14 +89,13 @@ public interface IPaymentApplicationService extends BaseService<PaymentApplicati
String retryKingdeeFailed();
/**
* 金蝶单据解锁(ADR-0005):金蝶侧单据已被反审核删除时解除本地锁定——
* 金蝶单据解锁:金蝶侧单据已被反审核删除时解除本地锁定——
* 金蝶状态置生成失败、清空金蝶单据内码与编号(原值日志留档),不自动重推,等待人工核实后重新推送。
* 触发来源:① B901 UNLOCK 回调(待建设);② A103-2 定时回写"已同步单据连续两轮查询消失"兜底。
* 仅对已生成/付款中单据生效,终态(已付款/已关闭)与生成失败单据自动跳过;
* 重推时 A103-2 勾稽预检兜底,误判场景(金蝶单据实际存在)自动回填不产生重复单据
* 触发来源:金蝶删单/解锁回调(KingdeeBillLockServiceImpl 分发)。
* 仅对已生成/付款中单据生效,终态(已付款/已关闭)与生成失败单据自动跳过
*
* @param id 付款申请id
* @param source 解锁来源标识(B901 / A103-2,用于日志追溯)
* @param source 解锁来源标识(用于日志追溯)
*/
void unlockBill(Long id, String source);
@@ -243,8 +243,8 @@ public class KingdeeBillLockServiceImpl extends ServiceImpl<KingdeeBillLockMappe
log.info("金蝶删单回调源付款申请[{}]已作废(幂等跳过)", application.getPaymentNo());
return;
}
// 复用 ADR-0005 解锁口径:不作废源单,kingdeeStatus→生成失败、清编号内码(原值留档),
// 不自动重推,人工核实后重新推送(A103-2 勾稽预检防重复推单)
// 解锁口径:不作废源单,kingdeeStatus→生成失败、清编号内码(原值留档),
// 不自动重推,人工核实后重新推送(重推时 A103-2 勾稽预检防重复推单)
SpringUtil.getBean(IPaymentApplicationService.class).unlockBill(application.getId(), "金蝶删单回调");
}
default -> log.warn("金蝶删单回调暂不支持的单据类型:targetBillType={}(映射预留)", lockRecord.getTargetBillType());
@@ -26,7 +26,6 @@ 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;
@@ -135,7 +134,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -153,12 +151,6 @@ 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;
@@ -185,7 +177,6 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private final KingdeeAttachmentSyncSupport kingdeeAttachmentSyncSupport;
private final BfiProperties bfiProperties;
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
private final BladeRedis bladeRedis;
private final ApplicationEventPublisher eventPublisher;
private final HcsCreditCheckSupport hcsCreditCheckSupport;
private final HcsProperties hcsProperties;
@@ -847,7 +838,6 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
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())) {
// 仅已生成/付款中单据处于"金蝶已确认收单"的锁定态;生成失败/终态无需解锁
@@ -870,54 +860,6 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
voidPreSettlementAdvance(entity);
}
/**
* 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("请至少选择一条审批通过的付款申请");
@@ -963,7 +905,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
* 查得即回填纠正为已生成,避免人工重推产生重复单据,未查得则保持生成失败等待人工重推;
* ② A103-2 付款申请单查询回填:指定单据号时按金蝶单据号(billno)精确查询(无金蝶单据号兜底按来源单号),
* 定时全量回写按 paymentNo(awrp_src_code)分批查询,同一来源单号取最近修改一条,
* 回填金蝶单号/内码/同步状态/已付金额并联动预结算与正式结算付款记录;连续两轮查询未命中按金蝶反审核删除解锁;
* 回填金蝶单号/内码/同步状态/已付金额并联动预结算与正式结算付款记录;未查得保持现状等待下一轮;
* ③ A103-4 付款处理查询补付款记录:付款中/已付款单据 + 已付款无付款记录单据(补偿),
* 按付款组织 + awrp_sourcebillno(金蝶单据号)数组精确查询,双键勾稽后回填付款记录;
* ④ 汇总执行摘要:各计数随流程累计,单笔失败仅计数不中断整轮
@@ -1002,15 +944,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
batch -> payApplyMap.putAll(this.latestPayApplyBySrcCode(this.queryPayApplyPagesByBillNo(batch))));
this.batchForeach(fallbackSrcCodes, PAY_RESULT_QUERY_BATCH,
batch -> payApplyMap.putAll(this.latestPayApplyBySrcCode(this.queryPayApplyPages(batch))));
// 逐单回写:查得即回填并清零消失计数;未查得累计消失轮次(批次其余单据正常返回,排除服务整体故障)
// 逐单回写:未查得保持现状等待下一轮,查得即回填(解锁仅由金蝶删单回调触发)
for (PaymentApplication application : applications) {
BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo());
if (payApply == null) {
// 累计消失轮次,连续两轮视为金蝶已反审核删除,解锁置失败交人工核实重推
this.handlePayApplyMissed(application);
continue;
}
this.clearPayApplyMissKey(application.getId());
try {
// 返回 true 表示付款完成(终态),预结算/正式结算联动由 applyPayApplyResult 内部处理
if (self.applyPayApplyResult(application, payApply)) {