feat: 金蝶认领推送完成后同步合同合规系统(C103-9/C107-1/C108-1)
- 新增 HcsClaimSyncSupport 认领链编排:金蝶认领推送(A301-3)成功后按合同分组推送 ① C103-9 认领金额原子累加收款计划行并重推(未推送合规随合同链路首推) ② C107-1 按「认领主单×合同」推送认领记录(合规流水ID经C104-2定位) ③ C108-1 按合同新增履约流水(fd_plan_id=收款计划行合规阶段ID) - IContractExeStageService 新增 findCollectionStageRow / accumulateClaimedAmountOnPush - ReceiptFlowServiceImpl.pushClaimLocked 事务提交后挂钩,失败仅日志不影响金蝶推送结果 - 单合同失败不影响其余合同;开关关闭零开销
This commit is contained in:
+20
@@ -30,6 +30,7 @@ import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,25 @@ public interface IContractExeStageService extends BaseService<HcsContractExeStag
|
||||
*/
|
||||
HcsContractExeStage findPaymentStageRow(Long contractId, String fundNature);
|
||||
|
||||
/**
|
||||
* 匹配合同的收款履约计划行(已同步,优先按款项性质精确匹配,无则取首条收款行兜底)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param fundNature 款项性质(progress/prepayment 等,空则直接取首条收款行)
|
||||
* @return 收款履约计划行,无已同步收款行时返回 null
|
||||
*/
|
||||
HcsContractExeStage findCollectionStageRow(Long contractId, String fundNature);
|
||||
|
||||
/**
|
||||
* 金蝶认领推送完成后累加收款履约计划行款项金额(认领金额累加,多次认领累加更新):
|
||||
* 原子累加 fund_amount;已同步行置 row_op=update 凭已存合规阶段ID 随 C103-9 重推,
|
||||
* 未同步行保持 add 待合同链路首推(首推即携带累加后金额)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param claimedAmount 本次累加的认领金额(须大于0)
|
||||
*/
|
||||
void accumulateClaimedAmountOnPush(Long contractId, BigDecimal claimedAmount);
|
||||
|
||||
/**
|
||||
* 付款审批通过累加履约计划行款项金额(款项金额=申请付款金额,多次付款累加更新):
|
||||
* 按款项性质匹配付款计划行,原子累加 fund_amount;已同步行置 row_op=update 凭已存合规阶段ID 随 C103-9 重推,
|
||||
|
||||
+54
@@ -43,6 +43,7 @@ import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -124,6 +125,59 @@ public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeS
|
||||
payment.getPaymentNo(), stageRow.getId(), payment.getAppliedAmount(), resyncNeeded ? "update" : "add(待合同链路首推)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HcsContractExeStage findCollectionStageRow(Long contractId, String fundNature) {
|
||||
if (contractId == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtil.isNotBlank(fundNature)) {
|
||||
HcsContractExeStage natureRow = getOne(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_COLLECTION)
|
||||
.eq(HcsContractExeStage::getFundNature, fundNature)
|
||||
.isNotNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.orderByAsc(HcsContractExeStage::getId)
|
||||
.last("limit 1"), false);
|
||||
if (natureRow != null) {
|
||||
return natureRow;
|
||||
}
|
||||
}
|
||||
// 兜底:无该款项性质已同步行时取首条已同步收款行
|
||||
return getOne(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_COLLECTION)
|
||||
.isNotNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.orderByAsc(HcsContractExeStage::getId)
|
||||
.last("limit 1"), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accumulateClaimedAmountOnPush(Long contractId, BigDecimal claimedAmount) {
|
||||
if (contractId == null || claimedAmount == null || claimedAmount.signum() <= 0) {
|
||||
return;
|
||||
}
|
||||
HcsContractExeStage stageRow = this.findCollectionStageRow(contractId, HcsSyncConstant.FUND_NATURE_PROGRESS);
|
||||
if (stageRow == null) {
|
||||
log.info("收款认领未匹配到已同步收款履约计划行,跳过款项金额累加:contractId={}", contractId);
|
||||
return;
|
||||
}
|
||||
// 原子累加防并发认领推送丢失更新(coalesce 兼容多数据库)
|
||||
update(Wrappers.<HcsContractExeStage>lambdaUpdate()
|
||||
.eq(HcsContractExeStage::getId, stageRow.getId())
|
||||
.setSql("fund_amount = coalesce(fund_amount, 0) + " + claimedAmount.toPlainString()));
|
||||
boolean resyncNeeded = StringUtil.isNotBlank(stageRow.getHcsStageFdId());
|
||||
if (resyncNeeded) {
|
||||
// 已同步行置 update 凭已存合规阶段ID 随 C103-9 重推;未同步行保持 add 待合同链路首推
|
||||
update(Wrappers.<HcsContractExeStage>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("收款认领履约计划款项金额累加:contractId={},stageRowId={},累加金额={},rowOp={}",
|
||||
contractId, stageRow.getId(), claimedAmount, resyncNeeded ? "update" : "add(待合同链路首推)");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submitRows(Long contractId, List<HcsContractExeStage> rows) {
|
||||
|
||||
+9
-2
@@ -65,6 +65,7 @@ import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IReceiptFlowService;
|
||||
import org.springblade.transport.support.KingdeeSyncLockSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.hcs.HcsClaimSyncSupport;
|
||||
import org.springblade.transport.wrapper.ReceiptFlowWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -130,6 +131,7 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
private final BfiProperties bfiProperties;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
|
||||
private final HcsClaimSyncSupport hcsClaimSyncSupport;
|
||||
|
||||
@Override
|
||||
public IPage<ReceiptFlowVO> selectPage(IPage<KingdeeReceiptFlow> page, ReceiptFlowVO query) {
|
||||
@@ -904,10 +906,15 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
|
||||
/**
|
||||
* 事务内推送:流水行锁覆盖闸门校验、对账、推送与状态落库全过程,
|
||||
* 防定时任务与手动按钮并发对同一流水双推
|
||||
* 防定时任务与手动按钮并发对同一流水双推;
|
||||
* 推送成功后触发合同合规认领链推送(C103-9/C107-1/C108-1,事务提交后执行,失败不影响金蝶推送结果)
|
||||
*/
|
||||
private ReceiptClaimPushResult pushClaimLocked(Long flowId, BfiClaimAnnounceVO announce) {
|
||||
return transactionTemplate.execute(status -> doPushClaimToKingdee(flowId, announce));
|
||||
ReceiptClaimPushResult result = transactionTemplate.execute(status -> doPushClaimToKingdee(flowId, announce));
|
||||
if (result != null && result.isSuccess()) {
|
||||
hcsClaimSyncSupport.pushClaimToHcs(flowId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* 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
|
||||
* is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for their own commercial purposes.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this license agreement and terms of use,
|
||||
* and you expressly agree to be bound by the terms of use of this software.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsClaimRecordAddDTO;
|
||||
import org.springblade.hcs.dto.HcsCollectionFlowVO;
|
||||
import org.springblade.hcs.dto.HcsFormSaveVO;
|
||||
import org.springblade.hcs.dto.HcsIdVO;
|
||||
import org.springblade.hcs.dto.HcsMechanismsDTO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageVO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordFormDataDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordPropsDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordSaveDTO;
|
||||
import org.springblade.hcs.dto.HcsRefVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsUserRefVO;
|
||||
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.FormalSettlementMapper;
|
||||
import org.springblade.transport.mapper.KingdeeReceiptFlowMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
|
||||
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.KingdeeReceiptFlow;
|
||||
import org.springblade.transport.pojo.entity.ReceiptClaim;
|
||||
import org.springblade.transport.pojo.entity.ReceiptClaimSettlement;
|
||||
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 收款认领链推送编排支撑组件(C103-9 更新履约计划 + C107-1 认领记录 + C108-1 履约流水)
|
||||
* <p>
|
||||
* 触发点:金蝶认领推送(A301-3)成功之后(手动按钮与定时任务共用钩子),开关 thirdParty.hcs.claim.sync-enabled。
|
||||
* 编排顺序:先 C103-9(认领金额累加到收款履约计划行并重推,C107-1/C108-1 引用其返回的阶段ID),
|
||||
* 再按「认领主单 × 合同」推 C107-1(合规流水ID 经 C104-2 按明细流水号定位),最后按合同推 C108-1 履约流水。
|
||||
* 合同未推送合规(hcs_fd_id 空)时仅累加计划行金额,随合同链路首推。
|
||||
* 推送失败仅记日志不回滚金蝶推送结果(单合同失败不影响其余合同)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsClaimSyncSupport {
|
||||
|
||||
/**
|
||||
* 认领完成状态(与 ReceiptFlowServiceImpl.CLAIMED 同值)
|
||||
*/
|
||||
private static final String CLAIMED = "claimed";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final KingdeeReceiptFlowMapper receiptFlowMapper;
|
||||
private final ReceiptClaimMapper claimMapper;
|
||||
private final ReceiptClaimSettlementMapper claimSettlementMapper;
|
||||
private final FormalSettlementMapper formalSettlementMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final IContractExeStageService contractExeStageService;
|
||||
private final HcsExeStageSyncSupport hcsExeStageSyncSupport;
|
||||
|
||||
/**
|
||||
* 金蝶认领推送成功后推送合同合规(C103-9 → C107-1 → C108-1)
|
||||
* <p>
|
||||
* 开关关闭或推送异常均静默返回,不影响金蝶认领推送主流程与事务。
|
||||
*
|
||||
* @param flowId 收款流水ID
|
||||
*/
|
||||
public void pushClaimToHcs(Long flowId) {
|
||||
if (!hcsProperties.getClaim().isSyncEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
doPushClaimToHcs(flowId);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS收款认领链推送异常 flowId:{}", flowId, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送主体:归集有效认领分摊(按合同分组)→ 逐合同推送(单合同失败不影响其余合同)
|
||||
*/
|
||||
private void doPushClaimToHcs(Long flowId) {
|
||||
KingdeeReceiptFlow flow = receiptFlowMapper.selectById(flowId);
|
||||
if (flow == null) {
|
||||
return;
|
||||
}
|
||||
// 未作废认领主单(作废认领不同步合规)
|
||||
List<ReceiptClaim> claims = claimMapper.selectList(Wrappers.<ReceiptClaim>lambdaQuery()
|
||||
.eq(ReceiptClaim::getReceiptFlowId, flowId)
|
||||
.eq(ReceiptClaim::getClaimStatus, CLAIMED));
|
||||
if (claims.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 有效分摊行(作废时已置 status=0)
|
||||
List<ReceiptClaimSettlement> allocations = claimSettlementMapper.selectList(
|
||||
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
|
||||
.eq(ReceiptClaimSettlement::getReceiptFlowId, flowId)
|
||||
.eq(ReceiptClaimSettlement::getStatus, 1)
|
||||
.eq(ReceiptClaimSettlement::getIsDeleted, 0));
|
||||
if (allocations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 结算单批量定位合同(一次 IN 查询,避免逐行 N+1)
|
||||
Map<Long, FormalSettlement> settlements = formalSettlementMapper.selectBatchIds(
|
||||
allocations.stream().map(ReceiptClaimSettlement::getFormalSettlementId)
|
||||
.filter(Objects::nonNull).distinct().toList()).stream()
|
||||
.collect(Collectors.toMap(FormalSettlement::getId, item -> item, (first, second) -> first));
|
||||
// 合同id → 认领主单id → 分摊金额
|
||||
Map<Long, Map<Long, BigDecimal>> amountByContractByClaim = new LinkedHashMap<>();
|
||||
for (ReceiptClaimSettlement allocation : allocations) {
|
||||
FormalSettlement settlement = settlements.get(allocation.getFormalSettlementId());
|
||||
if (settlement == null || settlement.getContractId() == null
|
||||
|| allocation.getReceiptClaimId() == null || allocation.getAllocatedReceiptAmount() == null) {
|
||||
continue;
|
||||
}
|
||||
amountByContractByClaim
|
||||
.computeIfAbsent(settlement.getContractId(), key -> new LinkedHashMap<>())
|
||||
.merge(allocation.getReceiptClaimId(), allocation.getAllocatedReceiptAmount(), BigDecimal::add);
|
||||
}
|
||||
if (amountByContractByClaim.isEmpty()) {
|
||||
log.info("HCS收款认领链推送跳过:分摊行未关联合同 flowId={}", flowId);
|
||||
return;
|
||||
}
|
||||
// 合规侧收款流水ID(C107-1 必填引用,一次定位全部合同共用;定位失败仅跳过 C107-1,不阻塞履约计划更新)
|
||||
String hcsCollectionFlowId = null;
|
||||
try {
|
||||
hcsCollectionFlowId = locateHcsCollectionFlowId(flow);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS收款流水定位失败 flowId:{},跳过C107-1认领记录推送", flowId, exception);
|
||||
}
|
||||
for (Map.Entry<Long, Map<Long, BigDecimal>> contractEntry : amountByContractByClaim.entrySet()) {
|
||||
try {
|
||||
pushOneContract(flow, claims, contractEntry.getKey(), contractEntry.getValue(), hcsCollectionFlowId);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS收款认领链推送失败 flowId:{},contractId:{}", flowId, contractEntry.getKey(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送单个合同:① C103-9 更新履约计划(认领金额累加重推)→ ② C107-1 认领记录(按认领主单分组)→ ③ C108-1 履约流水
|
||||
*/
|
||||
private void pushOneContract(KingdeeReceiptFlow flow, List<ReceiptClaim> claims, Long contractId,
|
||||
Map<Long, BigDecimal> amountByClaim, String hcsCollectionFlowId) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(contractId);
|
||||
if (contractManage == null) {
|
||||
log.warn("HCS收款认领链推送跳过:合同不存在 contractId={}", contractId);
|
||||
return;
|
||||
}
|
||||
BigDecimal claimedTotal = amountByClaim.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
// ① C103-9 更新合同履约计划:认领金额累加到收款计划行;已同步行置 update 凭阶段ID 重推
|
||||
contractExeStageService.accumulateClaimedAmountOnPush(contractId, claimedTotal);
|
||||
HcsContractExeStage stageRow = contractExeStageService.findCollectionStageRow(contractId, HcsSyncConstant.FUND_NATURE_PROGRESS);
|
||||
if (StringUtil.isNotBlank(contractManage.getHcsFdId())) {
|
||||
// 行级重推(无待同步行返回提示,不视为失败);合同未推送合规时随合同链路首推
|
||||
hcsExeStageSyncSupport.syncExeStages(contractManage);
|
||||
}
|
||||
// ② C107-1 认领记录:按「认领主单 × 合同」推送,金额为该认领主单分摊到本合同的金额
|
||||
if (StringUtil.isBlank(hcsCollectionFlowId)) {
|
||||
log.warn("HCS认领记录推送跳过:未定位到合规收款流水 flowId={},detailSerialNo={}", flow.getId(), flow.getDetailSerialNo());
|
||||
} else {
|
||||
Map<Long, ReceiptClaim> claimsById = claims.stream()
|
||||
.collect(Collectors.toMap(ReceiptClaim::getId, item -> item, (first, second) -> first));
|
||||
for (Map.Entry<Long, BigDecimal> claimEntry : amountByClaim.entrySet()) {
|
||||
pushClaimRecord(flow, claimsById.get(claimEntry.getKey()), contractManage, stageRow, claimEntry.getValue(), hcsCollectionFlowId);
|
||||
}
|
||||
}
|
||||
// ③ C108-1 履约流水:按合同一条,须已推送合规且存在已同步收款计划行(fd_plan_id 必填)
|
||||
if (StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
log.info("HCS履约流水推送跳过:合同尚未推送合规 contractNo={}", contractManage.getContractNo());
|
||||
return;
|
||||
}
|
||||
if (stageRow == null || StringUtil.isBlank(stageRow.getHcsStageFdId())) {
|
||||
log.info("HCS履约流水推送跳过:无已同步收款履约计划行 contractNo={}", contractManage.getContractNo());
|
||||
return;
|
||||
}
|
||||
pushPerfRecord(flow, contractManage, stageRow, claimedTotal, latestClaimDate(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* C107-1 认领记录推送(单认领主单 × 单合同)
|
||||
*/
|
||||
private void pushClaimRecord(KingdeeReceiptFlow flow, ReceiptClaim claim, ContractManage contractManage,
|
||||
HcsContractExeStage stageRow, BigDecimal claimAmount, String hcsCollectionFlowId) {
|
||||
if (claim == null || claimAmount == null || claimAmount.signum() <= 0) {
|
||||
return;
|
||||
}
|
||||
HcsClaimRecordAddDTO param = new HcsClaimRecordAddDTO();
|
||||
// 【必填】收款流水引用(合规侧流水ID,C104-2 按明细流水号定位)
|
||||
HcsRefVO collectionFlow = new HcsRefVO();
|
||||
collectionFlow.setFdId(hcsCollectionFlowId);
|
||||
param.setFdCollectionFlow(collectionFlow);
|
||||
// 认领合同引用(fdId 为合规合同ID)
|
||||
HcsRefVO contractRef = new HcsRefVO();
|
||||
contractRef.setFdId(contractManage.getHcsFdId());
|
||||
contractRef.setFdName(contractManage.getContractName());
|
||||
param.setFdContract(contractRef);
|
||||
// 【必填】认领合同编号(须在合规系统本地存在)
|
||||
param.setFdContractNumber(contractManage.getContractNo());
|
||||
// 对应履约项ID 与 认领款项类型(取合同收款计划行,无已同步行不传)
|
||||
if (stageRow != null) {
|
||||
param.setFdExePlanStageId(stageRow.getHcsStageFdId());
|
||||
param.setFdFundNature(stageRow.getFundNature());
|
||||
}
|
||||
// 认领人/部门:TMS 与合规用户不同源,仅传名称(§9-U2)
|
||||
param.setFdClaimer(HcsRefVO.ofName(claim.getClaimerName()));
|
||||
param.setFdClaimerName(claim.getClaimerName());
|
||||
param.setFdClaimerDept(HcsRefVO.ofName(claim.getClaimerDeptName()));
|
||||
param.setFdClaimerDeptName(claim.getClaimerDeptName());
|
||||
// 【必填】认领金额
|
||||
param.setFdClaimAmount(claimAmount);
|
||||
param.setFdClaimDate(toMillis(claim.getClaimDate()));
|
||||
param.setFdRemark(claim.getRemark());
|
||||
// 状态 0=已认领(仅推送未作废认领)
|
||||
param.setFdStatus(0);
|
||||
log.info("HCS认领记录推送开始 flowId:{},claimId:{},contractNo:{},认领金额 {}",
|
||||
flow.getId(), claim.getId(), contractManage.getContractNo(), claimAmount);
|
||||
HcsResult<HcsIdVO> result = hcsClient.claimRecordAdd(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS认领记录推送失败响应:{},claimId={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), claim.getId());
|
||||
throw new HcsException("C107-1 认领记录推送失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
// shortcut: C107-1 返回 fdId 未持久化(认领作废同步 C107-3 未实现,重推会重复);需作废联动时先落库
|
||||
log.info("HCS认领记录推送成功 flowId:{},claimId:{},contractNo:{},claimRecordFdId:{}",
|
||||
flow.getId(), claim.getId(), contractManage.getContractNo(),
|
||||
result.getData() == null ? "响应未返回" : result.getData().getFdId());
|
||||
}
|
||||
|
||||
/**
|
||||
* C108-1 履约流水推送(单合同一条:申请金额=实际金额=该合同累计认领金额)
|
||||
*/
|
||||
private void pushPerfRecord(KingdeeReceiptFlow flow, ContractManage contractManage,
|
||||
HcsContractExeStage stageRow, BigDecimal claimedTotal, LocalDate planDate) {
|
||||
// 表单模板ID(配置 perf-record.xform-id,环境值 §9-U9,未配置不传)
|
||||
String xformId = StringUtil.isBlank(hcsProperties.getPerfRecord().getXformId()) ? null : hcsProperties.getPerfRecord().getXformId();
|
||||
HcsPerfRecordPropsDTO props = new HcsPerfRecordPropsDTO();
|
||||
// 【必填】申请编号:收款通知单号,兜底明细流水号
|
||||
props.setFdApplyNo(firstNonBlank(flow.getReceiptNoticeNo(), flow.getDetailSerialNo()));
|
||||
// 申请金额 / 实际金额:该合同累计认领金额
|
||||
props.setFdApplyAmount(claimedTotal.toPlainString());
|
||||
props.setFdFactAmount(claimedTotal.toPlainString());
|
||||
// 【必填】履约日期:最近认领日期(毫秒时间戳)
|
||||
props.setFdPlanDate(toMillis(planDate));
|
||||
// 履约类型:收款
|
||||
props.setFdPalnType(HcsSyncConstant.PERFORM_TYPE_COLLECTION);
|
||||
// 履约性质:收款计划行款项性质
|
||||
props.setFdProperty(stageRow.getFundNature());
|
||||
// 履约计划id / 来源履约计划项ID:收款计划行合规阶段ID(HCS 只认自己的阶段ID)
|
||||
props.setFdId(stageRow.getHcsStageFdId());
|
||||
props.setFdPlanId(stageRow.getHcsStageFdId());
|
||||
// 金蝶单据号:A301-3 推送返回的金蝶收款单号
|
||||
props.setFdJdDocNo(flow.getKingdeeBillNo());
|
||||
// 【必填】合同ID:HCS 合同ID
|
||||
props.setFdContractId(contractManage.getHcsFdId());
|
||||
// 业务ID:TMS 收款流水ID(溯源)
|
||||
props.setFdBusinessId(String.valueOf(flow.getId()));
|
||||
props.setFdCreateTime(System.currentTimeMillis());
|
||||
props.setFdXformId(xformId);
|
||||
HcsPerfRecordFormDataDTO formData = new HcsPerfRecordFormDataDTO();
|
||||
formData.setFdXformId(xformId);
|
||||
formData.setDynamicProps(props);
|
||||
// 机制数据(C108-1 必填):认领附件暂不转传,传空列表
|
||||
HcsMechanismsDTO mechanisms = new HcsMechanismsDTO();
|
||||
mechanisms.setAttachment(List.of());
|
||||
HcsPerfRecordSaveDTO param = new HcsPerfRecordSaveDTO();
|
||||
param.setCurUser(HcsUserRefVO.ofLoginName(hcsProperties.getOperatorLoginName()));
|
||||
param.setFormData(formData);
|
||||
param.setMechanisms(mechanisms);
|
||||
log.info("HCS履约流水新建开始 flowId:{},contractNo:{},认领金额 {}", flow.getId(), contractManage.getContractNo(), claimedTotal);
|
||||
HcsResult<HcsFormSaveVO> result = hcsClient.perfRecordAdd(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS履约流水新建失败响应:{},contractNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo());
|
||||
throw new HcsException("C108-1 履约流水新建失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
// shortcut: 履约流水 fdId 未持久化(认领链无 C108-2 更新场景);需推进实际金额更新时先落库
|
||||
log.info("HCS履约流水新建成功 flowId:{},contractNo:{},perfFdId:{}",
|
||||
flow.getId(), contractManage.getContractNo(), result.getData().getFdId());
|
||||
}
|
||||
|
||||
/**
|
||||
* C104-2 定位合规侧收款流水ID:按明细流水号精确查询(合规流水由合规自 ERP 同步,TMS 镜像同源)
|
||||
*/
|
||||
private String locateHcsCollectionFlowId(KingdeeReceiptFlow flow) {
|
||||
if (StringUtil.isBlank(flow.getDetailSerialNo())) {
|
||||
log.warn("HCS收款流水定位跳过:流水缺少明细流水号 flowId={}", flow.getId());
|
||||
return null;
|
||||
}
|
||||
HcsPageQueryDTO param = new HcsPageQueryDTO();
|
||||
param.setColumns(List.of("fdId", "fdClaimNotice", "fdDetailSerialNumber", "fdCollectionAmount", "fdClaimStatus", "fdClaimedAmount"));
|
||||
param.setConditions(Map.of("fdDetailSerialNumber", flow.getDetailSerialNo()));
|
||||
param.setOffset(0);
|
||||
param.setPageSize(10);
|
||||
HcsResult<HcsPageVO<HcsCollectionFlowVO>> result = hcsClient.collectionFlowList(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
throw new HcsException("C104-2 收款流水查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getContent().stream()
|
||||
.filter(item -> flow.getDetailSerialNo().equals(item.getFdDetailSerialNumber()))
|
||||
.map(HcsCollectionFlowVO::getFdId)
|
||||
.filter(StringUtil::isNotBlank)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 最近认领日期(C108-1 履约日期)
|
||||
*/
|
||||
private LocalDate latestClaimDate(List<ReceiptClaim> claims) {
|
||||
return claims.stream()
|
||||
.map(ReceiptClaim::getClaimDate)
|
||||
.filter(Objects::nonNull)
|
||||
.max(LocalDate::compareTo)
|
||||
.orElse(LocalDate.now());
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
for (String value : values) {
|
||||
if (Func.isNotEmpty(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Long toMillis(LocalDate localDate) {
|
||||
return localDate == null ? null : localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user