feat: 查看页重推认领收票单并校验匹配金额

This commit is contained in:
刘泉佳
2026-11-02 18:38:11 +08:00
parent cb4aec5b22
commit 34e9030164
4 changed files with 90 additions and 0 deletions
@@ -43,4 +43,7 @@ public class FormalSettlementInvoice extends TenantEntity {
@TableField(exist = false)
@Schema(description = "收票单金蝶推送状态(unsynced未推送/pushed已保存收票单/assigned已指定应付/failed推送失败),详情回显用")
private String kingdeePushStatus;
@TableField(exist = false)
@Schema(description = "收票单金蝶推送失败原因(详情失败行悬浮展示用)")
private String kingdeePushMsg;
}
@@ -239,6 +239,13 @@ public class FormalSettlementController extends BladeController {
return R.data(formalSettlementService.pushInvoiceAssignKingdee(id));
}
@PostMapping("/repush-claimed-invoice")
@ApiOperationSupport(order = 16)
@Operation(summary = "重推认领收票单(查看页入口:收票单A303-2+指定应付A302-6 一步式,failed/pushed 通吃)")
public R<String> repushClaimedInvoice(@RequestParam Long id, @RequestParam String invoiceNo) {
return R.data(formalSettlementService.repushClaimedInvoice(id, invoiceNo));
}
@PostMapping("/sync-kingdee-ar-result")
@ApiOperationSupport(order = 15)
@Operation(summary = "手动勾稽金蝶应收单(A302-4 回写纠偏)")
@@ -72,6 +72,17 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
*/
String pushInvoiceAssignKingdee(Long settlementId);
/**
* 重推本单认领的收票登记(查看页「重新推送」入口,failed/pushed 通吃,assigned 拦截):
* 前置付款结算单 + 审批通过 + 财务应付单已同步;先复用收票推送状态机重推金蝶收票单(A303-2,
* A801-3 幂等预检防重复保存),再组装应付单防重键逐单 A302-6 指定应付(幂等)
*
* @param id 正式结算单id
* @param invoiceNo 认领发票号
* @return 重推结果描述
*/
String repushClaimedInvoice(Long id, String invoiceNo);
/**
* 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
* 仅更新审批通过且未同步的单据,未审批/已同步/已删除单据自动跳过,保持原状态
@@ -488,6 +488,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
InvoiceReceipt receipt = receiptByInvoiceNo.get(invoice.getInvoiceNo());
if (receipt != null) {
invoice.setKingdeePushStatus(receipt.getKingdeePushStatus());
invoice.setKingdeePushMsg(receipt.getKingdeePushMsg());
}
}
for (int index = 0; index < invoices.size(); index++) {
@@ -1103,6 +1104,65 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
return "发票登记推送金蝶成功:认领收票单已指定应付";
}
@Override
public String repushClaimedInvoice(Long id, String invoiceNo) {
FormalSettlement settlement = lockedExisting(id);
if (!"payable".equals(settlement.getSettlementType())) {
throw new ServiceException("仅付款结算单允许重推认领收票单");
}
if (!TransportBusiConstant.APPROVED.equals(settlement.getApprovalStatus())) {
throw new ServiceException("仅审批通过的付款结算单允许重推认领收票单");
}
if (!KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(settlement.getKingdeeSyncStatus())) {
throw new ServiceException("请先同步金蝶财务应付单后再重推认领收票单");
}
String trimmedInvoiceNo = requiredText(invoiceNo, "发票号");
InvoiceReceipt receipt = invoiceReceiptMapper.selectOne(Wrappers.<InvoiceReceipt>lambdaQuery()
.eq(InvoiceReceipt::getInvoiceNo, trimmedInvoiceNo).last("LIMIT 1"));
if (receipt == null || Objects.equals(receipt.getIsDeleted(), 1)) {
throw new ServiceException("收票登记不存在:发票" + trimmedInvoiceNo);
}
// 认领关系校验:该票必须认领在本单名下,防止跨结算单误推
Long relationCount = invoiceReceiptSettlementMapper.selectCount(Wrappers.<InvoiceReceiptSettlement>lambdaQuery()
.eq(InvoiceReceiptSettlement::getFormalSettlementId, id)
.eq(InvoiceReceiptSettlement::getInvoiceReceiptId, receipt.getId()));
if (relationCount == null || relationCount == 0) {
throw new ServiceException("发票" + trimmedInvoiceNo + "未认领在本结算单名下,不允许重推");
}
String pushStatus = Func.toStrWithEmpty(receipt.getKingdeePushStatus(), KingdeeBfiConstant.RECEIVED_PUSH_UNSYNCED);
if (KingdeeBfiConstant.RECEIVED_PUSH_ASSIGNED.equals(pushStatus)) {
throw new ServiceException("发票" + trimmedInvoiceNo + "已指定应付,无需重复推送");
}
// 第一步:复用收票推送状态机重推金蝶收票单(A303-2,A801-3 幂等预检防重复保存,单笔事务独立回滚)
String pushResult = invoiceReceiptService.pushKingdeeReceivedInvoice(List.of(receipt.getId()));
// 第二步:收票单就绪后按应付单防重键 A302-6 指定应付(幂等,已指定的跳过)
List<FormalSettlementDetail> details = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, id)
.eq(FormalSettlementDetail::getIsDeleted, 0)
.orderByAsc(FormalSettlementDetail::getLineNo));
if (Func.isEmpty(details)) {
throw new ServiceException("推送失败:正式结算单无结算明细,无法组装财务应付单防重键");
}
String apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
String assignFailures = invoiceReceiptService.assignClaimedReceipts(id, apBillSrcCode);
// 复读最新状态组装结果:assigned 即成功;收票单仍未推送成功按失败报因;pushed 未指定报因可重试
InvoiceReceipt latest = invoiceReceiptMapper.selectById(receipt.getId());
String latestStatus = Func.toStrWithEmpty(latest.getKingdeePushStatus(), KingdeeBfiConstant.RECEIVED_PUSH_UNSYNCED);
if (KingdeeBfiConstant.RECEIVED_PUSH_ASSIGNED.equals(latestStatus)) {
log.info("正式结算单[{}]认领发票[{}]重推完成:已指定应付", settlement.getFormalSettlementNo(), trimmedInvoiceNo);
return "发票" + trimmedInvoiceNo + "重推成功:已指定应付";
}
if (!KingdeeBfiConstant.RECEIVED_PUSH_PUSHED.equals(latestStatus)) {
throw new ServiceException("发票" + trimmedInvoiceNo + "重推收票单失败:" + pushResult);
}
if (Func.isNotBlank(assignFailures)) {
throw new ServiceException("发票" + trimmedInvoiceNo + "收票单已推送金蝶,指定应付失败:" + assignFailures
+ "(已指定的自动跳过,可重试)");
}
log.info("正式结算单[{}]认领发票[{}]重推完成:收票单已推送金蝶并指定应付", settlement.getFormalSettlementNo(), trimmedInvoiceNo);
return "发票" + trimmedInvoiceNo + "重推成功:已指定应付";
}
@Override
public String syncKingdeeArResult() {
return kingdeeSyncLockSupport.runSingleFlight(AR_RESULT_SYNC_LOCK_PREFIX, bfiProperties.getArResult().getLockLeaseSeconds(),
@@ -1243,6 +1303,15 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
for (FormalSettlementSaveRequest.Invoice item : invoices) {
String invoiceNo = item == null ? "" : requiredText(item.getInvoiceNo(), "发票号");
if (!invoiceNumbers.add(invoiceNo)) throw new ServiceException("发票号" + invoiceNo + "重复");
// 匹配结算单金额支持手动修改:后端兜底校验 0 ≤ 匹配金额 ≤ 发票金额(匹配合计不设上限,两个口径)
BigDecimal matched = money(item.getMatchedAmount());
BigDecimal invoiceAmount = money(item.getInvoiceAmount());
if (matched.compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("发票" + invoiceNo + "匹配结算单金额不能为负数");
}
if (matched.compareTo(invoiceAmount) > 0) {
throw new ServiceException("发票" + invoiceNo + "匹配结算单金额不能超过发票金额" + invoiceAmount);
}
}
// 认领闸门:确认前按票号重查金蝶复核可用性,选中到确认之间票被认领/使用/红冲在此拦截
invoiceReceiptService.assertInvoicesClaimable(invoiceNumbers);