feat: 收款认领金蝶对接业务侧接线(A301-2公告同步/A301-3认领推送/A301-1收款单回写)

This commit is contained in:
刘泉佳
2026-09-24 22:21:00 +08:00
parent e50f4cbba6
commit 50573e7263
8 changed files with 344 additions and 63 deletions
@@ -87,4 +87,11 @@ public class ReceiptClaimRecordController extends BladeController {
public R<String> voidClaim(@RequestParam Long id) {
return R.data(receiptClaimRecordService.voidClaim(id));
}
@PostMapping("/query-kingdee-result")
@ApiOperationSupport(order = 5)
@Operation(summary = "查询金蝶认领结果(A301-1 收款单查询回写)")
public R<Integer> queryKingdeeResult() {
return R.data(receiptClaimRecordService.refreshKingdeeResult());
}
}
@@ -36,7 +36,6 @@ 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.ReceiptClaimRequest;
import org.springblade.transport.pojo.dto.ReceiptFlowSyncRequest;
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
import org.springblade.transport.service.IReceiptFlowService;
import org.springframework.web.bind.annotation.GetMapping;
@@ -95,8 +94,8 @@ public class ReceiptFlowController extends BladeController {
@PostMapping("/sync")
@ApiOperationSupport(order = 5)
@Operation(summary = "手动同步金蝶收款流水")
public R<Integer> sync(@RequestBody(required = false) ReceiptFlowSyncRequest request) {
return R.data(receiptFlowService.sync(request));
@Operation(summary = "手动同步金蝶收款流水(A301-2 收款认领公告查询)")
public R<Integer> sync() {
return R.data(receiptFlowService.sync());
}
}
@@ -22,6 +22,7 @@
c.attachments_json,
c.remark,
c.claim_status,
c.awrp_src_code,
c.kingdee_bill_no,
c.kingdee_bill_status,
c.voided_by,
@@ -46,4 +46,11 @@ public interface IReceiptClaimRecordService extends BaseService<ReceiptClaim> {
void updateAttachments(ReceiptClaimAttachmentsRequest request);
String voidClaim(Long id);
/**
* A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进金蝶单据状态
*
* @return 回写条数
*/
int refreshKingdeeResult();
}
@@ -28,7 +28,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.ReceiptClaimRequest;
import org.springblade.transport.pojo.dto.ReceiptFlowSyncRequest;
import org.springblade.transport.pojo.entity.KingdeeReceiptFlow;
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
@@ -52,5 +51,10 @@ public interface IReceiptFlowService extends BaseService<KingdeeReceiptFlow> {
Long claim(ReceiptClaimRequest request);
int sync(ReceiptFlowSyncRequest request);
/**
* 手动同步金蝶收款流水:直连 A301-2 收款认领公告查询,按明细流水号幂等 upsert 收款流水表
*
* @return 本次同步条数
*/
int sync();
}
@@ -28,10 +28,17 @@ package org.springblade.transport.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.thirdparty.bfi.constant.BfiConstant;
import org.springblade.thirdparty.bfi.pojo.dto.BfiRecBillQueryDTO;
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.constant.KingdeeBfiConstant;
import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.KingdeeReceiptFlowMapper;
import org.springblade.transport.mapper.ReceiptClaimMapper;
@@ -51,6 +58,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
@@ -58,12 +66,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 认领记录服务实现类
*
* @author Chill
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimMapper, ReceiptClaim>
@@ -72,12 +84,21 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
private static final String CLAIMED = "claimed";
private static final String VOIDED = "voided";
private static final String APPROVED = "approved";
/**
* A301-1 收款单查询回看窗口天数(金蝶约束业务日期范围不可超过1个月)
*/
private static final int RESULT_WINDOW_DAYS = 30;
/**
* A301-1 收款单查询分页大小
*/
private static final int RESULT_PAGE_SIZE = 200;
private final ReceiptClaimSettlementMapper claimSettlementMapper;
private final IFormalSettlementService formalSettlementService;
private final KingdeeReceiptFlowMapper receiptFlowMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final ReceiptFlowRecordMapper recordMapper;
private final IBfiService bfiService;
@Override
public IPage<ReceiptClaimRecordVO> selectPage(IPage<ReceiptClaimRecordVO> page,
@@ -238,12 +259,107 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
return kingdeeBillNo;
}
/**
* A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进金蝶单据状态
* <p>
* 回写口径:A301-1 契约本身不支持按来源单号过滤,故按近 30 天业务日期窗口全量翻页后,
* 以金蝶收款单 sourcebillnumber == 认领记录 awrp_src_code(A301-3 推送时生成)在本地匹配;
* 仅推进金蝶单据状态与补登收款单号,不改本地认领金额/分摊。
*
* @return 回写条数
*/
@Override
public int refreshKingdeeResult() {
List<ReceiptClaim> pending = baseMapper.selectList(Wrappers.<ReceiptClaim>lambdaQuery()
.eq(ReceiptClaim::getClaimStatus, CLAIMED)
.isNotNull(ReceiptClaim::getAwrpSrcCode)
.in(ReceiptClaim::getKingdeeBillStatus,
List.of(KingdeeBfiConstant.KINGDEE_STATUS_PUSHED, APPROVED))
.orderByAsc(ReceiptClaim::getId));
if (pending.isEmpty()) {
log.info("金蝶认领结果回写:无待回写认领记录(已推送/已审核且未终态)");
return 0;
}
Set<String> srcCodes = pending.stream()
.map(ReceiptClaim::getAwrpSrcCode)
.filter(Func::isNotEmpty)
.collect(Collectors.toSet());
if (srcCodes.isEmpty()) {
return 0;
}
log.info("金蝶认领结果回写:待回写认领记录 {} 条", pending.size());
LocalDate end = LocalDate.now();
LocalDate begin = end.minusDays(RESULT_WINDOW_DAYS);
long start = System.currentTimeMillis();
log.info("调用BFI接口 收款单查询开始,业务日期窗口 {} ~ {}", begin, end);
List<BfiRecBillVO> rows = BfiUtil.queryAllPages(pageNo -> {
BfiRecBillQueryDTO dto = new BfiRecBillQueryDTO();
dto.setPageNo(pageNo);
dto.setPageSize(RESULT_PAGE_SIZE);
dto.setBeginBizdate(begin.format(DateTimeFormatter.ISO_LOCAL_DATE));
dto.setEndBizdate(end.format(DateTimeFormatter.ISO_LOCAL_DATE));
return bfiService.queryRecBill(dto);
});
log.info("调用BFI接口 收款单查询结束 耗时:{}ms,拉取 {} 条", System.currentTimeMillis() - start, rows.size());
// 窗口内收款单按 sourcebillnumber 匹配待回写来源编码,同键多单取首条
Map<String, BfiRecBillVO> matched = rows.stream()
.filter(row -> Func.isNotEmpty(row.getSourcebillnumber()))
.filter(row -> srcCodes.contains(row.getSourcebillnumber()))
.collect(Collectors.toMap(BfiRecBillVO::getSourcebillnumber, Function.identity(),
(first, second) -> first, HashMap::new));
int updated = 0;
for (ReceiptClaim claim : pending) {
BfiRecBillVO bill = matched.get(claim.getAwrpSrcCode());
if (bill == null) {
// 窗口内未查到:金蝶回单延迟/状态未同步,不改状态,由下次回写或人工处置兜底
continue;
}
if (Func.isEmpty(claim.getKingdeeBillNo())) {
claim.setKingdeeBillNo(bill.getBillno());
}
claim.setKingdeeBillStatus(billStatusOf(bill.getBillstatus()));
updateById(claim);
updated++;
log.info("金蝶认领结果回写 claimId:{},awrpSrcCode:{},金蝶收款单号:{},状态:{} → {}",
claim.getId(), claim.getAwrpSrcCode(), bill.getBillno(), bill.getBillstatus(),
claim.getKingdeeBillStatus());
}
log.info("金蝶认领结果回写完成:匹配 {} 条,更新 {} 条", matched.size(), updated);
return updated;
}
/**
* 金蝶收款单单据状态 → 本地认领金蝶状态
*
* @param billStatus 金蝶 billstatus(A暂存/B已提交/C已审核/D已收款/G已退单)
* @return 本地状态
*/
private String billStatusOf(String billStatus) {
if (BfiConstant.BILL_STATUS_RECEIVED.equals(billStatus)) {
return KingdeeBfiConstant.KINGDEE_STATUS_RECEIVED;
}
if (BfiConstant.BILL_STATUS_AUDITED.equals(billStatus)) {
return APPROVED;
}
if (BfiConstant.BILL_STATUS_DRAFT.equals(billStatus)
|| BfiConstant.BILL_STATUS_SUBMITTED.equals(billStatus)) {
return KingdeeBfiConstant.KINGDEE_STATUS_PUSHED;
}
// 其余状态(含 G 已退单)按处理失败回收,提示人工在金蝶核查
log.warn("金蝶收款单状态[{}]未匹配映射,认领回写按 failed 处理", billStatus);
return KingdeeBfiConstant.KINGDEE_STATUS_FAILED;
}
private void fillStatusNames(ReceiptClaimRecordVO record) {
record.setClaimStatusName(VOIDED.equals(normalizeClaimStatus(record.getClaimStatus()))
? "已作废" : "已认领");
record.setKingdeeBillStatusName(switch (record.getKingdeeBillStatus() == null
? "" : record.getKingdeeBillStatus()) {
case APPROVED -> "审核通过";
case "received" -> "已收款";
case "pushed" -> "已推送";
case "failed" -> "处理失败";
default -> "未生成";
});
@@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
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.secure.utils.AuthUtil;
@@ -36,13 +37,14 @@ 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.transport.config.BfiProperties;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.mapper.FormalSettlementMapper;
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.ReceiptClaimRequest;
import org.springblade.transport.pojo.dto.ReceiptFlowSyncRequest;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.KingdeeReceiptFlow;
import org.springblade.transport.pojo.entity.ReceiptClaim;
@@ -53,6 +55,13 @@ import org.springblade.transport.service.IReceiptFlowService;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.wrapper.ReceiptFlowWrapper;
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.service.IBfiService;
import org.springblade.thirdparty.bfi.util.BfiUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -60,6 +69,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -75,6 +85,7 @@ import java.util.stream.Collectors;
*
* @author Chill
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMapper, KingdeeReceiptFlow>
@@ -85,12 +96,23 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
private static final String CLAIMED = "claimed";
private static final String APPROVED = "approved";
private static final String RECEIVABLE = "receivable";
/**
* A301-2 认领公告同步分页大小
*/
private static final int SYNC_PAGE_SIZE = 200;
/**
* A301-2 交易时间格式
*/
private static final DateTimeFormatter BFI_DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final ReceiptClaimMapper claimMapper;
private final ReceiptClaimSettlementMapper claimSettlementMapper;
private final ReceiptFlowRecordMapper recordMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final IFormalSettlementService formalSettlementService;
private final IBfiService bfiService;
private final BfiProperties bfiProperties;
@Override
public IPage<ReceiptFlowVO> selectPage(IPage<KingdeeReceiptFlow> page, ReceiptFlowVO query) {
@@ -280,29 +302,52 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
updateById(flow);
record(flow.getId(), claim.getId(), "claim", "认领收款流水", fromStatus, toStatus,
allocatedTotal, "关联" + settlements.size() + "张应收正式结算单");
pushClaimResultToKingdee(flow, claim, settlements, allocationMap);
return claim.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public int sync(ReceiptFlowSyncRequest request) {
List<ReceiptFlowSyncRequest.FlowRow> rows = request == null || request.getFlows() == null
? List.of() : request.getFlows();
if (rows.isEmpty()) {
public int sync() {
String claimusers = UserCache.getUserRealName(AuthUtil.getUserId());
if (Func.isEmpty(claimusers)) {
throw new ServiceException("当前用户无姓名,无法同步金蝶认领公告");
}
long start = System.currentTimeMillis();
log.info("调用BFI接口 收款认领公告查询开始 claimusers:{}", claimusers);
List<BfiClaimAnnounceVO> announces = BfiUtil.queryAllPages(pageNo ->
bfiService.queryClaimAnnounce(claimusers, pageNo, SYNC_PAGE_SIZE));
log.info("调用BFI接口 收款认领公告查询结束 耗时:{}ms,拉取 {} 条",
System.currentTimeMillis() - start, announces.size());
if (announces.isEmpty()) {
record(null, null, "sync", "手动同步流水", null, null, BigDecimal.ZERO,
"未接收到金蝶流水数据");
"金蝶认领公告未查询到数据");
return 0;
}
// 同步口径:A301-2 按参与认领用户(当前登录用户真实姓名)拉取全量认领公告,
// 以金蝶明细流水号为幂等键 upsert 收款流水表;本地已认领金额/状态(claimedAmount/claimStatus)
// 由认领、作废驱动,不被公告顺带覆盖,公告侧状态单独存 kingdee_claim_status/unclaimed_amount。
Set<String> serialNumbers = new HashSet<>();
int syncedCount = 0;
for (ReceiptFlowSyncRequest.FlowRow row : rows) {
validateSyncRow(row);
if (!serialNumbers.add(row.getDetailSerialNo().trim())) {
throw new ServiceException("明细流水号" + row.getDetailSerialNo() + "重复");
int createdCount = 0;
int updatedCount = 0;
for (BfiClaimAnnounceVO announce : announces) {
// 明细流水号为幂等键,缺省依次回退金蝶交易明细编号、银行流水号
String detailSerialNo = firstNonBlank(announce.getDetailid(),
announce.getTradedetailno(), announce.getBankdetailno());
if (Func.isEmpty(detailSerialNo)) {
log.warn("收款流水同步跳过:认领公告[{}]缺少明细流水号", announce.getBillno());
continue;
}
if (!serialNumbers.add(detailSerialNo.trim())) {
throw new ServiceException("明细流水号" + detailSerialNo.trim() + "重复");
}
// 行级锁防并发:同一流水并发手动同步时避免重复落库或覆盖已认领状态
KingdeeReceiptFlow entity = baseMapper.selectOne(Wrappers.<KingdeeReceiptFlow>lambdaQuery()
.eq(KingdeeReceiptFlow::getDetailSerialNo, row.getDetailSerialNo().trim())
.eq(KingdeeReceiptFlow::getDetailSerialNo, detailSerialNo.trim())
.last("FOR UPDATE"));
boolean created = entity == null;
if (created) {
@@ -310,22 +355,27 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
entity.setClaimedAmount(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
entity.setClaimStatus(UNCLAIMED);
}
BigDecimal receiptAmount = positiveMoney(row.getReceiptAmount(), "收款金额");
BigDecimal claimedAmount = money(entity.getClaimedAmount());
if (claimedAmount.compareTo(receiptAmount) > 0) {
throw new ServiceException("流水" + row.getDetailSerialNo() + "同步金额不能小于已认领金额");
BigDecimal receiptAmount = money(announce.getReamount());
if (receiptAmount.compareTo(BigDecimal.ZERO) <= 0) {
// 公告金额异常时跳过单条并告警,不阻断整批同步、不破坏已有流水
log.warn("收款流水同步跳过:认领公告[{}]收款金额为空或小于0", announce.getBillno());
continue;
}
copySyncRow(entity, row, receiptAmount);
entity.setClaimStatus(amountClaimStatus(claimedAmount, receiptAmount));
copyAnnounceRow(entity, announce, detailSerialNo.trim(), receiptAmount);
entity.setClaimStatus(amountClaimStatus(money(entity.getClaimedAmount()), receiptAmount));
if (created) {
baseMapper.insert(entity);
createdCount++;
} else {
baseMapper.updateById(entity);
updatedCount++;
}
record(entity.getId(), null, "sync", created ? "新增金蝶收款流水" : "更新金蝶收款流水",
entity.getClaimStatus(), entity.getClaimStatus(), BigDecimal.ZERO, entity.getDetailSerialNo());
entity.getClaimStatus(), entity.getClaimStatus(), BigDecimal.ZERO, detailSerialNo.trim());
syncedCount++;
}
log.info("金蝶收款流水同步完成 claimusers:{},拉取 {} 条,新增 {} 条,更新 {} 条",
claimusers, announces.size(), createdCount, updatedCount);
return syncedCount;
}
@@ -440,42 +490,142 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
return flow;
}
private void validateSyncRow(ReceiptFlowSyncRequest.FlowRow row) {
if (row == null) {
throw new ServiceException("金蝶收款流水不能为空");
/**
* A301-3 收款认领结果推送:组装报文推送金蝶,成功后回写来源编码、金蝶收款单号与推送状态
* <p>
* 认领在本地落库后立即推送,推送失败抛异常由事务整体回滚,保证金蝶与本地认领口径一致;
* awrpSrcCode 同时作为 A301-1 收款单查询回写的匹配键(金蝶收款单 sourcebillnumber)。
*
* @param flow 收款流水(须含 billId)
* @param claim 本地认领主单(已落库)
* @param settlements 分摊的应收正式结算单(保持 id 升序)
* @param allocationMap 结算单 id → 分摊金额
*/
private void pushClaimResultToKingdee(KingdeeReceiptFlow flow, ReceiptClaim claim,
List<FormalSettlement> settlements, Map<Long, BigDecimal> allocationMap) {
if (flow.getBillId() == null) {
throw new ServiceException("认领公告id缺失,请先手动同步金蝶收款流水");
}
required(row.getReceiptNoticeNo(), "认领通知单");
required(row.getPayerName(), "付款人");
required(row.getCounterpartyName(), "对方户名");
required(row.getCounterpartyAccount(), "对方账号");
required(row.getCounterpartyBank(), "对方开户行");
required(row.getDetailSerialNo(), "明细流水号");
if (row.getTransactionTime() == null) {
throw new ServiceException("交易时间不能为空");
String awrpSrcCode = buildClaimSrcCode(claim.getId());
claim.setAwrpSrcCode(awrpSrcCode);
BfiClaimPushDTO param = new BfiClaimPushDTO();
param.setBillid(flow.getBillId());
param.setAwrpSrcSys(bfiProperties.getSrcSys());
param.setAwrpSrcCode(awrpSrcCode);
param.setRecpaytypeNumber(KingdeeBfiConstant.BFI_CLAIM_REC_PAY_TYPE);
param.setPaymenttype(KingdeeBfiConstant.BFI_CLAIM_PAYMENT_TYPE);
param.setRecviewpayer(flow.getPayerName());
param.setRecpayer(flow.getPayerId());
List<BfiClaimPushEntryDTO> entries = new ArrayList<>();
int seq = 1;
BigDecimal allocatedTotal = BigDecimal.ZERO;
for (FormalSettlement settlement : settlements) {
BfiClaimPushEntryDTO entry = new BfiClaimPushEntryDTO();
entry.setEReceivableamt(allocationMap.get(settlement.getId()));
entry.setSeq(seq++);
if (Func.isNotEmpty(settlement.getKingdeeBillNo())) {
// 结算单已同步金蝶应收单时携带核心单据引用,金蝶按应收单号核销认领金额
entry.setECorebilltype(KingdeeBfiConstant.BFI_AR_BILL_SRC_BT);
entry.setECorebillno(settlement.getKingdeeBillNo());
}
allocatedTotal = allocatedTotal.add(money(allocationMap.get(settlement.getId())));
entries.add(entry);
}
validateLength(row.getReceiptNoticeNo(), 100, "认领通知单不能超过100字");
validateLength(row.getPayerName(), 200, "付款人不能超过200字");
validateLength(row.getCounterpartyName(), 200, "对方户名不能超过200字");
validateLength(row.getCounterpartyAccount(), 100, "对方账号不能超过100字");
validateLength(row.getCounterpartyBank(), 200, "对方开户行不能超过200字");
validateLength(row.getSummary(), 500, "摘要不能超过500字");
validateLength(row.getDetailSerialNo(), 100, "明细流水号不能超过100字");
positiveMoney(row.getReceiptAmount(), "收款金额");
param.setEntryentity(entries);
log.info("调用BFI接口 收款认领结果推送开始 claimId:{},billId:{},awrpSrcCode:{},分摊结算单 {} 张,分摊金额 {}",
claim.getId(), flow.getBillId(), awrpSrcCode, settlements.size(), allocatedTotal);
BfiBillSaveResultVO result = bfiService.pushClaimResult(param);
applyClaimPushResult(claim, result);
claimMapper.updateById(claim);
log.info("调用BFI接口 收款认领结果推送成功 claimId:{},已回写金蝶状态:{}",
claim.getId(), claim.getKingdeeBillStatus());
}
private void copySyncRow(KingdeeReceiptFlow target, ReceiptFlowSyncRequest.FlowRow source,
BigDecimal receiptAmount) {
target.setReceiptNoticeNo(source.getReceiptNoticeNo().trim());
target.setPayerName(source.getPayerName().trim());
/**
* 解析 A301-3 推送结果并回写认领记录
*
* @param claim 认领记录
* @param result 推送结果
*/
private void applyClaimPushResult(ReceiptClaim claim, 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));
}
String kingdeeBillNo = items.stream().map(BfiBillSaveResultItemVO::getNumber)
.filter(Func::isNotEmpty).findFirst().orElse(null);
claim.setKingdeeBillNo(kingdeeBillNo);
claim.setKingdeeBillStatus(KingdeeBfiConstant.KINGDEE_STATUS_PUSHED);
log.info("金蝶认领结果推送成功 claimId:{},金蝶收款单号:{}",
claim.getId(), Func.isEmpty(kingdeeBillNo) ? "响应未返回" : kingdeeBillNo);
}
/**
* 生成 A301-3 认领推送来源单据编码:SKCL + yyyyMMddHHmmss + 认领id尾号
*
* @param claimId 认领记录id
* @return 来源单据编码
*/
private String buildClaimSrcCode(Long claimId) {
String time = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
String suffix = String.valueOf(claimId);
return KingdeeBfiConstant.BFI_CLAIM_SRC_CODE_PREFIX + time + suffix.substring(Math.max(0, suffix.length() - 6));
}
/**
* A301-2 认领公告 → 收款流水表字段拷贝(仅覆盖金蝶侧来源字段,不覆盖本地认领金额与状态)
*/
private void copyAnnounceRow(KingdeeReceiptFlow target, BfiClaimAnnounceVO source,
String detailSerialNo, BigDecimal receiptAmount) {
target.setBillId(source.getId());
target.setReceiptNoticeNo(trimToNull(source.getBillno()));
target.setPayerName(trimToNull(source.getRecviewpayer()));
target.setPayeeName(trimToNull(source.getRecpayee()));
target.setReceiptAmount(receiptAmount);
target.setCounterpartyName(source.getCounterpartyName().trim());
target.setCounterpartyAccount(source.getCounterpartyAccount().trim());
target.setCounterpartyBank(source.getCounterpartyBank().trim());
target.setSummary(trimToNull(source.getSummary()));
target.setTransactionTime(source.getTransactionTime());
target.setDetailSerialNo(source.getDetailSerialNo().trim());
target.setSourceUpdatedTime(source.getSourceUpdatedTime() == null
? LocalDateTime.now() : source.getSourceUpdatedTime());
target.setCounterpartyName(trimToNull(source.getOppunit()));
target.setCounterpartyAccount(trimToNull(source.getOppbanknumber()));
target.setCounterpartyBank(trimToNull(source.getOppbank()));
target.setSummary(trimToNull(source.getDescription()));
target.setTransactionTime(parseDateTime(source.getTradetime()));
target.setDetailSerialNo(detailSerialNo);
target.setUnclaimedAmount(money(source.getUnclaimamount()));
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());
}
private String firstNonBlank(String... values) {
for (String value : values) {
if (Func.isNotEmpty(value)) {
return value.trim();
}
}
return null;
}
private LocalDateTime parseDateTime(String value) {
if (Func.isEmpty(value)) {
return null;
}
try {
return LocalDateTime.parse(value.trim(), BFI_DATE_TIME_FORMATTER);
} catch (Exception e) {
log.warn("解析金蝶收款流水时间失败,原始值:{}", value, e);
return null;
}
}
private String amountClaimStatus(BigDecimal claimedAmount, BigDecimal receiptAmount) {
@@ -511,14 +661,6 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
: amount.setScale(2, RoundingMode.HALF_UP);
}
private String required(String value, String fieldName) {
String result = trimToNull(value);
if (result == null) {
throw new ServiceException(fieldName + "不能为空");
}
return result;
}
private String trimToNull(String value) {
return value == null || value.trim().isEmpty() ? null : value.trim();
}
@@ -53,6 +53,11 @@ public class ReceiptFlowWrapper extends BaseEntityWrapper<KingdeeReceiptFlow, Re
case "claimed" -> "认领完成";
default -> "未认领";
});
vo.setKingdeeClaimStatusName(switch (Objects.toString(entity.getKingdeeClaimStatus(), "")) {
case "1" -> "已认领";
case "0" -> "未认领";
default -> Objects.toString(entity.getKingdeeClaimStatus(), "");
});
BigDecimal receiptAmount = money(entity.getReceiptAmount());
BigDecimal claimedAmount = money(entity.getClaimedAmount());
vo.setRemainingAmount(receiptAmount.subtract(claimedAmount).max(BigDecimal.ZERO));