fix: - 修复BFI单据保存结果反序列化失败导致错误信息丢失。
- 财务应付单保存A102-2,传业务字段。并保存id - 付款管理批量同步方法优化
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.constant;
|
||||
|
||||
/**
|
||||
* 运输业务固定值。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class TransportBusiConstant {
|
||||
|
||||
/** 付款类型:结算付款(关联正式结算单的正式付款)。 */
|
||||
public static final String PAYMENT_TYPE_SETTLEMENT = "settlement_payment";
|
||||
|
||||
private TransportBusiConstant() {
|
||||
}
|
||||
}
|
||||
+28
@@ -43,8 +43,36 @@ public interface IPaymentApplicationService extends BaseService<PaymentApplicati
|
||||
void returnBill(PaymentApplicationStatusRequest request);
|
||||
void voidBill(PaymentApplicationStatusRequest request);
|
||||
String syncKingdee(Long id);
|
||||
|
||||
/**
|
||||
* 同步单笔付款申请到金蝶(携带批量共享上下文,消除重复档案查询),编排层无事务:
|
||||
* 单据级事务在 {@link #doSyncKingdee(Long, KingdeeSyncContext)} 中,失败状态落库在事务回滚后执行
|
||||
*
|
||||
* @param id 付款申请id
|
||||
* @param context 批量共享上下文(档案缓存),单笔调用传 {@link KingdeeSyncContext#empty()}
|
||||
* @return 金蝶单据id(存量数据兜底返回金蝶单据编号)
|
||||
*/
|
||||
String syncKingdee(Long id, KingdeeSyncContext context);
|
||||
|
||||
List<String> syncKingdeeBatch(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 单据级事务执行金蝶推送(由 syncKingdee 经自代理调用,保证整批同步不共用大事务)
|
||||
*
|
||||
* @param id 付款申请id
|
||||
* @param context 批量共享上下文(档案缓存)
|
||||
* @return 金蝶单据id
|
||||
*/
|
||||
String doSyncKingdee(Long id, KingdeeSyncContext context);
|
||||
|
||||
/**
|
||||
* 独立事务落"生成失败"状态(在推送事务回滚、行锁释放后执行);
|
||||
* 仅更新审批通过的单据,未审批/已删除单据自动跳过,保持原状态
|
||||
*
|
||||
* @param id 付款申请id
|
||||
*/
|
||||
void markKingdeeSyncFailed(Long id);
|
||||
|
||||
/**
|
||||
* 定时回写金蝶付款结果(A103-2 付款申请单查询 / A103-4 付款处理查询)
|
||||
*
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.service;
|
||||
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 金蝶同步上下文:批量同步时全批共享的档案缓存,消除逐单重复的远程/数据库查询(N+1)。
|
||||
* <p>单笔同步使用 {@link #empty()},各缓存为空时自动回退为逐单查询,行为与原逻辑一致。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public record KingdeeSyncContext(Map<String, String> cargoCodeByName, Map<Long, ContractManage> contractCache,
|
||||
Map<String, String> customerCodeCache, Map<Long, ProjectApply> projectCache) {
|
||||
|
||||
/** 空上下文:缓存可写但初始为空,首条记录命中后全批复用 */
|
||||
public static KingdeeSyncContext empty() {
|
||||
return new KingdeeSyncContext(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -35,6 +35,7 @@ import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationInvoiceMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
|
||||
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
|
||||
import org.springblade.transport.mapper.SettlementAdjustmentMapper;
|
||||
@@ -233,7 +234,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
query.or(wrapper -> wrapper.in(PaymentApplication::getId, relationApplicationIds));
|
||||
}
|
||||
})
|
||||
.eq(PaymentApplication::getPaymentType, "settlement_payment")
|
||||
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
|
||||
.eq(PaymentApplication::getIsDeleted, 0);
|
||||
vo.setPaymentApplications(paymentApplicationMapper.selectList(paymentApplicationQuery
|
||||
.orderByDesc(PaymentApplication::getCreateTime)).stream()
|
||||
@@ -1175,7 +1176,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
private PaymentSummary calculatePaymentSummary(FormalSettlement settlement) {
|
||||
LambdaQueryWrapper<PaymentApplication> query = Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.eq(PaymentApplication::getSettlementId, settlement.getId())
|
||||
.eq(PaymentApplication::getPaymentType, "settlement_payment")
|
||||
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
|
||||
.eq(PaymentApplication::getIsDeleted, 0);
|
||||
List<PaymentApplicationSettlement> relations = paymentApplicationSettlementMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
|
||||
+132
-85
@@ -33,6 +33,7 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.SpringUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.system.cache.DictBizCache;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.feign.ISysClient;
|
||||
@@ -95,7 +96,9 @@ import org.springblade.transport.pojo.entity.TemporaryCreditLimit;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springblade.transport.service.KingdeeSyncContext;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.support.ExternalSrcCodeSupport;
|
||||
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -201,7 +204,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
public PaymentApplicationReferenceAmountVO referenceAmount(String paymentType, Long referenceId, Long excludeId) {
|
||||
if (referenceId == null) throw new ServiceException("结算单不能为空");
|
||||
BigDecimal settlementAmount;
|
||||
if ("settlement_payment".equals(paymentType)) {
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(paymentType)) {
|
||||
FormalSettlement settlement = formalSettlementMapper.selectById(referenceId);
|
||||
if (settlement == null || Objects.equals(settlement.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("正式结算单不存在");
|
||||
@@ -263,7 +266,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
matchedInvoiceAmount = matchedInvoiceAmount.add(money(item.getMatchedAmount()));
|
||||
}
|
||||
entity.setMatchedInvoiceAmount(matchedInvoiceAmount);
|
||||
if ("settlement_payment".equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
&& request.getSettlementIds() != null && !request.getSettlementIds().isEmpty()) {
|
||||
request.setSettlementId(request.getSettlementIds().get(0));
|
||||
}
|
||||
@@ -353,8 +356,30 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String syncKingdee(Long id) {
|
||||
return syncKingdee(id, KingdeeSyncContext.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String syncKingdee(Long id, KingdeeSyncContext context) {
|
||||
try {
|
||||
// 单据级事务经自代理调用:单条失败只回滚自身,避免批量同步共用大事务长持锁
|
||||
return self().doSyncKingdee(id, context);
|
||||
} catch (ServiceException exception) {
|
||||
log.error("付款申请[{}]推送金蝶付款申请单异常", id, exception);
|
||||
// 推送事务已回滚、行锁已释放,此处独立事务落"生成失败"状态
|
||||
self().markKingdeeSyncFailed(id);
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
log.error("付款申请[{}]推送金蝶付款申请单异常", id, exception);
|
||||
self().markKingdeeSyncFailed(id);
|
||||
throw new ServiceException("推送金蝶付款申请失败:" + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String doSyncKingdee(Long id, KingdeeSyncContext context) {
|
||||
PaymentApplication entity = lockedPayment(id);
|
||||
if (!APPROVED.equals(entity.getApprovalStatus())) throw new ServiceException("仅审批通过的付款申请允许生成金蝶单据");
|
||||
if (KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(entity.getKingdeeStatus())
|
||||
@@ -365,32 +390,26 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return Func.isNotEmpty(entity.getKingdeeBillId()) ? entity.getKingdeeBillId() : entity.getKingdeeBillNo();
|
||||
}
|
||||
String kingdeeBillId;
|
||||
try {
|
||||
if ("settlement_payment".equals(entity.getPaymentType())) {
|
||||
// 单据类型路由:按正式结算单结算类型——应付对应财务应付单(A102-2),应收对应财务应收单(A302-2,推送规则待业务确认)
|
||||
requirePayableSettlement(entity);
|
||||
// 正式结算付款:先推财务应付单(A102-2,仅校验保存成功,应付单id不落库,
|
||||
// 后续如需可经 A102-4 按 awrp_src_sys + awrp_src_code 查询),再由应付下推付款申请(A103-1)
|
||||
// 分录按结算明细货物编码+计量单位分组拆分(物料编码取货物类型档案编码、计量单位暂传货物单位、
|
||||
// 数量取组内运输数量合计),财务应付单 detailentry 与付款申请 entry 一一对应
|
||||
BfiEntryGroupResult entryGroupResult = resolveBfiEntryGroups(entity);
|
||||
pushBfiApBill(entity, entryGroupResult.entryGroups(), entryGroupResult.documentNos());
|
||||
kingdeeBillId = pushBfiPayApplyPush(entity, entryGroupResult.entryGroups());
|
||||
} else {
|
||||
// 预付款:直接推付款申请单(A103-3),不绑应付单
|
||||
kingdeeBillId = pushBfiPayApply(entity);
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(entity.getPaymentType())) {
|
||||
// 单据类型路由:按正式结算单结算类型——应付对应财务应付单(A102-2),应收对应财务应收单(A302-2,推送规则待业务确认)
|
||||
requirePayableSettlement(entity);
|
||||
// 正式结算付款:先推财务应付单(A102-2,仅校验保存成功,应付单id不落库,
|
||||
// 后续如需可经 A102-4 按 awrp_src_sys + awrp_src_code 查询),再由应付下推付款申请(A103-1)
|
||||
// 分录按结算明细货物编码+计量单位分组拆分(物料编码取货物类型档案编码、计量单位暂传货物单位、
|
||||
// 数量取组内运输数量合计),财务应付单 detailentry 与付款申请 entry 一一对应
|
||||
BfiEntryGroupResult entryGroupResult = resolveBfiEntryGroups(entity, context);
|
||||
String apBillNo = pushBfiApBill(entity, entryGroupResult.entryGroups(), entryGroupResult.documentNos(), context);
|
||||
// 财务应付单单据编号即时回填 kingdeeBillNo(后续付款申请单编号经 A103-2 定时回写任务覆盖)
|
||||
if (Func.isNotEmpty(apBillNo)) {
|
||||
entity.setKingdeeBillNo(apBillNo);
|
||||
}
|
||||
} catch (ServiceException exception) {
|
||||
log.error("付款申请[{}]推送金蝶付款申请单异常", entity.getPaymentNo(), exception);
|
||||
markSyncFailed(entity);
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
log.error("付款申请[{}]推送金蝶付款申请单异常", entity.getPaymentNo(), exception);
|
||||
markSyncFailed(entity);
|
||||
throw new ServiceException("推送金蝶付款申请失败:" + exception.getMessage());
|
||||
kingdeeBillId = pushBfiPayApplyPush(entity, entryGroupResult.entryGroups(), context);
|
||||
} else {
|
||||
// 预付款:直接推付款申请单(A103-3),不绑应付单
|
||||
kingdeeBillId = pushBfiPayApply(entity, context);
|
||||
}
|
||||
// 星瀚单据内部主键落 kingdeeBillId(附件上传等使用);
|
||||
// 金蝶单据编号(billno)与付款状态由定时回写任务经 A103-2/A103-4 查询回填
|
||||
// 财务应付单单据编号已在推送时回填 kingdeeBillNo,付款申请单编号与付款状态由定时回写任务经 A103-2/A103-4 查询回填
|
||||
entity.setKingdeeBillId(kingdeeBillId);
|
||||
entity.setKingdeeStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
|
||||
// 付款完成模拟逻辑已停用(mockPaymentRecords / 履约付款数据直接置 paid),
|
||||
@@ -405,14 +424,28 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void markKingdeeSyncFailed(Long id) {
|
||||
if (id == null) return;
|
||||
// 条件更新:仅审批通过的单据落"生成失败"状态,未审批/已删除单据自动跳过保持原状态
|
||||
baseMapper.update(null, Wrappers.<PaymentApplication>lambdaUpdate()
|
||||
.eq(PaymentApplication::getId, id)
|
||||
.eq(PaymentApplication::getApprovalStatus, APPROVED)
|
||||
.set(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> syncKingdeeBatch(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) throw new ServiceException("请至少选择一条审批通过的付款申请");
|
||||
List<Long> distinctIds = ids.stream().filter(Objects::nonNull).distinct().toList();
|
||||
if (distinctIds.isEmpty()) throw new ServiceException("请至少选择一条审批通过的付款申请");
|
||||
// 全批共享档案缓存(货物类型/合同/客商/项目),首单加载后全批复用,消除逐单重复查询(N+1)
|
||||
KingdeeSyncContext context = KingdeeSyncContext.empty();
|
||||
IPaymentApplicationService self = SpringUtil.getBean(IPaymentApplicationService.class);
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Long id : distinctIds) {
|
||||
try {
|
||||
result.add(syncKingdee(id));
|
||||
// 经自代理逐条调用:单据级事务,单条失败仅回滚自身且不中断整批
|
||||
result.add(self.syncKingdee(id, context));
|
||||
} catch (Exception exception) {
|
||||
result.add("付款申请[" + id + "]同步失败:" + exception.getMessage());
|
||||
}
|
||||
@@ -420,6 +453,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 自代理:跨方法调用确保事务注解生效(批量逐条事务、失败状态独立事务) */
|
||||
private IPaymentApplicationService self() {
|
||||
return SpringUtil.getBean(IPaymentApplicationService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String syncKingdeePaymentResult() {
|
||||
// ① 待回写付款申请:审批通过且金蝶状态为 已生成/付款中(终态已付款/已关闭不再轮询)
|
||||
@@ -485,7 +523,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
if (Func.isBlank(orgNumber)) {
|
||||
try {
|
||||
// A103-4 付款人(公司)编码必填:优先取 A103-2 返回付款组织编码,兜底按合同所属组织解析
|
||||
orgNumber = bfiOrgNumber(requireContract(application));
|
||||
orgNumber = bfiOrgNumber(requireContract(application, KingdeeSyncContext.empty()));
|
||||
} catch (Exception exception) {
|
||||
log.warn("付款申请[{}]付款处理查询跳过,付款组织编码获取失败:{}", application.getPaymentNo(), exception.getMessage());
|
||||
continue;
|
||||
@@ -702,8 +740,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
* @param entity 付款申请实体
|
||||
* @return 金蝶付款申请单id
|
||||
*/
|
||||
private String pushBfiPayApply(PaymentApplication entity) {
|
||||
ContractManage contract = requireContract(entity);
|
||||
private String pushBfiPayApply(PaymentApplication entity, KingdeeSyncContext context) {
|
||||
ContractManage contract = requireContract(entity, context);
|
||||
String orgNumber = bfiOrgNumber(contract);
|
||||
String currency = bfiCurrency(contract);
|
||||
String applyDateText = (entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()).format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
@@ -733,10 +771,10 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// }
|
||||
|
||||
BfiPayApplySaveEntryDTO entry = new BfiPayApplySaveEntryDTO();
|
||||
entry.setEPaymenttypeNumber("settlement_payment".equals(entity.getPaymentType()) ? KingdeeBfiConstant.BFI_PAYMENT_TYPE_FORMAL : KingdeeBfiConstant.BFI_PAYMENT_TYPE_PREPAY);
|
||||
entry.setEPaymenttypeNumber(TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(entity.getPaymentType()) ? KingdeeBfiConstant.BFI_PAYMENT_TYPE_FORMAL : KingdeeBfiConstant.BFI_PAYMENT_TYPE_PREPAY);
|
||||
entry.setEAsstacttype(bfiAsstactType(contract));
|
||||
// 往来户.编码:合同乙方关联客商档案客商编码
|
||||
entry.setEAsstactNumber(bfiAsstactNumber(contract));
|
||||
entry.setEAsstactNumber(bfiAsstactNumber(contract, context));
|
||||
entry.setEAssacct(entity.getBankAccount());
|
||||
entry.setEBebankNumber(entity.getBankName());
|
||||
// 应付金额
|
||||
@@ -744,7 +782,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 申请金额
|
||||
entry.setEApplyamount(applyAmount);
|
||||
// entry.setEExpaydate(applyDateText);
|
||||
entry.setProjectNumber(bfiProjectNumber(entity));
|
||||
entry.setProjectNumber(bfiProjectNumber(entity, context));
|
||||
entry.setAwrpEntryCode(entity.getPaymentNo() + "-1");
|
||||
entry.setERemark(entity.getRemark());
|
||||
param.setEntry(List.of(entry));
|
||||
@@ -777,9 +815,10 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
* @param entity 付款申请实体
|
||||
* @param entryGroups 结算明细聚合出的应付单分录组
|
||||
* @param documentNos 结算明细单据号(去重),用于拼接外部系统来源单据号
|
||||
* @return 财务应付单金蝶单据编号(返回结果 number 字段),用于回填 kingdeeBillNo
|
||||
*/
|
||||
private void pushBfiApBill(PaymentApplication entity, List<BfiEntryGroup> entryGroups, List<String> documentNos) {
|
||||
ContractManage contract = requireContract(entity);
|
||||
private String pushBfiApBill(PaymentApplication entity, List<BfiEntryGroup> entryGroups, List<String> documentNos, KingdeeSyncContext context) {
|
||||
ContractManage contract = requireContract(entity, context);
|
||||
// 单据类型按映射字典转换:默认服务采购应付,金蝶确认其他类型后修改 BFI_AP_BILL_BILLTYPE_NAME 即可
|
||||
String billtypeNumber = KingdeeBfiConstant.billtypeBy(KingdeeBfiConstant.AP_BILL_BILLTYPE_MAP, KingdeeBfiConstant.BFI_AP_BILL_BILLTYPE_NAME);
|
||||
if (Func.isBlank(billtypeNumber)) {
|
||||
@@ -788,8 +827,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
String orgNumber = bfiOrgNumber(contract);
|
||||
// 币种取合同结算币种
|
||||
String currency = bfiCurrency(contract);
|
||||
// TODO 待确认:日期格式沿用现有 A103-3 的 yyyyMMdd,若金蝶校验要求 yyyy-MM-dd 再统一调整
|
||||
String billDateText = (entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()).format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
// 单据日期:yyyy-MM-dd 00:00:00
|
||||
String billDateText = (entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()).atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
BfiApBillSaveDTO param = new BfiApBillSaveDTO();
|
||||
// 【必填】单据类型.编码
|
||||
param.setBilltypeidNumber(billtypeNumber);
|
||||
@@ -800,7 +839,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 【必填】往来类型:合同类别为“客户合同”时为 bd_customer(客户),否则 bd_supplier(供应商)
|
||||
param.setAsstacttype(bfiAsstactType(contract));
|
||||
// 【必填】往来户.编码:合同乙方关联客商档案客商编码
|
||||
param.setAsstactNumber(bfiAsstactNumber(contract));
|
||||
param.setAsstactNumber(bfiAsstactNumber(contract, context));
|
||||
// 【必填】付款方式(CASH:现购, CREDIT:赊购)
|
||||
param.setPurmode(KingdeeBfiConstant.BFI_AP_BILL_PUR_MODE_CASH);
|
||||
// 【必填】结算币别.货币代码
|
||||
@@ -828,10 +867,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 【必填】外部系统编号(候选键)
|
||||
param.setAwrpSrcSys(bfiPayApplySrcSys);
|
||||
// 【必填】外部系统来源单据号(候选键):付款申请号_结算明细单据号1_单据号2...
|
||||
List<String> awrpSrcCodeParts = new ArrayList<>();
|
||||
awrpSrcCodeParts.add(entity.getPaymentNo());
|
||||
awrpSrcCodeParts.addAll(documentNos);
|
||||
param.setAwrpSrcCode(String.join("_", awrpSrcCodeParts));
|
||||
param.setAwrpSrcCode(ExternalSrcCodeSupport.buildSrcCode(entity.getPaymentNo(), documentNos));
|
||||
// 来源单据(非必传,有上游合同时传 awrp_contract_info)
|
||||
// param.setAwrpSrcBt(KingdeeBfiConstant.BFI_CONTRACT_SRC_BT);
|
||||
// 上游外部系统单据号/上游合同号(非必传)
|
||||
@@ -861,6 +897,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 【必填】外部系统来源分录标识(候选键),A103-1 下推时以 awrp_finapbill_entryid 关联此行
|
||||
entry.setAwrpEntryCode(entity.getPaymentNo() + "-" + (index + 1));
|
||||
|
||||
// 调试
|
||||
|
||||
if (true){
|
||||
// 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
|
||||
entry.setMaterialNumber(KingdeeBfiConstant.BFI_AP_BILL_MATERIAL_NUMBER_TEST);
|
||||
@@ -877,14 +915,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 付款计划 planentity 不传,由金蝶按明细自动汇总生成
|
||||
// TODO 待业务确认:存在预付款抵扣时是否需传预付信息 preentry(y_billtype/y_billid/y_settleamt),本期不传、后续迭代
|
||||
|
||||
// 调试
|
||||
// if (true) {
|
||||
// param.setm
|
||||
// }
|
||||
|
||||
|
||||
BfiBillSaveResultVO saveResult = bfiService.saveApBill(param);
|
||||
// 应付单id不落库,后续如需可经 A102-4 按防重键(awrp_src_sys + awrp_src_code)查询
|
||||
requireBfiSaveSuccess(saveResult, "推送金蝶财务应付单");
|
||||
return saveResult.getResult().get(0).getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -895,11 +931,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
* @param entryGroups 结算明细聚合出的应付单分录组
|
||||
* @return 星瀚付款申请单id
|
||||
*/
|
||||
private String pushBfiPayApplyPush(PaymentApplication entity, List<BfiEntryGroup> entryGroups) {
|
||||
ContractManage contract = requireContract(entity);
|
||||
private String pushBfiPayApplyPush(PaymentApplication entity, List<BfiEntryGroup> entryGroups, KingdeeSyncContext context) {
|
||||
ContractManage contract = requireContract(entity, context);
|
||||
String orgNumber = bfiOrgNumber(contract);
|
||||
// TODO 待确认:日期格式沿用现有 A103-3 的 yyyyMMdd,若金蝶校验要求 yyyy-MM-dd 再统一调整
|
||||
String applyDateText = (entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()).format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
// 申请日期:yyyy-MM-dd 00:00:00(与 A102-2 单据日期格式统一)
|
||||
String applyDateText = (entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()).atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
BfiPayApplyPushDTO param = new BfiPayApplyPushDTO();
|
||||
param.setAwrpSrcCode(entity.getPaymentNo());
|
||||
param.setAwrpSrcSys(bfiPayApplySrcSys);
|
||||
@@ -928,7 +964,9 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 申请金额:与对应应付单分录结算金额(含税)保持一致
|
||||
entry.setEApplyamount(entryGroup.amount());
|
||||
entry.setEExpaydate(applyDateText);
|
||||
entry.setProjectNumber(bfiProjectNumber(entity));
|
||||
// entry.setProjectNumber(bfiProjectNumber(entity));
|
||||
// 【必填】项目.项目编码(联调期临时传金蝶测试档案值)
|
||||
entry.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
|
||||
entry.setERemark(entity.getRemark());
|
||||
entry.setAwrpSrcType(bfiPayApplySrcType);
|
||||
entries.add(entry);
|
||||
@@ -970,7 +1008,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
* @param entity 付款申请实体
|
||||
* @return 应付单分录解析结果(分录组 + 结算明细单据号)
|
||||
*/
|
||||
private BfiEntryGroupResult resolveBfiEntryGroups(PaymentApplication entity) {
|
||||
private BfiEntryGroupResult resolveBfiEntryGroups(PaymentApplication entity, KingdeeSyncContext context) {
|
||||
List<FormalSettlementDetail> settlementDetails = formalSettlementDetails(entity);
|
||||
if (Func.isEmpty(settlementDetails)) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败:正式结算单无结算明细,无法解析物料与计量单位");
|
||||
@@ -982,7 +1020,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.toList();
|
||||
Map<String, String> cargoCodeByName = cargoCodeByName();
|
||||
Map<String, String> cargoCodeByName = cargoCodeByName(context);
|
||||
LinkedHashMap<String, BfiEntryGroup> groupMap = new LinkedHashMap<>();
|
||||
// 优先按货物费用行逐条取数(blade_formal_settlement_detail_fee 每货物一条,金额为结算调整后的精确值)
|
||||
List<FormalSettlementDetailFee> detailFees = formalSettlementDetailFees(settlementDetails);
|
||||
@@ -1075,25 +1113,30 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
.orderByAsc(FormalSettlementDetail::getLineNo));
|
||||
}
|
||||
|
||||
/** 货物类型名称->货物编码映射(取自货物类型档案;名称重复且编码不一致时拦截) */
|
||||
private Map<String, String> cargoCodeByName() {
|
||||
/** 货物类型名称->货物编码映射(取自货物类型档案;名称重复且编码不一致时拦截;批量同步时全批共享一次加载结果) */
|
||||
private Map<String, String> cargoCodeByName(KingdeeSyncContext context) {
|
||||
Map<String, String> cache = context.cargoCodeByName();
|
||||
if (!cache.isEmpty()) {
|
||||
return cache;
|
||||
}
|
||||
R<List<CargoType>> response = sysClient.getCargoTypes();
|
||||
if (response == null || !response.isSuccess() || Func.isEmpty(response.getData())) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败:货物类型档案读取失败,请稍后重试");
|
||||
}
|
||||
Map<String, String> cargoCodeByName = new HashMap<>();
|
||||
Map<String, String> loaded = new HashMap<>();
|
||||
for (CargoType cargoType : response.getData()) {
|
||||
if (cargoType == null || Func.isBlank(cargoType.getCargoName()) || Func.isBlank(cargoType.getCargoCode())) {
|
||||
continue;
|
||||
}
|
||||
String cargoName = cargoType.getCargoName().trim();
|
||||
String cargoCode = cargoType.getCargoCode().trim();
|
||||
String existingCode = cargoCodeByName.putIfAbsent(cargoName, cargoCode);
|
||||
String existingCode = loaded.putIfAbsent(cargoName, cargoCode);
|
||||
if (existingCode != null && !existingCode.equals(cargoCode)) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败:货物类型[" + cargoName + "]在货物类型档案中存在多个编码,请检查档案数据");
|
||||
}
|
||||
}
|
||||
return cargoCodeByName;
|
||||
cache.putAll(loaded);
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1150,7 +1193,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
BfiBillSaveResultItemVO item = saveResult.getResult().get(0);
|
||||
if (!Boolean.TRUE.equals(item.getBillStatus())) {
|
||||
String errorText = Func.isEmpty(item.getErrors()) ? "未知错误" : String.join(";", item.getErrors());
|
||||
String errorText = Func.isBlank(item.getErrorText()) ? "未知错误" : item.getErrorText();
|
||||
throw new ServiceException(action + "失败:" + errorText);
|
||||
}
|
||||
}
|
||||
@@ -1169,14 +1212,10 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return String.valueOf(billId);
|
||||
}
|
||||
|
||||
private void markSyncFailed(PaymentApplication entity) {
|
||||
entity.setKingdeeStatus(KingdeeBfiConstant.KINGDEE_STATUS_FAILED);
|
||||
updateById(entity);
|
||||
}
|
||||
|
||||
private ContractManage requireContract(PaymentApplication entity) {
|
||||
private ContractManage requireContract(PaymentApplication entity, KingdeeSyncContext context) {
|
||||
if (entity.getContractId() == null) throw new ServiceException("推送金蝶付款申请失败,付款申请未关联合同");
|
||||
ContractManage contract = contractManageMapper.selectById(entity.getContractId());
|
||||
// 批量同步时按合同id缓存复用(同一合同只查一次),单笔同步退化为直接查询
|
||||
ContractManage contract = context.contractCache().computeIfAbsent(entity.getContractId(), contractManageMapper::selectById);
|
||||
if (contract == null || Objects.equals(contract.getIsDeleted(), 1)) throw new ServiceException("推送金蝶付款申请失败,关联合同不存在");
|
||||
return contract;
|
||||
}
|
||||
@@ -1206,7 +1245,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return switch (paymentType == null ? "" : paymentType) {
|
||||
case "project_advance" -> "项目预付款申请";
|
||||
case "progress_advance" -> "进度预付款申请";
|
||||
case "settlement_payment" -> "结算付款申请";
|
||||
case TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT -> "结算付款申请";
|
||||
default -> "付款申请";
|
||||
};
|
||||
}
|
||||
@@ -1225,22 +1264,30 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
/** 财务应付单往来户.编码:合同乙方关联客商档案取客商编码,未维护时拦截 */
|
||||
private String bfiAsstactNumber(ContractManage contract) {
|
||||
private String bfiAsstactNumber(ContractManage contract, KingdeeSyncContext context) {
|
||||
if (Func.isBlank(contract.getPartyB())) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败,合同乙方不能为空,请检查合同[" + Func.toStr(contract.getContractNo()) + "]乙方信息");
|
||||
}
|
||||
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 || Func.isBlank(customer.getCustomerCode())) {
|
||||
// 批量同步时按乙方名称缓存客商编码(同批同一乙方只查一次),单笔同步退化为直接查询
|
||||
String customerCode = context.customerCodeCache().computeIfAbsent(contract.getPartyB(), this::queryCustomerCode);
|
||||
if (Func.isBlank(customerCode)) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败,合同乙方[" + contract.getPartyB() + "]未在客商档案中维护客商编码,请补充后重试");
|
||||
}
|
||||
return customer.getCustomerCode();
|
||||
return customerCode;
|
||||
}
|
||||
|
||||
private String bfiProjectNumber(PaymentApplication entity) {
|
||||
/** 按乙方名称(全称或简称)查询客商编码,未维护返回 null */
|
||||
private String queryCustomerCode(String partyB) {
|
||||
CustomerArchive customer = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
|
||||
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, partyB).or().eq(CustomerArchive::getShortName, partyB))
|
||||
.eq(CustomerArchive::getIsDeleted, 0).last("limit 1"));
|
||||
return customer == null ? null : customer.getCustomerCode();
|
||||
}
|
||||
|
||||
private String bfiProjectNumber(PaymentApplication entity, KingdeeSyncContext context) {
|
||||
if (entity.getProjectId() == null) return KingdeeBfiConstant.BFI_PROJECT_NUMBER_NONE;
|
||||
ProjectApply project = projectApplyMapper.selectById(entity.getProjectId());
|
||||
// 批量同步时按项目id缓存复用(同一项目只查一次),单笔同步退化为直接查询
|
||||
ProjectApply project = context.projectCache().computeIfAbsent(entity.getProjectId(), projectApplyMapper::selectById);
|
||||
return project == null || Func.isEmpty(project.getProjectCode()) ? KingdeeBfiConstant.BFI_PROJECT_NUMBER_NONE : project.getProjectCode();
|
||||
}
|
||||
|
||||
@@ -1344,7 +1391,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
private void syncFormalSettlementPayments(PaymentApplication entity) {
|
||||
if (!"settlement_payment".equals(entity.getPaymentType())) return;
|
||||
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(entity.getPaymentType())) return;
|
||||
List<PaymentApplicationSettlement> relations = settlementRelationMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId())
|
||||
@@ -1392,7 +1439,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
|
||||
private void validateRequest(PaymentApplicationSaveRequest request) {
|
||||
if (Func.isEmpty(request.getPaymentType())) throw new ServiceException("付款类型不能为空");
|
||||
if (!List.of("project_advance", "progress_advance", "settlement_payment").contains(request.getPaymentType())) throw new ServiceException("付款类型不合法");
|
||||
if (!List.of("project_advance", "progress_advance", TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT).contains(request.getPaymentType())) throw new ServiceException("付款类型不合法");
|
||||
if (Func.isEmpty(request.getPaymentMethod())) throw new ServiceException("付款方式不能为空");
|
||||
if (isBillPayment(request.getPaymentMethod()) && request.getBillLedgerId() == null) throw new ServiceException("汇票付款必须选择汇票台账");
|
||||
if (request.getPaymentRatio() != null && (request.getPaymentRatio().compareTo(BigDecimal.ZERO) < 0 || request.getPaymentRatio().compareTo(BigDecimal.valueOf(100)) > 0)) throw new ServiceException("付款比例必须在0-100之间");
|
||||
@@ -1401,7 +1448,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
&& request.getPreSettlementId() == null && Func.isEmpty(request.getSettlementIds())) throw new ServiceException("非项目预付必须关联结算单");
|
||||
if ("project_advance".equals(request.getPaymentType()) && request.getProjectId() == null) throw new ServiceException("项目预付必须选择所属项目");
|
||||
if ("progress_advance".equals(request.getPaymentType()) && request.getPreSettlementId() == null) throw new ServiceException("进度预付必须关联预结算单");
|
||||
if ("settlement_payment".equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
&& Func.isEmpty(request.getSettlementIds())) throw new ServiceException("结算付款必须关联正式结算单");
|
||||
}
|
||||
|
||||
@@ -1483,7 +1530,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
private List<FormalSettlement> resolveFormalSettlements(PaymentApplicationSaveRequest request, PaymentApplication entity) {
|
||||
if (!"settlement_payment".equals(request.getPaymentType())) return List.of();
|
||||
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType())) return List.of();
|
||||
List<Long> ids = request.getSettlementIds() == null ? List.of() : request.getSettlementIds();
|
||||
if (ids.isEmpty() && request.getSettlementId() != null) ids = List.of(request.getSettlementId());
|
||||
ids = ids.stream().filter(Objects::nonNull).distinct().toList();
|
||||
@@ -1500,7 +1547,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
private boolean hasMultipleFormalReferences(PaymentApplicationSaveRequest request) {
|
||||
return "settlement_payment".equals(request.getPaymentType()) && request.getSettlementIds() != null
|
||||
return TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) && request.getSettlementIds() != null
|
||||
&& request.getSettlementIds().stream().filter(Objects::nonNull).distinct().count() > 1;
|
||||
}
|
||||
|
||||
@@ -1508,7 +1555,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
BigDecimal settlementAmount = settlements.stream().map(FormalSettlement::getSettlementAmount)
|
||||
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal payableAmount = settlements.stream()
|
||||
.map(item -> money(item.getSettlementAmount()).subtract(cumulativeAppliedAmount("settlement_payment", item.getId(), entity.getId())).max(BigDecimal.ZERO))
|
||||
.map(item -> money(item.getSettlementAmount()).subtract(cumulativeAppliedAmount(TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT, item.getId(), entity.getId())).max(BigDecimal.ZERO))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
if (money(entity.getAppliedAmount()).compareTo(payableAmount) > 0) throw new ServiceException("申请付款金额不能超过所选结算单可付款金额合计");
|
||||
FormalSettlement first = settlements.get(0);
|
||||
@@ -1523,14 +1570,14 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId()));
|
||||
BigDecimal totalBase = settlements.stream().map(item -> money(item.getSettlementAmount()).subtract(
|
||||
cumulativeAppliedAmount("settlement_payment", item.getId(), entity.getId())).max(BigDecimal.ZERO))
|
||||
cumulativeAppliedAmount(TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT, item.getId(), entity.getId())).max(BigDecimal.ZERO))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal remainingApplied = money(entity.getAppliedAmount());
|
||||
BigDecimal remainingPaid = money(entity.getPaidAmount());
|
||||
for (int index = 0; index < settlements.size(); index++) {
|
||||
FormalSettlement settlement = settlements.get(index);
|
||||
BigDecimal base = money(settlement.getSettlementAmount()).subtract(
|
||||
cumulativeAppliedAmount("settlement_payment", settlement.getId(), entity.getId())).max(BigDecimal.ZERO);
|
||||
cumulativeAppliedAmount(TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT, settlement.getId(), entity.getId())).max(BigDecimal.ZERO);
|
||||
BigDecimal applied = index == settlements.size() - 1 ? remainingApplied
|
||||
: money(entity.getAppliedAmount()).multiply(base).divide(totalBase, 2, RoundingMode.DOWN);
|
||||
BigDecimal paid = index == settlements.size() - 1 ? remainingPaid
|
||||
@@ -1586,7 +1633,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
|
||||
private BigDecimal cumulativeAppliedAmount(String paymentType, Long referenceId, Long excludeId) {
|
||||
LambdaQueryWrapper<PaymentApplication> wrapper = Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.eq("settlement_payment".equals(paymentType), PaymentApplication::getSettlementId, referenceId)
|
||||
.eq(TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(paymentType), PaymentApplication::getSettlementId, referenceId)
|
||||
.eq("progress_advance".equals(paymentType), PaymentApplication::getPreSettlementId, referenceId)
|
||||
.ne(excludeId != null, PaymentApplication::getId, excludeId);
|
||||
return sumApplied(wrapper);
|
||||
@@ -1701,7 +1748,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
}
|
||||
private void validateNoTailPayment(PaymentApplication entity) {
|
||||
if (!"settlement_payment".equals(entity.getPaymentType())) return;
|
||||
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(entity.getPaymentType())) return;
|
||||
List<Long> formalSettlementIds = new java.util.ArrayList<>();
|
||||
if (entity.getSettlementId() != null) formalSettlementIds.add(entity.getSettlementId());
|
||||
formalSettlementIds.addAll(settlementRelationMapper.selectList(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
|
||||
+3
-2
@@ -9,6 +9,7 @@ import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementDetailMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementChangeRecordMapper;
|
||||
@@ -337,7 +338,7 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
result.addAll(paymentApplicationMapper.selectList(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.select(PaymentApplication::getSettlementId)
|
||||
.in(PaymentApplication::getSettlementId, formalSettlementIds)
|
||||
.eq(PaymentApplication::getPaymentType, "settlement_payment")
|
||||
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
|
||||
.eq(PaymentApplication::getIsDeleted, 0)
|
||||
.notIn(PaymentApplication::getApprovalStatus, RETURNED, VOIDED)).stream()
|
||||
.map(PaymentApplication::getSettlementId).filter(Objects::nonNull).toList());
|
||||
@@ -355,7 +356,7 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.select(PaymentApplication::getId)
|
||||
.in(PaymentApplication::getId, applicationIds)
|
||||
.eq(PaymentApplication::getPaymentType, "settlement_payment")
|
||||
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
|
||||
.eq(PaymentApplication::getIsDeleted, 0)
|
||||
.notIn(PaymentApplication::getApprovalStatus, RETURNED, VOIDED)).stream()
|
||||
.map(PaymentApplication::getId).filter(Objects::nonNull).toList());
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
|
||||
* Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 外部系统来源单据号组装工具
|
||||
* <p>
|
||||
* 统一外部单据号(如金蝶 awrp_src_code)的拼接规则:主单据号 + 子单据号列表按下划线拼接;
|
||||
* 自动去空白、去重,便于后续各链路通用编码与扩展处理
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class ExternalSrcCodeSupport {
|
||||
|
||||
/**
|
||||
* 外部单据号拼接分隔符
|
||||
*/
|
||||
public static final String SEPARATOR = "_";
|
||||
|
||||
private ExternalSrcCodeSupport() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装外部系统来源单据号:主单据号 + 子单据号按下划线拼接
|
||||
* <p>各部分自动去首尾空格、过滤空白并去重;主单据号为空时仅拼接子单据号</p>
|
||||
*
|
||||
* @param mainCode 主单据号(如付款申请号)
|
||||
* @param partCodes 子单据号列表(如结算明细单据号)
|
||||
* @return 拼接后的外部来源单据号,无有效值时返回空串
|
||||
*/
|
||||
public static String buildSrcCode(String mainCode, Collection<String> partCodes) {
|
||||
List<String> parts = new ArrayList<>();
|
||||
if (Func.isNotBlank(mainCode)) {
|
||||
parts.add(mainCode.trim());
|
||||
}
|
||||
if (Func.isNotEmpty(partCodes)) {
|
||||
partCodes.stream().filter(Func::isNotBlank).map(String::trim).distinct().forEach(parts::add);
|
||||
}
|
||||
return String.join(SEPARATOR, parts);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -23,6 +23,7 @@ import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
|
||||
|
||||
@@ -39,7 +40,7 @@ public class PaymentApplicationWrapper extends BaseEntityWrapper<PaymentApplicat
|
||||
vo.setPaymentTypeName(switch (entity.getPaymentType() == null ? "" : entity.getPaymentType()) {
|
||||
case "project_advance" -> "项目预付";
|
||||
case "progress_advance" -> "进度预付";
|
||||
case "settlement_payment" -> "结算付款";
|
||||
case TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT -> "结算付款";
|
||||
default -> entity.getPaymentType();
|
||||
});
|
||||
vo.setApprovalStatusName(switch (entity.getApprovalStatus() == null ? "" : entity.getApprovalStatus()) {
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.bfi.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 业财融合(BFI)单据保存返回错误明细
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BfiBillSaveResultErrorVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 实体键
|
||||
*/
|
||||
private String entityKey;
|
||||
/**
|
||||
* 分录行号
|
||||
*/
|
||||
private Integer entryRowIndex;
|
||||
/**
|
||||
* 单据键值,含 awrp_src_code、awrp_src_sys
|
||||
*/
|
||||
private Map<String, String> keys;
|
||||
/**
|
||||
* 行错误信息
|
||||
*/
|
||||
private List<String> rowMsg;
|
||||
/**
|
||||
* 子分录行号
|
||||
*/
|
||||
private Integer subEntryRowIndex;
|
||||
|
||||
/**
|
||||
* 拼接行错误信息
|
||||
*
|
||||
* @return 错误信息文本
|
||||
*/
|
||||
public String getRowMsgText() {
|
||||
return rowMsg == null || rowMsg.isEmpty() ? "" : String.join(";", rowMsg);
|
||||
}
|
||||
}
|
||||
+22
-3
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.thirdparty.bfi.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
@@ -33,6 +34,7 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 业财融合(BFI)A103-1 / A103-3 付款申请保存返回结果明细
|
||||
@@ -40,6 +42,7 @@ import java.util.Map;
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BfiBillSaveResultItemVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -52,9 +55,13 @@ public class BfiBillSaveResultItemVO implements Serializable {
|
||||
*/
|
||||
private Boolean billStatus;
|
||||
/**
|
||||
* 错误信息
|
||||
* 分录id集合
|
||||
*/
|
||||
private List<String> errors;
|
||||
private Map<String, Object> entryIds;
|
||||
/**
|
||||
* 错误明细
|
||||
*/
|
||||
private List<BfiBillSaveResultErrorVO> errors;
|
||||
/**
|
||||
* 单据id
|
||||
*/
|
||||
@@ -69,7 +76,19 @@ public class BfiBillSaveResultItemVO implements Serializable {
|
||||
*/
|
||||
private String number;
|
||||
/**
|
||||
* 操作类型:Add 新增、Push 下推
|
||||
* 操作类型:Add 新增、Push 下推、Update 修改
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 拼接错误明细信息
|
||||
*
|
||||
* @return 错误信息文本
|
||||
*/
|
||||
public String getErrorText() {
|
||||
return errors == null || errors.isEmpty() ? "" : errors.stream()
|
||||
.map(BfiBillSaveResultErrorVO::getRowMsgText)
|
||||
.filter(text -> !text.isEmpty())
|
||||
.collect(Collectors.joining(";"));
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.springblade.thirdparty.bfi.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -37,6 +38,7 @@ import java.util.List;
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BfiBillSaveResultVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
+8
-6
@@ -41,7 +41,6 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiArBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiArBillSaveBodyDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiArBillSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiClaimAnnounceQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiClaimPushBodyDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiClaimPushDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerQueryDTO;
|
||||
@@ -63,10 +62,10 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplySaveBodyDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplySaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiRecBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveBodyDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiRecBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiTokenDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiVerifyTokenDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiWithdrawTokenDTO;
|
||||
@@ -82,8 +81,8 @@ import org.springblade.thirdparty.bfi.pojo.vo.BfiOpenApiResultVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPageVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPayApplyVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPayBillVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiReceivedInvoiceVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiRecBillVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiReceivedInvoiceVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiResultVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiTokenVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiVerifyTokenVO;
|
||||
@@ -529,10 +528,13 @@ public class BfiServiceImpl implements IBfiService {
|
||||
if (result != null && result.isSuccess()) {
|
||||
return result;
|
||||
}
|
||||
log.error("调用BFI接口 {}异常响应参数:{}", type, JSONUtil.toJsonStr(result));
|
||||
String message = Optional.ofNullable(result).map(BfiResultVO::getMessage).orElse("");
|
||||
throw new BfiException("调用BFI接口" + type + "异常 " + message);
|
||||
log.error("调用BFI接口 {}异常响应参数:{}", type, JSONUtil.toJsonStr(result));
|
||||
String message = Optional.ofNullable(result).map(BfiResultVO::getMessage).filter(StringUtil::isNotBlank)
|
||||
.orElseGet(() -> Optional.ofNullable(result).map(BfiResultVO::getErrorCode)
|
||||
.map(errorCode -> "错误码 " + errorCode).orElse("响应为空或解析失败"));
|
||||
throw new BfiException("调用BFI接口" + type + "异常 " + message);
|
||||
} catch (BfiException e) {
|
||||
log.error("调用BFI接口 {},BfiException异常", type, e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("调用BFI接口 {}异常", type, e);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> 适用系统:TMS(BladeX 微服务平台,服务:blade-transport)
|
||||
> 对接系统:金蝶云·星瀚(业财融合 BFI,经 iPaaS 网关)
|
||||
> 关联文档:`doc/docs/TMS业财融合-集成流程图文字版与业务流程梳理.md`、`doc/docs/业财融合系统接口文档.xlsx`、`doc/docs/业务流程/付款申请同步金蝶(星瀚)技术文档.md`(A103-3 预付款路径)
|
||||
> 最后更新:2026-09-16
|
||||
> 最后更新:2026-09-18(字段映射与联调默认值按当前代码重新梳理)
|
||||
|
||||
---
|
||||
|
||||
@@ -63,12 +63,12 @@
|
||||
| 约定 | 说明 |
|
||||
| --- | --- |
|
||||
| 状态校验 | 仅 `approvalStatus = approved` 允许同步 |
|
||||
| 幂等跳过 | `kingdeeStatus = synced` 的单据直接返回已存单据号,不重复推送 |
|
||||
| ①防重键 | 星瀚应付单侧 `awrp_src_code`(=paymentNo)+ `awrp_src_sys`,②失败后重推时①走更新语义,不重复生成应付单 |
|
||||
| ②防重键 | 付款申请单侧 `awrp_src_code`(=paymentNo)+ `awrp_src_sys`;分录行 `awrp_entry_code`(=paymentNo + "-1")不得重复 |
|
||||
| ②关联键 | `awrp_finapbill_entryid` = ①明细的 `awrp_entry_code`(=paymentNo + "-1"),**不依赖①返回的内部 id** |
|
||||
| 幂等跳过 | `kingdeeStatus = synced/paying/paid/closed` 的单据直接返回已存单据号,不重复推送 |
|
||||
| ①防重键 | 星瀚应付单侧 `awrp_src_code`(=付款申请号_结算明细单据号1_单据号2...,去重拼接)+ `awrp_src_sys`,②失败后重推时①走更新语义,不重复生成应付单 |
|
||||
| ②防重键 | 付款申请单侧 `awrp_src_code`(=paymentNo)+ `awrp_src_sys`;分录行 `awrp_entry_code`(=paymentNo + "-序号")不得重复 |
|
||||
| ②关联键 | `awrp_finapbill_entryid` = ①明细的 `awrp_entry_code`(=paymentNo + "-序号"),**不依赖①返回的内部 id** |
|
||||
| 失败重推 | 任一步失败置 `kingdeeStatus = failed`,可重新勾选同步(①已成功的应付单按防重键走更新语义) |
|
||||
| 前置校验 | ①推送前校验物料/计量单位常量非空(见 5.2);未关联合同、合同不存在、组织编码为空、结算币种为空时同步失败 |
|
||||
| 前置校验 | 分录解析前校验:未关联正式结算明细、货物类型未维护档案编码、计量单位为空均报错;未关联合同、组织编码为空、结算币种为空时同步失败 |
|
||||
|
||||
---
|
||||
|
||||
@@ -95,37 +95,36 @@
|
||||
| 接口字段 | 含义 | 必填 | 来源 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| billtypeid_number | 单据类型.编码 | 是 | 按映射字典转换:`KingdeeBfiConstant.BFI_AP_BILL_BILLTYPE_NAME`(默认"服务采购应付")经 `AP_BILL_BILLTYPE_MAP` 名称→编码 | 路由规则见 5.3;未维护名称报错 |
|
||||
| org_number | 结算组织.编码 | 是 | **合同所属组织**:`contract.organizationId` → `dept.deptCode`,空回退组织名称(复用 `bfiOrgNumber`) | 缺失报错 |
|
||||
| org_number | 结算组织.编码 | 是 | **合同所属组织**:`contract.organizationId` → `dept.deptCode`(复用 `bfiOrgNumber`) | 缺失报错 |
|
||||
| bizdate | 单据日期 | 是 | `applyDate`(空取当天) | 沿用 yyyyMMdd,**待确认是否需 yyyy-MM-dd** |
|
||||
| asstacttype | 往来类型 | 是 | 常量 `bd_supplier` | 供应商 |
|
||||
| asstact_number | 往来户.编码 | 是 | 复用 `bfiSupplierCode`:payeeName 匹配客商档案 `customerCode`,未命中回退 payeeName | |
|
||||
| purmode | 付款方式 | 是 | 常量 `BFI_AP_BILL_PUR_MODE` | `CREDIT` 赊购(金蝶侧可调金额);`CASH` 会自动 100% 推付款 |
|
||||
| currency_number | 结算币别.货币代码 | 是 | **合同结算币种** `contract.settlementCurrency`(复用 `bfiCurrency`) | 缺失报错 |
|
||||
| payorg_number / purorg_number | 付款/采购组织.编码 | 是/否 | 同 org_number | |
|
||||
| asstacttype | 往来类型 | 是 | **按合同类别路由**(`bfiAsstactType`):`contractCategory = 客户合同` → `bd_customer`,否则 → `bd_supplier` | 与结算类型路由口径一致 |
|
||||
| asstact_number | 往来户.编码 | 是 | **合同乙方**(`bfiAsstactNumber`):`contract.partyB` → 客商档案按全称/简称匹配取 `customerCode` | 乙方为空或客商档案未维护编码均报错,不回退原值 |
|
||||
| purmode | 付款方式 | 是 | 常量 `BFI_AP_BILL_PUR_MODE_CASH` | `CASH` 现购(系统自动 100% 推付款) |
|
||||
| currency_number | 结算币别.货币代码 | 是 | **合同结算币种** `contract.settlementCurrency`(复用 `bfiCurrency`);`RMB` 统一映射为 `CNY` | 缺失报错 |
|
||||
| payorg_number | 付款组织.编码 | 是 | 同 org_number | 采购组织 purorg_number 暂不传 |
|
||||
| payproperty_number | 款项性质.编码 | 是 | 常量 `BFI_AP_BILL_PAY_PROPERTY` | 暂按 `2001`,**待金蝶确认** |
|
||||
| isincludetax / ispricetotal | 录入含税价 / 录入总价 | 否 | `true` / `true` | 录入总价免单价数量换算 |
|
||||
| exratetable_number / exratedate | 汇率表/汇率日期 | 否 | 复用配置 `thirdParty.bfi.payApply.exratetable`(默认 01)/ 同 bizdate | |
|
||||
| asstactname | 往来单位名称 | 否 | `payeeName` | |
|
||||
| remark | 备注 | 否 | `paymentNo + " " + settlementNo` | |
|
||||
| awrp_src_sys / awrp_src_code | 外部系统编号/来源单据号 | 是 | 配置 `thirdParty.bfi.payApply.srcSys` / `paymentNo` | 应付单候选键 |
|
||||
| awrp_src_bt / awrp_src_con_no | 来源单据/上游合同号 | 否 | `awrp_contract_info` / `contractNo` | 有合同即传 |
|
||||
| isincludetax / ispricetotal | 录入含税价 / 录入总价 | 否 | 固定 `true` / `true`;`isintax` 固定 `false` | 录入总价免单价换算 |
|
||||
| awrp_src_sys | 外部系统编号 | 是 | 配置 `thirdParty.bfi.payApply.srcSys` | 应付单候选键 |
|
||||
| awrp_src_code | 外部系统来源单据号 | 是 | **`paymentNo + "_" + 结算明细单据号1_单据号2...`**(单据号去重、保持行序,`String.join("_")`) | 应付单候选键 |
|
||||
| awrp_src_type | 外部单据类型 | 是 | 常量 `BFI_AP_BILL_SRC_TYPE` | 暂按 `TMS财务应付单`,**待金蝶确认** |
|
||||
|
||||
未传的可选字段:settlementtype_number(结算方式)、taxrateid_number(税率)、duedate、bookdate、department_number、preentry(预付信息)等。
|
||||
未传的可选字段(代码注释保留):exratetable_number/exratedate(汇率表/汇率日期)、asstactname(往来单位名称)、remark(备注)、awrp_src_bt(来源单据)、awrp_src_con_no(上游合同号)、settlementtype_number(结算方式)、taxrateid_number(税率)、purorg_number(采购组织)、duedate、bookdate、department_number、preentry(预付信息)等。
|
||||
|
||||
### 4.2 A102-2 明细(`BfiApBillSaveEntryDTO`,单条申请固定 1 行)
|
||||
### 4.2 A102-2 明细(`BfiApBillSaveEntryDTO`,按货物编码+计量单位聚合,多行)
|
||||
|
||||
分录数据由 `resolveBfiEntryGroups` 解析:**货物费用行**(`blade_formal_settlement_detail_fee`,每货物一条)优先,无费用行时兜底**结算明细行**;按货物编码+计量单位聚合(一行含多种货物类型时金额数量均分),已移除按申请付款金额分摊逻辑,**金额直接取结算金额**。
|
||||
|
||||
| 接口字段 | 含义 | 必填 | 来源 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| material_number | 物料编码.编码 | 是 | 常量 `BFI_AP_BILL_MATERIAL` | **TMS 无物料档案,当前为空串,推送前校验拦截;待金蝶提供运输服务物料编码** |
|
||||
| measureunit_number | 计量单位.编码 | 是 | 常量 `BFI_AP_BILL_MEASURE_UNIT` | **同上,待金蝶确认(如"吨")** |
|
||||
| quantity | 明细.数量 | 是 | 固定 `1` | 结算单无吨位数据,配合 ispricetotal=true 录入总价 |
|
||||
| e_pricetaxtotal | 明细.应付金额 | 是 | `appliedAmount`(2 位小数) | 含税应付金额 = 申请付款金额 |
|
||||
| project_number | 项目.项目编码 | 是 | 复用 `bfiProjectNumber`:`projectId` → `ProjectApply.projectCode`,无项目传 `999` | |
|
||||
| awrp_entry_code | 外部系统来源分录标识 | 是 | `paymentNo + "-1"` | 候选键;②下推时以 `awrp_finapbill_entryid` 关联此行 |
|
||||
| e_remark | 明细.备注 | 否 | `remark` | |
|
||||
| material_number | 物料编码.编码 | 是 | **联调临时:常量 `BFI_AP_BILL_MATERIAL_NUMBER_TEST`(`SP0324`)**(调试块 `if(true)` 强制覆盖) | 真实来源:货物类型 → 货物类型档案 `cargoCode`,档案对齐后恢复 |
|
||||
| measureunit_number | 计量单位.编码 | 是 | **联调临时:常量 `BFI_AP_BILL_MEASUREUNIT_NUMBER_TEST`(`TON`)**(同上) | 真实来源:快照运费计算单位 `quantityUnit`(档案暂无编码字段) |
|
||||
| quantity | 明细.数量 | 是 | 组内 `transportQuantity` 合计 | ≤0 兜底 1 |
|
||||
| e_pricetaxtotal | 明细.应付金额 | 是 | 组内 `settlementAmountTax`(结算金额含税)合计 | **必传**,缺失金蝶报"应付金额不能为空" |
|
||||
| e_amount | 明细.金额(不含税) | 否 | 组内 `settlementAmountNoTax`(结算金额不含税)合计 | |
|
||||
| project_number | 项目.项目编码 | 是 | **联调临时:常量 `BFI_AP_BILL_PROJECT_NUMBER_TEST`(`999`)** | 真实来源:`projectId` → `ProjectApply.projectCode`,无项目传 `999` |
|
||||
| awrp_entry_code | 外部系统来源分录标识 | 是 | `paymentNo + "-" + 组序号` | 候选键;②下推时以 `awrp_finapbill_entryid` 关联此行 |
|
||||
|
||||
未传:planentity 付款计划(不传由金蝶按明细自动汇总)、e_amount/e_tax、taxrateid_number、awrp_src_con_eno。
|
||||
未传:planentity 付款计划(不传由金蝶按明细自动汇总)、e_tax、taxrateid_number、e_remark、awrp_src_con_eno。
|
||||
|
||||
### 4.3 A103-1 主表(`BfiPayApplyPushDTO`)
|
||||
|
||||
@@ -139,20 +138,20 @@
|
||||
| awrp_textfield1 | 付款渠道 | 否 | 复用 `bfiPaymentMethodLabel`:`paymentMethod` 经字典 `pay_method` 翻译为标签 | |
|
||||
| creator_name | 申请人 | 否 | `applicantName` | |
|
||||
|
||||
### 4.4 A103-1 分录(`BfiPayApplyPushEntryDTO`,固定 1 行)
|
||||
### 4.4 A103-1 分录(`BfiPayApplyPushEntryDTO`,与应付单明细一一对应多行)
|
||||
|
||||
| 接口字段 | 含义 | 必填 | 来源 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| e_paymenttype_number | 付款类型.编码 | 是 | 常量 `201` | 正式付款 |
|
||||
| awrp_finapbill_entryid | 结算单分录id | 是 | `paymentNo + "-1"` | **对应 A102-2 明细 awrp_entry_code** |
|
||||
| awrp_entry_code | 对方唯一行 id | 是 | `paymentNo + "-1"` | 不得重复 |
|
||||
| awrp_finapbill_entryid | 结算单分录id | 是 | `paymentNo + "-" + 组序号` | **对应 A102-2 明细 awrp_entry_code** |
|
||||
| awrp_entry_code | 对方唯一行 id | 是 | `paymentNo + "-" + 组序号` | 不得重复 |
|
||||
| e_assacct | 往来账户 | 是 | `bankAccount` | 收款账号(页面"收款账户") |
|
||||
| e_bebank_number | 往来银行.编码 | 是 | `bankName` | **必填编码,当前暂传银行名称,待银行档案编码映射**(与 A103-3 遗留项一致) |
|
||||
| e_applyamount | 申请金额 | 是 | `appliedAmount`(2 位小数) | |
|
||||
| e_applyamount | 申请金额 | 是 | 组内 `settlementAmountTax`(结算金额含税) | 与对应应付单分录 `e_pricetaxtotal` 一致 |
|
||||
| e_expaydate | 期望付款日 | 否 | 同 applydate | |
|
||||
| project_number | 项目.项目编码 | 是 | 复用 `bfiProjectNumber` | 无项目传 `999` |
|
||||
| awrp_src_type | 外部单据类型 | 是 | 复用配置 `payApply.srcType` | |
|
||||
| project_number | 项目.项目编码 | 是 | **联调临时:常量 `BFI_AP_BILL_PROJECT_NUMBER_TEST`(`999`)**(真实取值 `bfiProjectNumber` 代码注释保留) | 无项目传 `999` |
|
||||
| e_remark | 备注 | 否 | `remark` | |
|
||||
| awrp_src_type | 外部单据类型 | 是 | 复用配置 `payApply.srcType` | |
|
||||
|
||||
未传:e_duedate、e_settlementtype_number、e_costcenter_number、awrp_splb_number。
|
||||
|
||||
@@ -170,12 +169,16 @@ A102-2 所需的金蝶侧固定档案编码**不进 Nacos 配置**,统一定
|
||||
| --- | --- | --- | --- |
|
||||
| BFI_AP_BILL_BILLTYPE_NAME | `服务采购应付` | A102-2 单据类型默认名称,经 `AP_BILL_BILLTYPE_MAP` 转换为编码(`ApFin_service_BT_S`) | 待金蝶确认 |
|
||||
| AP_BILL_BILLTYPE_MAP | 9 项映射 | 财务应付单类型字典(借贷项调整/费用采购应付/其他应付/产品委外应付/标准采购应付/采购费用应付/销售费用应付/服务采购应付/资产采购应付) | 全量字典 |
|
||||
| AR_BILL_BILLTYPE_MAP | 5 项映射 | 财务应收单类型字典(借贷项调整/其他应收/销售费用应收/服务销售应收/标准销售应收),当前付款链路未启用 | 全量字典 |
|
||||
| AR_BILL_BILLTYPE_MAP | 5 项映射 | 财务应收单类型字典,当前付款链路未启用 | 全量字典 |
|
||||
| PAY_APPLY_BILLTYPE_MAP | 2 项映射 | 付款申请单类型字典(采购付款申请/其他付款申请),使用规则待业务确认 | 全量字典 |
|
||||
| BFI_SUPPLIER_ASSTACT_TYPE | `bd_supplier` | 往来类型-供应商 | 启用 |
|
||||
| BFI_CUSTOMER_ASSTACT_TYPE | `bd_customer` | 往来类型-客户(合同类别为"客户合同"时使用) | 启用 |
|
||||
| BFI_AP_BILL_PAY_PROPERTY | `2001` | A102-2 款项性质 | 待金蝶确认 |
|
||||
| BFI_AP_BILL_PUR_MODE | `CREDIT` | A102-2 付款方式(赊购) | 待业务确认(CASH=现购自动推付款) |
|
||||
| BFI_AP_BILL_MATERIAL | 空串 | A102-2 明细物料编码 | **待金蝶提供,为空时推送前拦截报错** |
|
||||
| BFI_AP_BILL_MEASURE_UNIT | 空串 | A102-2 明细计量单位 | **待金蝶确认,为空时推送前拦截报错** |
|
||||
| BFI_AP_BILL_PUR_MODE_CASH | `CASH` | A102-2 付款方式(现购,自动 100% 推付款) | 启用 |
|
||||
| BFI_AP_BILL_PUR_MODE_CREDIT | `CREDIT` | A102-2 付款方式(赊购,金蝶侧可调金额) | 备用 |
|
||||
| BFI_AP_BILL_MATERIAL_NUMBER_TEST | `SP0324` | **联调临时**:明细物料编码,调试块强制覆盖真实取值 | 联调后恢复档案取值 |
|
||||
| BFI_AP_BILL_MEASUREUNIT_NUMBER_TEST | `TON` | **联调临时**:明细计量单位编码,同上 | 联调后恢复档案取值 |
|
||||
| BFI_AP_BILL_PROJECT_NUMBER_TEST | `999` | **联调临时**:A102-2/A103-1 项目编码 | 档案对齐后恢复 `bfiProjectNumber` |
|
||||
| BFI_AP_BILL_SRC_TYPE | `TMS财务应付单` | A102-2 外部单据类型 | 待金蝶确认 |
|
||||
|
||||
### 5.3 单据类型路由与映射转换
|
||||
@@ -195,6 +198,8 @@ A102-2 所需的金蝶侧固定档案编码**不进 Nacos 配置**,统一定
|
||||
| 结算方式/税率 | settlementtype_number、taxrateid_number 不传 | 金蝶是否强制、编码取值 |
|
||||
| 预付抵扣 | preentry(y_billtype/y_billid/y_settleamt)不传 | 存在预付款抵扣时是否需传,本期不传、后续迭代 |
|
||||
| 银行编码 | e_bebank_number 暂传银行名称 | 必填编码,待银行档案编码映射(与 A103-3 遗留项相同) |
|
||||
| 物料/计量单位/项目编码 | 联调期临时传金蝶测试档案值(SP0324/TON/999,见 4.2/4.4 与 5.2) | 档案对齐后移除 A102-2 明细中的调试块并恢复真实取值 |
|
||||
| 币种映射 | 合同结算币种 `RMB` 统一映射为 `CNY`(`bfiCurrency`) | 长期保留 |
|
||||
| 付款申请单/收入确认单类型映射 | 字典已存 `PAY_APPLY_BILLTYPE_MAP`(2 项);收入确认单(ar_revcfmbill_*)字典未入库 | 付款申请单、收入确认单的映射使用规则待业务确认 |
|
||||
|
||||
---
|
||||
@@ -212,7 +217,8 @@ A102-2 所需的金蝶侧固定档案编码**不进 Nacos 配置**,统一定
|
||||
| 单笔同步 `syncKingdee` | 校验失败/推送失败置 `failed` 并抛 `ServiceException`,前端提示原因(含金蝶 errors 明细) |
|
||||
| ①成功②失败 | `kingdeeStatus = failed`;重推时①按防重键走更新语义,继续② |
|
||||
| 批量同步 `syncKingdeeBatch` | 逐单循环,**单条失败不中断批次**;返回结果列表附 `付款申请[id]同步失败:原因` |
|
||||
| 物料/计量单位常量为空 | 推送前拦截,报错提示待金蝶侧确认补常量 |
|
||||
| 货物类型未维护档案编码 / 计量单位为空 | 分录解析(`collectEntryGroups`)时拦截,报错提示补充档案 |
|
||||
| 合同乙方为空 / 客商档案未维护客商编码 | 往来户取值(`bfiAsstactNumber`)时拦截,报错提示合同号与乙方名称 |
|
||||
| 金蝶接口级失败 | `BfiServiceImpl.commonCall` 抛 `BfiException`,同上处理 |
|
||||
| 重复点击同步 | 已 `synced` 单据跳过;`failed`/`unsynced` 允许重推 |
|
||||
|
||||
@@ -220,7 +226,7 @@ A102-2 所需的金蝶侧固定档案编码**不进 Nacos 配置**,统一定
|
||||
|
||||
## 8. 遗留事项
|
||||
|
||||
1. **物料/计量单位编码**:A102-2 明细必填,待金蝶侧提供后补充 `BFI_AP_BILL_MATERIAL` / `BFI_AP_BILL_MEASURE_UNIT` 常量,在此之前正式付款同步会主动拦截报错。
|
||||
1. **物料/计量单位/项目编码(联调临时)**:A102-2 明细当前经调试块固定传金蝶测试档案值(`SP0324`/`TON`,见 `pushBfiApBill` 内 `if(true)` 调试代码),项目编码 A102-2/A103-1 固定传 `999`;金蝶侧档案对齐后需**删除调试块**并恢复真实取值(货物类型档案编码 / 快照计量单位 / `bfiProjectNumber`)。
|
||||
2. **应付单 id 不落库**:A102-2 仅校验保存成功;后续 A102-4 勾稽、应付单附件、反审核删除等需要 id 的场景,按防重键经 `queryApBill` 二次查询。
|
||||
3. **银行编码映射**:e_bebank_number 暂传银行名称,待建立银行档案编码映射(与预付款路径遗留项相同)。
|
||||
4. **预付抵扣 preentry**:正式付款涉及预付款抵扣时是否传预付信息,本期不传、后续迭代。
|
||||
|
||||
Reference in New Issue
Block a user