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 new file mode 100644 index 00000000..95bdb385 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/HcsContractExeStage.java @@ -0,0 +1,101 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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) + *

+ * 合同维度的资金计划阶段,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; + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractExeStageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractExeStageController.java new file mode 100644 index 00000000..17eae220 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ContractExeStageController.java @@ -0,0 +1,91 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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) + *

+ * 行级差异(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(@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 submit(@Parameter(description = "合同ID", required = true) @RequestParam Long contractId, + @RequestBody List rows) { + return R.status(contractExeStageService.submitRows(contractId, rows)); + } + + /** + * 删除履约规则行(软删+标记,已同步行待 C103-9 上报删除) + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 3) + @Operation(summary = "删除履约规则行", description = "传入规则行ID,软删并标记 delete 待同步") + public R remove(@Parameter(description = "规则行ID", required = true) @RequestParam Long id) { + return R.status(contractExeStageService.removeRow(id)); + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/HcsContractExeStageMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/HcsContractExeStageMapper.java new file mode 100644 index 00000000..c2021347 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/HcsContractExeStageMapper.java @@ -0,0 +1,53 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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 { + + /** + * 查询合同的待同步行(含软删行: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 selectSyncRows(@Param("contractId") Long contractId); +} 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 new file mode 100644 index 00000000..15133197 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IContractExeStageService.java @@ -0,0 +1,64 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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 { + + /** + * 按合同查询履约规则行(表单维护用,不含软删行) + * + * @param contractId 合同ID + * @return 规则行列表 + */ + List listByContractId(Long contractId); + + /** + * 保存合同履约规则行(按行差异置同步标记:新行/未同步行 add,已同步行 update) + * + * @param contractId 合同ID + * @param rows 规则行 + * @return 保存结果 + */ + boolean submitRows(Long contractId, List rows); + + /** + * 删除履约规则行(软删+标记:已同步行置 row_op=delete 待 C103-9 上报;未同步行直接软删) + * + * @param id 规则行ID + * @return 删除结果 + */ + boolean removeRow(Long id); +} 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 new file mode 100644 index 00000000..08f0cc06 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractExeStageServiceImpl.java @@ -0,0 +1,154 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * 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) + *

+ * 行级差异标记:新行/未同步行 add;已同步行编辑 update;已同步行删除 delete(软删+标记,不可物理删除)。 + * 行级同步由扫描任务随合同链路调 C103-9 完成(HcsExeStageSyncSupport)。 + * + * @author Chill + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ContractExeStageServiceImpl extends BaseServiceImpl implements IContractExeStageService { + + /** + * 行级同步操作:无 + */ + private static final String ROW_OP_NONE = "none"; + + private final ContractManageMapper contractManageMapper; + private final HcsProperties hcsProperties; + + @Override + public List listByContractId(Long contractId) { + return list(Wrappers.lambdaQuery() + .eq(HcsContractExeStage::getContractId, contractId) + .orderByAsc(HcsContractExeStage::getId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean submitRows(Long contractId, List 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("计划金额不能为负数"); + } + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java index b6cf8840..41cc4285 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java @@ -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 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 BaseServiceImpllambdaUpdate() + .eq(HcsContractExeStage::getContractId, contractManage.getId()) + .isNull(HcsContractExeStage::getHcsStageFdId) + .set(HcsContractExeStage::getRowOp, "add")); return updateById(contractManage); } diff --git a/doc/sql/transport/blade_hcs_exe_stage_20260927.sql b/doc/sql/transport/blade_hcs_exe_stage_20260927.sql new file mode 100644 index 00000000..97b3b811 --- /dev/null +++ b/doc/sql/transport/blade_hcs_exe_stage_20260927.sql @@ -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;