From 1268561fd939d2275a2754f092f77990d2459973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Tue, 22 Sep 2026 14:07:25 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20A303-5=E5=BC=80=E7=A5=A8=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E5=9B=9E=E5=86=99=E6=94=AF=E6=8C=81Base64=E8=A7=A3?= =?UTF-8?q?=E7=A0=81=E5=93=8D=E5=BA=94=E5=B9=B6=E8=A1=A5=E5=85=85=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/InvoiceApplicationServiceImpl.java | 57 ++- .../thirdparty/bfi/feign/IBfiClient.java | 4 +- .../pojo/vo/BfiInvoiceApplyOpenApiBillVO.java | 51 +++ .../vo/BfiInvoiceApplyOpenApiInvoiceVO.java | 341 ++++++++++++++++++ .../thirdparty/bfi/service/IBfiService.java | 5 +- .../bfi/service/impl/BfiServiceImpl.java | 17 +- .../bfi/service/BfiServiceImplTest.java | 7 +- 7 files changed, 454 insertions(+), 28 deletions(-) create mode 100644 blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiBillVO.java create mode 100644 blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiInvoiceVO.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java index df4065a2..b3558f83 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java @@ -44,6 +44,8 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceApplyQueryDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceApplySaveDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceApplySaveEntryDTO; import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO; +import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyOpenApiBillVO; +import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyOpenApiInvoiceVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiOpenApiResultVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiPageVO; @@ -738,6 +740,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl applicationBySerialNo = new LinkedHashMap<>(); applications.forEach(application -> { @@ -766,6 +769,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl new ArrayList<>()).add(entry.getKey()); } + log.info("金蝶开票结果回写:按销方税号分组完成,共{}组,销方税号无法解析跳过{}笔", serialNosBySellerTaxNo.size(), unresolved); int invoiceWritten = 0; int failed = 0; int invoicedCount = 0; @@ -773,34 +777,28 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl groupSerialNos = group.getValue(); for (int from = 0; from < groupSerialNos.size(); from += INVOICE_RESULT_QUERY_BATCH) { List batch = groupSerialNos.subList(from, Math.min(from + INVOICE_RESULT_QUERY_BATCH, groupSerialNos.size())); + String batchSerialNos = String.join(",", batch); + log.info("金蝶开票结果回写批次查询:销方税号=[{}],单据编号=[{}],数量={}", group.getKey(), batchSerialNos, batch.size()); try { BfiInvoiceApplyOpenApiQueryDTO param = new BfiInvoiceApplyOpenApiQueryDTO(); - param.setSerialNo(String.join(",", batch)); + param.setSerialNo(batchSerialNos); param.setSellerTaxpayerId(group.getKey()); - BfiOpenApiResultVO> result = bfiService.queryInvoiceApplyInvoice(param); + BfiOpenApiResultVO> result = bfiService.queryInvoiceApplyInvoice(param); if (result == null || !result.isSuccess() || result.getData() == null) { failed++; - log.warn("金蝶开票结果回写批次查询未成功:serialNo=[{}]", String.join(",", batch)); + log.warn("金蝶开票结果回写批次查询未成功:serialNo=[{}]", batchSerialNos); continue; } - for (Object billItem : result.getData()) { - if (!(billItem instanceof Map)) { - continue; - } - Map billMap = (Map) billItem; - String billNo = firstText(billMap, "billNo", "billno"); + log.info("金蝶开票结果回写批次查询成功:serialNo=[{}],返回单据数={}", batchSerialNos, result.getData().size()); + for (BfiInvoiceApplyOpenApiBillVO bill : result.getData()) { + String billNo = bill.getBillNo(); InvoiceApplication application = applicationBySerialNo.get(billNo); - if (application == null || !(billMap.get("invoiceList") instanceof List)) { + if (application == null || bill.getInvoiceList() == null) { continue; } int inserted = 0; - for (Object invoiceItem : (List) billMap.get("invoiceList")) { - if (!(invoiceItem instanceof Map)) { - continue; - } - Map invoice = (Map) invoiceItem; - // 宽松提取关键字段(票易通原文,字段名联调校准后收敛),原文整体 JSON 留档 - String invoiceNo = firstText(invoice, "invoiceNo", "invoice_no", "invoiceNumber"); + for (BfiInvoiceApplyOpenApiInvoiceVO invoice : bill.getInvoiceList()) { + String invoiceNo = invoice.getInvoiceNumber(); if (Func.isBlank(invoiceNo)) { continue; } @@ -809,17 +807,20 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl 0) { + log.debug("金蝶开票结果回写:发票号码[{}]已存在,跳过", invoiceNo); continue; } InvoiceApplicationInvoice record = new InvoiceApplicationInvoice(); record.setInvoiceApplicationId(application.getId()); record.setSerialNo(billNo); record.setInvoiceNo(invoiceNo); - record.setInvoiceDate(parseInvoiceDate(firstText(invoice, "invoiceDate", "invoice_date", "issueDate", "billingDate"))); - record.setInvoiceAmount(parseDecimal(firstText(invoice, "amountWithTax", "totalAmount", "invoiceAmount", "priceTaxTotal"))); + record.setInvoiceDate(parseInvoiceDateTimestamp(invoice.getInvoiceDate())); + record.setInvoiceAmount(invoice.getIncludeTaxAmount()); record.setRawJson(JsonUtil.toJson(invoice)); invoiceApplicationInvoiceMapper.insert(record); inserted++; + log.info("金蝶开票结果回写:写入发票,申请单号=[{}],单据编号=[{}],发票号码=[{}],含税金额=[{}]", + application.getApplicationNo(), billNo, invoiceNo, invoice.getIncludeTaxAmount()); } if (inserted > 0) { invoiceWritten += inserted; @@ -827,14 +828,17 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl> invoiceApplyOpenApi(@RequestHeader(BfiConstant.HEADER_ACCESS_TOKEN) String accessToken, @RequestBody BfiInvoiceApplyOpenApiBodyDTO param); + BfiOpenApiResultVO invoiceApplyOpenApi(@RequestHeader(BfiConstant.HEADER_ACCESS_TOKEN) String accessToken, @RequestBody BfiInvoiceApplyOpenApiBodyDTO param); /** * A303-2 收票单新增(批量保存) diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiBillVO.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiBillVO.java new file mode 100644 index 00000000..c28c76bd --- /dev/null +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiBillVO.java @@ -0,0 +1,51 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. THE AUTHOR IS + * NOT LIABLE FOR ANY CLAIMS ARISING FROM SECONDARY OR ILLEGAL DEVELOPMENT. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.thirdparty.bfi.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业财融合(BFI)A303-5 开票申请发票查询返回单据信息 + * + * @author Chill + */ +@Data +public class BfiInvoiceApplyOpenApiBillVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 单据编号 + */ + private String billNo; + /** + * 发票列表 + */ + private List invoiceList; +} diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiInvoiceVO.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiInvoiceVO.java new file mode 100644 index 00000000..35ace40b --- /dev/null +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoiceApplyOpenApiInvoiceVO.java @@ -0,0 +1,341 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. THE AUTHOR IS + * NOT LIABLE FOR ANY CLAIMS ARISING FROM SECONDARY OR ILLEGAL DEVELOPMENT. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.thirdparty.bfi.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 业财融合(BFI)A303-5 开票申请发票查询返回发票明细 + * + * @author Chill + */ +@Data +public class BfiInvoiceApplyOpenApiInvoiceVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 发票ID + */ + private Long id; + /** + * 发票代码 + */ + private String invoiceCode; + /** + * 发票号码 + */ + private String invoiceNumber; + /** + * 开票日期(时间戳) + */ + private Long invoiceDate; + /** + * 发票类型 + */ + private String invoiceType; + /** + * 发票状态 + */ + private Integer invoiceStatus; + /** + * 发票属性 + */ + private Integer invoiceProperty; + /** + * 发票来源 + */ + private String invoiceSource; + /** + * 单据编号 + */ + private String billNo; + /** + * 流水号 + */ + private String serialNo; + /** + * 购方名称 + */ + private String buyerName; + /** + * 购方纳税人识别号 + */ + private String buyerTaxpayerId; + /** + * 购方地址及电话 + */ + private String buyerAddressAndTel; + /** + * 购方银行及账号 + */ + private String buyerBankAndAccount; + /** + * 购方性质 + */ + private Integer buyerProperty; + /** + * 购方收件人邮箱 + */ + private String buyerRecipientMail; + /** + * 购方收件人电话 + */ + private String buyerRecipientPhone; + /** + * 销方名称 + */ + private String sellerName; + /** + * 销方纳税人识别号 + */ + private String sellerTaxpayerId; + /** + * 销方地址及电话 + */ + private String sellerAddressAndTel; + /** + * 销方银行及账号 + */ + private String sellerBankAndAccount; + /** + * 不含税金额 + */ + private BigDecimal totalAmount; + /** + * 税额 + */ + private BigDecimal totalTaxAmount; + /** + * 价税合计 + */ + private BigDecimal includeTaxAmount; + /** + * 可红冲金额 + */ + private BigDecimal canRedAmount; + /** + * 可红冲税额 + */ + private BigDecimal canRedTaxAmount; + /** + * 合并差额税额 + */ + private BigDecimal mergeDifferTaxAmount; + /** + * 开票人 + */ + private String drawer; + /** + * 收款人 + */ + private String payee; + /** + * 复核人 + */ + private String reviewer; + /** + * 作废人 + */ + private String canceler; + /** + * 备注 + */ + private String remark; + /** + * 校验码 + */ + private String checkCode; + /** + * 合同编号 + */ + private String contractNo; + /** + * 设备编号 + */ + private String deviceNo; + /** + * 终端编号 + */ + private String terminalNo; + /** + * 机构名称 + */ + private String orgName; + /** + * 原发票代码 + */ + private String originalInvoiceCode; + /** + * 原发票号码 + */ + private String originalInvoiceNumber; + /** + * 红冲单据号 + */ + private String redInfoBillNo; + /** + * 红冲原因 + */ + private String redReason; + /** + * 作废原因 + */ + private String abolishReason; + /** + * 发票文件URL + */ + private String invoiceFileUrl; + /** + * 发票图片URL + */ + private String invoiceImageUrl; + /** + * 开具状态 + */ + private String issueStatus; + /** + * 下载标记 + */ + private Integer downloadFlag; + /** + * 打印标记 + */ + private Integer printFlag; + /** + * 库存标记 + */ + private Integer inventoryFlag; + /** + * 特殊红冲标记 + */ + private Integer specialRedFlag; + /** + * 拆分合并标记 + */ + private String splitOrMergeFlag; + /** + * 商品明细列表 + */ + private List goods; + + /** + * 业财融合(BFI)A303-5 开票申请发票查询返回商品明细 + */ + @Data + public static class BfiInvoiceApplyOpenApiGoodsVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 明细ID + */ + private String detailId; + /** + * 明细行号 + */ + private Integer detailRowNo; + /** + * 序号 + */ + private Integer seq; + /** + * 商品名称 + */ + private String goodsName; + /** + * 规格型号 + */ + private String specification; + /** + * 单位 + */ + private String units; + /** + * 数量 + */ + private String quantity; + /** + * 单价 + */ + private String price; + /** + * 金额 + */ + private BigDecimal amount; + /** + * 税率 + */ + private String taxRate; + /** + * 税额 + */ + private BigDecimal taxAmount; + /** + * 含税金额 + */ + private BigDecimal includeTaxAmount; + /** + * 含税单价 + */ + private String includeTaxPrice; + /** + * 行性质 + */ + private Integer lineProperty; + /** + * 税收分类编码 + */ + private String revenueCode; + /** + * 税收分类名称 + */ + private String revenueName; + /** + * 单据来源ID + */ + private String billSourceId; + /** + * 优惠标记 + */ + private Integer privilegeFlag; + /** + * 优惠内容 + */ + private String privilegeContent; + /** + * 征税标记 + */ + private String taxFlag; + /** + * 零税率标记 + */ + private String zeroTaxRateFlag; + } +} diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/IBfiService.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/IBfiService.java index 7b2ddf9f..0faceef1 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/IBfiService.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/IBfiService.java @@ -53,6 +53,7 @@ import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiClaimAnnounceVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiCustomerVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiExchangeRateVO; +import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyOpenApiBillVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoicePoolVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiOpenApiResultVO; @@ -231,9 +232,9 @@ public interface IBfiService { * A303-5 开票申请发票查询 * * @param param 查询原文参数,单据编号多个用英文逗号隔开,上限50个 - * @return 发票查询结果,data 内容参考票易通文档 + * @return 发票查询结果,data 为单据及发票明细列表 */ - BfiOpenApiResultVO> queryInvoiceApplyInvoice(BfiInvoiceApplyOpenApiQueryDTO param); + BfiOpenApiResultVO> queryInvoiceApplyInvoice(BfiInvoiceApplyOpenApiQueryDTO param); /** * A303-2 收票单新增(批量保存) diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java index 69533637..478ef6d0 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/service/impl/BfiServiceImpl.java @@ -25,6 +25,7 @@ */ package org.springblade.thirdparty.bfi.service.impl; +import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import feign.Response; import lombok.RequiredArgsConstructor; @@ -76,6 +77,7 @@ import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiClaimAnnounceVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiCustomerVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiExchangeRateVO; +import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyOpenApiBillVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoiceApplyVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoicePoolVO; import org.springblade.thirdparty.bfi.pojo.vo.BfiOpenApiResultVO; @@ -398,7 +400,7 @@ public class BfiServiceImpl implements IBfiService { } @Override - public BfiOpenApiResultVO> queryInvoiceApplyInvoice(BfiInvoiceApplyOpenApiQueryDTO param) { + public BfiOpenApiResultVO> queryInvoiceApplyInvoice(BfiInvoiceApplyOpenApiQueryDTO param) { if (param == null || StringUtil.isBlank(param.getSerialNo()) || StringUtil.isBlank(param.getSellerTaxpayerId())) { throw new BfiException("查询BFI开票申请发票失败,单据编号与销方税号不能为空"); } @@ -409,7 +411,18 @@ public class BfiServiceImpl implements IBfiService { body.setInterfaceCode(BfiConstant.INVOICE_APPLY_OPEN_API_CODE); body.setData(data); String accessToken = getAccessToken(); - BfiOpenApiResultVO> result = commonOpenApiCall("开票申请发票查询", () -> client.invoiceApplyOpenApi(accessToken, body)); + BfiOpenApiResultVO rawResult = commonOpenApiCall("开票申请发票查询", () -> client.invoiceApplyOpenApi(accessToken, body)); + // 响应 data 为 Base64 编码字符串,需解码后解析为 JSON + BfiOpenApiResultVO> result = new BfiOpenApiResultVO<>(); + result.setErrorCode(rawResult.getErrorCode()); + result.setSuccess(rawResult.getSuccess()); + if (rawResult.isSuccess() && StringUtil.isNotBlank(rawResult.getData())) { + String decodedJson = new String(Base64.getDecoder().decode(rawResult.getData()), StandardCharsets.UTF_8); + JSONObject decodedObj = JSONUtil.parseObj(decodedJson); + result.setErrorCode(decodedObj.getStr("errorCode")); + result.setSuccess(decodedObj.getBool("success")); + result.setData(decodedObj.getBeanList("data", BfiInvoiceApplyOpenApiBillVO.class)); + } return result; } diff --git a/blade-third-party-api/blade-bfi-api/src/test/java/org/springblade/thirdparty/bfi/service/BfiServiceImplTest.java b/blade-third-party-api/blade-bfi-api/src/test/java/org/springblade/thirdparty/bfi/service/BfiServiceImplTest.java index 0d03dcdc..2f579fce 100644 --- a/blade-third-party-api/blade-bfi-api/src/test/java/org/springblade/thirdparty/bfi/service/BfiServiceImplTest.java +++ b/blade-third-party-api/blade-bfi-api/src/test/java/org/springblade/thirdparty/bfi/service/BfiServiceImplTest.java @@ -385,10 +385,13 @@ class BfiServiceImplTest { @Test void queryInvoiceApplyInvoiceShouldEncodeBase64Data() { mockAccessToken(); - BfiOpenApiResultVO> openApiResult = new BfiOpenApiResultVO<>(); + // 模拟接口返回 base64 编码的 data + String responseData = "{\"data\":[{\"billNo\":\"KPSQ-1\",\"invoiceList\":[]}],\"errorCode\":\"0\",\"success\":true}"; + String encodedData = Base64.getEncoder().encodeToString(responseData.getBytes(StandardCharsets.UTF_8)); + BfiOpenApiResultVO openApiResult = new BfiOpenApiResultVO<>(); openApiResult.setSuccess(true); openApiResult.setErrorCode("S0000"); - openApiResult.setData(List.of()); + openApiResult.setData(encodedData); when(client.invoiceApplyOpenApi(anyString(), any())).thenReturn(openApiResult); BfiInvoiceApplyOpenApiQueryDTO param = new BfiInvoiceApplyOpenApiQueryDTO(); param.setSerialNo("KPSQ-1,KPSQ-2");