feat: 认领作废同步合规并下线金蝶认领结果回写

This commit is contained in:
刘泉佳
2026-10-29 11:19:53 +08:00
parent 0163f10d1c
commit 1200c5caa5
3 changed files with 14 additions and 151 deletions
@@ -83,16 +83,9 @@ public class ReceiptClaimRecordController extends BladeController {
@PostMapping("/void")
@ApiOperationSupport(order = 4)
@Operation(summary = "作废认领记录并生成金蝶认领冲单")
@Operation(summary = "作废认领记录(作废后同步合同合规系统)")
public R<Void> voidClaim(@RequestParam Long id) {
receiptClaimRecordService.voidClaim(id);
return R.success("作废成功");
}
@PostMapping("/query-kingdee-result")
@ApiOperationSupport(order = 5)
@Operation(summary = "查询金蝶认领结果(A301-1 收款单查询回写)")
public R<Integer> queryKingdeeResult() {
return R.data(receiptClaimRecordService.refreshKingdeeResult());
}
}
@@ -46,11 +46,4 @@ public interface IReceiptClaimRecordService extends BaseService<ReceiptClaim> {
void updateAttachments(ReceiptClaimAttachmentsRequest request);
void voidClaim(Long id);
/**
* A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进金蝶单据状态
*
* @return 回写条数
*/
int refreshKingdeeResult();
}
@@ -33,12 +33,6 @@ 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;
@@ -53,20 +47,21 @@ import org.springblade.transport.pojo.entity.ReceiptFlowRecord;
import org.springblade.transport.pojo.vo.ReceiptClaimRecordVO;
import org.springblade.transport.service.IReceiptClaimRecordService;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.support.hcs.HcsClaimSyncSupport;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
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;
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;
@@ -84,21 +79,13 @@ 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;
private final HcsClaimSyncSupport hcsClaimSyncSupport;
@Override
public IPage<ReceiptClaimRecordVO> selectPage(IPage<ReceiptClaimRecordVO> page,
@@ -203,10 +190,6 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
if (flow == null || Objects.equals(flow.getIsDeleted(), 1)) {
throw new ServiceException("关联收款流水不存在");
}
// 已推送金蝶的认领不允许作废:金蝶收款单已生成,冲销/调整由人工在金蝶处置
if (KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_PUSHED.equals(flow.getPushStatus())) {
throw new ServiceException("该认领已推送金蝶,不允许作废,请在金蝶侧人工处置");
}
List<ReceiptClaimSettlement> relations = claimSettlementMapper.selectList(
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
@@ -256,9 +239,8 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
flow.setClaimStatus(amountClaimStatus(flowClaimedAfter, claimCeiling(flow)));
receiptFlowMapper.updateById(flow);
// 作废不生成金蝶认领冲单:金蝶单号/状态保留原值(未推送为空,已推送为真实收款单信息),
// 金蝶侧冲销/红冲由人工处置;冲单逻辑后续迭代如需恢复,放开下行调用即可
// String kingdeeBillNo = buildKingdeeBillNo(claim.getId());
// 作废不生成金蝶认领冲单:金蝶单号/状态保留原值(金蝶收款单已在 A301-1 同步落库),
// 金蝶侧冲销/红冲由人工处置
claim.setClaimStatus(VOIDED);
claim.setVoidedBy(AuthUtil.getUserId());
claim.setVoidedByName(Func.isEmpty(AuthUtil.getUserName()) ? claim.getClaimerName()
@@ -278,108 +260,14 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
: AuthUtil.getUserName());
operationRecord.setContent("作废认领,回退认领金额 " + money(claim.getClaimAmount()));
recordMapper.insert(operationRecord);
}
/**
* A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进流水金蝶回写状态
* <p>
* 回写口径:A301-1 契约本身不支持按来源单号过滤,故按近 30 天业务日期窗口全量翻页后,
* 以金蝶收款单 sourcebillnumber == 流水 awrp_src_code(A301-3 推送时按流水生成、重试复用)在本地匹配;
* 仅推进金蝶回写状态与补登收款单号,不改本地认领金额/分摊;名下未作废认领主单冗余展示字段级联刷新。
*
* @return 回写条数
*/
@Override
public int refreshKingdeeResult() {
List<KingdeeReceiptFlow> pending = receiptFlowMapper.selectList(
Wrappers.<KingdeeReceiptFlow>lambdaQuery()
.eq(KingdeeReceiptFlow::getPushStatus, KingdeeBfiConstant.RECEIPT_CLAIM_PUSH_PUSHED)
.in(KingdeeReceiptFlow::getKingdeeBillStatus,
List.of(KingdeeBfiConstant.KINGDEE_STATUS_PUSHED, APPROVED))
.eq(KingdeeReceiptFlow::getStatus, 1)
.orderByAsc(KingdeeReceiptFlow::getId));
if (pending.isEmpty()) {
log.info("金蝶认领结果回写:无待回写流水(已推送/已审核且未终态)");
return 0;
}
Set<String> srcCodes = pending.stream()
.map(KingdeeReceiptFlow::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);
// 作废事务提交后同步合规 C107-3(fdStatus=1 已作废,失败仅记日志不回滚作废)
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
hcsClaimSyncSupport.voidClaimToHcs(claim.getId());
}
});
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 (KingdeeReceiptFlow flow : pending) {
BfiRecBillVO bill = matched.get(flow.getAwrpSrcCode());
if (bill == null) {
// 窗口内未查到:金蝶回单延迟/状态未同步,不改状态,由下次回写或人工处置兜底
continue;
}
// 推送成功时金蝶收款单号先以来源编码占位(A301-3 响应 number 为空,取回执 keys.awrp_src_code),
// 回写命中后以金蝶真实收款单号覆盖
if (Func.isNotEmpty(bill.getBillno())) {
flow.setKingdeeBillNo(bill.getBillno());
}
flow.setKingdeeBillStatus(billStatusOf(bill.getBillstatus()));
receiptFlowMapper.updateById(flow);
// 级联刷新名下未作废认领主单的冗余展示字段
baseMapper.update(null, Wrappers.<ReceiptClaim>lambdaUpdate()
.eq(ReceiptClaim::getReceiptFlowId, flow.getId())
.eq(ReceiptClaim::getClaimStatus, CLAIMED)
.set(ReceiptClaim::getKingdeeBillNo, flow.getKingdeeBillNo())
.set(ReceiptClaim::getKingdeeBillStatus, flow.getKingdeeBillStatus()));
updated++;
log.info("金蝶认领结果回写 flowId:{},awrpSrcCode:{},金蝶收款单号:{},状态:{} → {}",
flow.getId(), flow.getAwrpSrcCode(), bill.getBillno(), bill.getBillstatus(),
flow.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) {
@@ -399,17 +287,6 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
return VOIDED.equals(claimStatus) ? VOIDED : CLAIMED;
}
/**
* 生成本地金蝶认领冲单号(KDCX + 时间戳 + 认领id尾号6位)。
* 当前作废流程不生成冲单(调用点已注释保留),后续迭代接金蝶冲销时放开即可。
*/
@SuppressWarnings("unused")
private String buildKingdeeBillNo(Long claimId) {
String time = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
String suffix = String.valueOf(claimId);
return "KDCX" + time + suffix.substring(Math.max(0, suffix.length() - 6));
}
/**
* 累计认领金额 → 流水认领状态:对照可认领上限判定(与收款流水侧口径一致)
*/
@@ -421,7 +298,7 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
}
/**
* 可认领上限:公告未认领金额与收款金额取较小正值(与收款流水侧口径一致)
* 可认领上限:未匹配收款金额(A301-1 unmatchamountrec)与收款金额取较小正值(与收款流水侧口径一致)
*/
private BigDecimal claimCeiling(KingdeeReceiptFlow flow) {
BigDecimal unclaimed = money(flow.getUnclaimedAmount());