From 0c064f06c9c6dbb6836a452c31e516b497c7e01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Wed, 23 Sep 2026 16:01:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B6=E7=A5=A8=E5=8D=95=E5=88=86?= =?UTF-8?q?=E5=BD=95=E6=94=B9=E4=B8=BA=E4=B8=8E=E5=BA=94=E4=BB=98=E5=8D=95?= =?UTF-8?q?=E5=90=8C=E6=BA=90=E7=BB=93=E7=AE=97=E8=B4=B9=E7=94=A8=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E9=98=B2=E9=87=8D=E9=94=AE=E6=94=B9=E7=94=A8=E6=94=B6?= =?UTF-8?q?=E7=A5=A8=E7=99=BB=E8=AE=B0id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/entity/KingdeeInvoicePool.java | 2 +- .../KingdeeReceivedInvoiceSyncSupport.java | 139 +++++++----------- 2 files changed, 51 insertions(+), 90 deletions(-) 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 收票指定应付 ==================== /**