diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java index 6e128731..8515dabd 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/HcsSyncConstant.java @@ -64,6 +64,16 @@ public final class HcsSyncConstant { */ public static final String BIZ_TYPE_PAYMENT = "payment"; + /** + * 推送业务类型:开票申请(C111 开票登记) + */ + public static final String BIZ_TYPE_INVOICE_APPLICATION = "invoice-application"; + + /** + * 推送业务类型:收票单(C109) + */ + public static final String BIZ_TYPE_INVOICE_RECEIPT = "invoice-receipt"; + /** * 校验类型:客商风险(C101-1) */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java index f10871d1..d04ff7a7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsSyncController.java @@ -34,7 +34,9 @@ import lombok.extern.slf4j.Slf4j; import org.springblade.core.tool.api.R; import org.springblade.transport.constant.HcsSyncConstant; import org.springblade.transport.support.hcs.HcsContractSyncSupport; +import org.springblade.transport.support.hcs.HcsInvoiceRegSyncSupport; import org.springblade.transport.support.hcs.HcsPaymentSyncSupport; +import org.springblade.transport.support.hcs.HcsReceiptBillSyncSupport; import org.springblade.transport.support.hcs.HcsMasterDataSupport; import org.springblade.transport.support.hcs.HcsProjectSyncSupport; import org.springblade.transport.task.HcsPushTask; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsReceiptBillSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsReceiptBillSyncSupport.java new file mode 100644 index 00000000..e070a97e --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsReceiptBillSyncSupport.java @@ -0,0 +1,311 @@ +/** + * 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.support.hcs; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.hcs.config.HcsProperties; +import org.springblade.hcs.dto.HcsAttachmentRefDTO; +import org.springblade.hcs.dto.HcsFormSaveVO; +import org.springblade.hcs.dto.HcsMechanismsDTO; +import org.springblade.hcs.dto.HcsReceiptBillFormDataDTO; +import org.springblade.hcs.dto.HcsReceiptBillPropsDTO; +import org.springblade.hcs.dto.HcsReceiptBillSaveDTO; +import org.springblade.hcs.dto.HcsResult; +import org.springblade.hcs.exception.HcsException; +import org.springblade.hcs.feign.IHcsClient; +import org.springblade.system.cache.UserCache; +import org.springblade.system.pojo.entity.User; +import org.springblade.transport.constant.HcsSyncConstant; +import org.springblade.transport.mapper.ContractManageMapper; +import org.springblade.transport.mapper.FormalSettlementMapper; +import org.springblade.transport.mapper.InvoiceReceiptMapper; +import org.springblade.transport.mapper.InvoiceReceiptSettlementMapper; +import org.springblade.transport.pojo.entity.ContractManage; +import org.springblade.transport.pojo.entity.FormalSettlement; +import org.springblade.transport.pojo.entity.InvoiceReceipt; +import org.springblade.transport.pojo.entity.InvoiceReceiptSettlement; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * HCS 收票单同步支撑组件(C109-1 新建 / C109-2 更新,设计文档 §5.8,D13) + *

+ * 审批通过 → C109-1;票据变更 → C109-2(顶层 fdId=hcs_fd_id 必填,只传变更字段、不带文档状态; + * v4 无作废字段,作废推送待联调确认)。合同关联:应付结算链路回溯(收票 → 应收/应付结算关联 → 结算单合同), + * 回溯不到时取页面人工补录的 hcs_contract_id/hcs_contract_no(D13)。 + * fd_ap_bill_src_subject(认领应付单号键)待联调(§9-U11),一期不传。 + * 返回双层嵌套 data.data.fdId → hcs_fd_id。 + * + * @author Chill + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class HcsReceiptBillSyncSupport { + + /** + * 收票单附件字段标识 + */ + private static final String RECEIPT_ATTACH_ENTITY_KEY = "fd_attach"; + + /** + * 收票日期格式(fd_bill_date 为 YYYY-MM-DD 字符串,非毫秒时间戳) + */ + private static final DateTimeFormatter BILL_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private final IHcsClient hcsClient; + private final HcsProperties hcsProperties; + private final InvoiceReceiptMapper invoiceReceiptMapper; + private final InvoiceReceiptSettlementMapper invoiceReceiptSettlementMapper; + private final FormalSettlementMapper formalSettlementMapper; + private final ContractManageMapper contractManageMapper; + private final HcsAttachmentSupport hcsAttachmentSupport; + + /** + * 推送收票单:无合规ID 走 C109-1 新建,有合规ID 走 C109-2 更新;整体状态由调用方回写 + * + * @param invoiceReceipt 收票单(approvalStatus=approved) + * @return 推送结果描述 + */ + public String pushReceiptBill(InvoiceReceipt invoiceReceipt) { + String operatorLoginName = resolveLoginName(invoiceReceipt); + if (StringUtil.isBlank(invoiceReceipt.getHcsFdId())) { + HcsReceiptBillSaveDTO param = buildSaveParam(invoiceReceipt, operatorLoginName, true); + log.info("HCS收票单新建开始:invoiceNo={}", invoiceReceipt.getInvoiceNo()); + HcsResult> result = hcsClient.addReceiveBill(param); + String fdId = extractNestedFdId(invoiceReceipt, result); + // 收票单ID 立即持久化(调用方必须保存) + invoiceReceipt.setHcsFdId(fdId); + invoiceReceiptMapper.updateById(invoiceReceipt); + log.info("HCS收票单新建成功:invoiceNo={},fdId={}", invoiceReceipt.getInvoiceNo(), fdId); + return "C109-1 收票单新建成功"; + } + // C109-2 更新:顶层 fdId 必填,只传变更字段、不带文档状态 + HcsReceiptBillSaveDTO param = buildSaveParam(invoiceReceipt, operatorLoginName, false); + log.info("HCS收票单更新开始:invoiceNo={},fdId={}", invoiceReceipt.getInvoiceNo(), invoiceReceipt.getHcsFdId()); + HcsResult> result = hcsClient.updateReceiveBill(param); + if (result == null || !result.isSuccess()) { + log.error("HCS收票单更新失败响应:{},invoiceNo={}", result == null ? "无响应" : result.getMsg(), invoiceReceipt.getInvoiceNo()); + throw new HcsException("C109-2 收票单更新失败:" + (result == null ? "响应为空" : result.getMsg())); + } + return "C109-2 收票单更新成功"; + } + + /** + * 单条手动重推:重置为待同步并清零重试计数后立即推送 + * + * @param id 收票单ID + * @return 推送结果描述 + */ + public String repushReceiptBill(Long id) { + InvoiceReceipt invoiceReceipt = invoiceReceiptMapper.selectById(id); + if (invoiceReceipt == null) { + throw new ServiceException("收票单不存在:" + id); + } + if (!"approved".equals(invoiceReceipt.getApprovalStatus())) { + throw new ServiceException("仅审批通过的收票单可推送合规(当前状态:" + invoiceReceipt.getApprovalStatus() + ")"); + } + invoiceReceipt.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED); + invoiceReceipt.setHcsRetryCount(0); + invoiceReceiptMapper.updateById(invoiceReceipt); + try { + return pushReceiptBill(invoiceReceipt); + } catch (Exception exception) { + String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage(); + markFailed(invoiceReceipt, message); + throw new ServiceException("HCS收票单推送失败:" + message); + } + } + + /** + * 组装 C109-1/C109-2 入参(字段标注照 §3.4 规范,映射表见设计文档 §5.8) + */ + private HcsReceiptBillSaveDTO buildSaveParam(InvoiceReceipt invoiceReceipt, String operatorLoginName, boolean add) { + HcsReceiptBillSaveDTO param = new HcsReceiptBillSaveDTO(); + // 收票单主键:新建不传,更新必填(顶层 fdId) + param.setFdId(invoiceReceipt.getHcsFdId()); + // 【必填】操作人登录名:收票单创建人登录名(§9-U2) + param.setFdLoginName(operatorLoginName); + HcsReceiptBillFormDataDTO formData = new HcsReceiptBillFormDataDTO(); + HcsReceiptBillPropsDTO props = new HcsReceiptBillPropsDTO(); + // 发票号码:invoiceNo + props.setFdInvoiceCode(invoiceReceipt.getInvoiceNo()); + // 开票/收票日期:invoiceDate(YYYY-MM-DD 字符串) + props.setFdBillDate(formatBillDate(invoiceReceipt.getInvoiceDate())); + // 开票方(销方):issuerName + props.setFdInvoicer(invoiceReceipt.getIssuerName()); + // 收票方(购方):receiverName + props.setFdBuyer(invoiceReceipt.getReceiverName()); + // 价税合计金额:invoiceAmount + props.setFdInvoiceAmtTaxIncl(invoiceReceipt.getInvoiceAmount()); + // 税率(小数):TMS taxRate(如 13)÷ 100 → 0.13 + props.setFdTaxRate(toTaxRate(invoiceReceipt.getTaxRate())); + // 发票类型:receipt-bill.invoice-type-mapping 字典映射(§9-U3,未配置不传) + props.setFdInvoiceType(hcsProperties.getReceiptBill().getInvoiceTypeMapping().get(invoiceReceipt.getInvoiceType())); + // 合同主题/编号:应付结算链路回溯,回溯不到取人工补录(D13) + ContractReference contractReference = resolveContract(invoiceReceipt); + props.setFdContractSubject(contractReference.subject()); + props.setFdContractNo(contractReference.contractNo()); + // TODO 待联调(§9-U11):fd_ap_bill_src_subject 关联结算付款单的编号体系(应付单号键)v4 未明确,一期不传 + // 备注:remark + props.setFdRemark(invoiceReceipt.getRemark()); + formData.setDynamicProps(props); + param.setFormData(formData); + // 附件挂载(发票扫描件):fdEntityKey=fd_attach,发起带 fdBindType=ADD,更新不带 + if (hcsProperties.getAttachment().isTransferEnabled() && StringUtil.isNotBlank(invoiceReceipt.getAttachmentsJson())) { + List attachmentFdIds = hcsAttachmentSupport.transferAttachments(invoiceReceipt.getAttachmentsJson(), operatorLoginName); + if (!attachmentFdIds.isEmpty()) { + HcsMechanismsDTO mechanisms = new HcsMechanismsDTO(); + mechanisms.setAttachment(attachmentFdIds.stream().map(fdId -> { + HcsAttachmentRefDTO attachment = new HcsAttachmentRefDTO(); + // 附件ID(C103-2 转传返回) + attachment.setFdId(fdId); + // 收票单附件字段标识 + attachment.setFdEntityKey(RECEIPT_ATTACH_ENTITY_KEY); + // 绑定类型:仅发起传 ADD + attachment.setFdBindType(add ? "ADD" : null); + return attachment; + }).toList()); + param.setMechanisms(mechanisms); + } + } + return param; + } + + /** + * 解析 C109-1 双层嵌套返回 data.data.fdId + */ + private String extractNestedFdId(InvoiceReceipt invoiceReceipt, HcsResult> result) { + if (result == null || !result.isSuccess() || result.getData() == null || !result.getData().isSuccess() + || result.getData().getData() == null || StringUtil.isBlank(result.getData().getData().getFdId())) { + log.error("HCS收票单新建失败响应:{},invoiceNo={}", result == null ? "无响应" : result.getMsg(), invoiceReceipt.getInvoiceNo()); + throw new HcsException("C109-1 收票单新建失败:" + (result == null ? "响应为空" : result.getMsg())); + } + return result.getData().getData().getFdId(); + } + + /** + * 合同引用解析:收票 → 应收结算关联 → 结算单合同 → 合同已推送传合规ID,否则传人工补录值(D13) + */ + private ContractReference resolveContract(InvoiceReceipt invoiceReceipt) { + // ① 人工补录兜底优先可直接用;先走链路回溯 + InvoiceReceiptSettlement relation = invoiceReceiptSettlementMapper.selectOne(Wrappers.lambdaQuery() + .eq(InvoiceReceiptSettlement::getInvoiceReceiptId, invoiceReceipt.getId()) + .last("limit 1")); + if (relation != null && relation.getFormalSettlementId() != null) { + FormalSettlement settlement = formalSettlementMapper.selectById(relation.getFormalSettlementId()); + if (settlement != null && settlement.getContractId() != null) { + ContractManage contractManage = contractManageMapper.selectById(settlement.getContractId()); + if (contractManage != null) { + // 合同已推送传合规合同ID(fd_contract_subject 支持 ID 引用),编号传 TMS 合同编号 + String subject = StringUtil.isNotBlank(contractManage.getHcsFdId()) ? contractManage.getHcsFdId() : contractManage.getContractName(); + return new ContractReference(subject, contractManage.getContractNo()); + } + } + } + // ② 回溯不到:页面人工补录(hcs_contract_id/hcs_contract_no,D13) + if (invoiceReceipt.getHcsContractId() != null) { + ContractManage contractManage = contractManageMapper.selectById(invoiceReceipt.getHcsContractId()); + if (contractManage != null) { + String subject = StringUtil.isNotBlank(contractManage.getHcsFdId()) ? contractManage.getHcsFdId() : contractManage.getContractName(); + return new ContractReference(subject, StringUtil.isNotBlank(invoiceReceipt.getHcsContractNo()) ? invoiceReceipt.getHcsContractNo() : contractManage.getContractNo()); + } + } + if (StringUtil.isNotBlank(invoiceReceipt.getHcsContractNo())) { + return new ContractReference(null, invoiceReceipt.getHcsContractNo()); + } + log.warn("HCS收票单合同关联缺失:invoiceNo={},应付结算回溯与人工补录均为空,fd_contract_* 不传", invoiceReceipt.getInvoiceNo()); + return new ContractReference(null, null); + } + + /** + * 回填整体同步状态置 synced + */ + public void markSynced(InvoiceReceipt invoiceReceipt, String message) { + invoiceReceipt.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED); + invoiceReceipt.setHcsRetryCount(0); + invoiceReceipt.setHcsSyncTime(LocalDateTime.now()); + invoiceReceipt.setHcsSyncMsg(message); + invoiceReceiptMapper.updateById(invoiceReceipt); + log.info("HCS收票单推送成功:invoiceNo={},{}", invoiceReceipt.getInvoiceNo(), message); + } + + /** + * 推送失败回写:failed + 重试次数+1 + 失败原因 + */ + public void markFailed(InvoiceReceipt invoiceReceipt, String message) { + invoiceReceipt.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED); + invoiceReceipt.setHcsRetryCount(invoiceReceipt.getHcsRetryCount() == null ? 1 : invoiceReceipt.getHcsRetryCount() + 1); + invoiceReceipt.setHcsSyncTime(LocalDateTime.now()); + invoiceReceipt.setHcsSyncMsg(message); + invoiceReceiptMapper.updateById(invoiceReceipt); + log.error("HCS收票单推送失败:invoiceNo={},retryCount={},{}", invoiceReceipt.getInvoiceNo(), invoiceReceipt.getHcsRetryCount(), message); + } + + /** + * 操作人登录名(收票单创建人,§9-U2) + */ + private String resolveLoginName(InvoiceReceipt invoiceReceipt) { + if (invoiceReceipt.getCreateUser() == null) { + throw new HcsException("C109 收票单推送失败,收票单创建人未维护(invoiceNo=" + invoiceReceipt.getInvoiceNo() + ")"); + } + User user = UserCache.getUser(invoiceReceipt.getCreateUser()); + if (user == null || StringUtil.isBlank(user.getAccount())) { + throw new HcsException("C109 收票单推送失败,创建人登录名解析不到(createUser=" + invoiceReceipt.getCreateUser() + ")"); + } + return user.getAccount(); + } + + /** + * 收票日期格式化(fd_bill_date 为 YYYY-MM-DD 字符串) + */ + private String formatBillDate(LocalDate invoiceDate) { + return invoiceDate == null ? null : invoiceDate.format(BILL_DATE_FORMATTER); + } + + /** + * 税率换算:TMS 整数值(如 13)÷ 100 → 小数(0.13) + */ + private BigDecimal toTaxRate(BigDecimal taxRate) { + return taxRate == null ? null : taxRate.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP); + } + + /** + * 合同引用(fd_contract_subject / fd_contract_no) + */ + private record ContractReference(String subject, String contractNo) { + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java index dc6fcbf5..74f1b422 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/HcsPushTask.java @@ -33,14 +33,20 @@ import org.springblade.core.tool.utils.StringUtil; import org.springblade.hcs.config.HcsProperties; import org.springblade.transport.constant.HcsSyncConstant; import org.springblade.transport.mapper.ContractManageMapper; +import org.springblade.transport.mapper.InvoiceApplicationMapper; +import org.springblade.transport.mapper.InvoiceReceiptMapper; import org.springblade.transport.mapper.PaymentApplicationMapper; import org.springblade.transport.mapper.ProjectApplyMapper; import org.springblade.transport.pojo.entity.ContractManage; +import org.springblade.transport.pojo.entity.InvoiceApplication; +import org.springblade.transport.pojo.entity.InvoiceReceipt; import org.springblade.transport.pojo.entity.PaymentApplication; import org.springblade.transport.pojo.entity.ProjectApply; import org.springblade.transport.support.hcs.HcsContractSyncSupport; +import org.springblade.transport.support.hcs.HcsInvoiceRegSyncSupport; import org.springblade.transport.support.hcs.HcsPaymentSyncSupport; import org.springblade.transport.support.hcs.HcsProjectSyncSupport; +import org.springblade.transport.support.hcs.HcsReceiptBillSyncSupport; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -81,9 +87,13 @@ public class HcsPushTask { private final ProjectApplyMapper projectApplyMapper; private final ContractManageMapper contractManageMapper; private final PaymentApplicationMapper paymentApplicationMapper; + private final InvoiceApplicationMapper invoiceApplicationMapper; + private final InvoiceReceiptMapper invoiceReceiptMapper; private final HcsProjectSyncSupport hcsProjectSyncSupport; private final HcsContractSyncSupport hcsContractSyncSupport; private final HcsPaymentSyncSupport hcsPaymentSyncSupport; + private final HcsInvoiceRegSyncSupport hcsInvoiceRegSyncSupport; + private final HcsReceiptBillSyncSupport hcsReceiptBillSyncSupport; private final BladeRedis bladeRedis; /** @@ -109,10 +119,12 @@ public class HcsPushTask { return; } try { - // 任务顺序(设计文档 §5.0):项目 → 合同 → 付款;各类互不影响 + // 任务顺序(设计文档 §5.0):项目 → 合同 → 付款 → 开票 → 收票;各类互不影响 log.info("定时推送HCS项目立项:{}", pushUnsyncedProjects()); log.info("定时推送HCS合同:{}", pushUnsyncedContracts()); log.info("定时推送HCS付款链:{}", pushUnsyncedPayments()); + log.info("定时推送HCS开票登记:{}", pushUnsyncedInvoiceApplications()); + log.info("定时推送HCS收票单:{}", pushUnsyncedInvoiceReceipts()); } finally { if (lockHolder.equals(bladeRedis.get(HcsSyncConstant.PUSH_TASK_LOCK_KEY))) { bladeRedis.del(HcsSyncConstant.PUSH_TASK_LOCK_KEY); @@ -212,4 +224,67 @@ public class HcsPushTask { } return summary; } + + /** + * 扫描并推送待同步/待补偿的开票申请(C111 开票登记,含金蝶开票完成后回填),返回结果汇总 + * + * @return 汇总描述 + */ + public String pushUnsyncedInvoiceApplications() { + int maxRetry = hcsProperties.getPush().getMaxRetry(); + List applicationList = invoiceApplicationMapper.selectList(Wrappers.lambdaQuery() + .eq(InvoiceApplication::getApprovalStatus, "approved") + .in(InvoiceApplication::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED)) + .and(wrapper -> wrapper.isNull(InvoiceApplication::getHcsRetryCount).or().lt(InvoiceApplication::getHcsRetryCount, maxRetry)) + .last("limit " + hcsProperties.getPush().getBatchSize())); + int successCount = 0; + int failCount = 0; + for (InvoiceApplication application : applicationList) { + try { + hcsInvoiceRegSyncSupport.pushInvoiceRegistration(application); + successCount++; + } catch (Exception exception) { + String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage(); + hcsInvoiceRegSyncSupport.markFailed(application, message); + failCount++; + } + } + String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", applicationList.size(), successCount, failCount, maxRetry); + if (failCount > 0) { + log.warn("HCS开票登记推送存在失败:{}", summary); + } + return summary; + } + + /** + * 扫描并推送待同步/待补偿的收票单(C109),返回结果汇总 + * + * @return 汇总描述 + */ + public String pushUnsyncedInvoiceReceipts() { + int maxRetry = hcsProperties.getPush().getMaxRetry(); + List receiptList = invoiceReceiptMapper.selectList(Wrappers.lambdaQuery() + .eq(InvoiceReceipt::getApprovalStatus, "approved") + .in(InvoiceReceipt::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED)) + .and(wrapper -> wrapper.isNull(InvoiceReceipt::getHcsRetryCount).or().lt(InvoiceReceipt::getHcsRetryCount, maxRetry)) + .last("limit " + hcsProperties.getPush().getBatchSize())); + int successCount = 0; + int failCount = 0; + for (InvoiceReceipt invoiceReceipt : receiptList) { + try { + hcsReceiptBillSyncSupport.pushReceiptBill(invoiceReceipt); + hcsReceiptBillSyncSupport.markSynced(invoiceReceipt, "C109 收票单推送成功"); + successCount++; + } catch (Exception exception) { + String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage(); + hcsReceiptBillSyncSupport.markFailed(invoiceReceipt, message); + failCount++; + } + } + String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", receiptList.size(), successCount, failCount, maxRetry); + if (failCount > 0) { + log.warn("HCS收票单推送存在失败:{}", summary); + } + return summary; + } }