diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java index 7cfd3f85..9bd4310a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java @@ -87,4 +87,11 @@ public class ReceiptClaimRecordController extends BladeController { public R voidClaim(@RequestParam Long id) { return R.data(receiptClaimRecordService.voidClaim(id)); } + + @PostMapping("/query-kingdee-result") + @ApiOperationSupport(order = 5) + @Operation(summary = "查询金蝶认领结果(A301-1 收款单查询回写)") + public R queryKingdeeResult() { + return R.data(receiptClaimRecordService.refreshKingdeeResult()); + } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java index 4aa3f02b..8fe86d81 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java @@ -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 sync(@RequestBody(required = false) ReceiptFlowSyncRequest request) { - return R.data(receiptFlowService.sync(request)); + @Operation(summary = "手动同步金蝶收款流水(A301-2 收款认领公告查询)") + public R sync() { + return R.data(receiptFlowService.sync()); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml index 07a25531..fcbafc57 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml @@ -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, diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java index 66a8886e..1dd6f04f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java @@ -46,4 +46,11 @@ public interface IReceiptClaimRecordService extends BaseService { void updateAttachments(ReceiptClaimAttachmentsRequest request); String voidClaim(Long id); + + /** + * A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进金蝶单据状态 + * + * @return 回写条数 + */ + int refreshKingdeeResult(); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java index 4fe29056..0f7c5686 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java @@ -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 { Long claim(ReceiptClaimRequest request); - int sync(ReceiptFlowSyncRequest request); + /** + * 手动同步金蝶收款流水:直连 A301-2 收款认领公告查询,按明细流水号幂等 upsert 收款流水表 + * + * @return 本次同步条数 + */ + int sync(); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptClaimRecordServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptClaimRecordServiceImpl.java index 3affe442..270a5417 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptClaimRecordServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptClaimRecordServiceImpl.java @@ -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 @@ -72,12 +84,21 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl selectPage(IPage page, @@ -238,12 +259,107 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl + * 回写口径:A301-1 契约本身不支持按来源单号过滤,故按近 30 天业务日期窗口全量翻页后, + * 以金蝶收款单 sourcebillnumber == 认领记录 awrp_src_code(A301-3 推送时生成)在本地匹配; + * 仅推进金蝶单据状态与补登收款单号,不改本地认领金额/分摊。 + * + * @return 回写条数 + */ + @Override + public int refreshKingdeeResult() { + List pending = baseMapper.selectList(Wrappers.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 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 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 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 -> "未生成"; }); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptFlowServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptFlowServiceImpl.java index c78f80f4..b8961645 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptFlowServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceiptFlowServiceImpl.java @@ -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 @@ -85,12 +96,23 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl selectPage(IPage page, ReceiptFlowVO query) { @@ -280,29 +302,52 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl 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 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 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.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 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 + * 认领在本地落库后立即推送,推送失败抛异常由事务整体回滚,保证金蝶与本地认领口径一致; + * awrpSrcCode 同时作为 A301-1 收款单查询回写的匹配键(金蝶收款单 sourcebillnumber)。 + * + * @param flow 收款流水(须含 billId) + * @param claim 本地认领主单(已落库) + * @param settlements 分摊的应收正式结算单(保持 id 升序) + * @param allocationMap 结算单 id → 分摊金额 + */ + private void pushClaimResultToKingdee(KingdeeReceiptFlow flow, ReceiptClaim claim, + List settlements, Map 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 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 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 "认领完成"; 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));