feat: A103-1下推结算方式按付款方式映射并本地组装勾稽键,应付分录含税金额兜底修复

This commit is contained in:
刘泉佳
2026-09-24 04:51:50 +08:00
parent 3516282510
commit ff96cc8544
5 changed files with 58 additions and 50 deletions
@@ -96,4 +96,12 @@ public interface IPaymentApplicationService extends BaseService<PaymentApplicati
* @param payBill A103-4 查询返回的付款处理单
*/
void applyPayBillRecord(PaymentApplication entity, BfiPayBillVO payBill);
/**
* 付款方式是否为汇票付款(按业务字典 pay_method 条目文本匹配"汇票")
*
* @param paymentMethod 付款方式(字典键或值)
* @return true 表示汇票类付款
*/
boolean isBillPayment(String paymentMethod);
}
@@ -43,12 +43,11 @@ public record KingdeeSyncContext(Map<String, String> cargoCodeByName, Map<Long,
Map<String, String> customerCodeCache, Map<Long, ProjectApply> projectCache,
Map<String, String> apBillNoCache,
Map<Long, List<FormalSettlementDetail>> settlementDetailCache,
Map<String, String> apBillPlanCodeCache,
Map<Long, FormalSettlement> settlementCache) {
/** 空上下文:缓存可写但初始为空,首条记录命中后全批复用 */
public static KingdeeSyncContext empty() {
return new KingdeeSyncContext(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
new HashMap<>(), new HashMap<>(), new HashMap<>());
}
}
@@ -33,6 +33,7 @@ import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.system.cache.DictBizCache;
import org.springblade.system.pojo.entity.DictBiz;
import org.springblade.thirdparty.bfi.constant.BfiConstant;
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushEntryDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyQueryDTO;
@@ -955,14 +956,18 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
// 付款申请分录与财务应付单明细分录一一对应(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-4 反查其付款计划分录 awrp_entry_code_p(按防重键缓存,同一结算单分录只反查一次)
String finapbillEntryid = kingdeeApBillSyncSupport.apBillPlanEntryCode(
settlementApBillSrcCode(entity, entryGroup.formalSettlementId(), context), entity.getPaymentNo(), context);
// 【必填】结算单分录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,与本标识无关)
@@ -1599,7 +1604,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
}
}
private boolean isBillPayment(String paymentMethod) {
@Override
public boolean isBillPayment(String paymentMethod) {
if (Func.isEmpty(paymentMethod)) return false;
List<DictBiz> paymentMethods = DictBizCache.getList("pay_method");
if (Func.isEmpty(paymentMethods)) return false;
@@ -38,12 +38,12 @@ import org.springblade.system.pojo.entity.Dept;
import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillQueryDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillSaveDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillSaveEntryDTO;
import org.springblade.thirdparty.bfi.pojo.vo.BfiApBillPlanVO;
import org.springblade.thirdparty.bfi.pojo.vo.BfiApBillVO;
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultItemVO;
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
import org.springblade.thirdparty.bfi.service.IBfiService;
import org.springblade.thirdparty.bfi.util.BfiUtil;
import org.springblade.transport.config.BfiProperties;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.CustomerArchiveMapper;
@@ -53,7 +53,6 @@ import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.config.BfiProperties;
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
@@ -163,40 +162,6 @@ public class KingdeeApBillSyncSupport {
return existBillNos;
}
/**
* A102-4 反查财务应付单付款计划分录的外部系统来源分录标识(awrp_entry_code_p)。
* A102-2 保存未传 planentity,由金蝶按明细自动汇总生成(awrp_entry_code_p 不传时自动取外部系统单据号),
* A103-1 应付下推的 awrp_finapbill_entryid 需填该值勾稽应付单;
* 结果按防重键(awrp_src_code)缓存,恢复场景避免重复远程查询
*
* @param apBillSrcCode 财务应付单防重键(外部系统来源单据号)
* @param subject 业务单据号(付款申请号,异常提示用)
* @param context 金蝶同步上下文(缓存载体)
* @return 付款计划分录 awrp_entry_code_p
*/
public String apBillPlanEntryCode(String apBillSrcCode, String subject, KingdeeSyncContext context) {
return context.apBillPlanCodeCache().computeIfAbsent(apBillSrcCode, code -> {
BfiApBillQueryDTO param = new BfiApBillQueryDTO();
param.setAwrpSrcCode(List.of(code));
param.setAwrpSrcSys(bfiProperties.getSrcSys());
List<BfiApBillVO> bills = BfiUtil.queryAllPages(pageNo -> {
param.setPageNo(pageNo);
param.setPageSize(PAY_RESULT_PAGE_SIZE);
return bfiService.queryApBill(param);
});
for (BfiApBillVO apBill : bills) {
if (Func.isNotEmpty(apBill.getPlanentity())) {
for (BfiApBillPlanVO plan : apBill.getPlanentity()) {
if (Func.isNotBlank(plan.getAwrpEntryCodeP())) {
return plan.getAwrpEntryCodeP();
}
}
}
}
throw new ServiceException("推送金蝶付款申请(应付下推)失败:财务应付单[" + subject + "]付款计划分录 awrp_entry_code_p 未查询到,无法勾稽关联");
});
}
// ==================== A102-2 财务应付单保存 ====================
/**
@@ -255,7 +220,8 @@ public class KingdeeApBillSyncSupport {
// param.setPurorgNumber(orgNumber);
// 【必填】款项性质.编码
param.setPaypropertyNumber(KingdeeBfiConstant.BFI_AP_BILL_PAY_PROPERTY);
// TODO 待确认:结算方式编码(settlementtype_number)与税率编码(taxrateid_number)需金蝶侧确认后补充,当前不传
// 结算方式.编码(settlementtype_number)不在应付单送:按付款方式映射的结算方式仅 A103-1 付款申请(应付下推)分录需要;
// 税率编码(taxrateid_number)仍待金蝶侧确认后补充
// 录入含税价(非必传)
param.setIsincludetax(true);
// 录入总价,为 true 后可不录入单价(非必传)
@@ -335,7 +301,8 @@ public class KingdeeApBillSyncSupport {
* 解析正式结算明细,逐行生成应付单分录(不聚合,每条来源费用行一条分录),分录携带所属正式结算单id
* (多结算单付款申请 A103-1 下推时按结算单分组勾稽各自应付单付款计划分录)。
* <p>取数来源:应付明细货物费用行(blade_receivable_payable_cargo_fee,经结算明细 sourceDetailId 关联应付明细,
* 每货物一条)——货物类型/计量单位/运输数量/含税金额直接取应付费用行(含税金额取调整后总金额 after_amount);
* 每货物一条)——货物类型/计量单位/运输数量/含税金额直接取应付费用行(含税金额取数链:调整后总金额 after_amount →
* 原总金额 → 单价×数量兜底,见 {@link #resolveFeeAmount});
* 应付费用行无不含税字段,不含税金额经结算货物费用行 sourceFeeId 回链(直接选明细路径 sourceFeeId=应付费用行id,
* 预结算合并路径 sourceFeeId=预结算费用行id,经预结算费用行 sourceFeeId 回溯应付费用行id);
* 结算明细对应应付明细无货物费用行时直接报错拦截(取数必须来自应付费用明细,无兜底)。物料编码:货物类型(支持“、”分隔多类型)
@@ -365,14 +332,15 @@ public class KingdeeApBillSyncSupport {
+ "]对应应付明细无货物费用行,分录取数必须来自应付费用明细,请先维护费用数据");
}
for (ReceivablePayableCargoFee payableFee : payableFees) {
String sourceDesc = "应付货物费用[" + Func.toStr(payableFee.getCargoName()) + " 第" + Func.toStr(payableFee.getLineNo()) + "行]";
appendEntryGroups(entryGroups, cargoCodeByName, settlementDetail.getFormalSettlementId(),
payableFee.getCargoType(),
resolveMeasureunitNumber(payableFee.getPriceUnit(), payableFee.getQuantityUnit()),
payableFee.getId(), money(payableFee.getUnitPrice()),
money(payableFee.getAfterAmount()),
resolveFeeAmount(payableFee, sourceDesc),
noTaxByPayableFeeId.getOrDefault(payableFee.getId(), BigDecimal.ZERO),
money(payableFee.getTransportQuantity()),
"应付货物费用[" + Func.toStr(payableFee.getCargoName()) + " 第" + Func.toStr(payableFee.getLineNo()) + "行]");
sourceDesc);
}
}
return entryGroups;
@@ -661,6 +629,31 @@ public class KingdeeApBillSyncSupport {
return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.HALF_UP);
}
/**
* 费用行含税金额取数链:优先调整后总金额(after_amount),未维护(null/0)回退原总金额(original_amount),
* 仍取不到按运输单价×运输量兜底(单据头 isincludetax=true 录入含税价,单价按含税口径相乘);
* 全部无法取值时拦截报错,避免分录价税合计送 0 被金蝶校验拦截("整单'价税合计'不能为空"),
* 应付/应收/收票单分录共用(应收单同步组件复用)
*
* @param payableFee 应收应付货物费用行
* @param sourceDesc 来源行描述,用于异常提示
* @return 该行含税金额(保留 2 位小数)
*/
public BigDecimal resolveFeeAmount(ReceivablePayableCargoFee payableFee, String sourceDesc) {
BigDecimal amount = payableFee.getAfterAmount();
if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) {
amount = payableFee.getOriginalAmount();
}
if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) {
amount = money(payableFee.getUnitPrice()).multiply(money(payableFee.getTransportQuantity()));
}
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("推送金蝶失败:" + sourceDesc
+ "未维护费用金额(调整后总金额/原总金额/单价×数量均无法取值),无法组装分录价税合计,请先维护费用数据");
}
return money(amount);
}
/**
* 财务应付单/付款申请分录:每条来源费用行一条(不聚合)
*
@@ -239,7 +239,8 @@ public class KingdeeArBillSyncSupport {
/**
* 解析正式结算明细,逐行生成应收单分录(不聚合,每条来源费用行一条分录),分录携带所属正式结算单id。
* <p>取数来源与应付单分录同源:结算明细 sourceDetailId 关联应收应付货物费用行(blade_receivable_payable_cargo_fee,
* 每货物一条)——货物类型/计量单位/运输数量/含税金额(调整后总金额 after_amount)直接取费用行;不含税金额经结算
* 每货物一条)——货物类型/计量单位/运输数量/含税金额直接取费用行(含税金额取数链:调整后总金额 after_amount →
* 原总金额 → 单价×数量兜底,见 {@link KingdeeApBillSyncSupport#resolveFeeAmount});不含税金额经结算
* 货物费用行 sourceFeeId 回链;无货物费用行时直接报错拦截。物料编码取第一种货物类型匹配货物类型档案货物编码;
* 分录唯一标识(awrp_entry_code)取费用明细行id。</p>
*
@@ -265,14 +266,15 @@ public class KingdeeArBillSyncSupport {
+ "]对应应收明细无货物费用行,分录取数必须来自应收费用明细,请先维护费用数据");
}
for (ReceivablePayableCargoFee fee : fees) {
String sourceDesc = "应收货物费用[" + Func.toStr(fee.getCargoName()) + " 第" + Func.toStr(fee.getLineNo()) + "行]";
appendArEntryGroups(entryGroups, cargoCodeByName, settlementDetail.getFormalSettlementId(),
fee.getCargoType(),
kingdeeApBillSyncSupport.resolveMeasureunitNumber(fee.getPriceUnit(), fee.getQuantityUnit()),
fee.getId(), kingdeeApBillSyncSupport.money(fee.getUnitPrice()),
kingdeeApBillSyncSupport.money(fee.getAfterAmount()),
kingdeeApBillSyncSupport.resolveFeeAmount(fee, sourceDesc),
noTaxByFeeId.getOrDefault(fee.getId(), BigDecimal.ZERO),
kingdeeApBillSyncSupport.money(fee.getTransportQuantity()),
"应收货物费用[" + Func.toStr(fee.getCargoName()) + " 第" + Func.toStr(fee.getLineNo()) + "行]");
sourceDesc);
}
}
return entryGroups;