diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/constant/BfiConstant.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/constant/BfiConstant.java index 0103f77c..da49d861 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/constant/BfiConstant.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/constant/BfiConstant.java @@ -25,6 +25,8 @@ */ package org.springblade.thirdparty.bfi.constant; +import java.util.Map; + /** * 业财融合(BFI)常量类 * @@ -37,6 +39,16 @@ public class BfiConstant { */ public static final String SUCCESS_CODE = "0"; + /** + * 收票 rim/message 契约调用成功错误码 + */ + public static final String RIM_SUCCESS_CODE = "0000"; + + /** + * A801-1 查询全票池发票 消息类型,固定值 + */ + public static final String MESSAGE_TYPE_INVOICE_QUERY = "invoiceQuery"; + /** * 网关认证请求头名称 */ @@ -376,4 +388,42 @@ public class BfiConstant { * 预付信息单据类型:期初预付单 */ public static final String PREPAY_BILL_TYPE_INIT_PAID = "ap_paidbill"; + + /** + * 发票类型编码 -> 名称,编码明细参考票易通文档《发票数据结构》 + */ + public static final Map INVOICE_TYPE_NAMES = Map.ofEntries( + Map.entry("1", "增值税电子普通发票"), + Map.entry("2", "增值税电子专用发票"), + Map.entry("3", "增值税普通发票"), + Map.entry("4", "增值税专用发票"), + Map.entry("5", "增值税普通发票(卷票)"), + Map.entry("7", "通用机打发票"), + Map.entry("8", "出租车票"), + Map.entry("9", "火车票"), + Map.entry("10", "飞机票"), + Map.entry("11", "其他票"), + Map.entry("12", "机动车销售统一发票"), + Map.entry("13", "二手车销售统一发票"), + Map.entry("14", "定额发票"), + Map.entry("15", "增值税电子普通发票(通行费)"), + Map.entry("16", "汽车票"), + Map.entry("17", "过路(过桥)费发票"), + Map.entry("19", "税收完税证明"), + Map.entry("20", "轮船票"), + Map.entry("21", "海关缴款书"), + Map.entry("23", "通用机打电子发票"), + Map.entry("24", "火车票退票凭证"), + Map.entry("25", "财政电子票据"), + Map.entry("26", "数电发票(普通发票)"), + Map.entry("27", "数电发票(增值税专用发票)"), + Map.entry("28", "数电发票(航空运输电子客票行程单)"), + Map.entry("29", "数电发票(铁路电子客票)"), + Map.entry("30", "海外发票"), + Map.entry("31", "海外收据"), + Map.entry("47", "增值税代扣代缴完税凭证"), + Map.entry("72", "数电发票(通行费发票)"), + Map.entry("83", "数电发票(机动车销售统一发票)"), + Map.entry("84", "数电发票(二手车销售统一发票)") + ); } diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDTO.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDTO.java index 076f5464..33db138e 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDTO.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDTO.java @@ -30,20 +30,11 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.util.Map; /** * 业财融合(BFI)A801-1 查询全票池发票数据参数 *

- * 请求示例: - *

{@code
- * {
- *   "queryParam": {
- *     "invoiceNo": "25532000000044538098"
- *   }
- * }
- * }
- * queryParam 字段明细参考票易通文档 https://open.piaozone.com/api-145421056 + * 收票 rim/message 契约请求体,字段明细参考票易通文档 https://open.piaozone.com/api-145421056 * * @author Chill */ @@ -53,7 +44,15 @@ public class BfiInvoicePoolQueryDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; /** - * 【必填】查询参数,键值对形式,如 invoiceNo(发票号码),字段明细参考 https://open.piaozone.com/api-145421056 + * 请求类型,固定值 invoiceQuery,由服务端填充 */ - private Map queryParam; + private String messageType; + /** + * 请求id,唯一标识,由服务端填充 + */ + private String messageId; + /** + * 【必填】查询条件主体 + */ + private BfiInvoicePoolQueryDataDTO data; } diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDataDTO.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDataDTO.java new file mode 100644 index 00000000..dde54423 --- /dev/null +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/dto/BfiInvoicePoolQueryDataDTO.java @@ -0,0 +1,139 @@ +/** + * 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.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 业财融合(BFI)A801-1 查询全票池发票数据 查询条件主体 + *

+ * 字段明细参考票易通文档 https://open.piaozone.com/api-145421056 + * + * @author Chill + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BfiInvoicePoolQueryDataDTO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + /** + * 页码,从1开始 + */ + private Integer pageNo; + /** + * 每页数量,最大500 + */ + private Integer pageSize; + /** + * 开票日期起 yyyy-MM-dd + */ + private String invoiceDateStart; + /** + * 开票日期止 yyyy-MM-dd + */ + private String invoiceDateEnd; + /** + * 发票类型编码 + */ + private String invoiceType; + /** + * 增值税发票状态 + */ + private String invoiceStatus; + /** + * 发票代码 + */ + private String invoiceCode; + /** + * 发票号码 + */ + private String invoiceNo; + /** + * 增值税用途勾选状态[0未勾选,1已勾选,2勾选认证,3扫描认证,4预勾选] + */ + private String authenticateFlag; + /** + * 单据使用状态,多个用英文逗号分隔[1未用,30在用,60已用,65已入账] + */ + private String expenseStatus; + /** + * 核算组织编码数组 + */ + private List orgNumber; + /** + * 采集时间起 yyyy-MM-dd + */ + private String createTimeStart; + /** + * 采集时间止 yyyy-MM-dd(票开放平台原始拼写 creatTimeEnd) + */ + private String creatTimeEnd; + /** + * 采集用户编码 + */ + private String creatorNo; + /** + * 购方税号 + */ + private String buyerTaxNo; + /** + * 销方税号 + */ + private String salerTaxNo; + /** + * 发票流水号数组 + */ + private List serialNos; + /** + * 勾选时间起 yyyy-MM-dd + */ + private String selectTimeStart; + /** + * 勾选时间止 yyyy-MM-dd + */ + private String selectTimeEnd; + /** + * 抵扣税期 YYYY-MM + */ + private String taxPeriod; + /** + * 更新时间起 yyyy-MM-dd HH:mm:ss + */ + private String updateTime; + /** + * 更新时间止 yyyy-MM-dd HH:mm:ss + */ + private String updateTimeEnd; + /** + * 是否查询发票附件[1是,0否] + */ + private String queryAttach; +} diff --git a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoicePoolVO.java b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoicePoolVO.java index fb45dd96..300321a3 100644 --- a/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoicePoolVO.java +++ b/blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/pojo/vo/BfiInvoicePoolVO.java @@ -104,6 +104,11 @@ public class BfiInvoicePoolVO implements Serializable { */ @JsonProperty("totalTaxAmount") private String totalTaxAmount; + /** + * 税率(票开放平台顶层税率字段) + */ + @JsonProperty("taxRate") + private String taxRate; /** * 可用税额 */ @@ -114,6 +119,11 @@ public class BfiInvoicePoolVO implements Serializable { */ @JsonProperty("buyerTaxNo") private String buyerTaxNo; + /** + * 购方名称 + */ + @JsonProperty("buyerName") + private String buyerName; /** * 销方税号 */ 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 8599a270..0aadab51 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 @@ -278,10 +278,10 @@ public interface IBfiService { BfiBillSaveResultVO pushClaimResult(BfiClaimPushDTO param); /** - * A801-1 查询全票池发票数据 + * A801-1 查询全票池发票数据(自动翻页汇总全量) * - * @param param 查询参数,queryParam 键值对参考票易通文档 - * @return 票池发票数据列表 + * @param param 查询参数,messageType/messageId 由服务端填充;data.pageSize 必填,作为单页大小与翻页终止依据 + * @return 票池发票数据全量列表 */ List queryInvoicePool(BfiInvoicePoolQueryDTO param); 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 5f19471c..0613bdd1 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 @@ -56,6 +56,7 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignBodyDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceDownloadDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDTO; +import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDataDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushBodyDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyQueryDTO; @@ -98,6 +99,7 @@ import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.function.Supplier; /** @@ -483,12 +485,29 @@ public class BfiServiceImpl implements IBfiService { @Override public List queryInvoicePool(BfiInvoicePoolQueryDTO param) { - if (param == null || param.getQueryParam() == null || param.getQueryParam().isEmpty()) { + if (param == null || param.getData() == null) { throw new BfiException("查询BFI全票池发票失败,查询参数不能为空"); } - String accessToken = getAccessToken(); - BfiResultVO> result = commonCall("全票池发票查询", () -> client.invoicePoolQuery(accessToken, param)); - return result.getData() != null ? result.getData() : List.of(); + param.setMessageType(BfiConstant.MESSAGE_TYPE_INVOICE_QUERY); + // A801-1 rim 契约仅返回当前页数据且无总数,统一通过通用翻页工具取全量,由调用方自行分页 + BfiInvoicePoolQueryDataDTO data = param.getData(); + long start = System.currentTimeMillis(); + log.info("调用BFI接口 全票池发票查询开始 pageSize:{}", data.getPageSize()); + List invoices = BfiUtil.queryAllPages(pageNo -> { + // 翻页工具逐页注入页码;rim 契约要求每次请求携带唯一 messageId + data.setPageNo(pageNo); + param.setMessageId(UUID.randomUUID().toString()); + BfiResultVO> result = commonRimCall("全票池发票查询", + () -> client.invoicePoolQuery(getAccessToken(), param)); + List rows = result.getData() != null ? result.getData() : List.of(); + // rim 契约无 lastPage,包装为 BfiPageVO 借助"数据不满一页即末页"规则终止翻页 + BfiPageVO page = new BfiPageVO<>(); + page.setRows(rows); + page.setPageSize(data.getPageSize()); + return page; + }); + log.info("调用BFI接口 全票池发票查询结束 耗时:{}ms,全量 {} 条", System.currentTimeMillis() - start, invoices.size()); + return invoices; } @Override @@ -542,6 +561,34 @@ public class BfiServiceImpl implements IBfiService { } } + /** + * 收票 rim/message 契约公共调用方法,成功错误码兼容 0 与 0000 + * + * @param type 调用类型描述 + * @param supplier 实际调用 + * @param 响应数据类型 + * @return 响应结果 + */ + private BfiResultVO commonRimCall(String type, Supplier> supplier) { + try { + BfiResultVO result = supplier.get(); + if (result != null && Boolean.TRUE.equals(result.getStatus()) + && (BfiConstant.SUCCESS_CODE.equals(result.getErrorCode()) || BfiConstant.RIM_SUCCESS_CODE.equals(result.getErrorCode()))) { + return result; + } + 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) { + throw e; + } catch (Exception e) { + log.error("调用BFI接口 {}异常", type, e); + throw new BfiException("调用BFI接口" + type + "异常", e); + } + } + /** * 开放平台公共调用方法,校验 success 标识 * 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 4591ccb8..94dfaaf2 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 @@ -60,6 +60,7 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignBodyDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignRefDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDTO; +import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoicePoolQueryDataDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceQueryDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveEntryDTO; @@ -528,8 +529,11 @@ class BfiServiceImplTest { BfiInvoicePoolVO invoice = new BfiInvoicePoolVO(); when(client.invoicePoolQuery(anyString(), any(BfiInvoicePoolQueryDTO.class))) .thenReturn(success(List.of(invoice))); + BfiInvoicePoolQueryDataDTO data = new BfiInvoicePoolQueryDataDTO(); + data.setInvoiceNo("25532000000044538098"); + data.setPageSize(500); BfiInvoicePoolQueryDTO param = new BfiInvoicePoolQueryDTO(); - param.setQueryParam(Map.of("invoiceNo", "25532000000044538098")); + param.setData(data); List rows = service.queryInvoicePool(param); assertEquals(1, rows.size()); }