diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java index e61d785d..b8d3971d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java @@ -254,7 +254,7 @@ public class BfiProperties { private long lockLeaseSeconds = 1800; /** - * A103-4 付款处理查询业务日期窗口天数,默认 60 + * A103-4 付款处理查询业务日期窗口天数(接口必填参数,按 awrp_sourcebillno 精确过滤单据),默认 60 */ private int queryWindowDays = 60; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java index 1189a40d..41448c11 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java @@ -952,20 +952,28 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl + * ① 圈定待回写付款申请:审批通过且金蝶状态为 已生成/付款中/生成失败(终态已付款/已关闭不再轮询), + * 生成失败单据一并纳入:金蝶侧已生成说明上次推送实际成功(如下推超时但远端已落单、本地事务回滚), + * 查得即回填纠正为已生成,避免人工重推产生重复单据,未查得则保持生成失败等待人工重推; + * ② A103-2 付款申请单查询回填:按 paymentNo(awrp_src_code)分批查询,同一来源单号取最近修改一条, + * 回填金蝶单号/内码/同步状态/已付金额并联动预结算与正式结算付款记录;连续两轮查询未命中按金蝶反审核删除解锁; + * ③ A103-4 付款处理查询补付款记录:付款中/已付款单据 + 已付款无付款记录单据(补偿), + * 按付款组织 + awrp_sourcebillno(金蝶单据号)数组精确查询,双键勾稽后回填付款记录; + * ④ 汇总执行摘要:各计数随流程累计,单笔失败仅计数不中断整轮 + * * * @return 执行摘要 */ private String executePaymentResultSync() { - // ① 待回写付款申请:审批通过且金蝶状态为 已生成/付款中/生成失败(终态已付款/已关闭不再轮询)。 - // 生成失败单据一并纳入勾稽:金蝶侧已生成说明上次推送实际成功(如下推请求超时但远端已落单、本地事务回滚), - // 查到即回填纠正为已生成,避免人工重新推送产生重复单据;未查到则保持生成失败等待异常处理后重新推送 + // ① 待回写付款申请:审批通过且金蝶状态为 已生成/付款中/生成失败(终态已付款/已关闭不再轮询) List applications = list(Wrappers.lambdaQuery() .eq(PaymentApplication::getApprovalStatus, TransportBusiConstant.APPROVED) .in(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED, KingdeeBfiConstant.KINGDEE_STATUS_PAYING, KingdeeBfiConstant.KINGDEE_STATUS_FAILED) .orderByAsc(PaymentApplication::getId)); + // 自代理:applyPayApplyResult/applyPayBillRecord 带单据级事务,跨方法调用确保注解生效,单笔失败仅回滚自身 IPaymentApplicationService self = SpringUtil.getBean(IPaymentApplicationService.class); int updated = 0; int failed = 0; @@ -975,19 +983,22 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl paymentNos = applications.stream() .map(PaymentApplication::getPaymentNo).filter(Func::isNotEmpty).distinct().toList(); - batchForeach(paymentNos, PAY_RESULT_QUERY_BATCH, batch -> - payApplyMap.putAll(latestPayApplyBySrcCode(queryPayApplyPages(batch)))); + this.batchForeach(paymentNos, 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; } - clearPayApplyMissKey(application.getId()); + this.clearPayApplyMissKey(application.getId()); try { - if (self.applyPayApplyResult(application, payApply)) paidCount++; + // 返回 true 表示付款完成(终态),预结算/正式结算联动由 applyPayApplyResult 内部处理 + if (self.applyPayApplyResult(application, payApply)) { + paidCount++; + } updated++; } catch (Exception exception) { failed++; @@ -995,7 +1006,9 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl recordCandidates = new LinkedHashMap<>(); for (PaymentApplication application : applications) { BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo()); @@ -1006,18 +1019,19 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpllambdaQuery() .eq(PaymentApplication::getApprovalStatus, TransportBusiConstant.APPROVED) .eq(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_PAID)) - .stream().filter(application -> realRecordCount(application.getId()) == 0) + .stream().filter(application -> this.realRecordCount(application.getId()) == 0) .forEach(application -> recordCandidates.putIfAbsent(application.getId(), application)); int recordWritten = 0; int recordFailed = 0; if (!recordCandidates.isEmpty()) { + // 按付款组织分组(org_number 接口必传):优先取 A103-2 返回的付款组织编码, + // 兜底按合同关联项目解析(已付款补偿候选不在本轮 A103-2 返回内,须走兜底) Map> orgGroups = new LinkedHashMap<>(); for (PaymentApplication application : recordCandidates.values()) { BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo()); String orgNumber = payApply == null ? null : payApply.getPayorgNumber(); if (Func.isBlank(orgNumber)) { try { - // A103-4 付款人(公司)编码必填:优先取 A103-2 返回付款组织编码,兜底按合同关联项目解析 KingdeeSyncContext context = KingdeeSyncContext.empty(); orgNumber = kingdeeApBillSyncSupport.bfiOrgNumber( kingdeeApBillSyncSupport.requireContract(application.getContractId(), context), context); @@ -1028,26 +1042,25 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl new ArrayList<>()).add(application); } - // 以付款处理单源单id/源单编号建立索引,与本地金蝶单据id/单据编号勾稽 - Map payBillBySourceId = new HashMap<>(); - Map payBillBySourceNumber = new HashMap<>(); - for (String orgNumber : orgGroups.keySet()) { - for (BfiPayBillVO payBill : queryPayBillPages(orgNumber)) { - if (Func.isEmpty(payBill.getBillno())) continue; - if (payBill.getSourcebillid() != null) { - payBillBySourceId.put(String.valueOf(payBill.getSourcebillid()), payBill); - } - if (Func.isNotEmpty(payBill.getSourcebillnumber())) { - payBillBySourceNumber.put(payBill.getSourcebillnumber(), payBill); - } - } + // 按组织 + 金蝶单据号(awrp_sourcebillno 支持数组)分批精确查询付款处理单,结果汇总后统一勾稽 + List payBills = new ArrayList<>(); + for (Map.Entry> orgEntry : orgGroups.entrySet()) { + List kingdeeBillNos = orgEntry.getValue().stream() + .map(PaymentApplication::getKingdeeBillNo).filter(Func::isNotEmpty).distinct().toList(); + this.batchForeach(kingdeeBillNos, PAY_RESULT_QUERY_BATCH, + batch -> payBills.addAll(this.queryPayBillPages(orgEntry.getKey(), batch))); } + // 逐单勾稽回填:双键匹配(源单id=付款申请金蝶单据内码 / 源单编号=金蝶单据号),未匹配保持现状等下一轮 for (PaymentApplication application : recordCandidates.values()) { - BfiPayBillVO payBill = matchPayBill(application, payBillBySourceId, payBillBySourceNumber); - if (payBill == null) { + if (Func.isBlank(application.getKingdeeBillNo())) { + log.warn("付款申请[{}]付款处理查询跳过,金蝶单据编号为空", application.getPaymentNo()); continue; } try { + BfiPayBillVO payBill = this.matchPayBill(application, payBills); + if (payBill == null) { + continue; + } self.applyPayBillRecord(application, payBill); recordWritten++; } catch (Exception exception) { @@ -1072,7 +1085,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl 0) { + if (existing == null || this.strCompare(payApply.getModifytime(), existing.getModifytime()) > 0) { latestBySrcCode.put(payApply.getAwrpSrcCode(), payApply); } } @@ -1207,7 +1220,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl - * 窗口天数取 Nacos thirdParty.bfi.payResult.queryWindowDays(默认 60) + * 业务日期窗口与付款组织为接口必填参数,窗口天数取 Nacos thirdParty.bfi.payResult.queryWindowDays + * (默认 60),仅用于满足必填要求;awrp_sourcebillno 精确过滤后仅返回目标单据的付款处理单, + * 不再拉取组织内全量数据做内存勾稽 * - * @param orgNumber 付款人(公司)编码 + * @param orgNumber 付款人(公司)编码,接口必传 + * @param awrpSourceBillnos 付款申请金蝶单据号批次 * @return 付款处理单列表 */ - private List queryPayBillPages(String orgNumber) { + private List queryPayBillPages(String orgNumber, List awrpSourceBillnos) { LocalDate endDate = LocalDate.now(); int windowDays = Math.max(bfiProperties.getPayResult().getQueryWindowDays(), 1); LocalDate beginDate = endDate.minusDays(windowDays); BfiPayBillQueryDTO param = new BfiPayBillQueryDTO(); + // 接口必填参数:付款组织编码 + 业务日期窗口(beigin_bizdate/end_bizdate,金蝶侧拼写如此) param.setOrgNumber(List.of(orgNumber)); param.setBeginBizdate(beginDate.format(DateTimeFormatter.ISO_LOCAL_DATE)); param.setEndBizdate(endDate.format(DateTimeFormatter.ISO_LOCAL_DATE)); + // 精确过滤参数:待回填单据的金蝶单据号数组(sourcebillnumber 与之同源) + param.setAwrpSourceBillno(awrpSourceBillnos); return BfiUtil.queryAllPages(pageNo -> { param.setPageNo(pageNo); param.setPageSize(PAY_RESULT_PAGE_SIZE); @@ -1270,21 +1289,24 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl payBillBySourceId, - Map payBillBySourceNumber) { - if (Func.isNotEmpty(application.getKingdeeBillId()) - && payBillBySourceId.containsKey(application.getKingdeeBillId())) { - return payBillBySourceId.get(application.getKingdeeBillId()); - } - if (Func.isNotEmpty(application.getKingdeeBillNo())) { - return payBillBySourceNumber.get(application.getKingdeeBillNo()); + private BfiPayBillVO matchPayBill(PaymentApplication application, List payBills) { + for (BfiPayBillVO payBill : payBills) { + if (payBill.getSourcebillid() != null + && Func.isNotEmpty(application.getKingdeeBillId()) + && String.valueOf(payBill.getSourcebillid()).equals(application.getKingdeeBillId())) { + return payBill; + } + if (Func.isNotEmpty(payBill.getSourcebillnumber()) + && payBill.getSourcebillnumber().equals(application.getKingdeeBillNo())) { + return payBill; + } } return null; } diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/feign/IBfiClient.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/feign/IBfiClient.java index dd983387..e16bc3df 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/feign/IBfiClient.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/feign/IBfiClient.java @@ -220,10 +220,11 @@ public interface IBfiClient { * @param accessToken 访问令牌,通过 getToken 获取 * @param pageNo 页码,从1开始 * @param pageSize 页数 - * @param beginBizdate 开始业务日期 - * @param endBizdate 结束业务日期 + * @param beginBizdate 开始业务日期,必传 + * @param endBizdate 结束业务日期,必传 * @param billno 单据编号 - * @param orgNumber 付款人(公司).编码 + * @param orgNumber 付款人(公司).编码,必传,支持多个 + * @param awrpSourceBillno 外部来源单据编号(付款申请金蝶单据号),支持多个,精确过滤付款处理单 * @return 付款处理分页数据 */ @GetMapping(PAY_BILL_QUERY_URL) @@ -233,7 +234,8 @@ public interface IBfiClient { @RequestParam("beigin_bizdate") String beginBizdate, @RequestParam("end_bizdate") String endBizdate, @RequestParam(value = "billno", required = false) List billno, - @RequestParam("org_number") List orgNumber); + @RequestParam("org_number") List orgNumber, + @RequestParam(value = "awrp_sourcebillno", required = false) List awrpSourceBillno); /** * A902-1 客户查询 diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiPayBillQueryDTO.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiPayBillQueryDTO.java index 385a2dfa..e18f31ed 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiPayBillQueryDTO.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiPayBillQueryDTO.java @@ -64,7 +64,11 @@ public class BfiPayBillQueryDTO implements Serializable { */ private List billno; /** - * 付款人(公司).编码 + * 付款人(公司).编码,必传,支持多个 */ private List orgNumber; + /** + * 外部来源单据编号(付款申请金蝶单据号),支持多个,精确过滤付款处理单 + */ + private List awrpSourceBillno; } diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java index e010b0f5..bb6e2c1c 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java @@ -294,6 +294,7 @@ public class BfiServiceImpl implements IBfiService { if (param == null || param.getPageNo() == null || param.getPageSize() == null) { throw new BfiException("查询BFI付款处理失败,分页参数不能为空"); } + // 接口必填参数:业务日期范围 + 付款组织编码;awrp_sourcebillno 可选,精确过滤付款处理单 if (StringUtil.isBlank(param.getBeginBizdate()) || StringUtil.isBlank(param.getEndBizdate())) { throw new BfiException("查询BFI付款处理失败,业务日期范围不能为空"); } @@ -302,7 +303,8 @@ public class BfiServiceImpl implements IBfiService { } String accessToken = getAccessToken(); BfiResultVO> result = commonCall("付款处理查询", () -> client.payBillQuery(accessToken, - param.getPageNo(), param.getPageSize(), param.getBeginBizdate(), param.getEndBizdate(), param.getBillno(), param.getOrgNumber())); + param.getPageNo(), param.getPageSize(), param.getBeginBizdate(), param.getEndBizdate(), param.getBillno(), + param.getOrgNumber(), param.getAwrpSourceBillno())); return result.getData(); }