From dcda231c5169f9ded3c1243ed191080c188eaa7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 22 Oct 2026 16:33:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=98=E6=AC=BE=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E7=B4=AF=E5=8A=A0=E5=B1=A5=E7=BA=A6=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E6=AC=BE=E9=A1=B9=E9=87=91=E9=A2=9D=E5=B9=B6=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E5=90=88=E5=90=8C=E7=B3=BB=E7=BB=9F=EF=BC=8C=E9=87=91?= =?UTF-8?q?=E8=9D=B6=E4=BB=98=E6=AC=BE=E7=94=B3=E8=AF=B7=E6=90=BA=E5=B8=A6?= =?UTF-8?q?=E5=B1=A5=E7=BA=A6=E8=AE=A1=E5=88=92id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/entity/HcsContractExeStage.java | 2 +- .../transport/constant/HcsSyncConstant.java | 15 +++++ .../service/IContractExeStageService.java | 19 +++++++ .../impl/ContractExeStageServiceImpl.java | 57 +++++++++++++++++++ .../impl/PaymentApplicationServiceImpl.java | 37 ++++++++++-- .../support/hcs/HcsExeStageSyncSupport.java | 2 +- .../support/hcs/HcsPaymentSyncSupport.java | 40 +++++++++++-- .../support/hcs/HcsPerfRecordSyncSupport.java | 17 ++---- .../hcs/dto/HcsExeplanStageDTO.java | 2 +- 9 files changed, 168 insertions(+), 23 deletions(-) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/HcsContractExeStage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/HcsContractExeStage.java index 95bdb385..eb42723f 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/HcsContractExeStage.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/HcsContractExeStage.java @@ -67,7 +67,7 @@ public class HcsContractExeStage extends TenantEntity { @Schema(description = "款项性质 prepayment/progress/acceptance/retention") private String fundNature; - @Schema(description = "计划金额(元)") + @Schema(description = "款项金额(元,付款审批通过按申请付款金额累加更新)") private BigDecimal fundAmount; @Schema(description = "计划资金日期") 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 c4eb9884..42eaaf09 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 @@ -254,6 +254,21 @@ public final class HcsSyncConstant { */ public static final String FUND_NATURE_PROGRESS = "progress"; + /** + * 款项性质:预付款 + */ + public static final String FUND_NATURE_PREPAYMENT = "prepayment"; + + /** + * 付款类型→款项性质映射:正式结算付款对应进度款计划行,预付款(进度预付/项目预付)对应预付款计划行 + * + * @param paymentType 付款申请付款类型 + * @return 款项性质 + */ + public static String fundNatureOfPayment(String paymentType) { + return TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(paymentType) ? FUND_NATURE_PROGRESS : FUND_NATURE_PREPAYMENT; + } + // ==================== Redis 缓存 key(照 BFI blade:third: 前缀惯例) ==================== /** diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IContractExeStageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IContractExeStageService.java index 53421060..30c85811 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IContractExeStageService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IContractExeStageService.java @@ -28,6 +28,7 @@ package org.springblade.transport.service; import org.springblade.core.mp.base.BaseService; import org.springblade.transport.pojo.entity.ContractManage; import org.springblade.transport.pojo.entity.HcsContractExeStage; +import org.springblade.transport.pojo.entity.PaymentApplication; import java.util.List; @@ -46,6 +47,24 @@ public interface IContractExeStageService extends BaseService listByContractId(Long contractId); + /** + * 匹配合同的付款履约计划行(已同步,优先按款项性质精确匹配,无则取首条付款行兜底) + * + * @param contractId 合同ID + * @param fundNature 款项性质(progress/prepayment 等,空则直接取首条付款行) + * @return 付款履约计划行,无已同步付款行时返回 null + */ + HcsContractExeStage findPaymentStageRow(Long contractId, String fundNature); + + /** + * 付款审批通过累加履约计划行款项金额(款项金额=申请付款金额,多次付款累加更新): + * 按款项性质匹配付款计划行,原子累加 fund_amount;已同步行置 row_op=update 凭已存合规阶段ID 随 C103-9 重推, + * 未同步行保持 add 待合同链路首推(首推即携带累加后金额) + * + * @param payment 付款申请单(审批通过) + */ + void accumulatePaidAmountOnApprove(PaymentApplication payment); + /** * 审批通过自动创建默认履约计划行(合同下无规则行时):承运商合同=付款进度款(收票规则取合同结算模式), * 客户合同=收款进度款;金额/资金日期留空,随合同链路推 C103-9 diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractExeStageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractExeStageServiceImpl.java index 06d49d3f..fada11b8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractExeStageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractExeStageServiceImpl.java @@ -38,6 +38,7 @@ import org.springblade.transport.mapper.ContractManageMapper; import org.springblade.transport.mapper.HcsContractExeStageMapper; import org.springblade.transport.pojo.entity.ContractManage; import org.springblade.transport.pojo.entity.HcsContractExeStage; +import org.springblade.transport.pojo.entity.PaymentApplication; import org.springblade.transport.service.IContractExeStageService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,6 +68,62 @@ public class ContractExeStageServiceImpl extends BaseServiceImpllambdaQuery() + .eq(HcsContractExeStage::getContractId, contractId) + .eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_PAYMENT) + .eq(HcsContractExeStage::getFundNature, fundNature) + .isNotNull(HcsContractExeStage::getHcsStageFdId) + .orderByAsc(HcsContractExeStage::getId) + .last("limit 1"), false); + if (natureRow != null) { + return natureRow; + } + } + // 兜底:无该款项性质已同步行时取首条已同步付款行(与 C108 履约记录历史口径一致) + return getOne(Wrappers.lambdaQuery() + .eq(HcsContractExeStage::getContractId, contractId) + .eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_PAYMENT) + .isNotNull(HcsContractExeStage::getHcsStageFdId) + .orderByAsc(HcsContractExeStage::getId) + .last("limit 1"), false); + } + + @Override + public void accumulatePaidAmountOnApprove(PaymentApplication payment) { + if (payment == null || payment.getContractId() == null || payment.getAppliedAmount() == null) { + return; + } + String fundNature = HcsSyncConstant.fundNatureOfPayment(payment.getPaymentType()); + HcsContractExeStage stageRow = this.findPaymentStageRow(payment.getContractId(), fundNature); + // 仅精确匹配款项性质的计划行累加,兜底行款项性质不一致时不累加(避免预付款金额计入进度款计划) + if (stageRow == null || !fundNature.equals(stageRow.getFundNature())) { + log.info("付款审批未匹配到款项性质[{}]的履约计划行,跳过款项金额累加:paymentNo={},contractId={}", + fundNature, payment.getPaymentNo(), payment.getContractId()); + return; + } + // 原子累加防并发付款审批丢失更新(coalesce 兼容多数据库) + update(Wrappers.lambdaUpdate() + .eq(HcsContractExeStage::getId, stageRow.getId()) + .setSql("fund_amount = coalesce(fund_amount, 0) + " + payment.getAppliedAmount().toPlainString())); + boolean resyncNeeded = StringUtil.isNotBlank(stageRow.getHcsStageFdId()); + if (resyncNeeded) { + // 已同步行置 update 凭已存合规阶段ID 随 C103-9 重推;未同步行保持 add 待合同链路首推 + update(Wrappers.lambdaUpdate() + .eq(HcsContractExeStage::getId, stageRow.getId()) + .set(HcsContractExeStage::getRowOp, HcsSyncConstant.ROW_OP_UPDATE) + .set(HcsContractExeStage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_UNSYNCED) + .set(HcsContractExeStage::getHcsSyncMsg, "付款审批款项金额累加,待重推C103-9")); + } + log.info("付款审批履约计划款项金额累加:paymentNo={},stageRowId={},累加金额={},rowOp={}", + payment.getPaymentNo(), stageRow.getId(), payment.getAppliedAmount(), resyncNeeded ? "update" : "add(待合同链路首推)"); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean submitRows(Long contractId, List rows) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java index f9d256ef..d87ba919 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java @@ -86,6 +86,7 @@ import org.springblade.transport.pojo.entity.FormalSettlement; import org.springblade.transport.pojo.entity.FormalSettlementDetail; import org.springblade.transport.pojo.entity.FormalSettlementPayment; import org.springblade.transport.pojo.entity.FormalSettlementSource; +import org.springblade.transport.pojo.entity.HcsContractExeStage; import org.springblade.transport.pojo.entity.KingdeeBillLock; import org.springblade.transport.pojo.entity.PaymentApplication; import org.springblade.transport.pojo.entity.PaymentApplicationInvoice; @@ -98,6 +99,7 @@ import org.springblade.transport.pojo.entity.TemporaryCreditLimit; import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO; import org.springblade.transport.pojo.vo.PaymentApplicationVO; import org.springblade.transport.service.IFormalSettlementService; +import org.springblade.transport.service.IContractExeStageService; import org.springblade.transport.service.IKingdeeBillLockService; import org.springblade.transport.service.IPaymentApplicationService; import org.springblade.transport.service.ISettlementProjectAdvanceWriteOffService; @@ -177,6 +179,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl selectPage(IPage page, PaymentApplicationVO query) { @@ -524,6 +527,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl - * 编排顺序:特批(提交时高风险/黑名单须特批附件或额度超额标记 need 时发起)→ 履约记录(审批通过新建 / 实际付款推进更新)。 + * 编排顺序:特批(提交时高风险/黑名单须特批附件或额度超额标记 need 时发起)→ 履约计划重推(付款审批款项金额累加行)→ 履约记录(审批通过新建 / 实际付款推进更新)。 * 特批流程ID 与履约记录ID 在各自 Support 内即时持久化;整体同步状态(hcs_sync_status)由本组件统一回写。 * 特批发起后 TMS 挂起 currentNode="特批审核",合规 paymentLegal/result 回调联动付款审批状态 * (通过恢复"审批通过"放行 / 驳回联动审批驳回走重新提交闭环),人工确认等效兜底。 @@ -65,13 +67,15 @@ public class HcsPaymentSyncSupport { private static final List APPROVED_STATUSES = List.of("approved"); private final PaymentApplicationMapper paymentApplicationMapper; + private final ContractManageMapper contractManageMapper; private final HcsProperties hcsProperties; private final HcsSpecPaySyncSupport hcsSpecPaySyncSupport; + private final HcsExeStageSyncSupport hcsExeStageSyncSupport; private final HcsPerfRecordSyncSupport hcsPerfRecordSyncSupport; private final ApplicationEventPublisher eventPublisher; /** - * 推送付款申请到 HCS:特批(如需)→ 履约记录(新建/更新)→ 回填整体同步状态 + * 推送付款申请到 HCS:特批(如需)→ 履约计划重推(C103-9 款项金额累加行)→ 履约记录(新建/更新)→ 回填整体同步状态 * * @param payment 付款申请单(approvalStatus=approved) * @return 推送结果描述 @@ -82,7 +86,16 @@ public class HcsPaymentSyncSupport { if (HcsSyncConstant.SPEC_STATUS_NEED.equals(payment.getHcsSpecStatus())) { result.append(hcsSpecPaySyncSupport.pushSpecPay(payment)); } - // ② 履约记录:审批通过新建 C108-1 / 实际付款推进更新 C108-2(一期暂不对接默认关) + // ② 履约计划重推(C103-9 行级更新):付款审批通过累加款项金额置 row_op=update 的计划行, + // 凭已存合规阶段ID 重推;独立于履约记录开关,推送失败抛出由付款链置 failed 重试兜底 + String stageResult = this.pushExeStageUpdate(payment); + if (StringUtil.isNotBlank(stageResult)) { + if (result.length() > 0) { + result.append(";"); + } + result.append(stageResult); + } + // ③ 履约记录:审批通过新建 C108-1 / 实际付款推进更新 C108-2(一期暂不对接默认关) if (!hcsProperties.getPerfRecord().isSyncEnabled()) { markSynced(payment, result.toString()); return result.toString(); @@ -95,6 +108,25 @@ public class HcsPaymentSyncSupport { return result.toString(); } + /** + * 履约计划重推(C103-9):聚合合同下 row_op 非 none 的计划行(含付款审批累加款项金额的 update 行)凭已存 fdId 重推; + * 合同尚未推送合规时计划行随合同链路首推(首推即携带累加后金额),此处跳过 + * + * @param payment 付款申请单 + * @return 推送结果描述,无待同步行返回空串 + */ + private String pushExeStageUpdate(PaymentApplication payment) { + if (payment.getContractId() == null) { + return ""; + } + ContractManage contractManage = contractManageMapper.selectById(payment.getContractId()); + if (contractManage == null || StringUtil.isBlank(contractManage.getHcsFdId())) { + return ""; + } + String stageResult = hcsExeStageSyncSupport.syncExeStages(contractManage); + return "履约规则无待同步行".equals(stageResult) ? "" : stageResult; + } + /** * 单条手动重推:重置为待同步并清零重试计数后立即推送(页面"推送合规/重推"按钮入口) * diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsPerfRecordSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsPerfRecordSyncSupport.java index 149c8b0f..7e7aa5a0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsPerfRecordSyncSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/hcs/HcsPerfRecordSyncSupport.java @@ -25,7 +25,6 @@ */ 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; @@ -42,11 +41,11 @@ import org.springblade.hcs.exception.HcsException; import org.springblade.hcs.feign.IHcsClient; import org.springblade.transport.constant.HcsSyncConstant; import org.springblade.transport.mapper.ContractManageMapper; -import org.springblade.transport.mapper.HcsContractExeStageMapper; import org.springblade.transport.mapper.PaymentApplicationMapper; import org.springblade.transport.pojo.entity.ContractManage; import org.springblade.transport.pojo.entity.HcsContractExeStage; import org.springblade.transport.pojo.entity.PaymentApplication; +import org.springblade.transport.service.IContractExeStageService; import org.springframework.stereotype.Component; import java.time.LocalDate; @@ -76,7 +75,7 @@ public class HcsPerfRecordSyncSupport { private final IHcsClient hcsClient; private final HcsProperties hcsProperties; private final ContractManageMapper contractManageMapper; - private final HcsContractExeStageMapper hcsContractExeStageMapper; + private final IContractExeStageService contractExeStageService; private final PaymentApplicationMapper paymentApplicationMapper; private final HcsAttachmentSupport hcsAttachmentSupport; @@ -203,17 +202,11 @@ public class HcsPerfRecordSyncSupport { } /** - * 匹配合同的付款履约规则行(perform_type=payment 且已同步,取第一条) + * 匹配合同的付款履约计划行(优先按付款类型映射的款项性质精确匹配,无则取首条已同步付款行兜底) */ private HcsContractExeStage findPaymentStageRow(PaymentApplication payment) { - if (payment.getContractId() == null) { - return null; - } - return hcsContractExeStageMapper.selectOne(Wrappers.lambdaQuery() - .eq(HcsContractExeStage::getContractId, payment.getContractId()) - .eq(HcsContractExeStage::getPerformType, "payment") - .isNotNull(HcsContractExeStage::getHcsStageFdId) - .last("limit 1")); + return contractExeStageService.findPaymentStageRow(payment.getContractId(), + HcsSyncConstant.fundNatureOfPayment(payment.getPaymentType())); } /** diff --git a/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsExeplanStageDTO.java b/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsExeplanStageDTO.java index 1f6246c3..21dbc7ac 100644 --- a/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsExeplanStageDTO.java +++ b/blade-third-party-api/blade-hcs-api/src/main/java/org/springblade/hcs/dto/HcsExeplanStageDTO.java @@ -71,7 +71,7 @@ public class HcsExeplanStageDTO implements Serializable { */ private Integer fdInvoiceCycle; /** - * 计划金额(元) + * 款项金额(元,付款审批通过按申请付款金额累加更新) */ private BigDecimal fdFundAmount; /**