From 12e2825e55ad434c55026d88f178498c39895532 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com>
Date: Sun, 27 Sep 2026 23:57:43 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20HCS=E6=94=B6=E7=A5=A8=E5=8D=95=E5=90=8C?=
=?UTF-8?q?=E6=AD=A5=E4=B8=8E=E4=BB=BB=E5=8A=A1=E7=AB=AF=E7=82=B9=E6=8E=A5?=
=?UTF-8?q?=E7=BA=BF=EF=BC=88C109-1/2=E5=90=AB=E5=90=88=E5=90=8C=E5=9B=9E?=
=?UTF-8?q?=E6=BA=AF=E4=B8=8E=E4=BA=BA=E5=B7=A5=E8=A1=A5=E5=BD=95/?=
=?UTF-8?q?=E6=89=AB=E6=8F=8F=E7=BC=96=E6=8E=92=E6=89=A9=E5=B1=95=E5=BC=80?=
=?UTF-8?q?=E7=A5=A8=E6=94=B6=E7=A5=A8/=E9=87=8D=E6=8E=A8=E7=AB=AF?=
=?UTF-8?q?=E7=82=B9=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../transport/constant/HcsSyncConstant.java | 10 +
.../controller/HcsSyncController.java | 2 +
.../hcs/HcsReceiptBillSyncSupport.java | 311 ++++++++++++++++++
.../transport/task/HcsPushTask.java | 77 ++++-
4 files changed, 399 insertions(+), 1 deletion(-)
create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsReceiptBillSyncSupport.java
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;
+ }
}