diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java
index 446d8d86..fbde7a17 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java
@@ -136,7 +136,7 @@ public class KingdeeInvoicePool extends TenantEntity {
*/
private String attachmentsJson;
/**
- * A801-1 发票明细行JSON(BfiInvoicePoolItemVO 列表,A303-2 收票单分录取数来源)
+ * A801-1 发票明细行JSON(BfiInvoicePoolItemVO 列表,票池存档;收票单分录已改与应付单同源结算费用行)
*/
private String itemsJson;
/**
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeReceivedInvoiceSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeReceivedInvoiceSyncSupport.java
index 89b39956..d696d548 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeReceivedInvoiceSyncSupport.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeReceivedInvoiceSyncSupport.java
@@ -28,7 +28,6 @@ package org.springblade.transport.support;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
-import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.thirdparty.bfi.constant.BfiConstant;
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO;
@@ -37,7 +36,6 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceQueryDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveDTO;
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveEntryDTO;
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
-import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoicePoolItemVO;
import org.springblade.thirdparty.bfi.pojo.vo.BfiReceivedInvoiceVO;
import org.springblade.thirdparty.bfi.service.IBfiService;
import org.springblade.thirdparty.bfi.util.BfiUtil;
@@ -54,7 +52,6 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
@@ -64,9 +61,11 @@ import java.util.Map;
* 金蝶收票单同步支撑组件(图05 环节②③:A303-2 收票单批量保存 + A302-6 收票指定应付 + A801-3 收票单查询)。
*
收票登记「推送金蝶」由 {@link org.springblade.transport.service.IInvoiceReceiptService} 编排前置校验
* (审批通过、已绑定镜像、一票一应付、A102-4 应付单已生成预检、A801-3 幂等预检)后调用本组件;
- * 报文组装取数:主表组织/币种/往来户复用应付支撑组件的合同派生方法,票面字段与分录取自票池镜像
- * (invoice_type_code / items_json,A801-1 落库)。防重键:收票单 awrp_src_code=发票号码(一票一登记天然唯一),
- * awrp_entry_code=发票号码-明细行号;A302-6 按「收票单防重键 ↔ 财务应付单防重键」一票一应付指定。
+ * 报文组装取数:主表组织/币种/往来户复用应付支撑组件的合同派生方法,票面字段取自票池镜像
+ * (invoice_type_code 等,A801-1 落库),明细分录与财务应付单 A102-2 同源同口径
+ * (逐结算货物费用行一条,{@link KingdeeApBillSyncSupport.BfiEntryGroup},分录标识同取费用行id)。
+ * 防重键:收票单 awrp_src_code=收票登记id,awrp_entry_code=应付货物费用行id(与应付单分录同键,
+ * 便于金蝶侧逐行勾稽);A302-6 按「收票单防重键 ↔ 财务应付单防重键」一票一应付指定。
* 财务应付单侧(A102-2/A102-4)取数与校验复用 {@link KingdeeApBillSyncSupport}。
*
* @author Chill
@@ -149,27 +148,28 @@ public class KingdeeReceivedInvoiceSyncSupport {
/**
* 推送金蝶收票单(A303-2 批量保存,调用方已完成 A801-3 幂等预检)。
* 主表:组织/币种/往来户编码按分摊结算单关联合同派生(与应付单同源),销方=开票单位、购方=受票单位;
- * 分录逐票池明细行展开(items_json),物料编码为联调期临时配置值。
- * 联调口径沿用应付/应收侧:先只送 DTO 标注【必填】字段与票面核心字段(发票号码、税额金额),
- * 非必填的地址/电话/开户行等 setter 注释保留,联调确认后放开
+ * 明细分录与财务应付单 A102-2 同源同口径:逐结算货物费用行一条(不聚合),物料/数量/单价/金额取自
+ * 与应付单分录同一解析结果({@link KingdeeApBillSyncSupport.BfiEntryGroup}),分录标识同取费用行id。
+ * 联调口径沿用应付侧:先只送 DTO 标注【必填】字段与票面核心字段(发票号码),
+ * 非必填的税号/地址/电话/开户行等 setter 注释保留,联调确认后放开
*
- * @param receipt 收票登记(审批通过、已绑定镜像)
- * @param mirror 票池镜像(invoice_type_code/items_json 取数来源)
- * @param contract 分摊应付结算单关联合同(组织/币种/往来户派生)
- * @param context 金蝶同步上下文(缓存载体)
+ * @param receipt 收票登记(审批通过、已绑定镜像)
+ * @param mirror 票池镜像(票面主表字段取数来源)
+ * @param contract 分摊应付结算单关联合同(组织/币种/往来户派生)
+ * @param entryGroups 分摊结算单解析出的货物费用行分录(与应付单 A102-2 同一解析结果)
+ * @param context 金蝶同步上下文(缓存载体)
* @return 金蝶收票单单据编号
*/
public String pushReceivedInvoice(InvoiceReceipt receipt, KingdeeInvoicePool mirror, ContractManage contract,
- KingdeeSyncContext context) {
+ List entryGroups, KingdeeSyncContext context) {
if (Func.isBlank(mirror.getInvoiceTypeCode())) {
throw new ServiceException("推送金蝶收票单失败:镜像缺少票易通发票类型编码,请先重新绑定票池发票");
}
- List items = parseItems(mirror);
BfiReceivedInvoiceSaveDTO param = new BfiReceivedInvoiceSaveDTO();
// 【必填】外部系统编号(候选键)
param.setAwrpSrcSys(bfiProperties.getSrcSys());
// 【必填】外部系统来源单据号(候选键):发票号码(一票一登记天然唯一,重推同键幂等)
- param.setAwrpSrcCode(receipt.getInvoiceNo());
+ param.setAwrpSrcCode(String.valueOf(receipt.getId()));
// 发票号码(非必填但为收票单核心业务字段,必送)
param.setInvoiceno(receipt.getInvoiceNo());
// 【必填】收票日期:以推送当日为收票日期
@@ -199,20 +199,20 @@ public class KingdeeReceivedInvoiceSyncSupport {
// 录入价税合计(非必传,与应付/应收侧口径一致传 true)
param.setIsincludetax(true);
// 销方税号(非必传,联调确认后放开)
- // param.setSellertin(mirror.getSalerTaxNo());
+ param.setSellertin(mirror.getSalerTaxNo());
// 购方税号(非必传,联调确认后放开)
- // param.setBuyertin(mirror.getBuyerTaxNo());
+ param.setBuyertin(mirror.getBuyerTaxNo());
// 销方地址/电话/开户行/账号(非必传,被金蝶强校验时从镜像 bank_name/bank_account/issuing_bank/phone 补齐)
// param.setSelleraddr(null);
- // param.setSellertel(mirror.getPhone());
- // param.setSellerbank(mirror.getBankName());
- // param.setSellracct(mirror.getBankAccount());
+ param.setSellertel(mirror.getPhone());
+ param.setSellerbank(mirror.getBankName());
+ param.setSelleracct(mirror.getBankAccount());
// 备注(非必传)
// param.setRemark(receipt.getRemark());
// 【必填】外部单据类型
param.setAwrpSrcType(KingdeeBfiConstant.BFI_RECEIVED_INVOICE_SRC_TYPE);
- // 【必填】明细分录:逐票池明细行展开
- param.setEntry(buildEntries(receipt.getInvoiceNo(), items));
+ // 【必填】明细分录:与应付单 A102-2 同源同口径,逐结算货物费用行一条(不聚合)
+ param.setEntry(buildEntries(entryGroups));
BfiBillSaveResultVO saveResult = bfiService.saveReceivedInvoice(param);
kingdeeApBillSyncSupport.requireBfiSaveSuccess(saveResult, "推送金蝶收票单");
@@ -220,85 +220,46 @@ public class KingdeeReceivedInvoiceSyncSupport {
}
/**
- * 票池明细行 → A303-2 分录:金额/税额/数量/单价直取票面明细,税收分类编码取 goodsCode,
- * 税率换算为金蝶税率编码(0.13→V13),物料编码为联调期临时配置值(发票明细无金蝶物料概念)
+ * 分录组装与应付单 A102-2 明细同源同口径:逐结算货物费用行一条(不聚合),物料/数量/单价/含税/不含税金额
+ * 取自 {@link KingdeeApBillSyncSupport.BfiEntryGroup}(与应付单分录同一解析结果),分录标识同取费用行id,
+ * 便于金蝶侧收票单明细与应付单明细逐行勾稽;数量无吨位时兜底 1;
+ * 票面明细行(items_json)不再作为分录取数来源,税收分类/税率/税额随应付口径暂不送(联调强校验时再补)
*
- * @param invoiceNo 发票号码(分录防重键前缀)
- * @param items 票池明细行
+ * @param entryGroups 分摊结算单解析出的货物费用行分录(与应付单 A102-2 同一解析结果)
* @return A303-2 明细分录
*/
- private List buildEntries(String invoiceNo, List items) {
+ private List buildEntries(List entryGroups) {
List entries = new ArrayList<>();
- int index = 1;
- for (BfiInvoicePoolItemVO item : items) {
- BigDecimal amount = parseItemAmount(invoiceNo, item.getDetailAmount(), "金额");
- BigDecimal tax = parseItemAmount(invoiceNo, item.getTaxAmount(), "税额");
+ for (KingdeeApBillSyncSupport.BfiEntryGroup entryGroup : entryGroups) {
BfiReceivedInvoiceSaveEntryDTO entry = new BfiReceivedInvoiceSaveEntryDTO();
- // 【必填】价税合计:明细行金额+税额
- entry.setEPricetaxtotal(kingdeeApBillSyncSupport.money(amount.add(tax)));
- // 【必填】数量:票面数量,无数量兜底 1
- BigDecimal quantity = parseItemAmount(invoiceNo, item.getNum(), "数量");
- entry.setQuantity(quantity.compareTo(BigDecimal.ZERO) > 0 ? quantity : BigDecimal.ONE);
- // 【必填】单价:票面单价(金额口径为价税合计录入,单价仅作参考)
- entry.setPrice(parseItemAmount(invoiceNo, item.getUnitPrice(), "单价"));
- // 【必填】物料编码.编码:联调期临时值,真实口径与金蝶对齐后替换
- entry.setMaterialNumber(bfiProperties.getReceivedInvoice().getMaterialNumber());
- // 【必填】税收分类.编码:票面明细税收分类编码
- if (Func.isBlank(item.getGoodsCode())) {
- throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + index + "行明细无税收分类编码");
+ // 【必填】价税合计:该行含税金额(与应付单 e_pricetaxtotal 同源取数)
+ entry.setEPricetaxtotal(kingdeeApBillSyncSupport.money(entryGroup.amount()));
+ // 【必填】数量:该行运输数量,无吨位数据时兜底 1(与应付单口径一致)
+ entry.setQuantity(entryGroup.quantity().compareTo(BigDecimal.ZERO) > 0 ? entryGroup.quantity() : BigDecimal.ONE);
+ // 【必填】单价:该行运输单价(与应付单同源;单据头 isincludetax=true 录入价税合计,单价仅作参考)
+ entry.setPrice(entryGroup.price());
+ // 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
+ entry.setMaterialNumber(entryGroup.materialNumber());
+ // 金额(不含税):该行结算金额(不含税)(与应付单 e_amount 同源取数)
+ entry.setEAmount(kingdeeApBillSyncSupport.money(entryGroup.amountNoTax()));
+ // 【必填】外部系统来源分录标识(候选键):应付货物费用行id(与应付单分录同键,便于逐行勾稽)
+ entry.setAwrpEntryCode(entryGroup.entryCode());
+
+ // 调试(与应付单 A102-2 保持一致:联调期物料编码临时传金蝶测试档案值,联调通过后与应付侧同步移除)
+
+ if (true) {
+ // 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
+ entry.setMaterialNumber(KingdeeBfiConstant.BFI_AP_BILL_MATERIAL_NUMBER_TEST);
}
- entry.setETaxclassNumber(item.getGoodsCode().trim());
- // 【必填】税率.编码:票面税率(小数)换算金蝶税率编码(0.13→V13)
- entry.setTaxrateidNumber(taxRateCode(invoiceNo, index, item.getTaxRate()));
- // 金额(不含税)/税额(非必传,票面直取)
- entry.setEAmount(kingdeeApBillSyncSupport.money(amount));
- entry.setETax(kingdeeApBillSyncSupport.money(tax));
- // 【必填】外部系统来源分录标识(候选键):发票号码-行号
- entry.setAwrpEntryCode(invoiceNo + "-" + index);
+
entries.add(entry);
- index++;
}
if (entries.isEmpty()) {
- throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "票池明细为空,请先补查票池发票明细");
+ throw new ServiceException("推送金蝶收票单失败:分摊结算单无货物费用行,无法组装收票单明细");
}
return entries;
}
- /**
- * 票池明细行 JSON 解析(items 为空时返回空列表,由调用方拦截提示补查)
- */
- public List parseItems(KingdeeInvoicePool mirror) {
- if (Func.isBlank(mirror.getItemsJson())) {
- return List.of();
- }
- BfiInvoicePoolItemVO[] items = JsonUtil.parse(mirror.getItemsJson(), BfiInvoicePoolItemVO[].class);
- return items == null ? List.of() : Arrays.asList(items);
- }
-
- /** 票面税率(小数文本)换算金蝶税率编码:0.13→V13、0.01→V1、0→V0 */
- private String taxRateCode(String invoiceNo, int lineNo, String taxRateText) {
- if (Func.isBlank(taxRateText)) {
- throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + lineNo + "行明细税率为空");
- }
- try {
- BigDecimal percent = new BigDecimal(taxRateText.trim()).multiply(BigDecimal.valueOf(100)).stripTrailingZeros();
- return "V" + percent.toPlainString();
- } catch (NumberFormatException exception) {
- throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + lineNo + "行明细税率[" + taxRateText + "]无法解析");
- }
- }
-
- private BigDecimal parseItemAmount(String invoiceNo, String value, String name) {
- if (Func.isBlank(value)) {
- return BigDecimal.ZERO;
- }
- try {
- return new BigDecimal(value.trim());
- } catch (NumberFormatException exception) {
- throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "明细" + name + "[" + value + "]无法解析");
- }
- }
-
// ==================== A302-6 收票指定应付 ====================
/**