From d3f549bf42350bdb42a09e6e51ff05e133800898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Mon, 5 Oct 2026 17:52:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=91=E8=9D=B6=E7=A5=A8=E6=B1=A0?= =?UTF-8?q?=E9=80=89=E7=A5=A8=E5=9B=9E=E5=88=87=E9=95=9C=E5=83=8F=E8=AF=BB?= =?UTF-8?q?=EF=BC=88=E6=9C=AC=E5=9C=B0=E7=9C=9F=E5=88=86=E9=A1=B5+?= =?UTF-8?q?=E7=A5=A8=E5=8F=B7=E6=9C=AA=E5=91=BD=E4=B8=AD=E5=9B=9E=E6=BA=90?= =?UTF-8?q?+=E7=98=A6=E8=BA=AB=E7=89=88=E5=85=A8=E7=A5=A8=E6=B1=A0?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E5=90=8C=E6=AD=A5+=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E8=AE=A4=E9=A2=86=E7=A1=AE=E8=AE=A4=E9=87=8D=E6=9F=A5=E9=97=B8?= =?UTF-8?q?=E9=97=A8=EF=BC=8C=E4=B8=8B=E7=BA=BF=E5=AE=9E=E6=97=B6=E6=B8=B8?= =?UTF-8?q?=E6=A0=87=E9=80=8F=E4=BC=A0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 选票两门面端点改查 blade_kingdee_invoice_pool 本地镜像:Mapper.xml 真分页+total 跳页, 名称模糊/票种(invoice_type_code)/双税号/claimed/registered 三态过滤 SQL 直出, 仅出可用票(available_amount>0 且 amount_use_status='0',本地化执行原 isUsablePoolRow 口径) - 票号精确查本地未命中→窄查一次 A801-1 回源入库后重查,兜底增量窗口缝隙内当日新票 - 瘦身版同步任务回归:增量窗口分页拉取+镜像全列批量 upsert(无绑定/补偿/白名单/指纹), 复用 KingdeeSyncLockSupport single-flight;手动端点 POST /invoice-receipt/sync-invoice-pool 供 bootstrap - 镜像实体/表新增 available_amount/amount_use_status,fillInvoicePool 统一回填(存根/回源/同步同口径) - 结算认领确认补齐服务端按票号重查闸门(与收票保存 persistMirrorFromRemote 同闸门),收票保存重查原样保留 - 删除 KingdeeInvoicePoolPageVO 游标分页契约与 PoolScanCursor/decorateUseFlags 等实时链路代码 --- .../pojo/dto/KingdeeInvoicePoolQueryDTO.java | 51 ++- .../pojo/entity/KingdeeInvoicePool.java | 8 + .../pojo/vo/KingdeeInvoicePoolPageVO.java | 66 --- .../FormalSettlementController.java | 6 +- .../controller/InvoiceReceiptController.java | 13 +- .../mapper/KingdeeInvoicePoolMapper.java | 14 + .../mapper/KingdeeInvoicePoolMapper.xml | 86 ++++ .../service/IInvoiceReceiptService.java | 30 +- .../impl/FormalSettlementServiceImpl.java | 6 + .../impl/InvoiceReceiptServiceImpl.java | 395 ++++++++++-------- .../task/InvoiceReceiptKingdeeSyncTask.java | 69 +++ 11 files changed, 477 insertions(+), 267 deletions(-) delete mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolPageVO.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/task/InvoiceReceiptKingdeeSyncTask.java diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java index cf5a24e8..4e726481 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java @@ -5,23 +5,22 @@ * 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 + * 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 + * Using this software signifies agreement to the 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) + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package org.springblade.transport.pojo.dto; @@ -32,12 +31,12 @@ import java.io.Serial; import java.io.Serializable; /** - * 金蝶票池实时统一查询条件(收票登记选票与正式结算单认领共用) + * 金蝶票据池镜像统一查询条件(收票登记选票与正式结算单认领共用,查本地镜像) * * @author Chill */ @Data -@Schema(description = "金蝶票池实时统一查询条件") +@Schema(description = "金蝶票据池镜像统一查询条件") public class KingdeeInvoicePoolQueryDTO implements Serializable { @Serial @@ -73,6 +72,24 @@ public class KingdeeInvoicePoolQueryDTO implements Serializable { @Schema(description = "开票日期止 yyyy-MM-dd") private String invoiceDateEnd; + /** + * 开票单位(销方名称),模糊 + */ + @Schema(description = "开票单位(销方名称,模糊)") + private String salerName; + + /** + * 受票单位(购方名称),模糊 + */ + @Schema(description = "受票单位(购方名称,模糊)") + private String buyerName; + + /** + * 发票类型编码(票易通字典,如 26/27,镜像按 invoice_type_code 精确匹配) + */ + @Schema(description = "发票类型编码(票易通字典,如 26/27)") + private String invoiceType; + /** * 销方税号,精确 */ @@ -86,14 +103,14 @@ public class KingdeeInvoicePoolQueryDTO implements Serializable { private String buyerTaxNo; /** - * 发票类型编码(票易通字典,如 26/27) + * 认领过滤:true 仅已认领,false 仅未认领,null 不过滤(已认领=存在非作废正式结算单发票行) */ - @Schema(description = "发票类型编码(票易通字典,如 26/27)") - private String invoiceType; + @Schema(description = "认领过滤:true已认领/false未认领/null不限") + private Boolean claimed; /** - * 续扫游标:下一页翻页时原样回传上页返回的 nextCursor,空表示按页码锚定重新扫描 + * 登记过滤:true 仅已登记,false 仅未登记,null 不过滤(已登记=存在非作废收票登记) */ - @Schema(description = "续扫游标(下一页请求回传上页 nextCursor)") - private String cursor; + @Schema(description = "登记过滤:true已登记/false未登记/null不限") + private Boolean registered; } 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 fbde7a17..79eb12e7 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 @@ -143,4 +143,12 @@ public class KingdeeInvoicePool extends TenantEntity { * 票池数据更新时间(A801-1 billCreateTime,镜像排序字段) */ private LocalDateTime sourceUpdatedTime; + /** + * 剩余可用金额(A801-1 availableAmount,金蝶可用税额口径,选票本地过滤用:>0 才可选) + */ + private BigDecimal availableAmount; + /** + * 金额使用状态(A801-1 amountUseStatus,0 未使用可选 / 2 全部使用;选票本地过滤用) + */ + private String amountUseStatus; } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolPageVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolPageVO.java deleted file mode 100644 index 4e80ad61..00000000 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolPageVO.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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.transport.pojo.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.io.Serial; -import java.io.Serializable; -import java.util.List; - -/** - * 金蝶票池实时查询游标分页返回体(A801-1 无总数:hasMore 驱动上一页/下一页) - * - * @author Chill - */ -@Data -@Schema(description = "金蝶票池游标分页返回体") -public class KingdeeInvoicePoolPageVO implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** 本页数据(金蝶实时行 + 本地认领/登记装饰标记) */ - @Schema(description = "本页数据") - private List records; - - /** 当前页码,从 1 开始 */ - @Schema(description = "当前页码") - private Long current; - - /** 每页条数 */ - @Schema(description = "每页条数") - private Long size; - - /** 是否还有下一页(服务端有界补页扫描判断) */ - @Schema(description = "是否还有下一页") - private Boolean hasMore; - - /** 下一页续扫游标("远程页码:已消费过滤行数"),null 表示没有更多;翻下一页时原样回传 */ - @Schema(description = "下一页续扫游标,null 表示没有更多") - private String nextCursor; -} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java index 4ad3efc1..313fe1e0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java @@ -30,7 +30,7 @@ import org.springblade.transport.pojo.dto.FormalSettlementInvoiceClaimRequest; import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO; import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest; import org.springblade.transport.pojo.entity.FormalSettlementDetailFee; -import org.springblade.transport.pojo.vo.KingdeeInvoicePoolPageVO; +import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO; import org.springblade.transport.pojo.vo.FormalSettlementVO; import org.springblade.transport.excel.FormalSettlementExcel; import org.springblade.transport.pojo.vo.PreSettlementVO; @@ -262,8 +262,8 @@ public class FormalSettlementController extends BladeController { @GetMapping("/invoice-pool") @ApiOperationSupport(order = 21) - @Operation(summary = "金蝶票池实时分页查询(认领查询)") - public R invoicePool(KingdeeInvoicePoolQueryDTO query) { + @Operation(summary = "金蝶票据池镜像分页查询(认领查询)") + public R> invoicePool(KingdeeInvoicePoolQueryDTO query) { return R.data(invoiceReceiptService.invoicePoolPage(query)); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java index 24cf3584..7dcb6f0a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java @@ -40,7 +40,7 @@ import org.springblade.transport.pojo.dto.InvoiceReceiptSaveRequest; import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest; import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO; import org.springblade.transport.pojo.vo.InvoiceReceiptVO; -import org.springblade.transport.pojo.vo.KingdeeInvoicePoolPageVO; +import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO; import org.springblade.transport.service.IInvoiceReceiptService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -82,11 +82,18 @@ public class InvoiceReceiptController extends BladeController { @GetMapping("/invoice-pool") @ApiOperationSupport(order = 3) - @Operation(summary = "金蝶票池实时分页查询(含认领/登记标记)") - public R invoicePool(KingdeeInvoicePoolQueryDTO query) { + @Operation(summary = "金蝶票据池镜像分页查询(含认领/登记标记)") + public R> invoicePool(KingdeeInvoicePoolQueryDTO query) { return R.data(invoiceReceiptService.invoicePoolPage(query)); } + @PostMapping("/sync-invoice-pool") + @ApiOperationSupport(order = 13) + @Operation(summary = "手动触发金蝶全票池发票同步(窗口拉取+镜像全列upsert)") + public R syncInvoicePool() { + return R.data(invoiceReceiptService.syncKingdeeInvoicePool()); + } + @GetMapping("/invoice-pool/attachment/download") @ApiOperationSupport(order = 17) @Operation(summary = "金蝶票池发票文件下载(A801-2 流式回源)", diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java index 69663196..29ddf75d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java @@ -26,8 +26,12 @@ package org.springblade.transport.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO; import org.springblade.transport.pojo.entity.KingdeeInvoicePool; +import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO; /** * 金蝶进项发票票据池 Mapper 接口 @@ -37,4 +41,14 @@ import org.springblade.transport.pojo.entity.KingdeeInvoicePool; @Mapper public interface KingdeeInvoicePoolMapper extends BaseMapper { + /** + * 金蝶票据池镜像统一分页查询:仅出可用票(剩余可用金额>0 且未使用),认领/登记标记由 EXISTS 子查询直出 + * + * @param page 分页参数 + * @param query 统一查询条件(已在上层完成 trim 与日期格式校验) + * @param tenantId 当前租户 + * @return 统一视图分页 + */ + IPage selectInvoicePoolPage(IPage page, + @Param("query") KingdeeInvoicePoolQueryDTO query, @Param("tenantId") String tenantId); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml new file mode 100644 index 00000000..b25b0ddc --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml @@ -0,0 +1,86 @@ + + + + + + + EXISTS (SELECT 1 FROM blade_formal_settlement_invoice c + WHERE c.is_deleted = 0 AND c.tenant_id = p.tenant_id AND c.invoice_no = p.invoice_no + AND EXISTS (SELECT 1 FROM blade_formal_settlement s + WHERE s.id = c.formal_settlement_id AND s.is_deleted = 0 + AND s.approval_status != 'voided')) + + + + + EXISTS (SELECT 1 FROM blade_invoice_receipt r + WHERE r.is_deleted = 0 AND r.tenant_id = p.tenant_id + AND r.approval_status != 'voided' AND r.invoice_no = p.invoice_no) + + + + + p.id, p.invoice_no, p.serial_no, + DATE_FORMAT(p.invoice_date, '%Y-%m-%d') AS invoice_date, + p.invoice_type, p.tax_rate, p.invoice_amount, p.tax_amount, + p.receiver_name, p.buyer_tax_no, p.issuer_name, p.saler_tax_no, + p.bank_name, p.bank_account, p.issuing_bank, p.phone, + p.customer_emails, p.department_emails, + p.kingdee_bill_no, p.kingdee_status, p.check_status, p.attachments_json, + p.invoice_amount AS available_invoice_amount, + p.invoice_amount AS matched_amount, + p.available_amount AS available_tax_amount, + AS claimed, + AS registered + + + + + AND p.available_amount > 0 AND p.amount_use_status = '0' + + + + diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceReceiptService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceReceiptService.java index 7220003e..a03b78d8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceReceiptService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceReceiptService.java @@ -33,8 +33,9 @@ import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest; import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO; import org.springblade.transport.pojo.entity.InvoiceReceipt; import org.springblade.transport.pojo.vo.InvoiceReceiptVO; -import org.springblade.transport.pojo.vo.KingdeeInvoicePoolPageVO; +import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -50,14 +51,31 @@ public interface IInvoiceReceiptService extends BaseService { InvoiceReceiptVO detail(Long id); /** - * 金蝶票池实时统一查询(A801-1 游标分页 + 本地认领/登记装饰标记),收票登记选票与正式结算单认领共用 - *

A801-1 不返回总数,改用「多取 1 条探测」得出 hasMore 驱动上一页/下一页; - * claimed/registered 仅本地展示装饰,不下推金蝶过滤。 + * 金蝶票据池镜像统一查询(本地真分页,收票登记选票与正式结算单认领共用)。 + *

仅出可用票(剩余可用金额>0 且金额使用状态未用),认领/登记三态过滤在 SQL 内直出; + * 票号精确查本地未命中时窄查一次 A801-1 回源入库后重查,兜底增量窗口缝隙内的当日新票。 * * @param query 统一查询条件 - * @return 实时游标分页 + * @return 镜像统一视图分页 */ - KingdeeInvoicePoolPageVO invoicePoolPage(KingdeeInvoicePoolQueryDTO query); + IPage invoicePoolPage(KingdeeInvoicePoolQueryDTO query); + + /** + * 金蝶全票池定时同步(瘦身版):滚动增量窗口分页拉取 A801-1 + 镜像批量全列 upsert。 + *

无绑定回填/漏绑补偿/白名单(保存即写存根绑定,登记侧无需定时绑定); + * single-flight 锁防多副本并发,手动触发与定时任务共用。 + * + * @return 执行摘要 + */ + String syncKingdeeInvoicePool(); + + /** + * 结算单认领确认闸门:按票号集合逐张重查 A801-1 并按选票同口径复核可用性, + * 选中到确认之间票被认领/使用/红冲在此拦截(与收票保存重查同闸门) + * + * @param invoiceNos 待认领发票号码集合 + */ + void assertInvoicesClaimable(Collection invoiceNos); /** * 金蝶票池发票文件下载(A801-2 流式回源,前端查看/下载代理端点)。 diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java index ff4ab0f2..7ebf7728 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java @@ -81,6 +81,7 @@ import org.springblade.transport.pojo.vo.FormalSettlementVO; import org.springblade.transport.pojo.vo.PreSettlementVO; import org.springblade.transport.pojo.vo.SettlementAdjustmentVO; import org.springblade.transport.service.IFormalSettlementService; +import org.springblade.transport.service.IInvoiceReceiptService; import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.IPreSettlementService; import org.springblade.transport.service.IWaybillService; @@ -127,6 +128,9 @@ import java.util.stream.Collectors; public class FormalSettlementServiceImpl extends BaseServiceImpl implements IFormalSettlementService { + /** 金蝶票池认领闸门(收票服务承载 A801-1 重查与可用性口径) */ + private final IInvoiceReceiptService invoiceReceiptService; + /** 金蝶应收单勾稽 single-flight 锁键前缀(多副本定时任务与手动触发共用) */ private static final String AR_RESULT_SYNC_LOCK_PREFIX = "transport:formal-settlement:kingdee-ar-result-sync:"; @@ -822,6 +826,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl 0) { throw new ServiceException("发票匹配结算单金额合计不能超过结算金额"); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java index edac2eb8..3b699a6d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java @@ -28,6 +28,8 @@ package org.springblade.transport.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.toolkit.Db; import feign.Response; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -74,7 +76,6 @@ import org.springblade.transport.pojo.entity.InvoiceReceiptRecord; import org.springblade.transport.pojo.entity.InvoiceReceiptSettlement; import org.springblade.transport.pojo.entity.KingdeeInvoicePool; import org.springblade.transport.pojo.vo.InvoiceReceiptVO; -import org.springblade.transport.pojo.vo.KingdeeInvoicePoolPageVO; import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO; import org.springblade.transport.service.IInvoiceReceiptService; import org.springblade.transport.service.KingdeeSyncContext; @@ -123,6 +124,15 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl selectPage(IPage page, InvoiceReceiptVO query) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() @@ -220,82 +238,61 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl invoicePoolPage(KingdeeInvoicePoolQueryDTO query) { long current = query.getCurrent() == null ? 1L : Math.max(query.getCurrent(), 1L); long size = query.getSize() == null ? 10L : Math.min(Math.max(query.getSize(), 1L), 100L); - // 查询票号(已 trim 归一,方法级可见):用于对远程模糊命中做全等防御 - String queryInvoiceNo = trimOrNull(query.getInvoiceNo()); - // WHY: 金额可用过滤发生在拉取之后,下一页必须按游标续扫才能跨请求对齐过滤进度,按页码锚定会漏行/重行 - PoolScanCursor cursor = PoolScanCursor.parse(query.getCursor()); - List window = new ArrayList<>(); - // scanPageNo 指向待拉取的远程页;游标续扫固定按 500 行窗口取数,保证"页码:过滤偏移"语义稳定 - long scanPageNo = cursor == null ? current : cursor.pageNo(); - int skip = cursor == null ? 0 : cursor.skip(); - boolean exhausted = false; - // 末次拉取前的过滤累计行数与末次拉取页码:未消费的过滤行只可能落在末次拉取页内 - int filteredBeforeLastFetch = 0; - long lastFetchPageNo = scanPageNo; - int remoteCalls = 0; - // 有界循环补页:单请求最多 POOL_MAX_REMOTE_CALLS 次远程调用凑满一页,仍不足则返回稀疏页并给出游标 - for (int call = 0; call < KingdeeBfiConstant.POOL_MAX_REMOTE_CALLS && window.size() - skip < size; call++) { - // 首拉按 size+1 轻量探测,补拉按契约上限 500 行以减少调用次数 - int fetchSize = call == 0 && cursor == null ? (int) size + 1 : POOL_PAGE_SIZE; - List rawRows = this.queryPoolPage(scanPageNo, fetchSize, query); - remoteCalls++; - List usableRows = rawRows.stream() - .filter(this::isUsablePoolRow) - // 按票号查询时全等防御:远程为模糊匹配,命中"包含"关系的其他票号不得混入选票结果 - .filter(row -> queryInvoiceNo == null || queryInvoiceNo.equals(row.getInvoiceNo().trim())) - .toList(); - filteredBeforeLastFetch = window.size(); - window.addAll(usableRows); - lastFetchPageNo = scanPageNo; - // WHY: A801-1 无总数,远程短页是唯一耗尽信号 - if (rawRows.size() < fetchSize) { - exhausted = true; - break; - } - scanPageNo++; + query.setCurrent(current); + query.setSize(size); + query.setInvoiceNo(trimOrNull(query.getInvoiceNo())); + query.setSalerName(trimOrNull(query.getSalerName())); + query.setBuyerName(trimOrNull(query.getBuyerName())); + query.setInvoiceType(trimOrNull(query.getInvoiceType())); + query.setSalerTaxNo(trimOrNull(query.getSalerTaxNo())); + query.setBuyerTaxNo(trimOrNull(query.getBuyerTaxNo())); + query.setInvoiceDateStart(normalizeQueryDate(query.getInvoiceDateStart())); + query.setInvoiceDateEnd(normalizeQueryDate(query.getInvoiceDateEnd())); + IPage page = this.selectLocalPoolPage(query, current, size); + // 票号精确查本地未命中(且无认领/登记过滤干扰)→ 窄查一次 A801-1 回源入库再本地重查: + // 兜底增量窗口缝隙内的当日新票;名称模糊本地为空不回源(远程不支持名称条件,回源救不了) + if (page.getRecords().isEmpty() && query.getInvoiceNo() != null + && query.getClaimed() == null && query.getRegistered() == null + && this.fetchInvoicePoolFromRemote(query.getInvoiceNo())) { + page = this.selectLocalPoolPage(query, current, size); } - // 按游标偏移切页:skip 为游标窗口内已消费的过滤行数 - int fromIndex = Math.min(skip, window.size()); - int end = Math.min(fromIndex + (int) size, window.size()); - List pageRows = window.subList(fromIndex, end); - // 末次拉取页内已消费的过滤行数:翻页游标锚定在该页,页内剩余行下页续用 - int consumedInLastPage = Math.max(0, end - Math.max(fromIndex, filteredBeforeLastFetch)); - boolean hasMore = !exhausted || end < window.size(); - List records = pageRows.stream().map(this::toPoolViewVO).toList(); - this.decorateUseFlags(records); - KingdeeInvoicePoolPageVO result = new KingdeeInvoicePoolPageVO(); - result.setCurrent(current); - result.setSize(size); - result.setHasMore(hasMore); - result.setRecords(records); - result.setNextCursor(hasMore ? PoolScanCursor.encode(lastFetchPageNo, consumedInLastPage) : null); - log.info("金蝶票池实时查询 第{}页 {}条/页 返回{}条 hasMore:{} 远程调用{}次 游标:{}", current, size, - records.size(), hasMore, remoteCalls, result.getNextCursor()); - return result; + log.info("金蝶票池镜像查询 第{}页 {}条/页 返回{}条 total:{}", current, size, page.getRecords().size(), page.getTotal()); + return page; } - /** A801-1 按页拉取原始行:查询条件清洗与选票口径集中在此,循环补页多次复用 */ - private List queryPoolPage(long pageNo, int pageSize, KingdeeInvoicePoolQueryDTO query) { - BfiInvoicePoolQueryDataDTO data = new BfiInvoicePoolQueryDataDTO(); - data.setPageNo((int) pageNo); - data.setPageSize(pageSize); - // 固定只认正常状态票,作废/异常票不进选票结果 - data.setInvoiceStatus(KingdeeBfiConstant.POOL_INVOICE_STATUS_NORMAL); - data.setInvoiceNo(trimOrNull(query.getInvoiceNo())); - data.setSalerTaxNo(trimOrNull(query.getSalerTaxNo())); - data.setBuyerTaxNo(trimOrNull(query.getBuyerTaxNo())); - data.setInvoiceType(trimOrNull(query.getInvoiceType())); - // 单据使用状态固定查未用,不走前端条件 - data.setExpenseStatus(KingdeeBfiConstant.POOL_EXPENSE_STATUS_UNUSED); - data.setInvoiceDateStart(normalizeQueryDate(query.getInvoiceDateStart())); - data.setInvoiceDateEnd(normalizeQueryDate(query.getInvoiceDateEnd())); - BfiInvoicePoolQueryDTO param = new BfiInvoicePoolQueryDTO(); - param.setQueryParam(data); - BfiPageVO poolPage = bfiService.queryInvoicePool(param); - return Optional.ofNullable(poolPage.getRows()).orElse(List.of()); + /** 本地镜像真分页(可用过滤/认领登记标记均在 SQL 内直出) */ + private IPage selectLocalPoolPage(KingdeeInvoicePoolQueryDTO query, long current, long size) { + return invoicePoolMapper.selectInvoicePoolPage(new Page<>(current, size), query, AuthUtil.getTenantId()); + } + + /** + * 按发票号码窄查 A801-1 回源入库(票号全等防御 + 选票同口径可用过滤,不可用票不入镜像)。 + * + * @param invoiceNo 已 trim 的发票号码 + * @return 是否有可用票入库(false:远程未命中或不可用,调用方维持本地空页) + */ + private boolean fetchInvoicePoolFromRemote(String invoiceNo) { + List poolRows = queryPoolByInvoiceNo(invoiceNo); + BfiInvoicePoolVO poolRow = poolRows.stream() + .filter(row -> StringUtil.isNotBlank(row.getInvoiceNo()) + && invoiceNo.equals(row.getInvoiceNo().trim())) + .findFirst() + .orElse(null); + if (poolRow == null) { + return false; + } + if (!this.isUsablePoolRow(poolRow)) { + log.info("金蝶票池回源命中但不可用,不入镜像,发票号码={}", invoiceNo); + return false; + } + KingdeeInvoicePool existing = invoicePoolMapper.selectOne(Wrappers.lambdaQuery() + .eq(KingdeeInvoicePool::getInvoiceNo, invoiceNo)); + upsertInvoicePool(existing, poolRow); + log.info("金蝶票池回源入库完成,发票号码={}", invoiceNo); + return true; } /** @@ -313,39 +310,6 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl 0; } - /** - * 票池扫描游标("远程页码:已消费过滤行数"):过滤发生在拉取之后,翻页按游标续扫才能跨请求对齐过滤进度; - * 游标页码统一按 500 行窗口定位,解析失败降级为按页码锚定(仅存在翻页错位风险,不阻断查询) - */ - private record PoolScanCursor(long pageNo, int skip) { - - static PoolScanCursor parse(String text) { - if (StringUtil.isBlank(text)) { - return null; - } - String[] parts = text.trim().split(":"); - if (parts.length != 2) { - log.warn("金蝶票池续扫游标格式非法,忽略后按页码锚定,游标={}", text); - return null; - } - try { - long pageNo = Long.parseLong(parts[0].trim()); - int skip = Integer.parseInt(parts[1].trim()); - if (pageNo < 1 || skip < 0) { - throw new NumberFormatException("游标数值越界"); - } - return new PoolScanCursor(pageNo, skip); - } catch (NumberFormatException exception) { - log.warn("金蝶票池续扫游标解析失败,忽略后按页码锚定,游标={}", text); - return null; - } - } - - static String encode(long pageNo, int skip) { - return pageNo + ":" + skip; - } - } - /** 空白串归一为 null,便于远程查询条件与 MyBatis-Plus 判空 */ private String trimOrNull(String text) { return StringUtil.isBlank(text) ? null : text.trim(); @@ -363,69 +327,6 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl 统一视图:镜像未生成故 id 为空;金额口径价税合计三值同源; - * 银行/邮箱/电话远端无来源保持空,与镜像存根同形 - */ - private KingdeeInvoicePoolVO toPoolViewVO(BfiInvoicePoolVO row) { - KingdeeInvoicePoolVO vo = new KingdeeInvoicePoolVO(); - vo.setInvoiceNo(row.getInvoiceNo().trim()); - vo.setSerialNo(row.getSerialNo()); - vo.setInvoiceDate(row.getInvoiceDate()); - vo.setInvoiceType(BfiConstant.INVOICE_TYPE_NAMES.getOrDefault(row.getInvoiceType(), row.getInvoiceType())); - vo.setTaxRate(resolveTaxRate(row)); - vo.setTaxAmount(parseAmount(row.getInvoiceNo(), row.getTaxAmount())); - BigDecimal totalAmount = parseAmount(row.getInvoiceNo(), row.getTotalAmount()); - vo.setInvoiceAmount(totalAmount); - vo.setAvailableInvoiceAmount(totalAmount); - vo.setMatchedAmount(totalAmount); - // 剩余可用金额为金蝶可用税额口径,仅用于选票展示,与可认领金额的价税合计口径不同 - vo.setAvailableTaxAmount(parseAmount(row.getInvoiceNo(), row.getAvailableAmount())); - vo.setReceiverName(row.getBuyerName()); - vo.setBuyerTaxNo(row.getBuyerTaxNo()); - vo.setIssuerName(row.getSalerName()); - vo.setSalerTaxNo(row.getSalerTaxNo()); - vo.setKingdeeBillNo(row.getSerialNo()); - vo.setCheckStatus(row.getCheckStatus()); - vo.setAttachmentsJson(buildAttachmentJson(row)); - return vo; - } - - /** - * claimed/registered 只读装饰:本页票号三次批量本地查询内存打标,金蝶不感知、不作过滤下推; - * claimed=票关联的结算单中存在未作废主单(作废结算单不占坑) - */ - private void decorateUseFlags(List records) { - if (records.isEmpty()) { - return; - } - List invoiceNos = records.stream().map(KingdeeInvoicePoolVO::getInvoiceNo).distinct().toList(); - Set registeredNos = list(Wrappers.lambdaQuery() - .select(InvoiceReceipt::getInvoiceNo) - .in(InvoiceReceipt::getInvoiceNo, invoiceNos) - .ne(InvoiceReceipt::getApprovalStatus, TransportBusiConstant.VOIDED) - .eq(InvoiceReceipt::getIsDeleted, 0)).stream() - .map(InvoiceReceipt::getInvoiceNo).collect(Collectors.toSet()); - Map> claimIdsByInvoice = formalSettlementInvoiceMapper.selectList( - Wrappers.lambdaQuery() - .select(FormalSettlementInvoice::getInvoiceNo, FormalSettlementInvoice::getFormalSettlementId) - .in(FormalSettlementInvoice::getInvoiceNo, invoiceNos)).stream() - .collect(Collectors.groupingBy(FormalSettlementInvoice::getInvoiceNo, - Collectors.mapping(FormalSettlementInvoice::getFormalSettlementId, Collectors.toSet()))); - Set allClaimIds = claimIdsByInvoice.values().stream().flatMap(Set::stream).collect(Collectors.toSet()); - Set aliveSettlementIds = allClaimIds.isEmpty() ? Set.of() - : formalSettlementMapper.selectList(Wrappers.lambdaQuery() - .select(FormalSettlement::getId) - .in(FormalSettlement::getId, allClaimIds) - .ne(FormalSettlement::getApprovalStatus, TransportBusiConstant.VOIDED)).stream() - .map(FormalSettlement::getId).collect(Collectors.toSet()); - records.forEach(record -> { - record.setRegistered(registeredNos.contains(record.getInvoiceNo())); - record.setClaimed(claimIdsByInvoice.getOrDefault(record.getInvoiceNo(), Set.of()).stream() - .anyMatch(aliveSettlementIds::contains)); - }); - } - @Override public List> settlementCandidates(String keyword, Long receiptId) { List settlements = formalSettlementMapper.selectList( @@ -1213,7 +1114,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl rows = this.pullPoolWindowPage(windowStart, windowEnd, pageNo); + if (rows.isEmpty()) { + break; + } + pageCount++; + pulledCount += rows.size(); + BatchUpsertResult upsertResult = this.batchUpsertInvoicePool(rows); + created += upsertResult.created(); + updated += upsertResult.updated(); + if (rows.size() < POOL_PAGE_SIZE) { + break; + } + } + if (pageCount >= MAX_POOL_PAGES) { + // 末轮仍满页说明窗口数据量已超过翻页保护,尾部数据本轮未处理(须缩小 incrementDays 或调大保护页数) + log.warn("金蝶全票池同步翻页达最大保护 {} 页({} 条)仍未到底,窗口尾部本轮未处理", + MAX_POOL_PAGES, MAX_POOL_PAGES * POOL_PAGE_SIZE); + } + String summary = "金蝶全票池同步:翻页" + pageCount + "页拉取" + pulledCount + "张发票,镜像新增" + created + + "更新" + updated + ",耗时" + (System.currentTimeMillis() - startMillis) + "ms"; + log.info("金蝶全票池同步结束,{}", summary); + return summary; + } + + /** A801-1 按票池更新时间窗口分页拉取(只认正常票,不推单据使用状态,票面可用性由本地过滤) */ + private List pullPoolWindowPage(LocalDateTime windowStart, LocalDateTime windowEnd, int pageNo) { + BfiInvoicePoolQueryDataDTO data = new BfiInvoicePoolQueryDataDTO(); + data.setPageNo(pageNo); + data.setPageSize(POOL_PAGE_SIZE); + data.setUpdateTime(windowStart.format(POOL_TIME_FORMATTER)); + data.setUpdateTimeEnd(windowEnd.format(POOL_TIME_FORMATTER)); + data.setInvoiceStatus(KingdeeBfiConstant.POOL_INVOICE_STATUS_NORMAL); + BfiInvoicePoolQueryDTO param = new BfiInvoicePoolQueryDTO(); + param.setQueryParam(data); + BfiPageVO poolPage = bfiService.queryInvoicePool(param); + log.info("金蝶全票池同步 第{}页拉取 {} 条", pageNo, poolPage.getRows() == null ? 0 : poolPage.getRows().size()); + return poolPage.getRows() == null ? List.of() : poolPage.getRows(); + } + + /** + * 票池镜像批量 upsert:一次查询同页既有镜像,全列覆盖批量写库(新增批量插入、既有批量更新), + * 避免逐条查改 N+1;MyBatis-Plus 非空字段更新策略下源侧空字段维持库中原值,与手动同步路径同口径 + * + * @param rows A801-1 窗口拉取的票池发票 + * @return 批量 upsert 结果 + */ + private BatchUpsertResult batchUpsertInvoicePool(List rows) { + Map mirrorMap = new LinkedHashMap<>(); + if (rows.isEmpty()) { + return new BatchUpsertResult(mirrorMap, 0, 0); + } + List invoiceNos = rows.stream() + .map(row -> row.getInvoiceNo().trim()) + .distinct() + .toList(); + // 同页发票号码一次批量查询既有镜像 + Map existingMap = invoicePoolMapper.selectList( + Wrappers.lambdaQuery() + .in(KingdeeInvoicePool::getInvoiceNo, invoiceNos)).stream() + .collect(Collectors.toMap(KingdeeInvoicePool::getInvoiceNo, Function.identity(), (first, second) -> first)); + List toInsert = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + for (BfiInvoicePoolVO row : rows) { + if (Func.isEmpty(row.getInvoiceNo())) { + continue; + } + KingdeeInvoicePool mirror = fillInvoicePool(existingMap.get(row.getInvoiceNo().trim()), row); + mirrorMap.put(mirror.getInvoiceNo(), mirror); + if (mirror.getId() == null) { + toInsert.add(mirror); + } else { + toUpdate.add(mirror); + } + } + // 批量写库:失败中断本轮,由任务层记录异常、下轮重试(upsert 幂等) + if (!toInsert.isEmpty()) { + Db.saveBatch(toInsert); + } + if (!toUpdate.isEmpty()) { + Db.updateBatchById(toUpdate); + } + log.info("金蝶全票池镜像批量写入完成,新增 {} 条,更新 {} 条", toInsert.size(), toUpdate.size()); + return new BatchUpsertResult(mirrorMap, toInsert.size(), toUpdate.size()); + } + + /** 票池镜像批量 upsert 结果(invoiceNo -> 镜像,新增条数、更新条数) */ + private record BatchUpsertResult(Map mirrors, int created, int updated) { + } + + // ==================== 结算单认领确认重查闸门 ==================== + + @Override + public void assertInvoicesClaimable(Collection invoiceNos) { + List distinctNos = invoiceNos == null ? List.of() : invoiceNos.stream() + .filter(Func::isNotEmpty) + .map(String::trim) + .distinct() + .toList(); + for (String invoiceNo : distinctNos) { + // 窄查 + 票号全等防御 + 选票同口径可用复核:选中到确认之间票被认领/使用/红冲在此拦截 + BfiInvoicePoolVO poolRow = queryPoolByInvoiceNo(invoiceNo).stream() + .filter(row -> StringUtil.isNotBlank(row.getInvoiceNo()) + && invoiceNo.equals(row.getInvoiceNo().trim())) + .findFirst() + .orElse(null); + if (poolRow == null) { + throw new ServiceException("金蝶票池未查询到发票 " + invoiceNo + ",不能认领"); + } + if (!this.isUsablePoolRow(poolRow)) { + throw new ServiceException("发票 " + invoiceNo + " 剩余可用金额为0或金额使用状态已变更,不能认领"); + } + } + } + // ==================== 金蝶票池发票文件下载(A801-2 前端代理,见 2026-09-27 设计文档) ==================== @Override @@ -1351,9 +1404,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl rows = queryPoolPage(1, POOL_PAGE_SIZE, query); + List rows = queryPoolByInvoiceNo(invoiceNo); for (BfiInvoicePoolVO row : rows) { if (row.getInvoiceNo() != null && invoiceNo.equals(row.getInvoiceNo().trim())) { return kingdeeAttachmentSyncSupport.parseInvoicePoolFile(buildAttachmentJson(row)) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/InvoiceReceiptKingdeeSyncTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/InvoiceReceiptKingdeeSyncTask.java new file mode 100644 index 00000000..ee8a269b --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/InvoiceReceiptKingdeeSyncTask.java @@ -0,0 +1,69 @@ +/** + * 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. + *

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

+ * 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 the License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.springblade.transport.task; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.transport.service.IInvoiceReceiptService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 收票管理金蝶全票池发票定时同步任务(瘦身版) + *

+ * 定时按增量窗口分页拉取金蝶发票云全票池发票数据(A801-1,经 BFI 网关 rim 契约透传), + * 镜像表 blade_kingdee_invoice_pool 全列批量 upsert,供选票统一查本地(真分页/名称模糊/认领登记过滤)。 + * 业务逻辑收敛在 {@link IInvoiceReceiptService#syncKingdeeInvoicePool()},任务类只做开关与调度,便于手动触发联调。 + *

+ * 调度配置(Nacos thirdParty.bfi.invoiceReceipt.*):syncEnabled 开关、cron 表达式、incrementDays 增量窗口。 + * + * @author Chill + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class InvoiceReceiptKingdeeSyncTask { + + private final IInvoiceReceiptService invoiceReceiptService; + + /** 全票池同步任务开关,默认关闭(上线先手动跑一次 bootstrap 铺底,联调稳定后开启) */ + @Value("${thirdParty.bfi.invoiceReceipt.syncEnabled:false}") + private boolean syncEnabled; + + /** 金蝶全票池同步调度,默认每30分钟执行一次 */ + @Scheduled(cron = "${thirdParty.bfi.invoiceReceipt.cron:0 */30 * * * ?}") + public void syncKingdeeInvoicePool() { + if (!syncEnabled) { + return; + } + try { + String summary = invoiceReceiptService.syncKingdeeInvoicePool(); + log.info("定时同步金蝶全票池发票:{}", summary); + } catch (Exception exception) { + log.error("定时同步金蝶全票池发票异常", exception); + } + } +}