feat: 收款流水同步数据源A301-2改为A301-1并下线金蝶推送链路
This commit is contained in:
+22
-22
@@ -52,68 +52,68 @@ public class KingdeeReceiptFlow extends TenantEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 金蝶认领公告id(A301-2 返回 id,认领推送 A301-3 必填)
|
||||
* 金蝶认领公告id(历史 A301-2 同步遗留,A301-1 口径不再使用,恒为空)
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long billId;
|
||||
/**
|
||||
* 金蝶付款人id(A301-2 公告对应往来单位 id,认领推送 A301-3 用)
|
||||
* 金蝶付款人id(历史 A301-2 同步遗留,A301-1 口径不再使用,恒为空)
|
||||
*/
|
||||
private String payerId;
|
||||
/**
|
||||
* 公告认领状态(A301-2 claimstatus:0未认领/1已认领)
|
||||
* 公告认领状态(历史 A301-2 同步遗留,A301-1 口径不再使用,恒为空)
|
||||
*/
|
||||
private String kingdeeClaimStatus;
|
||||
/**
|
||||
* 我已认领(A301-2 claimme)
|
||||
* 我已认领(历史 A301-2 同步遗留,A301-1 口径不再使用,恒为空)
|
||||
*/
|
||||
private Boolean claimMe;
|
||||
/**
|
||||
* 未认领金额(A301-2 unclaimamount)
|
||||
* 未匹配收款金额(A301-1 unmatchamountrec,认领上限口径)
|
||||
*/
|
||||
private BigDecimal unclaimedAmount;
|
||||
/**
|
||||
* 手续费(A301-2 fee)
|
||||
* 手续费(A301-1 fee)
|
||||
*/
|
||||
private BigDecimal fee;
|
||||
/**
|
||||
* 收款人(A301-2 recpayee)
|
||||
* 收款人(A301-1 收款公司名称 org_name)
|
||||
*/
|
||||
private String payeeName;
|
||||
/**
|
||||
* 收款通知单号(A301-2 billno)
|
||||
* 金蝶收款单号(A301-1 billno)
|
||||
*/
|
||||
private String receiptNoticeNo;
|
||||
/**
|
||||
* 付款人名称(A301-2 recviewpayer)
|
||||
* 付款人名称(A301-1 payername)
|
||||
*/
|
||||
private String payerName;
|
||||
/**
|
||||
* 收款金额(A301-2 reamount)
|
||||
* 收款金额(A301-1 流水分录 bei_creditamount,兜底主单 actrecamt)
|
||||
*/
|
||||
private BigDecimal receiptAmount;
|
||||
/**
|
||||
* 对方单位名称(A301-2 oppunit)
|
||||
* 对方单位名称(A301-1 付款人名称)
|
||||
*/
|
||||
private String counterpartyName;
|
||||
/**
|
||||
* 对方银行账号(A301-2 oppbanknumber)
|
||||
* 对方银行账号(A301-1 付款账户 payeracctbanknum)
|
||||
*/
|
||||
private String counterpartyAccount;
|
||||
/**
|
||||
* 对方开户行(A301-2 oppbank)
|
||||
* 对方开户行(A301-1 付款银行 payerbankname)
|
||||
*/
|
||||
private String counterpartyBank;
|
||||
/**
|
||||
* 摘要(A301-2 description)
|
||||
* 摘要(A301-1 txt_description)
|
||||
*/
|
||||
private String summary;
|
||||
/**
|
||||
* 交易日期(A301-2 tradetime)
|
||||
* 交易日期(A301-1 业务日期 bizdate)
|
||||
*/
|
||||
private LocalDateTime transactionTime;
|
||||
/**
|
||||
* 明细流水号(A301-2 detailid,回退 tradedetailno / bankdetailno,幂等键)
|
||||
* 明细流水号(A301-1 流水分录 bei_detailid,兜底 bei_bankdetailno,幂等键)
|
||||
*/
|
||||
private String detailSerialNo;
|
||||
/**
|
||||
@@ -125,27 +125,27 @@ public class KingdeeReceiptFlow extends TenantEntity {
|
||||
*/
|
||||
private String claimStatus;
|
||||
/**
|
||||
* 推送状态(none未推送/pushed已推送/failed推送失败;已推送为终态闸门,不允许二次推送)
|
||||
* 推送状态(none未推送/pushed已推送/failed推送失败;历史 A301-3 推送遗留,A301-1 口径下恒为 none,仅存量的已推送数据保留展示)
|
||||
*/
|
||||
private String pushStatus;
|
||||
/**
|
||||
* 最近一次推送失败原因(成功后清空)
|
||||
* 最近一次推送失败原因(历史 A301-3 推送遗留)
|
||||
*/
|
||||
private String pushFailReason;
|
||||
/**
|
||||
* 首次推送金蝶成功时间
|
||||
* 首次推送金蝶成功时间(历史 A301-3 推送遗留)
|
||||
*/
|
||||
private LocalDateTime pushedTime;
|
||||
/**
|
||||
* 金蝶推送来源单据编码(分摊结算单号下划线拼接,推送时生成落库;分摊不变则重试编码不变,A301-1 回写匹配键)
|
||||
* 金蝶推送来源单据编码(历史 A301-3 推送遗留,A301-1 口径不再生成)
|
||||
*/
|
||||
private String awrpSrcCode;
|
||||
/**
|
||||
* 金蝶收款单号(推送响应回填 / A301-1 回写推进)
|
||||
* 金蝶收款单号(A301-1 同步落库 billno)
|
||||
*/
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶回写状态(none未推送/pushed已推送/approved审核通过/received已收款/failed失败)
|
||||
* 金蝶单据状态(A301-1 billstatus 映射:none未推送/pushed已推送/approved审核通过/received已收款/failed失败,同步时随单据刷新)
|
||||
*/
|
||||
private String kingdeeBillStatus;
|
||||
/**
|
||||
|
||||
+6
-33
@@ -71,9 +71,9 @@ public class BfiProperties {
|
||||
private ReceivedInvoice receivedInvoice = new ReceivedInvoice();
|
||||
|
||||
/**
|
||||
* 收款认领公告查询配置(A301-2)
|
||||
* 收款流水同步配置(A301-1 收款单查询)
|
||||
*/
|
||||
private ClaimAnnounce claimAnnounce = new ClaimAnnounce();
|
||||
private RecBillSync recBillSync = new RecBillSync();
|
||||
|
||||
/**
|
||||
* 付款结果定时回写配置(A103-2/A103-4,总开关同时控制应收勾稽、开票结果回写)
|
||||
@@ -85,11 +85,6 @@ public class BfiProperties {
|
||||
*/
|
||||
private ReceivedInvoiceResult receivedInvoiceResult = new ReceivedInvoiceResult();
|
||||
|
||||
/**
|
||||
* 收款认领推送配置(A301-3)
|
||||
*/
|
||||
private ReceiptClaimPush receiptClaimPush = new ReceiptClaimPush();
|
||||
|
||||
/**
|
||||
* 全票池发票定时同步配置(A801-1)
|
||||
*/
|
||||
@@ -197,20 +192,15 @@ public class BfiProperties {
|
||||
}
|
||||
|
||||
/**
|
||||
* 收款认领公告查询配置项(A301-2)
|
||||
* 收款流水同步配置项(A301-1 收款单查询)
|
||||
*/
|
||||
@Data
|
||||
public static class ClaimAnnounce {
|
||||
public static class RecBillSync {
|
||||
|
||||
/**
|
||||
* 查询时间窗口(月数),默认 6(近半年)
|
||||
* 同步业务日期窗口天数,默认 30(A301-1 契约约束业务日期范围不可超过 1 个月)
|
||||
*/
|
||||
private int months = 6;
|
||||
|
||||
/**
|
||||
* 认领人(claimusers):配置后公告查询固定使用该值,不再按当前登录用户姓名查询
|
||||
*/
|
||||
private String claimusers;
|
||||
private int days = 30;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,23 +261,6 @@ public class BfiProperties {
|
||||
private boolean syncEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收款认领推送配置项(A301-3)
|
||||
*/
|
||||
@Data
|
||||
public static class ReceiptClaimPush {
|
||||
|
||||
/**
|
||||
* 推送任务开关,默认关闭(联调稳定后开启)
|
||||
*/
|
||||
private boolean syncEnabled = false;
|
||||
|
||||
/**
|
||||
* 推送 single-flight 锁租约秒数,须大于最坏一轮耗时(逐认领人远程查公告 + 逐条推送)
|
||||
*/
|
||||
private long lockLeaseSeconds = 1800;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全票池发票同步配置项(A801-1)
|
||||
*/
|
||||
|
||||
+1
-10
@@ -35,7 +35,6 @@ import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.dto.ReceiptClaimPushResult;
|
||||
import org.springblade.transport.pojo.dto.ReceiptClaimRequest;
|
||||
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
|
||||
import org.springblade.transport.service.IReceiptFlowService;
|
||||
@@ -95,16 +94,8 @@ public class ReceiptFlowController extends BladeController {
|
||||
|
||||
@PostMapping("/sync")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "手动同步金蝶收款流水(A301-2 收款认领公告查询)")
|
||||
@Operation(summary = "手动同步金蝶收款流水(A301-1 收款单查询)")
|
||||
public R<Integer> sync() {
|
||||
return R.data(receiptFlowService.sync());
|
||||
}
|
||||
|
||||
@PostMapping("/push-kingdee")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "推送金蝶收款认领(A301-3 收款认领结果推送,仅认领完成流水可推送)")
|
||||
public R<ReceiptClaimPushResult> pushKingdee(@RequestParam Long flowId) {
|
||||
ReceiptClaimPushResult result = receiptFlowService.pushClaimToKingdee(flowId);
|
||||
return result.isSuccess() ? R.data(result) : R.fail(result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-18
@@ -27,7 +27,6 @@ package org.springblade.transport.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.transport.pojo.dto.ReceiptClaimPushResult;
|
||||
import org.springblade.transport.pojo.dto.ReceiptClaimRequest;
|
||||
import org.springblade.transport.pojo.entity.KingdeeReceiptFlow;
|
||||
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
|
||||
@@ -53,25 +52,9 @@ public interface IReceiptFlowService extends BaseService<KingdeeReceiptFlow> {
|
||||
Long claim(ReceiptClaimRequest request);
|
||||
|
||||
/**
|
||||
* 手动同步金蝶收款流水:直连 A301-2 收款认领公告查询,按明细流水号幂等 upsert 收款流水表
|
||||
* 手动同步金蝶收款流水:直连 A301-1 收款单查询(收款处理查询),按流水分录明细流水号幂等 upsert 收款流水表
|
||||
*
|
||||
* @return 本次同步条数
|
||||
*/
|
||||
int sync();
|
||||
|
||||
/**
|
||||
* 手动推送金蝶(A301-3 收款认领结果推送):认领完成且未推送过的流水才可推送,
|
||||
* 推送前以当前用户姓名重查 A301-2 最新公告对账。与定时任务共用同一推送实现。
|
||||
*
|
||||
* @param flowId 收款流水id
|
||||
* @return 推送结果(失败不抛异常,原因随结果返回)
|
||||
*/
|
||||
ReceiptClaimPushResult pushClaimToKingdee(Long flowId);
|
||||
|
||||
/**
|
||||
* 定时推送金蝶:扫描认领完成且未推送过的流水,按认领人姓名分组重查 A301-2 最新公告后逐条推送。
|
||||
*
|
||||
* @return 执行摘要
|
||||
*/
|
||||
String pushClaimByTask();
|
||||
}
|
||||
|
||||
+151
-452
@@ -38,12 +38,9 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiClaimAnnounceQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiClaimPushDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiClaimPushEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultItemVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiClaimAnnounceVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiRecBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiRecBillFlowVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiRecBillVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.thirdparty.bfi.util.BfiUtil;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
@@ -53,7 +50,6 @@ import org.springblade.transport.mapper.KingdeeReceiptFlowMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
|
||||
import org.springblade.transport.mapper.ReceiptFlowRecordMapper;
|
||||
import org.springblade.transport.pojo.dto.ReceiptClaimPushResult;
|
||||
import org.springblade.transport.pojo.dto.ReceiptClaimRequest;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.entity.KingdeeReceiptFlow;
|
||||
@@ -63,13 +59,13 @@ import org.springblade.transport.pojo.entity.ReceiptFlowRecord;
|
||||
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IReceiptFlowService;
|
||||
import org.springblade.transport.support.KingdeeSyncLockSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.hcs.HcsClaimSyncSupport;
|
||||
import org.springblade.transport.wrapper.ReceiptFlowWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -77,9 +73,8 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -109,18 +104,14 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
// 应收款
|
||||
private static final String RECEIVABLE = "receivable";
|
||||
/**
|
||||
* A301-2 认领公告同步分页大小
|
||||
* A301-1 收款单查询分页大小
|
||||
*/
|
||||
private static final int SYNC_PAGE_SIZE = 200;
|
||||
/**
|
||||
* A301-2 交易时间格式
|
||||
* A301-1 业务日期时间格式(请求参数与返回 bizdate 均为 yyyy-MM-dd HH:mm:ss)
|
||||
*/
|
||||
private static final DateTimeFormatter BFI_DATE_TIME_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
/**
|
||||
* 定时推送收款认领 single-flight 锁键前缀(多副本定时任务共用)
|
||||
*/
|
||||
private static final String RECEIPT_CLAIM_PUSH_LOCK_PREFIX = "transport:receipt-flow:kingdee-claim-push:";
|
||||
|
||||
private final ReceiptClaimMapper claimMapper;
|
||||
private final ReceiptClaimSettlementMapper claimSettlementMapper;
|
||||
@@ -129,8 +120,6 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
private final IFormalSettlementService formalSettlementService;
|
||||
private final IBfiService bfiService;
|
||||
private final BfiProperties bfiProperties;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
|
||||
private final HcsClaimSyncSupport hcsClaimSyncSupport;
|
||||
|
||||
@Override
|
||||
@@ -316,9 +305,6 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
|
||||
// 收款流水
|
||||
KingdeeReceiptFlow flow = lockedFlow(request.getFlowId());
|
||||
if (flow.getBillId() == null) {
|
||||
throw new ServiceException("认领公告id缺失,请先手动同步金蝶收款流水");
|
||||
}
|
||||
// 结算单id → 分摊金额
|
||||
Map<Long, BigDecimal> allocationMap = allocationMap(request.getSettlements());
|
||||
// 结算单id列表(升序)
|
||||
@@ -347,9 +333,9 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
allocatedTotal = allocatedTotal.add(allocated);
|
||||
}
|
||||
|
||||
// 认领上限:公告未认领金额与收款金额取较小值(避免未认领金额异常偏大导致无法认领完成)
|
||||
// 认领上限:未匹配收款金额(A301-1 unmatchamountrec)与收款金额取较小值(避免未匹配金额异常偏大导致无法认领完成)
|
||||
if (flow.getUnclaimedAmount() == null) {
|
||||
throw new ServiceException("公告未认领金额缺失,请先手动同步金蝶收款流水");
|
||||
throw new ServiceException("未匹配收款金额缺失,请先手动同步金蝶收款流水");
|
||||
}
|
||||
BigDecimal claimCeiling = claimCeiling(flow);
|
||||
if (claimCeiling.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
@@ -386,8 +372,9 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
claim.setRemark(trimToNull(request.getRemark()));
|
||||
// 认领状态
|
||||
claim.setClaimStatus(CLAIMED);
|
||||
// 金蝶回写状态(待推送)
|
||||
claim.setKingdeeBillStatus(KingdeeBfiConstant.RECEIPT_CLAIM_BILL_NONE);
|
||||
// 金蝶收款单号/状态(冗余展示:A301-1 同步落库的单据信息)
|
||||
claim.setKingdeeBillNo(flow.getKingdeeBillNo());
|
||||
claim.setKingdeeBillStatus(flow.getKingdeeBillStatus());
|
||||
claimMapper.insert(claim);
|
||||
|
||||
for (FormalSettlement settlement : settlements) {
|
||||
@@ -436,74 +423,83 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
record(flow.getId(), claim.getId(), "claim", "认领收款流水", fromStatus, toStatus,
|
||||
allocatedTotal, "关联" + settlements.size() + "张应收正式结算单");
|
||||
|
||||
// 认领只登记本地,不推送金蝶;推送由定时任务/手动按钮在认领完成后触发
|
||||
// 认领只登记本地,不推送金蝶;事务提交后按认领主单推送合同合规(C103-9/C107-1/C108-1,失败仅落标记)
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
hcsClaimSyncSupport.pushClaimToHcs(claim.getId());
|
||||
}
|
||||
});
|
||||
return claim.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int sync() {
|
||||
String claimusers = UserCache.getUserRealName(AuthUtil.getUserId());
|
||||
if (Func.isEmpty(claimusers)) {
|
||||
throw new ServiceException("当前用户无姓名,无法同步金蝶认领公告");
|
||||
}
|
||||
List<BfiClaimAnnounceVO> announces = queryClaimAnnounces(claimusers);
|
||||
List<BfiRecBillVO> recBills = queryRecBills();
|
||||
|
||||
if (announces.isEmpty()) {
|
||||
if (recBills.isEmpty()) {
|
||||
record(null, null, "sync", "手动同步流水", null, null, BigDecimal.ZERO,
|
||||
"金蝶认领公告未查询到数据");
|
||||
"金蝶收款单未查询到数据");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 同步口径:A301-2 按参与认领用户(当前登录用户真实姓名)拉取全量认领公告,
|
||||
// 同步口径:A301-1 按业务日期窗口拉取金蝶收款单(收款处理),按流水分录展开,
|
||||
// 以金蝶明细流水号为幂等键 upsert 收款流水表;本地已认领金额/状态(claimedAmount/claimStatus)
|
||||
// 由认领、作废驱动,不被公告顺带覆盖,公告侧状态单独存 kingdee_claim_status/unclaimed_amount。
|
||||
// 由认领、作废驱动,不被同步顺带覆盖,未匹配收款金额单独存 unclaimed_amount 作为认领上限。
|
||||
|
||||
// 第一遍:数据校验与过滤,收集有效的 detailSerialNo
|
||||
log.info("收款流水同步开始数据校验,共 {} 条认领公告", announces.size());
|
||||
Map<String, BfiClaimAnnounceVO> validAnnounces = new LinkedHashMap<>();
|
||||
// 第一遍:数据校验与过滤,主单+流水分录展开为流水行,收集有效的 detailSerialNo
|
||||
log.info("收款流水同步开始数据校验,共 {} 张收款单", recBills.size());
|
||||
Map<String, BfiRecBillRow> validRows = new LinkedHashMap<>();
|
||||
int skippedCount = 0;
|
||||
for (BfiClaimAnnounceVO announce : announces) {
|
||||
// 明细流水号优先级:detailid > tradedetailno > bankdetailno
|
||||
String detailSerialNo = firstNonBlank(announce.getDetailid(),
|
||||
announce.getTradedetailno(), announce.getBankdetailno());
|
||||
if (Func.isEmpty(detailSerialNo)) {
|
||||
log.warn("收款流水同步跳过:认领公告[{}]缺少明细流水号", announce.getBillno());
|
||||
for (BfiRecBillVO recBill : recBills) {
|
||||
List<BfiRecBillFlowVO> flowEntries = recBill.getInfoentry();
|
||||
if (Func.isEmpty(flowEntries)) {
|
||||
log.warn("收款流水同步跳过:收款单[{}]无流水分录", recBill.getBillno());
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
detailSerialNo = detailSerialNo.trim();
|
||||
if (validAnnounces.containsKey(detailSerialNo)) {
|
||||
throw new ServiceException("明细流水号" + detailSerialNo + "重复");
|
||||
for (BfiRecBillFlowVO flowEntry : flowEntries) {
|
||||
// 明细流水号优先级:bei_detailid > bei_bankdetailno
|
||||
String detailSerialNo = firstNonBlank(flowEntry.getBeiDetailid(), flowEntry.getBeiBankdetailno());
|
||||
if (Func.isEmpty(detailSerialNo)) {
|
||||
log.warn("收款流水同步跳过:收款单[{}]流水分录缺少明细流水号", recBill.getBillno());
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
detailSerialNo = detailSerialNo.trim();
|
||||
if (validRows.containsKey(detailSerialNo)) {
|
||||
throw new ServiceException("明细流水号" + detailSerialNo + "重复");
|
||||
}
|
||||
BigDecimal receiptAmount = parseEntryAmount(flowEntry.getBeiCreditamount(), recBill.getActrecamt());
|
||||
if (receiptAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.warn("收款流水同步跳过:收款单[{}]流水分录收款金额为空或小于0", recBill.getBillno());
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
validRows.put(detailSerialNo, new BfiRecBillRow(recBill, flowEntry, detailSerialNo, receiptAmount));
|
||||
}
|
||||
BigDecimal receiptAmount = money(announce.getReamount());
|
||||
if (receiptAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.warn("收款流水同步跳过:认领公告[{}]收款金额为空或小于0", announce.getBillno());
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
validAnnounces.put(detailSerialNo, announce);
|
||||
}
|
||||
log.info("收款流水数据校验完成:有效 {} 条,跳过 {} 条", validAnnounces.size(), skippedCount);
|
||||
log.info("收款流水数据校验完成:有效 {} 条,跳过 {} 条", validRows.size(), skippedCount);
|
||||
|
||||
if (validAnnounces.isEmpty()) {
|
||||
if (validRows.isEmpty()) {
|
||||
log.info("收款流水同步无有效数据,记录操作日志后返回");
|
||||
record(null, null, "sync", "手动同步流水", null, null, BigDecimal.ZERO,
|
||||
"金蝶认领公告无有效数据");
|
||||
"金蝶收款单无有效数据");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 批量查询已存在的记录(FOR UPDATE 防并发)
|
||||
log.info("收款流水同步开始批量查询已存在记录,查询条件:detailSerialNo IN {} 条", validAnnounces.size());
|
||||
log.info("收款流水同步开始批量查询已存在记录,查询条件:detailSerialNo IN {} 条", validRows.size());
|
||||
List<KingdeeReceiptFlow> existingEntities = baseMapper.selectList(
|
||||
Wrappers.<KingdeeReceiptFlow>lambdaQuery()
|
||||
.in(KingdeeReceiptFlow::getDetailSerialNo, validAnnounces.keySet())
|
||||
.in(KingdeeReceiptFlow::getDetailSerialNo, validRows.keySet())
|
||||
.last("FOR UPDATE")
|
||||
);
|
||||
Map<String, KingdeeReceiptFlow> existingMap = existingEntities.stream()
|
||||
.collect(Collectors.toMap(KingdeeReceiptFlow::getDetailSerialNo, e -> e));
|
||||
log.info("收款流水批量查询完成:已存在 {} 条,待新增 {} 条",
|
||||
existingEntities.size(), validAnnounces.size() - existingEntities.size());
|
||||
existingEntities.size(), validRows.size() - existingEntities.size());
|
||||
|
||||
// 第二遍:构建新增和更新列表
|
||||
log.info("收款流水同步开始构建新增/更新列表");
|
||||
@@ -512,10 +508,9 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
int createdCount = 0;
|
||||
int updatedCount = 0;
|
||||
|
||||
for (Map.Entry<String, BfiClaimAnnounceVO> entry : validAnnounces.entrySet()) {
|
||||
for (Map.Entry<String, BfiRecBillRow> entry : validRows.entrySet()) {
|
||||
String detailSerialNo = entry.getKey();
|
||||
BfiClaimAnnounceVO announce = entry.getValue();
|
||||
BigDecimal receiptAmount = money(announce.getReamount());
|
||||
BfiRecBillRow row = entry.getValue();
|
||||
|
||||
// 判断是新增还是更新
|
||||
KingdeeReceiptFlow existing = existingMap.get(detailSerialNo);
|
||||
@@ -532,8 +527,8 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
entity = existing;
|
||||
}
|
||||
|
||||
// 复制公告数据到实体(不覆盖本地认领字段)
|
||||
copyAnnounceRow(entity, announce, detailSerialNo, receiptAmount);
|
||||
// 复制收款单数据到实体(不覆盖本地认领字段)
|
||||
copyRecBillRow(entity, row);
|
||||
// 根据本地累计认领金额与可认领上限重新计算认领状态
|
||||
entity.setClaimStatus(amountClaimStatus(money(entity.getClaimedAmount()),
|
||||
claimCeiling(entity)));
|
||||
@@ -576,11 +571,18 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
}
|
||||
log.info("收款流水操作日志记录完成");
|
||||
|
||||
log.info("金蝶收款流水同步完成 claimusers:{},拉取 {} 条,新增 {} 条,更新 {} 条",
|
||||
claimusers, announces.size(), createdCount, updatedCount);
|
||||
log.info("金蝶收款流水同步完成,拉取收款单 {} 张,新增 {} 条,更新 {} 条",
|
||||
recBills.size(), createdCount, updatedCount);
|
||||
return createdCount + updatedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* A301-1 收款单查询返回的收款单 + 流水分录展开行(同步过程传递用)
|
||||
*/
|
||||
private record BfiRecBillRow(BfiRecBillVO recBill, BfiRecBillFlowVO flowEntry,
|
||||
String detailSerialNo, BigDecimal receiptAmount) {
|
||||
}
|
||||
|
||||
private Map<String, Object> candidateRow(FormalSettlement settlement, BigDecimal claimedAmount) {
|
||||
BigDecimal settlementAmount = money(settlement.getSettlementAmount());
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
@@ -724,8 +726,8 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
}
|
||||
|
||||
/**
|
||||
* 可认领上限:公告未认领金额与收款金额取较小正值。
|
||||
* 主口径仍为 A301-2 公告未认领金额;与收款金额取 min,避免未认领金额异常偏大导致无法进入「认领完成」。
|
||||
* 可认领上限:未匹配收款金额(A301-1 unmatchamountrec)与收款金额取较小正值。
|
||||
* 主口径为金蝶侧未匹配收款金额;与收款金额取 min,避免未匹配金额异常偏大导致无法进入「认领完成」。
|
||||
*/
|
||||
private BigDecimal claimCeiling(KingdeeReceiptFlow flow) {
|
||||
BigDecimal unclaimed = money(flow.getUnclaimedAmount());
|
||||
@@ -774,396 +776,100 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动推送金蝶(A301-3 收款认领结果推送)
|
||||
* <p>
|
||||
* 仅认领完成且未推送过的流水可推送;推送前以当前用户姓名重查 A301-2 最新公告对账。
|
||||
* 推送失败不抛异常跨事务边界(保证失败状态落库),原因随结果对象返回。
|
||||
* A301-1 收款单查询:业务日期窗口全量翻页,窗口天数取 Nacos 配置(默认近30天,契约约束 ≤1 个月)。
|
||||
* 收款流水同步专用。
|
||||
*/
|
||||
@Override
|
||||
public ReceiptClaimPushResult pushClaimToKingdee(Long flowId) {
|
||||
if (flowId == null) {
|
||||
throw new ServiceException("收款流水ID不能为空");
|
||||
}
|
||||
KingdeeReceiptFlow flow = existing(flowId);
|
||||
if (flow.getBillId() == null) {
|
||||
return ReceiptClaimPushResult.fail("认领公告id缺失,请先手动同步金蝶收款流水");
|
||||
}
|
||||
String claimusers = UserCache.getUserRealName(AuthUtil.getUserId());
|
||||
if (Func.isEmpty(claimusers)) {
|
||||
throw new ServiceException("当前用户无姓名,无法推送金蝶收款认领");
|
||||
}
|
||||
// 推送前对账:以当前用户身份重查 A301-2 拿最新公告(手动推送者须在公告认领范围内)
|
||||
BfiClaimAnnounceVO announce = queryClaimAnnounces(claimusers).stream()
|
||||
.filter(item -> flow.getBillId().equals(item.getId()))
|
||||
.findFirst().orElse(null);
|
||||
if (announce == null) {
|
||||
return ReceiptClaimPushResult.fail("金蝶侧未查询到该认领公告,当前用户可能不在公告认领范围内,请重新同步后重试");
|
||||
}
|
||||
return pushClaimLocked(flow.getId(), announce);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时推送金蝶:扫描认领完成且未推送过的流水,按认领人姓名分组重查 A301-2 最新公告后逐条推送。
|
||||
* 多副本部署下由 single-flight 分布式锁保证同一轮全局最多一份在跑,已有轮次进行中则本轮跳过。
|
||||
*
|
||||
* @return 执行摘要
|
||||
*/
|
||||
@Override
|
||||
public String pushClaimByTask() {
|
||||
return kingdeeSyncLockSupport.runSingleFlight(RECEIPT_CLAIM_PUSH_LOCK_PREFIX, bfiProperties.getReceiptClaimPush().getLockLeaseSeconds(),
|
||||
"定时推送收款认领", this::executeReceiptClaimPush);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时推送收款认领主体:按认领人姓名串行处理——
|
||||
* 先重查该认领人的 A301-2 最新公告并推完其名下全部流水,再到下一个认领人。
|
||||
* A301-2 的 claimusers 必填且为单个用户姓名,定时任务无登录人上下文,
|
||||
* 故按认领人逐人查询公告,按公告id(bill_id)匹配流水。
|
||||
* 单条流水推送失败独立消化,不影响同批其他流水。
|
||||
*
|
||||
* @return 执行摘要
|
||||
*/
|
||||
private String executeReceiptClaimPush() {
|
||||
List<KingdeeReceiptFlow> candidates = list(Wrappers.<KingdeeReceiptFlow>lambdaQuery()
|
||||
.isNotNull(KingdeeReceiptFlow::getBillId)
|
||||
.in(KingdeeReceiptFlow::getPushStatus, List.of(
|
||||
KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_NONE, KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_FAILED))
|
||||
.eq(KingdeeReceiptFlow::getClaimStatus, CLAIMED)
|
||||
.eq(KingdeeReceiptFlow::getStatus, 1)
|
||||
.orderByAsc(KingdeeReceiptFlow::getId));
|
||||
if (candidates.isEmpty()) {
|
||||
return "无待推送流水";
|
||||
}
|
||||
Map<Long, KingdeeReceiptFlow> candidateById = candidates.stream()
|
||||
.collect(Collectors.toMap(KingdeeReceiptFlow::getId, Function.identity(),
|
||||
(first, second) -> first, LinkedHashMap::new));
|
||||
// 认领人 → 其有效认领的流水id
|
||||
Map<String, Set<Long>> claimedFlowIdsByClaimer = claimMapper.selectList(
|
||||
Wrappers.<ReceiptClaim>lambdaQuery()
|
||||
.in(ReceiptClaim::getReceiptFlowId, candidateById.keySet())
|
||||
.eq(ReceiptClaim::getClaimStatus, CLAIMED)).stream()
|
||||
.filter(claim -> Func.isNotEmpty(claim.getClaimerName()) && claim.getReceiptFlowId() != null)
|
||||
.collect(Collectors.groupingBy(ReceiptClaim::getClaimerName,
|
||||
Collectors.mapping(ReceiptClaim::getReceiptFlowId, Collectors.toSet())));
|
||||
if (claimedFlowIdsByClaimer.isEmpty()) {
|
||||
return "待推送流水 " + candidates.size() + " 条,但无有效认领人,跳过";
|
||||
}
|
||||
int successCount = 0;
|
||||
int failedCount = 0;
|
||||
int skippedCount = 0;
|
||||
// 本轮已推送成功的流水:同一流水被多个认领人认领时,只在首个覆盖它的认领人处推送一次
|
||||
Set<Long> pushedFlowIds = new HashSet<>();
|
||||
// 按认领人逐人串行:查该认领人公告 → 推完其名下流水 → 下一个认领人
|
||||
for (String claimerName : claimedFlowIdsByClaimer.keySet().stream().sorted().toList()) {
|
||||
Set<Long> claimerFlowIds = claimedFlowIdsByClaimer.get(claimerName);
|
||||
List<KingdeeReceiptFlow> claimerFlows = candidateById.values().stream()
|
||||
.filter(flow -> claimerFlowIds.contains(flow.getId()))
|
||||
.filter(flow -> !pushedFlowIds.contains(flow.getId()))
|
||||
.toList();
|
||||
if (claimerFlows.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// 该认领人的公告一次查询,按公告id归集
|
||||
Map<Long, BfiClaimAnnounceVO> announceByBillId = new HashMap<>();
|
||||
try {
|
||||
for (BfiClaimAnnounceVO announce : queryClaimAnnounces(claimerName)) {
|
||||
if (announce.getId() != null) {
|
||||
announceByBillId.putIfAbsent(announce.getId(), announce);
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
log.error("定时推送收款认领:认领人[{}]公告查询失败,其名下流水全部跳过", claimerName, exception);
|
||||
skippedCount += claimerFlows.size();
|
||||
continue;
|
||||
}
|
||||
for (KingdeeReceiptFlow flow : claimerFlows) {
|
||||
BfiClaimAnnounceVO announce = announceByBillId.get(flow.getBillId());
|
||||
if (announce == null) {
|
||||
skippedCount++;
|
||||
log.info("定时推送收款认领:认领人[{}]流水[{}]未在公告查询结果中命中,跳过",
|
||||
claimerName, flow.getId());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ReceiptClaimPushResult result = pushClaimLocked(flow.getId(), announce);
|
||||
if (result.isSuccess()) {
|
||||
successCount++;
|
||||
pushedFlowIds.add(flow.getId());
|
||||
} else {
|
||||
failedCount++;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
failedCount++;
|
||||
log.error("定时推送收款认领:认领人[{}]流水[{}]推送异常", claimerName, flow.getId(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
String summary = "扫描 " + candidates.size() + " 条,推送成功 " + successCount
|
||||
+ " 条,推送失败 " + failedCount + " 条,公告未命中跳过 " + skippedCount + " 条";
|
||||
log.info("定时推送收款认领完成:{}", summary);
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务内推送:流水行锁覆盖闸门校验、对账、推送与状态落库全过程,
|
||||
* 防定时任务与手动按钮并发对同一流水双推;
|
||||
* 推送成功后触发合同合规认领链推送(C103-9/C107-1/C108-1,事务提交后执行,失败不影响金蝶推送结果)
|
||||
*/
|
||||
private ReceiptClaimPushResult pushClaimLocked(Long flowId, BfiClaimAnnounceVO announce) {
|
||||
ReceiptClaimPushResult result = transactionTemplate.execute(status -> doPushClaimToKingdee(flowId, announce));
|
||||
if (result != null && result.isSuccess()) {
|
||||
hcsClaimSyncSupport.pushClaimToHcs(flowId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送单条流水:静态闸门 → 最新公告对账 → 按流水归集分摊行推送 → 成败状态落库
|
||||
*/
|
||||
private ReceiptClaimPushResult doPushClaimToKingdee(Long flowId, BfiClaimAnnounceVO announce) {
|
||||
KingdeeReceiptFlow flow = lockedFlow(flowId);
|
||||
if (flow.getBillId() == null) {
|
||||
return ReceiptClaimPushResult.fail("认领公告id缺失,请先手动同步金蝶收款流水");
|
||||
}
|
||||
// 终态闸门:已推送流水不允许二次推送(作废重认领使等式再满足也不推,金蝶侧人工处置)
|
||||
if (KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_PUSHED.equals(flow.getPushStatus())) {
|
||||
return ReceiptClaimPushResult.fail("该流水已推送金蝶,不允许重复推送");
|
||||
}
|
||||
// 推送前对账:以最新公告刷新公告侧未认领金额并重算认领状态(快照落库,页面口径同步修正)
|
||||
flow.setUnclaimedAmount(money(announce.getUnclaimamount()));
|
||||
flow.setKingdeeClaimStatus(trimToNull(announce.getClaimstatus()));
|
||||
flow.setClaimMe(announce.getClaimme());
|
||||
BigDecimal claimedTotal = flowClaimedAmount(flow.getId());
|
||||
flow.setClaimedAmount(claimedTotal);
|
||||
BigDecimal claimCeiling = claimCeiling(flow);
|
||||
String claimStatus = amountClaimStatus(claimedTotal, claimCeiling);
|
||||
flow.setClaimStatus(claimStatus);
|
||||
updateById(flow);
|
||||
if (!CLAIMED.equals(claimStatus)) {
|
||||
return ReceiptClaimPushResult.fail("累计认领金额(" + claimedTotal + ")与可认领上限("
|
||||
+ claimCeiling + ")不一致,不满足推送条件");
|
||||
}
|
||||
// 分录取该流水全部有效分摊行(结算单id升序,跨认领主单归集)
|
||||
List<ReceiptClaimSettlement> allocations = claimSettlementMapper.selectList(
|
||||
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
|
||||
.eq(ReceiptClaimSettlement::getReceiptFlowId, flow.getId())
|
||||
.eq(ReceiptClaimSettlement::getStatus, 1)
|
||||
.eq(ReceiptClaimSettlement::getIsDeleted, 0)
|
||||
.orderByAsc(ReceiptClaimSettlement::getFormalSettlementId));
|
||||
if (allocations.isEmpty()) {
|
||||
return ReceiptClaimPushResult.fail("该流水无有效认领分摊,无法推送");
|
||||
}
|
||||
// 核心单据编号取第一张结算单同步金蝶回写的星瀚内码(应收/应付单金蝶id),TMS本地id金蝶无法识别
|
||||
FormalSettlement firstSettlement = formalSettlementMapper.selectById(allocations.get(0).getFormalSettlementId());
|
||||
if (firstSettlement == null || Func.isBlank(firstSettlement.getKingdeeBillId())) {
|
||||
return ReceiptClaimPushResult.fail("结算单" + allocations.get(0).getFormalSettlementNo()
|
||||
+ "未同步金蝶(缺少金蝶单据id),不满足推送条件");
|
||||
}
|
||||
// 推送来源编码:分摊结算单号下划线拼接(A301-3 契约口径;分摊不变则重试编码不变,
|
||||
// 金蝶按 awrp_src_sys + awrp_src_code 幂等防重试双单)
|
||||
String awrpSrcCode = allocations.stream()
|
||||
.map(ReceiptClaimSettlement::getFormalSettlementNo)
|
||||
.filter(Func::isNotEmpty)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("_"));
|
||||
flow.setAwrpSrcCode(awrpSrcCode);
|
||||
|
||||
BfiClaimPushDTO param = buildClaimPushDTO(flow, claimedTotal, allocations, firstSettlement.getKingdeeBillId());
|
||||
log.info("调用BFI接口 收款认领结果推送开始 flowId:{},billId:{},awrpSrcCode:{},分摊明细 {} 行,认领金额 {}",
|
||||
flow.getId(), flow.getBillId(), flow.getAwrpSrcCode(), allocations.size(), claimedTotal);
|
||||
try {
|
||||
BfiBillSaveResultVO result = bfiService.pushClaimResult(param);
|
||||
return applyClaimPushSuccess(flow, extractClaimPushBillNo(result));
|
||||
} catch (Exception exception) {
|
||||
return applyClaimPushFailure(flow, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A301-3 推送报文组装:billid=公告id,单条分录、金额=全部未推送金额(一笔流水一张金蝶收款单),
|
||||
* 核心单据编号=第一张结算单的金蝶单据id(星瀚内码)
|
||||
*/
|
||||
private BfiClaimPushDTO buildClaimPushDTO(KingdeeReceiptFlow flow, BigDecimal claimedTotal,
|
||||
List<ReceiptClaimSettlement> allocations, String coreBillId) {
|
||||
BfiClaimPushDTO param = new BfiClaimPushDTO();
|
||||
// 【必填】认领公告id
|
||||
param.setBillid(flow.getBillId());
|
||||
// 【必填】来源系统编码
|
||||
param.setAwrpSrcSys(bfiProperties.getSrcSys());
|
||||
// 【必填】来源系统单据编码(分摊结算单号下划线拼接)
|
||||
param.setAwrpSrcCode(flow.getAwrpSrcCode());
|
||||
// 【必填】收款类型.编码
|
||||
param.setRecpaytypeNumber(KingdeeBfiConstant.BFI_CLAIM_REC_PAY_TYPE);
|
||||
// 【必填】付款人类型
|
||||
param.setPaymenttype(KingdeeBfiConstant.BFI_CLAIM_PAYMENT_TYPE);
|
||||
// 【必填】付款人名称
|
||||
param.setRecviewpayer(flow.getPayerName());
|
||||
// 【必填】付款人id
|
||||
param.setRecpayer(flow.getPayerId());
|
||||
// 【必填】单据体分录:仅一条,应收金额=全部未推送金额(累计有效认领)
|
||||
BfiClaimPushEntryDTO entry = new BfiClaimPushEntryDTO();
|
||||
// 【必填】应收金额(全部未推送金额)
|
||||
entry.setEReceivableamt(claimedTotal);
|
||||
// 分录行序号(第一张结算单id,分摊已按结算单id升序,多张取最小)
|
||||
entry.setSeq(allocations.get(0).getFormalSettlementId());
|
||||
// 核心单据类型
|
||||
// entry.setECorebilltype(KingdeeBfiConstant.BFI_CLAIM_CORE_BILL_TYPE);
|
||||
// 核心单据编号(第一张结算单的金蝶单据id,星瀚内码)
|
||||
entry.setECorebillno(coreBillId);
|
||||
param.setEntryentity(List.of(entry));
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 A301-3 推送结果:全项成功返回单据号,否则抛出汇总错误文本(由失败路径落库)。
|
||||
* 成功响应的 number 为空,金蝶收款单号取回执 keys 中的 awrp_src_code(来源编码)。
|
||||
*/
|
||||
private String extractClaimPushBillNo(BfiBillSaveResultVO result) {
|
||||
if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
|
||||
throw new ServiceException("金蝶认领结果推送失败,返回结果为空");
|
||||
}
|
||||
List<BfiBillSaveResultItemVO> items = result.getResult();
|
||||
boolean allSuccess = items.stream().allMatch(item -> Boolean.TRUE.equals(item.getBillStatus()));
|
||||
if (!allSuccess) {
|
||||
String errorText = items.stream()
|
||||
.map(BfiBillSaveResultItemVO::getErrorText)
|
||||
.filter(text -> !text.isEmpty())
|
||||
.collect(Collectors.joining(";"));
|
||||
throw new ServiceException("金蝶认领结果推送失败"
|
||||
+ (errorText.isEmpty() ? "" : ":" + errorText));
|
||||
}
|
||||
return items.stream()
|
||||
.map(this::claimPushBillNoOf)
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单项推送结果 → 单据号:A301-3 成功响应 number 为空,取回执键值中的 awrp_src_code
|
||||
*/
|
||||
private String claimPushBillNoOf(BfiBillSaveResultItemVO item) {
|
||||
if (Func.isNotEmpty(item.getNumber())) {
|
||||
return item.getNumber();
|
||||
}
|
||||
return item.getKeys() == null ? null : item.getKeys().get("awrp_src_code");
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送成功:流水置已推送并回填金蝶收款单号,级联刷新名下未作废认领主单的冗余展示字段
|
||||
*/
|
||||
private ReceiptClaimPushResult applyClaimPushSuccess(KingdeeReceiptFlow flow, String kingdeeBillNo) {
|
||||
log.info("调用BFI接口 收款认领结果推送成功 flowId:{},金蝶收款单号:{}",
|
||||
flow.getId(), Func.isEmpty(kingdeeBillNo) ? "响应未返回" : kingdeeBillNo);
|
||||
flow.setPushStatus(KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_PUSHED);
|
||||
flow.setPushedTime(LocalDateTime.now());
|
||||
flow.setKingdeeBillNo(kingdeeBillNo);
|
||||
flow.setKingdeeBillStatus(KingdeeBfiConstant.KINGDEE_STATUS_PUSHED);
|
||||
// 失败原因需显式置空(实体 update 忽略 null 字段,走 lambdaUpdate)
|
||||
this.update(Wrappers.<KingdeeReceiptFlow>lambdaUpdate()
|
||||
.eq(KingdeeReceiptFlow::getId, flow.getId())
|
||||
.set(KingdeeReceiptFlow::getPushStatus, flow.getPushStatus())
|
||||
.set(KingdeeReceiptFlow::getPushFailReason, null)
|
||||
.set(KingdeeReceiptFlow::getPushedTime, flow.getPushedTime())
|
||||
.set(KingdeeReceiptFlow::getAwrpSrcCode, flow.getAwrpSrcCode())
|
||||
.set(KingdeeReceiptFlow::getKingdeeBillNo, kingdeeBillNo)
|
||||
.set(KingdeeReceiptFlow::getKingdeeBillStatus, flow.getKingdeeBillStatus()));
|
||||
// 级联刷新名下未作废认领主单的冗余展示字段
|
||||
claimMapper.update(null, Wrappers.<ReceiptClaim>lambdaUpdate()
|
||||
.eq(ReceiptClaim::getReceiptFlowId, flow.getId())
|
||||
.eq(ReceiptClaim::getClaimStatus, CLAIMED)
|
||||
.set(ReceiptClaim::getKingdeeBillNo, kingdeeBillNo)
|
||||
.set(ReceiptClaim::getKingdeeBillStatus, KingdeeBfiConstant.KINGDEE_STATUS_PUSHED));
|
||||
record(flow.getId(), null, "push", "推送金蝶收款认领", CLAIMED, CLAIMED, flow.getClaimedAmount(),
|
||||
"金蝶收款单号:" + (Func.isEmpty(kingdeeBillNo) ? "响应未返回" : kingdeeBillNo));
|
||||
return ReceiptClaimPushResult.success(kingdeeBillNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送失败:流水置推送失败并记录原因,本地认领数据保留,可重新推送(重试复用同一来源编码)
|
||||
*/
|
||||
private ReceiptClaimPushResult applyClaimPushFailure(KingdeeReceiptFlow flow, Exception exception) {
|
||||
String reason = Func.isEmpty(exception.getMessage())
|
||||
? exception.getClass().getSimpleName() : exception.getMessage();
|
||||
reason = truncateText(reason, 1000);
|
||||
log.error("调用BFI接口 收款认领结果推送失败 flowId:{},billId:{},awrpSrcCode:{}",
|
||||
flow.getId(), flow.getBillId(), flow.getAwrpSrcCode(), exception);
|
||||
this.update(Wrappers.<KingdeeReceiptFlow>lambdaUpdate()
|
||||
.eq(KingdeeReceiptFlow::getId, flow.getId())
|
||||
.set(KingdeeReceiptFlow::getPushStatus, KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_FAILED)
|
||||
.set(KingdeeReceiptFlow::getPushFailReason, reason)
|
||||
.set(KingdeeReceiptFlow::getAwrpSrcCode, flow.getAwrpSrcCode()));
|
||||
record(flow.getId(), null, "push", "推送金蝶收款认领", CLAIMED, CLAIMED, flow.getClaimedAmount(),
|
||||
"推送失败:" + truncateText(reason, 200));
|
||||
return ReceiptClaimPushResult.fail("金蝶认领结果推送失败:" + reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* A301-2 收款认领公告查询:按认领人姓名全量翻页,时间窗口取 Nacos 配置(默认近半年)。
|
||||
* 认领公告同步、手动推送对账与定时任务对账共用。
|
||||
*/
|
||||
private List<BfiClaimAnnounceVO> queryClaimAnnounces(String claimusers) {
|
||||
int months = bfiProperties.getClaimAnnounce().getMonths();
|
||||
// 认领人直接取 Nacos 配置值
|
||||
String effectiveClaimusers = bfiProperties.getClaimAnnounce().getClaimusers();
|
||||
LocalDateTime endDate = LocalDateTime.now();
|
||||
LocalDateTime startDate = endDate.minusMonths(months);
|
||||
private List<BfiRecBillVO> queryRecBills() {
|
||||
int days = bfiProperties.getRecBillSync().getDays();
|
||||
LocalDate endDate = LocalDate.now();
|
||||
LocalDate startDate = endDate.minusDays(days);
|
||||
// 金蝶 A301-1 仅接受带时分秒的日期参数,传纯日期会报 bos.filterSqlBuilder 时区时间格式错误
|
||||
String beginBizdate = startDate.atStartOfDay().format(BFI_DATE_TIME_FORMATTER);
|
||||
String endBizdate = endDate.atTime(23, 59, 59).format(BFI_DATE_TIME_FORMATTER);
|
||||
long start = System.currentTimeMillis();
|
||||
log.info("调用BFI接口 收款认领公告查询开始 claimusers:{},时间窗口:{} ~ {}(近{}个月)",
|
||||
effectiveClaimusers, startDate.format(BFI_DATE_TIME_FORMATTER), endDate.format(BFI_DATE_TIME_FORMATTER), months);
|
||||
BfiClaimAnnounceQueryDTO queryDTO = new BfiClaimAnnounceQueryDTO();
|
||||
queryDTO.setClaimusers(effectiveClaimusers);
|
||||
queryDTO.setModifytimeS(startDate.format(BFI_DATE_TIME_FORMATTER));
|
||||
queryDTO.setModifytimeE(endDate.format(BFI_DATE_TIME_FORMATTER));
|
||||
log.info("调用BFI接口 收款单查询开始,业务日期窗口:{} ~ {}(近{}天)", beginBizdate, endBizdate, days);
|
||||
BfiRecBillQueryDTO queryDTO = new BfiRecBillQueryDTO();
|
||||
queryDTO.setBeginBizdate(beginBizdate);
|
||||
queryDTO.setEndBizdate(endBizdate);
|
||||
queryDTO.setPageSize(SYNC_PAGE_SIZE);
|
||||
List<BfiClaimAnnounceVO> announces = BfiUtil.queryAllPages(pageNo -> {
|
||||
List<BfiRecBillVO> recBills = BfiUtil.queryAllPages(pageNo -> {
|
||||
queryDTO.setPageNo(pageNo);
|
||||
return bfiService.queryClaimAnnounce(queryDTO);
|
||||
return bfiService.queryRecBill(queryDTO);
|
||||
});
|
||||
log.info("调用BFI接口 收款认领公告查询结束 claimusers:{},耗时:{}ms,拉取 {} 条",
|
||||
effectiveClaimusers, System.currentTimeMillis() - start, announces.size());
|
||||
return announces;
|
||||
log.info("调用BFI接口 收款单查询结束 耗时:{}ms,拉取 {} 条", System.currentTimeMillis() - start, recBills.size());
|
||||
return recBills;
|
||||
}
|
||||
|
||||
/**
|
||||
* A301-2 认领公告 → 收款流水表字段拷贝(仅覆盖金蝶侧来源字段,不覆盖本地认领金额与状态)
|
||||
* A301-1 收款单 → 收款流水表字段拷贝(仅覆盖金蝶侧来源字段,不覆盖本地认领金额与状态)。
|
||||
* 认领上限口径:未匹配收款金额 unmatchamountrec 存 unclaimed_amount。
|
||||
*/
|
||||
private void copyAnnounceRow(KingdeeReceiptFlow target, BfiClaimAnnounceVO source,
|
||||
String detailSerialNo, BigDecimal receiptAmount) {
|
||||
// 金蝶认领公告id
|
||||
target.setBillId(source.getId());
|
||||
// 收款通知单号
|
||||
private void copyRecBillRow(KingdeeReceiptFlow target, BfiRecBillRow row) {
|
||||
BfiRecBillVO source = row.recBill();
|
||||
// 金蝶收款单号(既是收款通知单号也是金蝶单据号)
|
||||
target.setReceiptNoticeNo(trimToEmpty(source.getBillno()));
|
||||
// 付款人名称(取自对方户名)
|
||||
target.setPayerName(trimToEmpty(source.getOppunit()));
|
||||
// 付款人id(取自对方账号)
|
||||
target.setPayerId(trimToEmpty(source.getOppbanknumber()));
|
||||
// 收款人
|
||||
target.setPayeeName(trimToNull(source.getRecpayee()));
|
||||
// 收款金额
|
||||
target.setReceiptAmount(receiptAmount);
|
||||
// 对方单位名称
|
||||
target.setCounterpartyName(trimToEmpty(source.getOppunit()));
|
||||
// 对方银行账号
|
||||
target.setCounterpartyAccount(trimToEmpty(source.getOppbanknumber()));
|
||||
// 对方开户行
|
||||
target.setCounterpartyBank(trimToEmpty(source.getOppbank()));
|
||||
target.setKingdeeBillNo(trimToNull(source.getBillno()));
|
||||
// 金蝶回写状态随单据状态同步刷新
|
||||
target.setKingdeeBillStatus(billStatusOf(source.getBillstatus()));
|
||||
// 付款人名称
|
||||
target.setPayerName(trimToEmpty(source.getPayername()));
|
||||
// 收款人(收款公司名称)
|
||||
target.setPayeeName(trimToNull(source.getOrgName()));
|
||||
// 收款金额(流水分录金额)
|
||||
target.setReceiptAmount(row.receiptAmount());
|
||||
// 对方单位名称(取付款人名称)
|
||||
target.setCounterpartyName(trimToEmpty(source.getPayername()));
|
||||
// 对方银行账号(付款账户)
|
||||
target.setCounterpartyAccount(trimToEmpty(source.getPayeracctbanknum()));
|
||||
// 对方开户行(付款银行)
|
||||
target.setCounterpartyBank(trimToEmpty(source.getPayerbankname()));
|
||||
// 摘要
|
||||
target.setSummary(trimToNull(source.getDescription()));
|
||||
// 交易日期
|
||||
target.setTransactionTime(parseDateTime(source.getTradetime()));
|
||||
target.setSummary(trimToNull(source.getTxtDescription()));
|
||||
// 交易日期(业务日期)
|
||||
target.setTransactionTime(parseDateTime(source.getBizdate()));
|
||||
// 明细流水号
|
||||
target.setDetailSerialNo(detailSerialNo);
|
||||
// 未认领金额
|
||||
target.setUnclaimedAmount(money(source.getUnclaimamount()));
|
||||
target.setDetailSerialNo(row.detailSerialNo());
|
||||
// 未匹配收款金额(认领上限口径)
|
||||
target.setUnclaimedAmount(money(source.getUnmatchamountrec()));
|
||||
// 手续费
|
||||
target.setFee(source.getFee() == null ? null : source.getFee().setScale(2, RoundingMode.HALF_UP));
|
||||
// 公告认领状态
|
||||
target.setKingdeeClaimStatus(trimToNull(source.getClaimstatus()));
|
||||
// 我已认领
|
||||
target.setClaimMe(source.getClaimme());
|
||||
// 来源更新时间
|
||||
target.setSourceUpdatedTime(target.getTransactionTime() == null
|
||||
? LocalDateTime.now() : target.getTransactionTime());
|
||||
target.setSourceUpdatedTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* A301-1 单据状态 → 本地金蝶回写状态(同步时随单据刷新)
|
||||
*
|
||||
* @param billStatus 金蝶 billstatus(A暂存/B已提交/C已审核/D已收款/G已退单)
|
||||
* @return 本地状态
|
||||
*/
|
||||
private String billStatusOf(String billStatus) {
|
||||
if ("D".equals(billStatus)) {
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_RECEIVED;
|
||||
}
|
||||
if ("C".equals(billStatus)) {
|
||||
return APPROVED;
|
||||
}
|
||||
if ("A".equals(billStatus) || "B".equals(billStatus)) {
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_PUSHED;
|
||||
}
|
||||
// 其余状态(含 G 已退单/E 变更中)按处理失败记录,提示人工在金蝶核查
|
||||
log.warn("金蝶收款单单据状态[{}]未匹配映射,同步按 failed 处理", billStatus);
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流水分录收款金额解析:bei_creditamount 为字符串,空或非法时回退主单收款金额
|
||||
*/
|
||||
private BigDecimal parseEntryAmount(String entryAmount, BigDecimal fallbackAmount) {
|
||||
if (Func.isNotEmpty(entryAmount)) {
|
||||
try {
|
||||
return new BigDecimal(entryAmount.trim());
|
||||
} catch (NumberFormatException exception) {
|
||||
log.warn("解析金蝶流水分录收款金额失败,原始值:{},回退主单收款金额", entryAmount);
|
||||
}
|
||||
}
|
||||
return money(fallbackAmount);
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
@@ -1237,13 +943,6 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
}
|
||||
}
|
||||
|
||||
private String truncateText(String value, int maxLength) {
|
||||
if (value == null || value.length() <= maxLength) {
|
||||
return value;
|
||||
}
|
||||
return value.substring(0, maxLength);
|
||||
}
|
||||
|
||||
private void record(Long flowId, Long claimId, String actionType, String actionName,
|
||||
String fromStatus, String toStatus, BigDecimal operationAmount, String content) {
|
||||
recordMapper.insert(buildRecord(flowId, claimId, actionType, actionName,
|
||||
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for their own commercial purposes.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this license agreement and terms of use,
|
||||
* and you expressly agree to be bound by the terms of use of this software.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.task;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.service.IReceiptFlowService;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 收款认领推送金蝶定时任务(A301-3 收款认领结果推送)
|
||||
* <p>
|
||||
* 认领动作只在本地登记,本任务扫描「认领完成且未推送过」的收款流水自动推送金蝶,
|
||||
* 与收款流水页「推送金蝶」手动按钮共用同一服务方法(推送前按认领人重查 A301-2 最新公告对账)。
|
||||
* 单条流水推送失败独立消化、可重试,不影响同批其他流水。
|
||||
* <p>
|
||||
* 调度配置(Nacos thirdParty.bfi.receiptClaimPush.*):syncEnabled 开关、cron 表达式。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReceiptClaimPushTask {
|
||||
|
||||
private final IReceiptFlowService receiptFlowService;
|
||||
private final BfiProperties bfiProperties;
|
||||
|
||||
/** 收款认领推送调度,默认每10分钟执行一次 */
|
||||
@Scheduled(cron = "${thirdParty.bfi.receiptClaimPush.cron:0 4/10 * * * ?}")
|
||||
public void pushClaimToKingdee() {
|
||||
if (!bfiProperties.getReceiptClaimPush().isSyncEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String summary = receiptFlowService.pushClaimByTask();
|
||||
log.info("定时推送收款认领:{}", summary);
|
||||
} catch (Exception exception) {
|
||||
log.error("定时推送收款认领异常", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -38,7 +38,7 @@ import java.util.List;
|
||||
* <p>
|
||||
* 请求示例:
|
||||
* <pre>{@code
|
||||
* GET /ierp/kapi/v2/awrp/cas/cas_recbill/queryRecBill?begin_bizdate=2021-09-08&end_bizdate=2026-09-08&org_number=01.01.20.02.04.01&pageSize=10&pageNo=1
|
||||
* GET /ierp/kapi/v2/awrp/cas/cas_recbill/queryRecBill?begin_bizdate=2021-09-08 00:00:00&end_bizdate=2026-09-08 23:59:59&org_number=01.01.20.02.04.01&pageSize=10&pageNo=1
|
||||
* }</pre>
|
||||
*
|
||||
* @author Chill
|
||||
@@ -57,12 +57,13 @@ public class BfiRecBillQueryDTO implements Serializable {
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 【必填】开始业务日期,格式:yyyy-MM-dd,限定时间范围不可超过1个月
|
||||
* 【必填】开始业务日期,格式:yyyy-MM-dd HH:mm:ss,限定时间范围不可超过1个月;
|
||||
* 传纯日期 yyyy-MM-dd 会被金蝶拒绝(bos.filterSqlBuilder 时间格式错误)
|
||||
*/
|
||||
@JsonProperty("begin_bizdate")
|
||||
private String beginBizdate;
|
||||
/**
|
||||
* 【必填】结束业务日期,格式:yyyy-MM-dd
|
||||
* 【必填】结束业务日期,格式:yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
@JsonProperty("end_bizdate")
|
||||
private String endBizdate;
|
||||
|
||||
+1
-1
@@ -277,7 +277,7 @@ public interface IBfiService {
|
||||
/**
|
||||
* A301-1 收款单查询(收款处理查询)
|
||||
*
|
||||
* @param param 查询参数,业务日期范围不可超过1个月
|
||||
* @param param 查询参数,业务日期格式 yyyy-MM-dd HH:mm:ss,范围不可超过1个月
|
||||
* @return 收款处理分页数据
|
||||
*/
|
||||
BfiPageVO<BfiRecBillVO> queryRecBill(BfiRecBillQueryDTO param);
|
||||
|
||||
Reference in New Issue
Block a user