feat: 付款申请金蝶推送完善往来账户(客商收款账户联行号)与结算方式映射

This commit is contained in:
刘泉佳
2026-09-24 19:42:58 +08:00
parent 851deb2959
commit aa06379737
@@ -53,6 +53,7 @@ import org.springblade.transport.mapper.BillLedgerMapper;
import org.springblade.transport.mapper.BillLedgerUsageMapper;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.CustomerReceiptAccountMapper;
import org.springblade.transport.mapper.FormalSettlementDetailMapper;
import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.FormalSettlementPaymentMapper;
@@ -73,6 +74,7 @@ import org.springblade.transport.pojo.entity.BillLedger;
import org.springblade.transport.pojo.entity.BillLedgerUsage;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.CustomerReceiptAccount;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
@@ -134,6 +136,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private final ProjectApplyMapper projectApplyMapper;
private final ContractManageMapper contractManageMapper;
private final CustomerArchiveMapper customerArchiveMapper;
private final CustomerReceiptAccountMapper receiptAccountMapper;
private final BillLedgerMapper billLedgerMapper;
private final BillLedgerUsageMapper billLedgerUsageMapper;
private final TemporaryCreditLimitMapper temporaryCreditLimitMapper;
@@ -414,13 +417,9 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
// 已生成直接回填单据id与编号视为同步成功,未生成才继续 A103-1 下推,下推接口真正报错才算同步失败
kingdeeBillId = reconcileExistPayApply(entity);
if (Func.isEmpty(kingdeeBillId)) {
// A103-1 分录与财务应付单明细分录一一对应(awrp_entry_code 取费用明细行id),
// 多结算单付款申请各分录按所属结算单勾稽各自应付单的付款计划分录 awrp_entry_code_p
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups =
kingdeeApBillSyncSupport.resolveEntryGroups(settlementDetails(entity, context), context);
log.info("付款申请[{}]正式结算付款:金蝶财务应付单(A102-2)已生成,开始推送金蝶付款申请单(A103-1 应付下推),分录{}条",
entity.getPaymentNo(), entryGroups.size());
kingdeeBillId = pushBfiPayApplyPush(entity, entryGroups, context);
log.info("付款申请[{}]正式结算付款:金蝶财务应付单(A102-2)已生成,开始推送金蝶付款申请单(A103-1 应付下推)",
entity.getPaymentNo());
kingdeeBillId = pushBfiPayApplyPush(entity, context);
log.info("付款申请[{}]金蝶付款申请单(A103-1 应付下推)推送成功:kingdeeBillId={}", entity.getPaymentNo(), kingdeeBillId);
}
} else {
@@ -917,17 +916,16 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
/**
* 推送金蝶付款申请(A103-1 应付下推付款申请,正式结算付款),返回星瀚付款申请单id。
* 付款申请分录与财务应付单明细分录一一对应,申请金额取各组含税金额;
* 付款申请仅推一条分录:申请金额取付款申请填写的申请付款金额;
* awrp_finapbill_entryid 勾稽 A102-2 付款计划分录 awrp_entry_code_p:财务应付单(A102-2)由正式结算单
* 「同步金蝶」推送且未传付款计划(planentity),统一经 A102-4 按各分录所属正式结算单的防重键反查
* 金蝶自动生成的 awrp_entry_code_p(多结算单付款申请各分录勾稽各自应付单);awrp_entry_code 填费用明细行id
* 「同步金蝶」推送且未传付款计划(planentity),统一经 A102-4 按付款申请主结算单的防重键反查
* 金蝶自动生成的 awrp_entry_code_p(多结算单聚合付款申请按主结算单勾稽);awrp_entry_code 填付款申请号-1。
*
* @param entity 付款申请实体
* @param entryGroups 结算明细解析出的应付单分录(含分录所属正式结算单id)
* @param context 金蝶同步上下文(缓存载体)
* @return 星瀚付款申请单id
*/
private String pushBfiPayApplyPush(PaymentApplication entity, List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups, KingdeeSyncContext context) {
private String pushBfiPayApplyPush(PaymentApplication entity, KingdeeSyncContext context) {
ContractManage contract = kingdeeApBillSyncSupport.requireContract(entity.getContractId(), context);
String orgNumber = kingdeeApBillSyncSupport.bfiOrgNumber(contract);
// 申请日期:yyyy-MM-dd HH:mm:ss(与 A102-2 单据日期格式统一)
@@ -954,46 +952,113 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
// 申请人(非必传)
// param.setCreatorName(entity.getApplicantName());
// 付款申请分录与财务应付单明细分录一一对应(awrp_finapbill_entryid 关联应付单付款计划分录 awrp_entry_code_p,A102-4 反查)
List<BfiPayApplyPushEntryDTO> entries = new ArrayList<>();
// 结算方式.编码:按本单付款方式映射(汇票付款→金蝶应付票据电子银行承兑汇票 012-02,其余→银行转账 02),仅 A103-1 下推需要
String settlementtypeNumber = isBillPayment(entity.getPaymentMethod())
? BfiConstant.SETTLE_TYPE_PAYABLE_BANK_ELECTRONIC : BfiConstant.SETTLE_TYPE_BANK;
for (KingdeeApBillSyncSupport.BfiEntryGroup entryGroup : entryGroups) {
// 【必填】结算单分录id勾稽值(awrp_finapbill_entryid):A102-2 保存未传 planentity,金蝶自动生成的付款计划分录
// awrp_entry_code_p 即外部系统单据号(应付单防重键:正式结算单号_结算明细单据号拼接,本地按数据库数据组装,不远程反查)
String finapbillEntryid = settlementApBillSrcCode(entity, entryGroup.formalSettlementId(), context);
BfiPayApplyPushEntryDTO entry = new BfiPayApplyPushEntryDTO();
// 【必填】付款类型.编码:正式付款固定 201
entry.setEPaymenttypeNumber(KingdeeBfiConstant.BFI_PAYMENT_TYPE_FORMAL);
// 结算方式.编码:不传时金蝶按应付单自动带出,此处按付款方式映射显式送值
entry.setESettlementtypeNumber(settlementtypeNumber);
// 【必填】结算单分录id:A102-2 财务应付单付款计划分录的外部系统来源分录标识(awrp_entry_code_p)
entry.setAwrpFinapbillEntryid(finapbillEntryid);
// 【必填】对方系统唯一行id(不能重复):费用明细行id(与应付单的勾稽走 awrp_finapbill_entryid,与本标识无关)
entry.setAwrpEntryCode(entryGroup.entryCode());
// 【必填】往来账户:乙方(收款方)银行账号
entry.setEAssacct(entity.getBankAccount());
// 【必填】往来银行.编码(联调期临时传金蝶测试档案值,待建立银行档案编码映射后调整)todo
entry.setEBebankNumber(KingdeeBfiConstant.BFI_AP_BILL_BANKACCOUNT_NUMBER_TEST);
// 【必填】申请金额:与对应应付单分录结算金额(含税)保持一致
entry.setEApplyamount(entryGroup.amount());
// 【必填】项目.项目编码(联调期临时传金蝶测试档案值)
entry.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
// 期望付款日(非必传,不传时根据应付单自动带出)
// entry.setEExpaydate(applyDateText);
// 备注(非必传)
// entry.setERemark(entity.getRemark());
// 【必填】外部单据类型 todo
entry.setAwrpSrcType(bfiProperties.getPayApply().getSrcType());
entries.add(entry);
}
param.setEntry(entries);
// 付款申请分录与财务应付单付款计划分录勾稽(awrp_finapbill_entryid 关联应付单 awrp_entry_code_p,A102-4 反查)
BfiPayApplyPushEntryDTO entry = new BfiPayApplyPushEntryDTO();
// 【必填】付款类型.编码:正式付款固定 201
entry.setEPaymenttypeNumber(KingdeeBfiConstant.BFI_PAYMENT_TYPE_FORMAL);
// 结算方式.编码:不传时金蝶按应付单自动带出,此处按付款方式映射显式送值(汇票付款→金蝶应付票据电子银行承兑汇票 012-02,其余→银行转账 02)
entry.setESettlementtypeNumber(isBillPayment(entity.getPaymentMethod())
? BfiConstant.SETTLE_TYPE_PAYABLE_BANK_ELECTRONIC : BfiConstant.SETTLE_TYPE_BANK);
// 【必填】结算单分录id:A102-2 财务应付单付款计划分录的外部系统来源分录标识(awrp_entry_code_p),按付款申请主结算单防重键勾稽
entry.setAwrpFinapbillEntryid(settlementApBillSrcCode(entity, requireMainSettlementId(entity), context));
// 【必填】对方系统唯一行id(不能重复):付款申请号-1
entry.setAwrpEntryCode(bfiEntryCode(entity.getPaymentNo(), 1));
// 【必填】往来账户:乙方(收款方)银行账号
entry.setEAssacct(entity.getBankAccount());
// 【必填】往来银行.编码:取客商收款信息(blade_customer_receipt_account)联行号
entry.setEBebankNumber(bfiBebankNumber(entity, context));
// 【必填】申请金额:取付款申请填写的申请付款金额
entry.setEApplyamount(money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP));
// 【必填】项目.项目编码(联调期临时传金蝶测试档案值)
entry.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
// 期望付款日(非必传,不传时根据应付单自动带出)
// entry.setEExpaydate(applyDateText);
// 备注(非必传)
// entry.setERemark(entity.getRemark());
// 【必填】外部单据类型 todo
entry.setAwrpSrcType(bfiProperties.getPayApply().getSrcType());
param.setEntry(List.of(entry));
BfiBillSaveResultVO saveResult = bfiService.pushPayApply(param);
return requireBfiBillIdAndFillNo(entity, saveResult, "推送金蝶付款申请(应付下推)");
}
/**
* 付款申请勾稽的主结算单id:付款申请仅推一条分录,按主结算单的财务应付单防重键勾稽。
* 优先取付款申请记录的主结算单id;聚合多结算单场景主结算单id为空时,取关联结算明细中首个结算单id。
*
* @param entity 付款申请实体
* @return 主结算单id
*/
private Long requireMainSettlementId(PaymentApplication entity) {
if (entity.getSettlementId() != null) {
return entity.getSettlementId();
}
Long settlementId = formalSettlementDetails(entity).stream()
.map(FormalSettlementDetail::getFormalSettlementId)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (settlementId == null) {
throw new ServiceException("推送金蝶付款申请失败:付款申请[" + entity.getPaymentNo() + "]未关联正式结算单,无法勾稽财务应付单");
}
return settlementId;
}
/**
* 往来银行编码(金蝶 e_bebank_number):取付款申请所选客商收款信息(blade_customer_receipt_account)的联行号。
* 优先按收款账户id直查,批量同步时经上下文缓存复用;
* 收款账户id缺失时,按合同乙方客商 + 收款账号兜底匹配(默认账户优先)。
*
* @param entity 付款申请实体
* @param context 金蝶同步上下文(缓存载体)
* @return 联行号
*/
private String bfiBebankNumber(PaymentApplication entity, KingdeeSyncContext context) {
CustomerReceiptAccount receiptAccount = receiptAccount(entity, context);
if (receiptAccount == null || Func.isBlank(receiptAccount.getCnapsCode())) {
throw new ServiceException("推送金蝶付款申请失败:付款申请[" + entity.getPaymentNo()
+ "]收款账户未维护联行号,请先在客商档案维护收款信息联行号");
}
return receiptAccount.getCnapsCode().trim();
}
/** 付款申请对应的客商收款信息(上下文缓存优先),收款账户id缺失时按合同乙方客商 + 收款账号兜底匹配 */
private CustomerReceiptAccount receiptAccount(PaymentApplication entity, KingdeeSyncContext context) {
if (entity.getReceiptAccountId() != null) {
return context.receiptAccountCache().computeIfAbsent(entity.getReceiptAccountId(), receiptAccountId -> {
CustomerReceiptAccount receiptAccount = receiptAccountMapper.selectById(receiptAccountId);
if (receiptAccount == null || Objects.equals(receiptAccount.getIsDeleted(), 1)) {
throw new ServiceException("推送金蝶付款申请失败:付款申请[" + entity.getPaymentNo()
+ "]收款账户[" + receiptAccountId + "]不存在或已删除");
}
return receiptAccount;
});
}
ContractManage contract = kingdeeApBillSyncSupport.requireContract(entity.getContractId(), context);
if (Func.isBlank(contract.getPartyB())) {
return null;
}
CustomerArchive customer = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, contract.getPartyB())
.or().eq(CustomerArchive::getShortName, contract.getPartyB()))
.eq(CustomerArchive::getIsDeleted, 0).last("limit 1"));
if (customer == null) {
return null;
}
List<CustomerReceiptAccount> receiptAccounts = receiptAccountMapper.selectList(Wrappers.<CustomerReceiptAccount>lambdaQuery()
.eq(CustomerReceiptAccount::getCustomerId, customer.getId())
.eq(Func.isNotBlank(entity.getBankAccount()), CustomerReceiptAccount::getBankAccount, entity.getBankAccount())
.eq(CustomerReceiptAccount::getIsDeleted, 0)
.orderByDesc(CustomerReceiptAccount::getIsDefault)
.orderByAsc(CustomerReceiptAccount::getCreateTime));
CustomerReceiptAccount receiptAccount = receiptAccounts.isEmpty() ? null : receiptAccounts.get(0);
if (receiptAccount != null && receiptAccount.getId() != null) {
context.receiptAccountCache().putIfAbsent(receiptAccount.getId(), receiptAccount);
}
return receiptAccount;
}
/**
* 校验金蝶保存/下推返回结果,取星瀚单据id并即时回填付款申请单编号(kingdeeBillNo):
* 推送结果携带金蝶单据编号(number),同步完成列表立即可见,无需等待 A103-2 定时回写;
@@ -1060,7 +1125,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
/**
* 金蝶分录标识:付款申请号-分录序号(1 起)。
* 现仅 A103-3 预付款直接保存路径使用(序号固定 1);正式结算链路 A102-2 明细 awrp_entry_code 与
* A103-1 awrp_entry_code 已改为费用明细行id(KingdeeApBillSyncSupport.resolveEntryGroups 统一解析),
* A103-1 awrp_entry_code 同键(结算明细行单据号/结算合计费用项拼接,KingdeeApBillSyncSupport.resolveEntryGroups 统一解析),
* A103-1 awrp_finapbill_entryid 改为财务应付单付款计划 awrp_entry_code_p(A102-4 反查),禁止各接口自行拼接
*
* @param paymentNo 付款申请号