feat: 新增履约开票记录与履约收票记录推送链路
This commit is contained in:
+3
-2
@@ -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);
|
||||
|
||||
+222
@@ -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();
|
||||
}
|
||||
}
|
||||
+211
@@ -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();
|
||||
}
|
||||
}
|
||||
+26
-4
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user