feat: 新增履约开票记录与履约收票记录推送链路

This commit is contained in:
刘泉佳
2026-10-28 19:30:59 +08:00
parent 7c0509392a
commit bd0c4d0981
4 changed files with 462 additions and 6 deletions
@@ -130,8 +130,9 @@ public class HcsSyncController {
case HcsSyncConstant.BIZ_TYPE_PROJECT -> hcsPushTask.pushUnsyncedProjects();
case HcsSyncConstant.BIZ_TYPE_CONTRACT -> hcsPushTask.pushUnsyncedContracts();
case HcsSyncConstant.BIZ_TYPE_PAYMENT -> hcsPushTask.pushUnsyncedPayments();
case HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION -> hcsPushTask.pushUnsyncedInvoiceApplications();
case HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT -> hcsPushTask.pushUnsyncedInvoiceReceipts();
// 开票/收票走组合动作(C111/C109 + 履约记录 C108-1),与定时任务行为一致
case HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION -> hcsPushTask.syncHcsInvoiceApplications();
case HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT -> hcsPushTask.syncHcsInvoiceReceipts();
default -> throw new IllegalArgumentException("不支持的HCS推送业务类型:" + bizType);
};
return R.data(result);
@@ -0,0 +1,222 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.hcs.config.HcsProperties;
import org.springblade.hcs.exception.HcsException;
import org.springblade.transport.constant.HcsSyncConstant;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.InvoiceApplicationInvoiceMapper;
import org.springblade.transport.mapper.InvoiceApplicationMapper;
import org.springblade.transport.mapper.InvoiceApplicationSettlementMapper;
import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.HcsContractExeStage;
import org.springblade.transport.pojo.entity.InvoiceApplication;
import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
import org.springblade.transport.pojo.entity.InvoiceApplicationSettlement;
import org.springblade.transport.service.IContractExeStageService;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* HCS 履约开票记录同步支撑组件(C108-1,需求:金蝶开票完成后推送合同系统)
* <p>
* 粒度:金蝶回写的每张发票(blade_invoice_application_invoice)对应一条履约开票记录;
* 触发:A303-5 开票结果回写落库发票后,随开票登记扫描任务(HcsPushTask invoice-application)推送;
* 履约日期为发票完成开票日期(invoice_date),履约类型 collection(收款方向,关联合同收款履约计划行);
* 合同经应收结算链路回溯(开票申请 → 应收结算关联 → 正式结算单合同,同 C111 §9-U10 口径);
* 幂等凭 hcs_perf_fd_id(非空跳过),失败置重试计数与原因由下轮扫描自动补偿。
*
* @author Chill
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class HcsInvoicePerfSyncSupport {
private final HcsProperties hcsProperties;
private final InvoiceApplicationInvoiceMapper invoiceApplicationInvoiceMapper;
private final InvoiceApplicationMapper invoiceApplicationMapper;
private final InvoiceApplicationSettlementMapper invoiceApplicationSettlementMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final ContractManageMapper contractManageMapper;
private final IContractExeStageService contractExeStageService;
private final HcsPerfRecordAddSupport hcsPerfRecordAddSupport;
/**
* 扫描并推送待同步的履约开票记录(每张发票一条 C108-1),返回结果汇总
*
* @return 汇总描述
*/
public String pushUnsyncedInvoicePerfRecords() {
if (!hcsProperties.getPerfRecord().isSyncEnabled()) {
return "履约记录同步开关未开启";
}
int maxRetry = hcsProperties.getPush().getMaxRetry();
// 待推送发票:未建过履约记录且重试未超限
List<InvoiceApplicationInvoice> invoiceList = invoiceApplicationInvoiceMapper.selectList(
Wrappers.<InvoiceApplicationInvoice>lambdaQuery()
.isNull(InvoiceApplicationInvoice::getHcsPerfFdId)
.and(wrapper -> wrapper.isNull(InvoiceApplicationInvoice::getHcsPerfRetryCount)
.or().lt(InvoiceApplicationInvoice::getHcsPerfRetryCount, maxRetry))
.last("limit " + hcsProperties.getPush().getBatchSize()));
if (invoiceList.isEmpty()) {
return "履约开票记录:无待推送发票";
}
// 批量装配上下文(申请单/合同/计划行,一次 IN 查询避免逐行 N+1)
Map<Long, InvoiceApplication> applicationById = loadApplications(invoiceList);
Map<Long, ContractManage> contractByApplicationId = loadContracts(List.copyOf(applicationById.values()));
int successCount = 0;
int failCount = 0;
for (InvoiceApplicationInvoice invoice : invoiceList) {
try {
pushOneInvoice(invoice, applicationById.get(invoice.getInvoiceApplicationId()),
contractByApplicationId.get(invoice.getInvoiceApplicationId()));
successCount++;
} catch (Exception exception) {
markFailed(invoice, StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage());
failCount++;
}
}
String summary = String.format("履约开票记录:扫描 %d 张,成功 %d 张,失败 %d 张(重试上限 %d)",
invoiceList.size(), successCount, failCount, maxRetry);
if (failCount > 0) {
log.warn("HCS履约开票记录推送存在失败:{}", summary);
}
return summary;
}
/**
* 推送单张发票的履约开票记录
*/
private void pushOneInvoice(InvoiceApplicationInvoice invoice, InvoiceApplication application, ContractManage contractManage) {
if (application == null || !"approved".equals(application.getApprovalStatus())) {
throw new HcsException("开票申请不存在或未审批通过:invoiceApplicationId=" + invoice.getInvoiceApplicationId());
}
if (contractManage == null) {
throw new HcsException("应收结算链路回溯不到合同(applicationNo=" + application.getApplicationNo() + ",同 C111 §9-U10 口径)");
}
// 关联合同的收款履约计划行(fd_plan_id 必填,无已同步计划行时通用组件抛出重试,合同链路首推后自愈)
HcsContractExeStage stageRow = contractExeStageService.findCollectionStageRow(
contractManage.getId(), HcsSyncConstant.FUND_NATURE_PROGRESS);
BigDecimal amount = invoice.getInvoiceAmount() == null ? BigDecimal.ZERO : invoice.getInvoiceAmount();
String perfFdId = hcsPerfRecordAddSupport.pushPerfRecordAdd(contractManage, stageRow,
invoice.getInvoiceNo(), amount, toMillis(invoice.getInvoiceDate()),
HcsSyncConstant.PERFORM_TYPE_COLLECTION, invoice.getSerialNo(), invoice.getId(),
"开票履约-" + invoice.getInvoiceNo());
markSynced(invoice, perfFdId);
}
/**
* 批量装配发票所属开票申请(过滤已作废)
*/
private Map<Long, InvoiceApplication> loadApplications(List<InvoiceApplicationInvoice> invoiceList) {
List<Long> applicationIds = invoiceList.stream()
.map(InvoiceApplicationInvoice::getInvoiceApplicationId).filter(Objects::nonNull).distinct().toList();
if (applicationIds.isEmpty()) {
return Map.of();
}
return invoiceApplicationMapper.selectBatchIds(applicationIds).stream()
.collect(Collectors.toMap(InvoiceApplication::getId, item -> item, (first, second) -> first));
}
/**
* 批量回溯合同:申请单 → 应收结算关联 → 正式结算单合同(一次 IN 查询,同 C111 §9-U10 口径)
*/
private Map<Long, ContractManage> loadContracts(List<InvoiceApplication> applicationList) { List<Long> applicationIds = applicationList.stream().map(InvoiceApplication::getId).toList();
if (applicationIds.isEmpty()) {
return Map.of();
}
Map<Long, InvoiceApplicationSettlement> relationByApplicationId = invoiceApplicationSettlementMapper.selectList(
Wrappers.<InvoiceApplicationSettlement>lambdaQuery()
.in(InvoiceApplicationSettlement::getInvoiceApplicationId, applicationIds)).stream()
.filter(relation -> relation.getFormalSettlementId() != null)
.collect(Collectors.toMap(InvoiceApplicationSettlement::getInvoiceApplicationId, relation -> relation,
(first, second) -> first, LinkedHashMap::new));
if (relationByApplicationId.isEmpty()) {
return Map.of();
}
Map<Long, FormalSettlement> settlementById = formalSettlementMapper.selectBatchIds(
relationByApplicationId.values().stream()
.map(InvoiceApplicationSettlement::getFormalSettlementId).distinct().toList()).stream()
.filter(settlement -> settlement.getContractId() != null)
.collect(Collectors.toMap(FormalSettlement::getId, item -> item, (first, second) -> first));
if (settlementById.isEmpty()) {
return Map.of();
}
Map<Long, ContractManage> contractById = contractManageMapper.selectBatchIds(
settlementById.values().stream().map(FormalSettlement::getContractId).distinct().toList()).stream()
.collect(Collectors.toMap(ContractManage::getId, item -> item, (first, second) -> first));
Map<Long, ContractManage> contractByApplicationId = new LinkedHashMap<>();
relationByApplicationId.forEach((applicationId, relation) -> {
FormalSettlement settlement = settlementById.get(relation.getFormalSettlementId());
if (settlement != null) {
contractByApplicationId.put(applicationId, contractById.get(settlement.getContractId()));
}
});
return contractByApplicationId;
}
/**
* 推送成功回填:履约记录ID 立即持久化(幂等键),清失败原因
*/
private void markSynced(InvoiceApplicationInvoice invoice, String perfFdId) {
invoice.setHcsPerfFdId(perfFdId);
invoice.setHcsPerfRetryCount(0);
invoice.setHcsPerfSyncMsg(null);
invoiceApplicationInvoiceMapper.updateById(invoice);
log.info("HCS履约开票记录推送成功:invoiceNo={},perfFdId={}", invoice.getInvoiceNo(), perfFdId);
}
/**
* 推送失败回写:重试次数+1 + 失败原因(下轮扫描自动补偿)
*/
private void markFailed(InvoiceApplicationInvoice invoice, String message) {
invoice.setHcsPerfRetryCount(invoice.getHcsPerfRetryCount() == null ? 1 : invoice.getHcsPerfRetryCount() + 1);
invoice.setHcsPerfSyncMsg(HcsSyncConstant.truncateMsg(message));
invoiceApplicationInvoiceMapper.updateById(invoice);
log.error("HCS履约开票记录推送失败:invoiceNo={},retryCount={},{}", invoice.getInvoiceNo(), invoice.getHcsPerfRetryCount(), message);
}
private Long toMillis(java.time.LocalDateTime localDateTime) {
return localDateTime == null ? null
: localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
}
@@ -0,0 +1,211 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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.tool.utils.StringUtil;
import org.springblade.hcs.config.HcsProperties;
import org.springblade.hcs.exception.HcsException;
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.HcsContractExeStage;
import org.springblade.transport.pojo.entity.InvoiceReceipt;
import org.springblade.transport.pojo.entity.InvoiceReceiptSettlement;
import org.springblade.transport.service.IContractExeStageService;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* HCS 履约收票记录同步支撑组件(C108-1,需求:收票单每条结算信息对应一条履约收票记录)
* <p>
* 粒度:收票登记结算单分摊行(blade_invoice_receipt_settlement)对应一条履约收票记录;
* 触发:收票单推送合规(C109)成功后,随收票单扫描任务(HcsPushTask invoice-receipt)推送,
* 扫描范围限定所属收票单 hcs_sync_status=synced(C109 已推送),失败下轮自动补偿;
* 履约日期为发票日期(收票单 invoice_date),履约类型 payment(付款方向,关联合同付款履约计划行);
* 合同经应付结算链路回溯(结算分摊行 → 正式结算单合同);
* 幂等凭 hcs_perf_fd_id(非空跳过),失败置重试计数与原因由下轮扫描自动补偿。
*
* @author Chill
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class HcsReceiptPerfSyncSupport {
private final HcsProperties hcsProperties;
private final InvoiceReceiptSettlementMapper invoiceReceiptSettlementMapper;
private final InvoiceReceiptMapper invoiceReceiptMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final ContractManageMapper contractManageMapper;
private final IContractExeStageService contractExeStageService;
private final HcsPerfRecordAddSupport hcsPerfRecordAddSupport;
/**
* 扫描并推送待同步的履约收票记录(每条结算信息一条 C108-1),返回结果汇总
*
* @return 汇总描述
*/
public String pushUnsyncedReceiptPerfRecords() {
if (!hcsProperties.getPerfRecord().isSyncEnabled()) {
return "履约记录同步开关未开启";
}
int maxRetry = hcsProperties.getPush().getMaxRetry();
// 待推送结算信息:未建过履约记录且重试未超限
List<InvoiceReceiptSettlement> settlementList = invoiceReceiptSettlementMapper.selectList(
Wrappers.<InvoiceReceiptSettlement>lambdaQuery()
.isNull(InvoiceReceiptSettlement::getHcsPerfFdId)
.and(wrapper -> wrapper.isNull(InvoiceReceiptSettlement::getHcsPerfRetryCount)
.or().lt(InvoiceReceiptSettlement::getHcsPerfRetryCount, maxRetry))
.last("limit " + hcsProperties.getPush().getBatchSize()));
if (settlementList.isEmpty()) {
return "履约收票记录:无待推送结算信息";
}
// 批量装配上下文(收票单/结算单/合同/计划行,一次 IN 查询避免逐行 N+1)
Map<Long, InvoiceReceipt> receiptById = loadReceipts(settlementList);
Map<Long, FormalSettlement> settlementById = loadSettlements(settlementList);
int successCount = 0;
int failCount = 0;
for (InvoiceReceiptSettlement settlementLine : settlementList) {
try {
pushOneSettlementLine(settlementLine, receiptById.get(settlementLine.getInvoiceReceiptId()),
settlementById.get(settlementLine.getFormalSettlementId()));
successCount++;
} catch (Exception exception) {
markFailed(settlementLine, StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage());
failCount++;
}
}
String summary = String.format("履约收票记录:扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)",
settlementList.size(), successCount, failCount, maxRetry);
if (failCount > 0) {
log.warn("HCS履约收票记录推送存在失败:{}", summary);
}
return summary;
}
/**
* 推送单条结算信息的履约收票记录
*/
private void pushOneSettlementLine(InvoiceReceiptSettlement settlementLine, InvoiceReceipt receipt, FormalSettlement settlement) {
if (receipt == null || !"approved".equals(receipt.getApprovalStatus())) {
throw new HcsException("收票单不存在或未审批通过:invoiceReceiptId=" + settlementLine.getInvoiceReceiptId());
}
// C109 收票单须已推送合规(履约记录引用合规合同与计划行)
if (!HcsSyncConstant.HCS_STATUS_SYNCED.equals(receipt.getHcsSyncStatus())) {
throw new HcsException("收票单尚未推送合规(invoiceNo=" + receipt.getInvoiceNo() + "),待 C109 推送成功后重试");
}
if (settlement == null || settlement.getContractId() == null) {
throw new HcsException("结算分摊行回溯不到合同(settlementId=" + settlementLine.getFormalSettlementId() + ")");
}
ContractManage contractManage = contractManageMapper.selectById(settlement.getContractId());
if (contractManage == null) {
throw new HcsException("合同不存在:contractId=" + settlement.getContractId());
}
// 关联合同的付款履约计划行(fd_plan_id 必填,无已同步计划行时通用组件抛出重试,合同链路首推后自愈)
HcsContractExeStage stageRow = contractExeStageService.findPaymentStageRow(
contractManage.getId(), HcsSyncConstant.FUND_NATURE_PROGRESS);
// 金额:分摊到该结算单的收票金额
BigDecimal amount = settlementLine.getAllocatedInvoiceAmount() == null
? settlementLine.getSettlementAmount() : settlementLine.getAllocatedInvoiceAmount();
if (amount == null) {
throw new HcsException("结算分摊行金额为空(id=" + settlementLine.getId() + ")");
}
// 申请编号:发票号-结算单号(同一发票分摊多结算单时逐条可区分)
String settlementNo = StringUtil.isBlank(settlementLine.getFormalSettlementNo()) ? settlement.getFormalSettlementNo() : settlementLine.getFormalSettlementNo();
String applyNo = receipt.getInvoiceNo() + "-" + (settlementNo == null ? "" : settlementNo);
String perfFdId = hcsPerfRecordAddSupport.pushPerfRecordAdd(contractManage, stageRow,
applyNo, amount, toMillis(receipt.getInvoiceDate()),
HcsSyncConstant.PERFORM_TYPE_PAYMENT, receipt.getKingdeeReceivedInvoiceNo(), settlementLine.getId(),
"收票履约-" + applyNo);
markSynced(settlementLine, perfFdId);
}
/**
* 批量装配结算信息所属收票单(须审批通过且 C109 已推送)
*/
private Map<Long, InvoiceReceipt> loadReceipts(List<InvoiceReceiptSettlement> settlementList) {
List<Long> receiptIds = settlementList.stream()
.map(InvoiceReceiptSettlement::getInvoiceReceiptId).filter(Objects::nonNull).distinct().toList();
if (receiptIds.isEmpty()) {
return Map.of();
}
return invoiceReceiptMapper.selectBatchIds(receiptIds).stream()
.collect(Collectors.toMap(InvoiceReceipt::getId, item -> item, (first, second) -> first));
}
/**
* 批量装配结算信息关联的正式结算单
*/
private Map<Long, FormalSettlement> loadSettlements(List<InvoiceReceiptSettlement> settlementList) {
List<Long> settlementIds = settlementList.stream()
.map(InvoiceReceiptSettlement::getFormalSettlementId).filter(Objects::nonNull).distinct().toList();
if (settlementIds.isEmpty()) {
return Map.of();
}
return formalSettlementMapper.selectBatchIds(settlementIds).stream()
.collect(Collectors.toMap(FormalSettlement::getId, item -> item, (first, second) -> first, LinkedHashMap::new));
}
/**
* 推送成功回填:履约记录ID 立即持久化(幂等键),清失败原因
*/
private void markSynced(InvoiceReceiptSettlement settlementLine, String perfFdId) {
settlementLine.setHcsPerfFdId(perfFdId);
settlementLine.setHcsPerfRetryCount(0);
settlementLine.setHcsPerfSyncMsg(null);
invoiceReceiptSettlementMapper.updateById(settlementLine);
log.info("HCS履约收票记录推送成功:settlementId={},perfFdId={}", settlementLine.getId(), perfFdId);
}
/**
* 推送失败回写:重试次数+1 + 失败原因(下轮扫描自动补偿)
*/
private void markFailed(InvoiceReceiptSettlement settlementLine, String message) {
settlementLine.setHcsPerfRetryCount(settlementLine.getHcsPerfRetryCount() == null ? 1 : settlementLine.getHcsPerfRetryCount() + 1);
settlementLine.setHcsPerfSyncMsg(HcsSyncConstant.truncateMsg(message));
invoiceReceiptSettlementMapper.updateById(settlementLine);
log.error("HCS履约收票记录推送失败:settlementId={},retryCount={},{}", settlementLine.getId(), settlementLine.getHcsPerfRetryCount(), message);
}
private Long toMillis(LocalDate localDate) {
return localDate == null ? null : localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
}
@@ -46,10 +46,12 @@ import org.springblade.transport.service.IContractChangeService;
import org.springblade.transport.service.IProjectChangeService;
import org.springblade.transport.support.hcs.HcsContractSpecSyncSupport;
import org.springblade.transport.support.hcs.HcsContractSyncSupport;
import org.springblade.transport.support.hcs.HcsInvoicePerfSyncSupport;
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.springblade.transport.support.hcs.HcsReceiptPerfSyncSupport;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -110,6 +112,8 @@ public class HcsPushTask {
private final HcsPaymentSyncSupport hcsPaymentSyncSupport;
private final HcsInvoiceRegSyncSupport hcsInvoiceRegSyncSupport;
private final HcsReceiptBillSyncSupport hcsReceiptBillSyncSupport;
private final HcsInvoicePerfSyncSupport hcsInvoicePerfSyncSupport;
private final HcsReceiptPerfSyncSupport hcsReceiptPerfSyncSupport;
private final IContractChangeService contractChangeService;
private final IProjectChangeService projectChangeService;
private final BladeRedis bladeRedis;
@@ -148,19 +152,37 @@ public class HcsPushTask {
}
/**
* 开票登记(C111)定时推送:扫描审批通过且待同步/待补偿的开票申请(含金蝶开票完成后回填),独立开关与 cron 调度
* 开票登记(C111)定时推送:扫描审批通过且待同步/待补偿的开票申请(含金蝶开票完成后回填),
* 并推送履约开票记录(C108-1,金蝶回写发票逐张推送),独立开关与 cron 调度
*/
@Scheduled(cron = "${thirdParty.hcs.push.invoice-application.cron:0 3/5 * * * ?}")
public void pushHcsInvoiceApplication() {
this.runOnce("开票登记", HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION, hcsProperties.getPush().getInvoiceApplication().isSyncEnabled(), this::pushUnsyncedInvoiceApplications);
this.runOnce("开票登记", HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION, hcsProperties.getPush().getInvoiceApplication().isSyncEnabled(), this::syncHcsInvoiceApplications);
}
/**
* 收票单(C109)定时推送:扫描审批通过且待同步/待补偿的收票单,独立开关与 cron 调度
* 开票链路组合动作:开票登记(C111)推送 + 履约开票记录(C108-1)推送(定时任务与手动批量入口共用)
*/
public String syncHcsInvoiceApplications() {
String pushSummary = this.pushUnsyncedInvoiceApplications();
return pushSummary + ";" + hcsInvoicePerfSyncSupport.pushUnsyncedInvoicePerfRecords();
}
/**
* 收票单(C109)定时推送:扫描审批通过且待同步/待补偿的收票单,并推送履约收票记录
* (C108-1,收票单每条结算信息一条,限 C109 已推送),独立开关与 cron 调度
*/
@Scheduled(cron = "${thirdParty.hcs.push.invoice-receipt.cron:0 4/5 * * * ?}")
public void pushHcsInvoiceReceipt() {
this.runOnce("收票单", HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT, hcsProperties.getPush().getInvoiceReceipt().isSyncEnabled(), this::pushUnsyncedInvoiceReceipts);
this.runOnce("收票单", HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT, hcsProperties.getPush().getInvoiceReceipt().isSyncEnabled(), this::syncHcsInvoiceReceipts);
}
/**
* 收票链路组合动作:收票单(C109)推送 + 履约收票记录(C108-1)推送(定时任务与手动批量入口共用)
*/
public String syncHcsInvoiceReceipts() {
String pushSummary = this.pushUnsyncedInvoiceReceipts();
return pushSummary + ";" + hcsReceiptPerfSyncSupport.pushUnsyncedReceiptPerfRecords();
}
/**