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,101 @@
/**
* 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.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 合同履约规则实体类(HCS 履约阶段,设计文档 §4.1,ADR-0002)
* <p>
* 合同维度的资金计划阶段,C103-9 行级同步的数据源:合同审批通过置 row_op=add,
* 后续编辑按行差异置 add/update/delete,由扫描任务随合同链路同步;
* 合规返回的阶段ID 必须持久化(hcsStageFdId),行更新/删除全靠它。
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("blade_contract_exe_stage")
@Schema(description = "合同履约规则(HCS 履约阶段)")
public class HcsContractExeStage extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "合同ID")
private Long contractId;
@Schema(description = "履约类型 payment=付款/collection=收款")
private String performType;
@Schema(description = "款项性质 prepayment/progress/acceptance/retention")
private String fundNature;
@Schema(description = "计划金额(元)")
private BigDecimal fundAmount;
@Schema(description = "计划资金日期")
private LocalDateTime fundDate;
@Schema(description = "开票规则(如 payFirst)")
private String invoiceRule;
@Schema(description = "账期")
private Integer invoiceCycle;
@Schema(description = "金额类型")
private String fundType;
@Schema(description = "是否完成")
private Integer isFinish;
@Schema(description = "行级同步操作 none/add/update/delete")
private String rowOp;
@Schema(description = "合规阶段ID(C103-9 返回,update/delete 必用)")
private String hcsStageFdId;
@Schema(description = "同步状态 unsynced/synced/failed")
private String hcsSyncStatus;
@Schema(description = "同步结果信息/失败原因")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private String hcsSyncMsg;
}
@@ -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);
}
@@ -0,0 +1,36 @@
-- 桂物合同合规系统(HCS)接入:履约规则表(设计文档 §4.1,ADR-0002,P2)。
-- 合同维度资金计划阶段,C103-9 行级同步的数据源;行删除为软删+标记,不可物理删除。
-- 通过 information_schema 判断表是否存在,可重复执行。
SET @db_name = DATABASE();
SET @sql = IF(
EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = @db_name AND table_name = 'blade_contract_exe_stage'),
'SELECT 1',
'CREATE TABLE blade_contract_exe_stage (
id bigint NOT NULL COMMENT ''主键'',
contract_id bigint NOT NULL COMMENT ''合同ID(blade_contract_manage.id)'',
perform_type varchar(16) NOT NULL COMMENT ''履约类型 payment=付款/collection=收款'',
fund_nature varchar(16) DEFAULT NULL COMMENT ''款项性质 prepayment/progress/acceptance/retention'',
fund_amount decimal(18,2) DEFAULT NULL COMMENT ''计划金额(元)'',
fund_date datetime DEFAULT NULL COMMENT ''计划资金日期'',
invoice_rule varchar(16) DEFAULT NULL COMMENT ''开票规则(如 payFirst)'',
invoice_cycle int DEFAULT NULL COMMENT ''账期'',
fund_type varchar(16) DEFAULT ''byAmount'' COMMENT ''金额类型'',
is_finish tinyint DEFAULT 0 COMMENT ''是否完成'',
row_op varchar(8) DEFAULT ''none'' COMMENT ''行级同步操作 none/add/update/delete'',
hcs_stage_fd_id varchar(64) DEFAULT NULL COMMENT ''合规阶段ID(C103-9 返回,update/delete 必用)'',
hcs_sync_status varchar(16) DEFAULT ''unsynced'' COMMENT ''同步状态 unsynced/synced/failed'',
hcs_sync_msg varchar(512) DEFAULT NULL COMMENT ''同步结果信息/失败原因'',
tenant_id varchar(12) DEFAULT ''000000'' COMMENT ''租户ID'',
create_user bigint DEFAULT NULL COMMENT ''创建人'',
create_dept bigint DEFAULT NULL COMMENT ''创建部门'',
create_time datetime DEFAULT NULL COMMENT ''创建时间'',
update_user bigint DEFAULT NULL COMMENT ''更新人'',
update_time datetime DEFAULT NULL COMMENT ''更新时间'',
status int DEFAULT 1 COMMENT ''业务状态'',
is_deleted int DEFAULT 0 COMMENT ''是否已删除'',
PRIMARY KEY (id),
KEY idx_hcs_exe_stage_contract (contract_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''合同履约规则(HCS 履约阶段)'''
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;