feat: 付款结算单认领收票单同步关系并在应付单推送后逐单指定应付

This commit is contained in:
刘泉佳
2026-10-31 16:06:46 +08:00
parent 02e6f06f37
commit d28a51af4a
3 changed files with 78 additions and 1 deletions
@@ -232,6 +232,13 @@ public class FormalSettlementController extends BladeController {
@Operation(summary = "推送金蝶(应付单/应收单)")
public R<String> syncKingdee(@RequestParam Long id) { return R.data(formalSettlementService.syncKingdee(id)); }
@PostMapping("/invoice-assign-kingdee")
@ApiOperationSupport(order = 14)
@Operation(summary = "发票登记确认推送金蝶(先票后款:认领收票单逐单 A302-6 指定应付)")
public R<String> invoiceAssignKingdee(@RequestParam Long id) {
return R.data(formalSettlementService.pushInvoiceAssignKingdee(id));
}
@PostMapping("/sync-kingdee-ar-result")
@ApiOperationSupport(order = 15)
@Operation(summary = "手动勾稽金蝶应收单(A302-4 回写纠偏)")
@@ -62,6 +62,16 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
*/
String doSyncKingdee(Long id);
/**
* 发票登记确认推送金蝶(先票后款路径B:结算单审批通过后"发票登记"关联收票单 → 指定应付)。
* 前置:付款结算单 + 审批通过 + 财务应付单已同步;对本单认领且收票单已推(pushed)的
* 逐单调 A302-6 指定应付,单笔失败整体报因(已指定的幂等跳过,可重试)
*
* @param settlementId 正式结算单id
* @return 推送结果描述
*/
String pushInvoiceAssignKingdee(Long settlementId);
/**
* 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
* 仅更新审批通过且未同步的单据,未审批/已同步/已删除单据自动跳过,保持原状态
@@ -1004,6 +1004,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
String billNo;
String billPkId;
String changeDesc;
// 应付结算:财务应付单防重键,先票后款链路 A102-2 成功后据此对本单认领收票单逐单 A302-6 指定应付
String apBillSrcCode = null;
if ("receivable".equals(settlement.getSettlementType())) {
// 应收结算:财务应收单防重键(awrp_src_code):正式结算单号_结算明细单据号下划线拼接
String arBillSrcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement, details);
@@ -1016,7 +1018,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
} else {
// 应付结算:财务应付单防重键,付款申请「同步金蝶」(A103-1 应付下推)推送前按该键经 A102-4 预检本单应付单已生成
// 付款申请 A103-1 下推前的前置
String apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups = kingdeeApBillSyncSupport.resolveEntryGroups(settlement, details, context);
KingdeeApBillSyncSupport.BfiApBillPushOutcome outcome = kingdeeApBillSyncSupport.pushApBill(
apBillSrcCode, settlement, entryGroups, settlement.getFormalSettlementNo(), context);
@@ -1038,11 +1040,58 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
.eq(SettlementAdjustment::getApprovalStatus, TransportBusiConstant.APPROVED)
.set(SettlementAdjustment::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED)
.set(SettlementAdjustment::getKingdeeBillNo, billNo));
// 先票后款:应付单推送成功后,对本单认领且收票单已推金蝶(pushed)的登记逐单 A302-6 指定应付;
// 单笔失败不阻断结算同步,由 A801-3 回写"指定补偿"按认领关系自动重试
if (apBillSrcCode != null) {
String assignFailures = invoiceReceiptService.assignClaimedReceipts(settlement.getId(), apBillSrcCode);
if (Func.isNotBlank(assignFailures)) {
log.warn("正式结算单[{}]收票单指定应付存在失败(由 A801-3 指定补偿重试):{}",
settlement.getFormalSettlementNo(), assignFailures);
}
}
log.info("正式结算单[{}]同步金蝶完成:settlementType={},billNo={},billPkId={}",
settlement.getFormalSettlementNo(), settlement.getSettlementType(), billNo, billPkId);
return billNo;
}
/**
* 发票登记确认推送金蝶(先票后款路径B:结算单审批通过后列表"发票登记"关联收票单 → 指定应付)。
* 前置:付款结算单 + 审批通过 + 财务应付单已同步(kingdeeSyncStatus=synced);
* 对本单认领且收票单已推金蝶(pushed)的登记逐单 A302-6 指定应付,单笔失败整体报因(已指定的幂等跳过,可重试)
*
* @param settlementId 正式结算单id
* @return 推送结果描述
*/
@Override
public String pushInvoiceAssignKingdee(Long settlementId) {
FormalSettlement settlement = lockedExisting(settlementId);
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("请先同步金蝶财务应付单后再执行发票登记推送");
}
List<FormalSettlementDetail> details = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, settlementId)
.eq(FormalSettlementDetail::getIsDeleted, 0)
.orderByAsc(FormalSettlementDetail::getLineNo));
if (Func.isEmpty(details)) {
throw new ServiceException("推送失败:正式结算单无结算明细,无法组装财务应付单防重键");
}
String apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
String assignFailures = invoiceReceiptService.assignClaimedReceipts(settlement.getId(), apBillSrcCode);
if (Func.isNotBlank(assignFailures)) {
log.warn("正式结算单[{}]发票登记推送金蝶存在失败:{}", settlement.getFormalSettlementNo(), assignFailures);
throw new ServiceException("发票登记推送金蝶完成,存在失败(已指定的幂等跳过,处理后可重试):" + assignFailures);
}
log.info("正式结算单[{}]发票登记推送金蝶完成:认领收票单已全部指定应付,应付单防重键={}",
settlement.getFormalSettlementNo(), apBillSrcCode);
return "发票登记推送金蝶成功:认领收票单已指定应付";
}
@Override
public String syncKingdeeArResult() {
return kingdeeSyncLockSupport.runSingleFlight(AR_RESULT_SYNC_LOCK_PREFIX, bfiProperties.getArResult().getLockLeaseSeconds(),
@@ -1246,6 +1295,13 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setInvoiceAmount(money(settlement.getInvoiceAmount()).add(insertedMatchedTotal));
settlement.setInvoiceStatus(invoiceStatus(settlement.getInvoiceAmount(), settlement.getSettlementAmount()));
updateById(settlement);
// 先票后款:同步收票认领关系(一票一结算单校验 + 重建收票登记侧关系 + 刷新收票状态);
// 认领为增量添加,关系须覆盖存量+本次新增的发票号
if ("payable".equals(settlement.getSettlementType())) {
Set<String> allInvoiceNos = new LinkedHashSet<>(existingNumbers);
allInvoiceNos.addAll(invoiceNumbers);
invoiceReceiptService.syncSettlementReceiptRelations(settlement.getId(), new ArrayList<>(allInvoiceNos));
}
}
private String createPayment(FormalSettlement settlement, BigDecimal appliedAmount, String remark) {
@@ -1716,6 +1772,10 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setInvoiceAmount(money(matchedTotal));
settlement.setInvoiceStatus(invoiceStatus(matchedTotal, settlement.getSettlementAmount()));
updateById(settlement);
// 先票后款:保存/编辑按发票行同步收票认领关系(一票一结算单校验 + 重建收票登记侧关系 + 刷新收票状态)
if ("payable".equals(settlement.getSettlementType())) {
invoiceReceiptService.syncSettlementReceiptRelations(settlement.getId(), new ArrayList<>(invoiceNumbers));
}
}
private void refreshSettlementAmount(FormalSettlement settlement) {