From 17b8da0a6523ecf5e5b2f680dd2dfd6006af963f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com>
Date: Fri, 18 Sep 2026 19:13:07 +0800
Subject: [PATCH] =?UTF-8?q?:zap:=20=E8=B0=83=E6=95=B4=E9=87=91=E8=9D=B6?=
=?UTF-8?q?=E5=BA=94=E4=BB=98=E5=8D=95=E5=88=86=E5=BD=95=E5=8F=96=E6=95=B0?=
=?UTF-8?q?=EF=BC=9A=E6=94=B9=E7=94=B1=E5=BA=94=E4=BB=98=E8=B4=A7=E7=89=A9?=
=?UTF-8?q?=E8=B4=B9=E7=94=A8=E6=98=8E=E7=BB=86=E9=80=90=E8=A1=8C=E4=B8=8D?=
=?UTF-8?q?=E8=81=9A=E5=90=88=E6=8E=A8=E9=80=81=EF=BC=8C=E5=88=86=E5=BD=95?=
=?UTF-8?q?=E6=A0=87=E8=AF=86=E9=80=81=E8=B4=B9=E7=94=A8=E6=98=8E=E7=BB=86?=
=?UTF-8?q?id?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../impl/PaymentApplicationServiceImpl.java | 214 +++++++++++-------
1 file changed, 130 insertions(+), 84 deletions(-)
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java
index 7fe0dd99..34b22cb8 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java
@@ -72,8 +72,10 @@ import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.mapper.PaymentApplicationRecordMapper;
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
import org.springblade.transport.mapper.PreSettlementAdvanceMapper;
+import org.springblade.transport.mapper.PreSettlementDetailFeeMapper;
import org.springblade.transport.mapper.PreSettlementMapper;
import org.springblade.transport.mapper.ProjectApplyMapper;
+import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.mapper.TemporaryCreditLimitMapper;
import org.springblade.transport.pojo.dto.PaymentApplicationInvoiceRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationRecordRequest;
@@ -94,7 +96,9 @@ import org.springblade.transport.pojo.entity.PaymentApplicationRecord;
import org.springblade.transport.pojo.entity.PaymentApplicationSettlement;
import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.entity.PreSettlementAdvance;
+import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
import org.springblade.transport.pojo.entity.ProjectApply;
+import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
import org.springblade.transport.pojo.entity.TemporaryCreditLimit;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
@@ -148,6 +152,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl detailentry = new ArrayList<>();
for (int index = 0; index < entryGroups.size(); index++) {
BfiEntryGroup entryGroup = entryGroups.get(index);
@@ -1126,16 +1132,16 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl 0 ? entryGroup.quantity() : BigDecimal.ONE);
- // 应付金额(含税):组内结算金额(含税)合计
+ // 应付金额(含税):该行应付货物费用行调整后总金额(含税)
entry.setEPricetaxtotal(entryGroup.amount());
- // 金额(不含税):组内结算金额(不含税)合计
+ // 金额(不含税):该行结算金额(不含税)(应付费用行无不含税字段,经结算货物费用行回链)
entry.setEAmount(entryGroup.amountNoTax());
// 【必填】项目.项目编码(联调期临时传金蝶测试档案值)
entry.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
- // 【必填】外部系统来源分录标识(候选键):结算明细单据号(多行并入同组时下划线拼接),A103-1 下推时以 awrp_finapbill_entryid 关联此行
- entry.setAwrpEntryCode(entryGroup.documentNo());
+ // 【必填】外部系统来源分录标识(候选键):费用明细行id(唯一),A103-1 下推时 awrp_entry_code 与此对应
+ entry.setAwrpEntryCode(entryGroup.entryCode());
// 调试
@@ -1186,12 +1192,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl entries = new ArrayList<>();
for (int index = 0; index < entryGroups.size(); index++) {
BfiEntryGroup entryGroup = entryGroups.get(index);
@@ -1240,8 +1246,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl entryGroups, List documentNos) {
@@ -1299,14 +1305,18 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl物料编码:货物费用行/结算明细货物类型(支持“、”分隔多类型)匹配货物类型档案货物编码;
- * 计量单位:快照运费计算单位(历史快照为空时兜底数量单位),档案暂无编码字段暂以单位名称作为编码;
- * 分录唯一标识(awrp_entry_code)取所属结算明细单据号,A103-1 下推时以 awrp_finapbill_entryid 关联此行;
- * 分录金额直接取结算金额(含税/不含税),不按申请付款金额分摊。
+ * 解析付款申请关联的正式结算明细,逐行生成应付单分录(不聚合,每条来源费用行一条分录)。
+ * 取数来源:应付明细货物费用行(blade_receivable_payable_cargo_fee,经结算明细 sourceDetailId 关联应付明细,
+ * 每货物一条)——货物类型/计量单位/运输数量/含税金额直接取应付费用行(含税金额取调整后总金额 after_amount);
+ * 应付费用行无不含税字段,不含税金额经结算货物费用行 sourceFeeId 回链(直接选明细路径 sourceFeeId=应付费用行id,
+ * 预结算合并路径 sourceFeeId=预结算费用行id,经预结算费用行 sourceFeeId 回溯应付费用行id);
+ * 结算明细无应付费用行时兜底结算明细行(金额取结算金额)。物料编码:货物类型(支持“、”分隔多类型)
+ * 匹配货物类型档案货物编码;分录唯一标识(awrp_entry_code)取费用明细行id(雪花id天然唯一,满足 A103-1
+ * 对方系统唯一行id不能重复;A103-1 与应付单的勾稽关联走 awrp_finapbill_entryid=付款计划
+ * awrp_entry_code_p,与本标识无关);分录金额不按申请付款金额分摊。
*
* @param entity 付款申请实体
- * @return 应付单分录解析结果(分录组 + 结算明细单据号)
+ * @return 应付单分录解析结果(分录 + 结算明细单据号)
*/
private BfiEntryGroupResult resolveBfiEntryGroups(PaymentApplication entity, KingdeeSyncContext context) {
List settlementDetails = settlementDetails(entity, context);
@@ -1316,37 +1326,37 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl documentNos = settlementDocumentNos(settlementDetails);
Map cargoCodeByName = cargoCodeByName(context);
- LinkedHashMap groupMap = new LinkedHashMap<>();
- // 优先按货物费用行逐条取数(blade_formal_settlement_detail_fee 每货物一条,金额为结算调整后的精确值)
- List detailFees = formalSettlementDetailFees(settlementDetails);
- if (Func.isNotEmpty(detailFees)) {
- // 结算明细id->单据号映射:货物费用行经此回溯所属结算明细单据号(作为分录唯一标识)
- Map detailDocumentNoMap = new LinkedHashMap<>();
- for (FormalSettlementDetail settlementDetail : settlementDetails) {
- detailDocumentNoMap.putIfAbsent(settlementDetail.getId(), settlementDetail.getDocumentNo());
- }
- for (FormalSettlementDetailFee detailFee : detailFees) {
- collectEntryGroups(groupMap, cargoCodeByName, detailFee.getCargoType(),
- resolveMeasureunitNumber(null, detailFee.getQuantityUnit()),
- detailDocumentNoMap.get(detailFee.getFormalSettlementDetailId()),
- money(detailFee.getSettlementAmountTax()), money(detailFee.getSettlementAmountNoTax()),
- money(detailFee.getTransportQuantity()),
- "货物费用[" + Func.toStr(detailFee.getCargoName()) + " 第" + Func.toStr(detailFee.getLineNo()) + "行]");
- }
- } else {
- for (FormalSettlementDetail settlementDetail : settlementDetails) {
- collectEntryGroups(groupMap, cargoCodeByName, settlementDetail.getCargoType(),
+ // 应付明细id->应付货物费用行(分录主取数来源)
+ Map> payableFeesByDetailId = payableCargoFeesByDetailId(settlementDetails);
+ // 应付费用行id->结算金额(不含税):应付费用行无不含税字段,经结算货物费用行 sourceFeeId 回链
+ Map noTaxByPayableFeeId = settlementNoTaxByPayableFeeId(settlementDetails);
+ List entryGroups = new ArrayList<>();
+ for (FormalSettlementDetail settlementDetail : settlementDetails) {
+ List payableFees =
+ payableFeesByDetailId.getOrDefault(settlementDetail.getSourceDetailId(), List.of());
+ if (Func.isNotEmpty(payableFees)) {
+ for (ReceivablePayableCargoFee payableFee : payableFees) {
+ appendEntryGroups(entryGroups, cargoCodeByName, payableFee.getCargoType(),
+ resolveMeasureunitNumber(payableFee.getPriceUnit(), payableFee.getQuantityUnit()),
+ payableFee.getId(),
+ money(payableFee.getAfterAmount()),
+ noTaxByPayableFeeId.getOrDefault(payableFee.getId(), BigDecimal.ZERO),
+ money(payableFee.getTransportQuantity()),
+ "应付货物费用[" + Func.toStr(payableFee.getCargoName()) + " 第" + Func.toStr(payableFee.getLineNo()) + "行]");
+ }
+ } else {
+ appendEntryGroups(entryGroups, cargoCodeByName, settlementDetail.getCargoType(),
resolveMeasureunitNumber(null, settlementDetail.getQuantityUnit()),
- settlementDetail.getDocumentNo(),
+ settlementDetail.getId(),
money(settlementDetail.getSettlementAmountTax()), money(settlementDetail.getSettlementAmountNoTax()),
money(settlementDetail.getTransportQuantity()),
"结算明细[" + Func.toStr(settlementDetail.getDocumentNo()) + " 第" + Func.toStr(settlementDetail.getLineNo()) + "行]");
}
}
- if (groupMap.isEmpty()) {
- throw new ServiceException("推送金蝶财务应付单失败:结算明细未解析出任何分录组,请检查货物类型维护");
+ if (entryGroups.isEmpty()) {
+ throw new ServiceException("推送金蝶财务应付单失败:结算明细未解析出任何分录,请检查货物类型维护");
}
- return new BfiEntryGroupResult(new ArrayList<>(groupMap.values()), documentNos);
+ return new BfiEntryGroupResult(entryGroups, documentNos);
}
/** 结算明细单据号去重收集(保持行序),用于拼接财务应付单外部系统来源单据号 */
@@ -1372,7 +1382,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl货物编码映射
* @param cargoType 货物类型(支持“、”分隔多类型)
* @param measureunitNumber 计量单位编码(运费计算单位,档案暂无编码字段暂传单位名称)
- * @param documentNo 结算明细单据号,作为分录唯一标识(awrp_entry_code)
- * @param amount 结算金额(含税)
- * @param amountNoTax 结算金额(不含税)
- * @param quantity 运输数量
+ * @param sourceRowId 来源行id(应付货物费用行id,兜底结算明细行id),雪花id天然唯一
+ * @param amount 该行金额(含税)
+ * @param amountNoTax 该行金额(不含税)
+ * @param quantity 该行运输数量
* @param sourceDesc 来源行描述,用于异常提示
*/
- private void collectEntryGroups(LinkedHashMap groupMap, Map cargoCodeByName,
- String cargoType, String measureunitNumber, String documentNo, BigDecimal amount, BigDecimal amountNoTax,
- BigDecimal quantity, String sourceDesc) {
+ private void appendEntryGroups(List entryGroups, Map cargoCodeByName,
+ String cargoType, String measureunitNumber, Long sourceRowId,
+ BigDecimal amount, BigDecimal amountNoTax, BigDecimal quantity, String sourceDesc) {
List cargoNames = splitCargoTypes(cargoType);
if (Func.isBlank(measureunitNumber)) {
throw new ServiceException("推送金蝶财务应付单失败:" + sourceDesc + "计量单位为空,请先维护运费计算单位");
}
- if (Func.isBlank(documentNo)) {
- throw new ServiceException("推送金蝶财务应付单失败:" + sourceDesc + "单据号为空,无法生成金蝶分录标识(awrp_entry_code)");
- }
+ // 分录唯一标识(awrp_entry_code,A103-1 要求对方系统唯一行id不能重复):雪花id天然唯一,同行多类型拆分时追加序号
+ String rowEntryCode = String.valueOf(sourceRowId);
List amountShares = splitEqually(amount, cargoNames.size());
List amountNoTaxShares = splitEqually(amountNoTax, cargoNames.size());
List quantityShares = splitEqually(quantity, cargoNames.size());
@@ -1452,26 +1462,16 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl new BfiEntryGroup(left.materialNumber(), left.measureunitNumber(),
- joinEntryDocumentNos(left.documentNo(), right.documentNo()),
- left.quantity().add(right.quantity()), left.amount().add(right.amount()), left.amountNoTax().add(right.amountNoTax())));
+ String entryCode = cargoNames.size() > 1 ? rowEntryCode + "-" + (index + 1) : rowEntryCode;
+ entryGroups.add(new BfiEntryGroup(materialNumber, measureunitNumber, entryCode,
+ quantityShares.get(index), amountShares.get(index), amountNoTaxShares.get(index)));
+ log.info("金蝶应付单分录生成:来源[{}],分录标识[{}],物料编码[{}],计量单位[{}],数量[{}],含税金额[{}],不含税金额[{}]",
+ sourceDesc, entryCode, materialNumber, measureunitNumber,
+ quantityShares.get(index), amountShares.get(index), amountNoTaxShares.get(index));
}
}
- /** 分录组合并时结算明细单据号去重下划线拼接(保持先后顺序),与外部系统来源单据号拼接规则一致 */
- private String joinEntryDocumentNos(String left, String right) {
- if (Func.isBlank(left)) {
- return right;
- }
- if (Func.isBlank(right) || left.equals(right)) {
- return left;
- }
- return left.contains(right) ? left : right.contains(left) ? right : left + "_" + right;
- }
-
- /** 付款申请关联的正式结算货物费用行(blade_formal_settlement_detail_fee,每货物一条,金额为结算调整后精确值) */
+ /** 付款申请关联的正式结算货物费用行(blade_formal_settlement_detail_fee,用于不含税金额回链应付费用行) */
private List formalSettlementDetailFees(List settlementDetails) {
List settlementDetailIds = settlementDetails.stream().map(FormalSettlementDetail::getId)
.filter(Objects::nonNull).distinct().toList();
@@ -1484,6 +1484,52 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl应付货物费用行(blade_receivable_payable_cargo_fee,经结算明细 sourceDetailId 关联,每货物一条,分录主取数来源) */
+ private Map> payableCargoFeesByDetailId(List settlementDetails) {
+ List sourceDetailIds = settlementDetails.stream()
+ .map(FormalSettlementDetail::getSourceDetailId).filter(Objects::nonNull).distinct().toList();
+ if (Func.isEmpty(sourceDetailIds)) {
+ return Map.of();
+ }
+ return receivablePayableCargoFeeMapper.selectList(Wrappers.lambdaQuery()
+ .in(ReceivablePayableCargoFee::getDetailId, sourceDetailIds)
+ .eq(ReceivablePayableCargoFee::getIsDeleted, 0)
+ .orderByAsc(ReceivablePayableCargoFee::getLineNo))
+ .stream().collect(Collectors.groupingBy(ReceivablePayableCargoFee::getDetailId));
+ }
+
+ /**
+ * 应付货物费用行id->结算金额(不含税):应付费用行无不含税字段,经结算货物费用行 sourceFeeId 回链获取。
+ * 直接选应收应付明细路径 sourceFeeId=应付费用行id;预结算合并路径 sourceFeeId=预结算费用行id,
+ * 经预结算费用行 sourceFeeId(指向应付费用行)回溯;无对应结算费用行时无映射,调用方兜底 0
+ */
+ private Map settlementNoTaxByPayableFeeId(List settlementDetails) {
+ List settlementFees = formalSettlementDetailFees(settlementDetails);
+ if (Func.isEmpty(settlementFees)) {
+ return Map.of();
+ }
+ // 上游费用行id(应付费用行id或预结算费用行id)-> 不含税合计
+ Map noTaxBySourceFeeId = new LinkedHashMap<>();
+ for (FormalSettlementDetailFee settlementFee : settlementFees) {
+ if (settlementFee.getSourceFeeId() == null) {
+ continue;
+ }
+ noTaxBySourceFeeId.merge(settlementFee.getSourceFeeId(), money(settlementFee.getSettlementAmountNoTax()), BigDecimal::add);
+ }
+ if (noTaxBySourceFeeId.isEmpty()) {
+ return Map.of();
+ }
+ // sourceFeeId 指向预结算费用行的:经预结算费用行回溯应付费用行id(直接路径无预结算费用行,保持原 id)
+ Map payableFeeIdByPreFeeId = preSettlementDetailFeeMapper.selectList(
+ Wrappers.lambdaQuery().in(PreSettlementDetailFee::getId, new ArrayList<>(noTaxBySourceFeeId.keySet())))
+ .stream().filter(preSettlementFee -> preSettlementFee.getSourceFeeId() != null)
+ .collect(Collectors.toMap(PreSettlementDetailFee::getId, PreSettlementDetailFee::getSourceFeeId, (left, right) -> left));
+ Map noTaxByPayableFeeId = new LinkedHashMap<>();
+ noTaxBySourceFeeId.forEach((sourceFeeId, noTax) ->
+ noTaxByPayableFeeId.merge(payableFeeIdByPreFeeId.getOrDefault(sourceFeeId, sourceFeeId), noTax, BigDecimal::add));
+ return noTaxByPayableFeeId;
+ }
+
/** 付款申请关联的正式结算明细(关联表优先,兼容仅有 settlementId 的存量数据) */
private List formalSettlementDetails(PaymentApplication entity) {
List settlementIds = settlementRelationMapper.selectList(Wrappers.lambdaQuery()