:feat: 审批通过自动创建履约计划行,C103-9同步前置至特批挂起之前

This commit is contained in:
刘泉佳
2026-10-14 14:11:31 +08:00
parent 433015fde0
commit 076145d7f6
6 changed files with 209 additions and 33 deletions
@@ -212,6 +212,43 @@ public final class HcsSyncConstant {
*/
public static final String SPEC_REVIEW_NODE = "特批审核";
// ==================== 履约计划行(blade_contract_exe_stage,C103-9) ====================
/**
* 行级同步操作:无(同步成功后清除标记)
*/
public static final String ROW_OP_NONE = "none";
/**
* 行级同步操作:新增
*/
public static final String ROW_OP_ADD = "add";
/**
* 行级同步操作:更新
*/
public static final String ROW_OP_UPDATE = "update";
/**
* 行级同步操作:删除
*/
public static final String ROW_OP_DELETE = "delete";
/**
* 履约类型:付款
*/
public static final String PERFORM_TYPE_PAYMENT = "payment";
/**
* 履约类型:收款
*/
public static final String PERFORM_TYPE_COLLECTION = "collection";
/**
* 款项性质:进度款
*/
public static final String FUND_NATURE_PROGRESS = "progress";
// ==================== Redis 缓存 key(照 BFI blade:third: 前缀惯例) ====================
/**
@@ -26,6 +26,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 java.util.List;
@@ -45,6 +46,14 @@ public interface IContractExeStageService extends BaseService<HcsContractExeStag
*/
List<HcsContractExeStage> listByContractId(Long contractId);
/**
* 审批通过自动创建默认履约计划行(合同下无规则行时):承运商合同=付款进度款(收票规则取合同结算模式),
* 客户合同=收款进度款;金额/资金日期留空,随合同链路推 C103-9
*
* @param contractManage 合同单(审批通过)
*/
void createDefaultRowOnApprove(ContractManage contractManage);
/**
* 保存合同履约规则行(按行差异置同步标记:新行/未同步行 add,已同步行 update)
*
@@ -57,11 +57,6 @@ import java.util.List;
@RequiredArgsConstructor
public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeStageMapper, HcsContractExeStage> implements IContractExeStageService {
/**
* 行级同步操作:无
*/
private static final String ROW_OP_NONE = "none";
private final ContractManageMapper contractManageMapper;
private final HcsProperties hcsProperties;
@@ -87,7 +82,7 @@ public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeS
row.setContractId(contractId);
if (row.getId() == null) {
// 新行:待随合同链路首推(row_op=add)
row.setRowOp("add");
row.setRowOp(HcsSyncConstant.ROW_OP_ADD);
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
row.setHcsSyncMsg("待随合同链路同步(C103-9)");
row.setFundType(StringUtil.isBlank(row.getFundType()) ? hcsProperties.getExeStage().getFundType() : row.getFundType());
@@ -99,7 +94,7 @@ public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeS
throw new ServiceException("履约规则行不属于该合同:" + row.getId());
}
// 已同步行编辑 → update;未同步行保持 add
String rowOp = StringUtil.isBlank(existing.getHcsStageFdId()) ? "add" : "update";
String rowOp = StringUtil.isBlank(existing.getHcsStageFdId()) ? HcsSyncConstant.ROW_OP_ADD : HcsSyncConstant.ROW_OP_UPDATE;
existing.setPerformType(row.getPerformType());
existing.setFundNature(row.getFundNature());
existing.setFundAmount(row.getFundAmount());
@@ -127,7 +122,7 @@ public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeS
}
if (StringUtil.isNotBlank(row.getHcsStageFdId())) {
// 已同步行:软删+标记 delete,由 C103-9 凭合规阶段ID 上报删除(ADR-0002,不可物理删除)
row.setRowOp("delete");
row.setRowOp(HcsSyncConstant.ROW_OP_DELETE);
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
row.setHcsSyncMsg("待随合同链路同步(C103-9 delete)");
updateById(row);
@@ -137,6 +132,48 @@ public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeS
return true;
}
@Override
public void createDefaultRowOnApprove(ContractManage contractManage) {
Long contractId = contractManage.getId();
if (count(Wrappers.<HcsContractExeStage>lambdaQuery().eq(HcsContractExeStage::getContractId, contractId)) > 0) {
return;
}
HcsContractExeStage row = new HcsContractExeStage();
row.setContractId(contractId);
if ("承运商合同".equals(contractManage.getContractCategory())) {
row.setPerformType(HcsSyncConstant.PERFORM_TYPE_PAYMENT);
row.setInvoiceRule(mapInvoiceRule(contractManage.getSettlementMode()));
} else if ("客户合同".equals(contractManage.getContractCategory())) {
row.setPerformType(HcsSyncConstant.PERFORM_TYPE_COLLECTION);
} else {
log.info("合同类别非承运商/客户,不自动创建履约计划行:contractNo={},contractCategory={}", contractManage.getContractNo(), contractManage.getContractCategory());
return;
}
// 默认进度款计划行:金额/资金日期留空,随合同链路推 C103-9
row.setFundNature(HcsSyncConstant.FUND_NATURE_PROGRESS);
row.setFundType(hcsProperties.getExeStage().getFundType());
row.setIsFinish(0);
row.setRowOp(HcsSyncConstant.ROW_OP_ADD);
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
row.setHcsSyncMsg("审批通过自动创建,待随合同链路同步(C103-9)");
save(row);
log.info("审批通过自动创建履约计划行:contractNo={},contractCategory={},performType={},invoiceRule={}",
contractManage.getContractNo(), contractManage.getContractCategory(), row.getPerformType(), row.getInvoiceRule());
}
/**
* 结算模式文本映射 C103-9 开票规则:先款后票=payFirst、先票后款=invoiceFirst,其余(月结/现结等)不传
*/
private String mapInvoiceRule(String settlementMode) {
if ("先款后票".equals(settlementMode)) {
return "payFirst";
}
if ("先票后款".equals(settlementMode)) {
return "invoiceFirst";
}
return null;
}
/**
* 行字段校验
*/
@@ -144,7 +181,7 @@ public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeS
if (StringUtil.isBlank(row.getPerformType())) {
throw new ServiceException("履约类型不能为空(payment/collection)");
}
if (!List.of("payment", "collection").contains(row.getPerformType())) {
if (!List.of(HcsSyncConstant.PERFORM_TYPE_PAYMENT, HcsSyncConstant.PERFORM_TYPE_COLLECTION).contains(row.getPerformType())) {
throw new ServiceException("履约类型非法:" + row.getPerformType());
}
if (row.getFundAmount() != null && row.getFundAmount().signum() < 0) {
@@ -42,6 +42,7 @@ 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.vo.ContractManageVO;
import org.springblade.transport.service.IContractExeStageService;
import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.support.hcs.HcsCreditCheckSupport;
@@ -76,6 +77,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private final HcsContractExeStageMapper hcsContractExeStageMapper;
private final HcsImmediatePushSupport hcsImmediatePushSupport;
private final HcsCreditCheckSupport hcsCreditCheckSupport;
private final IContractExeStageService contractExeStageService;
private static final String STAGE_DRAFT = "draft";
private static final String STAGE_TEMPORARY = "temporary";
@@ -236,6 +238,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
// HCS 推送:审批通过(含变更通过)置待同步状态,事务提交后即时推送,扫描任务保留兜底(ADR-0001 修订,前端接入设计 §6)
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
// 合同审批通过:履约规则未同步行统一置待同步标记,随合同链路调 C103-9(ADR-0002)
// 审批通过自动创建默认履约计划行(承运商=付款进度款/客户=收款进度款),已有规则行时不重复创建
contractExeStageService.createDefaultRowOnApprove(contractManage);
hcsContractExeStageMapper.update(null, Wrappers.<HcsContractExeStage>lambdaUpdate()
.eq(HcsContractExeStage::getContractId, contractManage.getId())
.isNull(HcsContractExeStage::getHcsStageFdId)
@@ -72,9 +72,9 @@ import java.util.Objects;
* <p>
* 扫描任务按序执行:⓪ 特批挂起门禁 → ① 客商风险校验(可配开关,仅留痕,高风险不再阻断)→
* ② 附件转传(v4 挂载点 fdFieldJson.attachment,随 C103-1 挂载)→ ③ C103-3 判重(§9-U4 查询能力联调确认)→
* ④ C103-1 起草 / 变更单 C103-11 更新(§9-U5 定位方式待联调)→ ⑤ 高风险/黑名单挂起"特批审核"
* (外部客商管控:报文随 fdSepcailContract=true 在合规侧走特批,{@link HcsContractSpecSyncSupport} 接收结果)→
* ⑥ 履约规则 C103-9 行级同步({@link HcsExeStageSyncSupport})。
* ④ C103-1 起草 / 变更单 C103-11 更新(§9-U5 定位方式待联调)→ ⑤ 履约计划 C103-9 行级同步({@link HcsExeStageSyncSupport},
* 仅依赖 hcs_fd_id,先于特批挂起执行,失败补偿不被特批门禁阻塞)→ ⑥ 高风险/黑名单挂起"特批审核"
* (外部客商管控:报文随 fdSepcailContract=true 在合规侧走特批,{@link HcsContractSpecSyncSupport} 接收结果)。
* 结果留痕:风险校验写 blade_hcs_check_log 与合同 hcs_risk_* 字段;推送结果回填 hcs_fd_id/hcs_sync_status。
*
* @author Chill
@@ -94,6 +94,11 @@ public class HcsContractSyncSupport {
*/
private static final String BLACK_STATUS_YES = "yes";
/**
* signer 相对方单位类型:暂统一传 customer(客户,2026-10-14 联调确认,§9-U6 后续细化)
*/
private static final String SIGNER_FD_TYPE_CUSTOMER = "customer";
private final IHcsClient hcsClient;
private final HcsProperties hcsProperties;
private final ContractManageMapper contractManageMapper;
@@ -105,7 +110,7 @@ public class HcsContractSyncSupport {
/**
* 推送合同到 HCS:特批挂起门禁 → 风险校验(仅留痕,高风险不再阻断)→ 附件转传 → 判重 → 起草/更新 → 回填合规 fdId 置 synced
* → 高风险/黑名单客商合同挂起"特批审核"等待合规特批结果(报文随 fdSepcailContract=true,外部客商管控)
* → 履约计划行级同步(C103-9,先于特批挂起)→ 高风险/黑名单客商合同挂起"特批审核"等待合规特批结果(报文随 fdSepcailContract=true,外部客商管控)
*
* @param contractManage 合同单(approvalStatus 须为 approved/change_approved)
* @return 推送结果描述(供任务汇总与 hcs_sync_msg 落库)
@@ -145,17 +150,17 @@ public class HcsContractSyncSupport {
markSynced(contractManage, contractManage.getHcsFdId(), "C103-11 合同更新成功");
resultMessage = "C103-11 合同更新成功";
}
// ⑤ 高风险/黑名单客商(外部客商管控):起草/更新报文已随 fdSepcailContract=true 在合规侧发起特批流程,
// ⑤ 履约规则 C103-9 行级同步(随合同链路,§5.3;仅依赖 hcs_fd_id 与特批无关,先于特批挂起执行——
// 失败抛出落 failed 时特批未挂起,修复后可直接重推补偿,不被特批门禁阻塞)
if (hcsProperties.getExeStage().isSyncEnabled()) {
resultMessage = resultMessage + ";" + hcsExeStageSyncSupport.syncExeStages(contractManage);
}
// ⑥ 高风险/黑名单客商(外部客商管控):起草/更新报文已随 fdSepcailContract=true 在合规侧发起特批流程,
// 此处挂起"特批审核"等待结果(合规推送 contractLegal/result 或定时 C103-4 查询联动恢复/驳回)
if (risky) {
hangSpecReview(contractManage);
}
// ⑥ 履约规则 C103-9 行级同步(随合同链路,§5.3;一期暂不对接默认关,打开后失败抛出由任务落 failed,下轮凭 hcs_fd_id 走更新补偿)
if (!hcsProperties.getExeStage().isSyncEnabled()) {
return resultMessage;
}
String exeStageResult = hcsExeStageSyncSupport.syncExeStages(contractManage);
return resultMessage + ";" + exeStageResult;
return resultMessage;
}
/**
@@ -361,7 +366,10 @@ public class HcsContractSyncSupport {
settlementType = HcsContractFieldConstant.SETTLEMENT_TYPE_COLLECTION;
}
if (StringUtil.isNotBlank(settlementType)) {
fields.add(field(HcsContractFieldConstant.FIELD_FD_SETTLEMENT_TYPE, "合同结算类型", settlementType));
HcsXformFieldDTO settlementField = field(HcsContractFieldConstant.FIELD_FD_SETTLEMENT_TYPE, "合同结算类型", settlementType);
// 字典类字段需带 fdIsSystem=true(合规侧按系统字典值解析,2026-10-14 联调确认)
settlementField.setFdIsSystem("true");
fields.add(settlementField);
} else {
log.warn("HCS合同结算类型无法确定:contractCategory=[{}] 非承运商合同/客户合同,fdSettlementType 不传", contractManage.getContractCategory());
}
@@ -409,7 +417,7 @@ public class HcsContractSyncSupport {
}
/**
* signer 相对方(签署方):甲/乙双方,fdType(inner/外部)判定规则待联调(§9-U6)暂不传
* signer 相对方(签署方):甲/乙双方,fdType 暂统一传 customer(客户,2026-10-14 联调确认)
*/
private List<HcsSignerDTO> buildSigners(ContractManage contractManage) {
List<HcsSignerDTO> signers = new ArrayList<>();
@@ -431,6 +439,8 @@ public class HcsContractSyncSupport {
signer.setFdName(partyName);
// 相对方编号:按名称反查 CustomerArchive 客商编码,反查不到留空(§9-U6)
signer.setFdNumber(queryCustomerCode(partyName));
// 单位类型:暂统一传 customer(客户),inner/外部判定规则后续联调细化(§9-U6)
signer.setFdType(SIGNER_FD_TYPE_CUSTOMER);
return signer;
}