feat: 收款认领金蝶对接业务侧接线(A301-2公告同步/A301-3认领推送/A301-1收款单回写)
This commit is contained in:
+7
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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,
|
||||
|
||||
+7
@@ -46,4 +46,11 @@ public interface IReceiptClaimRecordService extends BaseService<ReceiptClaim> {
|
||||
void updateAttachments(ReceiptClaimAttachmentsRequest request);
|
||||
|
||||
String voidClaim(Long id);
|
||||
|
||||
/**
|
||||
* A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进金蝶单据状态
|
||||
*
|
||||
* @return 回写条数
|
||||
*/
|
||||
int refreshKingdeeResult();
|
||||
}
|
||||
|
||||
+6
-2
@@ -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();
|
||||
}
|
||||
|
||||
+116
@@ -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 -> "未生成";
|
||||
});
|
||||
|
||||
+199
-57
@@ -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();
|
||||
}
|
||||
|
||||
+5
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user