feat: HCS履约规则数据层与表单维护(blade_contract_exe_stage表/行级差异标记维护接口/合同审批置行待同步)

This commit is contained in:
刘泉佳
2026-09-27 23:27:29 +08:00
parent eaab17b220
commit 892035ee6f
7 changed files with 512 additions and 0 deletions
@@ -0,0 +1,91 @@
/**
* 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.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.api.R;
import org.springblade.transport.pojo.entity.HcsContractExeStage;
import org.springblade.transport.service.IContractExeStageService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 合同履约规则维护端点(合同表单"履约规则"页签,设计文档 §4.1,ADR-0002)
* <p>
* 行级差异(add/update/delete)由本端点落库标记,行级同步由扫描任务随合同链路调 C103-9 完成;
* 行删除为软删+标记,不可物理删除。
*
* @author Chill
*/
@RestController
@AllArgsConstructor
@RequestMapping("contract-exe-stage")
@Tag(name = "合同履约规则", description = "合同履约规则维护(HCS 履约阶段同步数据源)")
public class ContractExeStageController {
private final IContractExeStageService contractExeStageService;
/**
* 按合同查询履约规则行
*/
@GetMapping("/list")
@ApiOperationSupport(order = 1)
@Operation(summary = "按合同查询履约规则", description = "传入合同ID,返回规则行列表")
public R<List<HcsContractExeStage>> list(@Parameter(description = "合同ID", required = true) @RequestParam Long contractId) {
return R.data(contractExeStageService.listByContractId(contractId));
}
/**
* 保存履约规则行(整批提交,按行差异置同步标记)
*/
@PostMapping("/submit")
@ApiOperationSupport(order = 2)
@Operation(summary = "保存履约规则", description = "传入合同ID与规则行列表,新行/未同步行标记 add、已同步行标记 update")
public R<Boolean> submit(@Parameter(description = "合同ID", required = true) @RequestParam Long contractId,
@RequestBody List<HcsContractExeStage> rows) {
return R.status(contractExeStageService.submitRows(contractId, rows));
}
/**
* 删除履约规则行(软删+标记,已同步行待 C103-9 上报删除)
*/
@PostMapping("/remove")
@ApiOperationSupport(order = 3)
@Operation(summary = "删除履约规则行", description = "传入规则行ID,软删并标记 delete 待同步")
public R<Boolean> remove(@Parameter(description = "规则行ID", required = true) @RequestParam Long id) {
return R.status(contractExeStageService.removeRow(id));
}
}
@@ -0,0 +1,53 @@
/**
* 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.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springblade.transport.pojo.entity.HcsContractExeStage;
import java.util.List;
/**
* 合同履约规则 Mapper
*
* @author Chill
*/
@Mapper
public interface HcsContractExeStageMapper extends BaseMapper<HcsContractExeStage> {
/**
* 查询合同的待同步行(含软删行:C103-9 的 delete 行已逻辑删除但须随行级同步上报,
* 原生 SQL 绕过 @TableLogic 过滤)
*
* @param contractId 合同ID
* @return 行级同步操作非 none 的规则行
*/
@Select("select * from blade_contract_exe_stage where contract_id = #{contractId} and row_op <> 'none' order by id")
List<HcsContractExeStage> selectSyncRows(@Param("contractId") Long contractId);
}
@@ -0,0 +1,64 @@
/**
* 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.service;
import org.springblade.core.mp.base.BaseService;
import org.springblade.transport.pojo.entity.HcsContractExeStage;
import java.util.List;
/**
* 合同履约规则 服务接口
*
* @author Chill
*/
public interface IContractExeStageService extends BaseService<HcsContractExeStage> {
/**
* 按合同查询履约规则行(表单维护用,不含软删行)
*
* @param contractId 合同ID
* @return 规则行列表
*/
List<HcsContractExeStage> listByContractId(Long contractId);
/**
* 保存合同履约规则行(按行差异置同步标记:新行/未同步行 add,已同步行 update)
*
* @param contractId 合同ID
* @param rows 规则行
* @return 保存结果
*/
boolean submitRows(Long contractId, List<HcsContractExeStage> rows);
/**
* 删除履约规则行(软删+标记:已同步行置 row_op=delete 待 C103-9 上报;未同步行直接软删)
*
* @param id 规则行ID
* @return 删除结果
*/
boolean removeRow(Long id);
}
@@ -0,0 +1,154 @@
/**
* 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.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.hcs.config.HcsProperties;
import org.springblade.transport.constant.HcsSyncConstant;
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.service.IContractExeStageService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 合同履约规则 服务实现类(设计文档 §4.1,ADR-0002)
* <p>
* 行级差异标记:新行/未同步行 add;已同步行编辑 update;已同步行删除 delete(软删+标记,不可物理删除)。
* 行级同步由扫描任务随合同链路调 C103-9 完成(HcsExeStageSyncSupport)。
*
* @author Chill
*/
@Slf4j
@Service
@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;
@Override
public List<HcsContractExeStage> listByContractId(Long contractId) {
return list(Wrappers.<HcsContractExeStage>lambdaQuery()
.eq(HcsContractExeStage::getContractId, contractId)
.orderByAsc(HcsContractExeStage::getId));
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean submitRows(Long contractId, List<HcsContractExeStage> rows) {
ContractManage contractManage = contractManageMapper.selectById(contractId);
if (contractManage == null) {
throw new ServiceException("合同不存在:" + contractId);
}
if (Func.isEmpty(rows)) {
throw new ServiceException("履约规则行不能为空");
}
for (HcsContractExeStage row : rows) {
validateRow(row);
row.setContractId(contractId);
if (row.getId() == null) {
// 新行:待随合同链路首推(row_op=add)
row.setRowOp("add");
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
row.setHcsSyncMsg("待随合同链路同步(C103-9)");
row.setFundType(StringUtil.isBlank(row.getFundType()) ? hcsProperties.getExeStage().getFundType() : row.getFundType());
row.setIsFinish(row.getIsFinish() == null ? 0 : row.getIsFinish());
save(row);
} else {
HcsContractExeStage existing = getById(row.getId());
if (existing == null || !contractId.equals(existing.getContractId())) {
throw new ServiceException("履约规则行不属于该合同:" + row.getId());
}
// 已同步行编辑 → update;未同步行保持 add
String rowOp = StringUtil.isBlank(existing.getHcsStageFdId()) ? "add" : "update";
existing.setPerformType(row.getPerformType());
existing.setFundNature(row.getFundNature());
existing.setFundAmount(row.getFundAmount());
existing.setFundDate(row.getFundDate());
existing.setInvoiceRule(row.getInvoiceRule());
existing.setInvoiceCycle(row.getInvoiceCycle());
existing.setFundType(StringUtil.isBlank(row.getFundType()) ? hcsProperties.getExeStage().getFundType() : row.getFundType());
existing.setIsFinish(row.getIsFinish() == null ? 0 : row.getIsFinish());
existing.setRowOp(rowOp);
existing.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
existing.setHcsSyncMsg("待随合同链路同步(C103-9 " + rowOp + ")");
updateById(existing);
}
}
log.info("合同履约规则保存:contractId={},共 {} 行", contractId, rows.size());
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeRow(Long id) {
HcsContractExeStage row = getById(id);
if (row == null) {
throw new ServiceException("履约规则行不存在:" + id);
}
if (StringUtil.isNotBlank(row.getHcsStageFdId())) {
// 已同步行:软删+标记 delete,由 C103-9 凭合规阶段ID 上报删除(ADR-0002,不可物理删除)
row.setRowOp("delete");
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
row.setHcsSyncMsg("待随合同链路同步(C103-9 delete)");
updateById(row);
}
removeById(id);
log.info("合同履约规则行删除:id={},contractId={},rowOp={}", id, row.getContractId(), row.getRowOp());
return true;
}
/**
* 行字段校验
*/
private void validateRow(HcsContractExeStage row) {
if (StringUtil.isBlank(row.getPerformType())) {
throw new ServiceException("履约类型不能为空(payment/collection)");
}
if (!List.of("payment", "collection").contains(row.getPerformType())) {
throw new ServiceException("履约类型非法:" + row.getPerformType());
}
if (row.getFundAmount() != null && row.getFundAmount().signum() < 0) {
throw new ServiceException("计划金额不能为负数");
}
}
}
@@ -25,6 +25,8 @@ package org.springblade.transport.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
@@ -36,7 +38,9 @@ import org.springblade.system.cache.UserCache;
import org.springblade.transport.constant.HcsSyncConstant;
import org.springblade.transport.excel.ContractManageExcel;
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.vo.ContractManageVO;
import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.support.TransportBusinessSupport;
@@ -62,9 +66,13 @@ import java.util.Set;
*
* @author Chill
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMapper, ContractManage> implements IContractManageService {
private final HcsContractExeStageMapper hcsContractExeStageMapper;
private static final String STAGE_DRAFT = "draft";
private static final String STAGE_TEMPORARY = "temporary";
private static final String STAGE_FORMAL = "formal";
@@ -217,6 +225,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
updateLatestReviewingChangeRecord(contractManage, contractManage.getApprovalStatus(), "审核通过");
// HCS 推送零埋点:审批通过(含变更通过)仅置待同步状态,由定时扫描任务准实时首推(ADR-0001)
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
// 合同审批通过:履约规则未同步行统一置待同步标记,随合同链路调 C103-9(ADR-0002)
hcsContractExeStageMapper.update(null, Wrappers.<HcsContractExeStage>lambdaUpdate()
.eq(HcsContractExeStage::getContractId, contractManage.getId())
.isNull(HcsContractExeStage::getHcsStageFdId)
.set(HcsContractExeStage::getRowOp, "add"));
return updateById(contractManage);
}