功能修改

This commit is contained in:
2026-06-01 16:06:42 +08:00
parent 69f904b23c
commit bed00bd3e3
12 changed files with 375 additions and 1 deletions
@@ -8,7 +8,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
"com.nbport.zgwl.mapper",
"com.nbport.zgwl.partner.mapper",
"com.nbport.zgwl.manifest.mapper",
"com.nbport.zgwl.contractlaunch.mapper"
"com.nbport.zgwl.contractlaunch.mapper",
"com.nbport.zgwl.contractlog.mapper"
})
@SpringBootApplication
public class ZgwlEndApplication {
@@ -133,6 +133,19 @@ public class ContractLaunchController {
return R.success(contractLaunchService.copyDraft(id));
}
/**
* 基于现有合同发起变更/附加合同草稿。
*
* 处理原则:
* 1. 复制现有业务内容
* 2. 自动关联主合同编码 mainContractCode
* 3. 生成新的草稿,供业务在此基础上做变更补充
*/
@PostMapping("/{id}/change")
public R<ContractLaunchDto> createChangeDraft(@PathVariable("id") Long id) {
return R.success(contractLaunchService.createChangeDraft(id));
}
/**
* 提交法务审批。
*
@@ -283,6 +296,20 @@ public class ContractLaunchController {
return R.success(contractLaunchService.archive(id, archiveDto));
}
/**
* 合同终止。
*
* 允许除 completed 外的任意环节执行终止。
* 当前仅在本系统内变更状态并记录日志,法务系统终止需后续分别操作。
*/
@PostMapping("/{id}/terminate")
public R<ContractLaunchDto> terminate(@PathVariable("id") Long id,
@RequestBody(required = false) Map<String, Object> payload) {
Object reasonValue = payload == null ? null : payload.get("terminateReason");
String terminateReason = reasonValue == null ? null : String.valueOf(reasonValue).trim();
return R.success(contractLaunchService.terminate(id, terminateReason));
}
/**
* 删除合同草稿(逻辑删除)。
*
@@ -26,6 +26,9 @@ public interface ContractLaunchService {
/** 复制合同并生成新的草稿 */
ContractLaunchDto copyDraft(Long id);
/** 基于现有合同发起变更/附加合同草稿 */
ContractLaunchDto createChangeDraft(Long id);
/** 提交法务审批(调用结算平台 pushContract 接口) */
ContractLaunchDto submitToLaw(Long id);
@@ -50,6 +53,9 @@ public interface ContractLaunchService {
/** 合同归档(状态变更为已完成,TODO:同时推送归档到法务) */
ContractLaunchDto archive(Long id, ContractLaunchArchiveDto archiveDto);
/** 合同终止 */
ContractLaunchDto terminate(Long id, String terminateReason);
/** 删除草稿(仅 draft/rejected 状态允许) */
void deleteDraft(Long id);
}
@@ -158,6 +158,20 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
return persistDraft(copiedContract, currentUser, true, ContractLaunchConstants.ACTION_COPY, "复制合同", "合同复制成功,已生成新草稿");
}
@Override
@Transactional(rollbackFor = Exception.class)
public ContractLaunchDto createChangeDraft(Long id) {
ContractLaunchDto source = getDetail(id);
SysUserEntity currentUser = adminSecurityManage.getUser();
ContractLaunchDto changedContract = buildCopiedDraft(source);
changedContract.setMainContractCode(source.getContractCode());
changedContract.setContractName(buildChangedContractName(source.getContractName()));
changedContract.setIsAddit(1);
normalizeContract(changedContract);
validateDraft(changedContract);
return persistDraft(changedContract, currentUser, true, ContractLaunchConstants.ACTION_CHANGE, "发起变更合同", "已基于原合同生成变更草稿");
}
/**
* 提交法务审批。
*
@@ -481,6 +495,33 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
return getDetail(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ContractLaunchDto terminate(Long id, String terminateReason) {
ContractLaunchDto contract = getDetail(id);
if (ContractLaunchConstants.STATUS_COMPLETED.equals(contract.getStatus())) {
throw new ServiceException("已完成合同不允许终止");
}
if (ContractLaunchConstants.STATUS_TERMINATED.equals(contract.getStatus())) {
throw new ServiceException("合同已终止,请勿重复操作");
}
SysUserEntity currentUser = adminSecurityManage.getUser();
contract.setStatus(ContractLaunchConstants.STATUS_TERMINATED);
contract.setIsTerminated("Y");
contract.setApproveMessage(terminateReason == null || terminateReason.isBlank() ? "合同已终止" : terminateReason);
contract.setUpdateBy(currentUser.getUsername());
contract.setUpdateTime(LocalDateTime.now());
contractLaunchMapper.updateContract(contract);
insertLog(id,
ContractLaunchConstants.ACTION_TERMINATE,
"终止合同",
ContractLaunchConstants.ACTION_STATUS_SUCCESS,
contract.getApproveMessage(),
null,
currentUser);
return getDetail(id);
}
/**
* 删除合同草稿(逻辑删除)。
*
@@ -613,6 +654,17 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
return baseName + "-副本";
}
private String buildChangedContractName(String sourceName) {
String baseName = trim(sourceName);
if (baseName == null || baseName.isBlank()) {
return "变更合同";
}
if (baseName.endsWith("-变更")) {
return baseName;
}
return baseName + "-变更";
}
private List<ContractLaunchPartyDto> copyPartyList(List<ContractLaunchPartyDto> sourceList) {
if (sourceList == null || sourceList.isEmpty()) {
return new ArrayList<>();
@@ -42,6 +42,8 @@ public final class ContractLaunchConstants {
public static final String ACTION_ARCHIVE = "archive";
public static final String ACTION_DELETE = "delete";
public static final String ACTION_COPY = "copy";
public static final String ACTION_TERMINATE = "terminate";
public static final String ACTION_CHANGE = "change";
public static final String ACTION_STATUS_SUCCESS = "success";
public static final String ACTION_STATUS_FAILED = "failed";
@@ -0,0 +1,49 @@
package com.nbport.zgwl.contractlog.controller;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.common.R;
import com.nbport.zgwl.contractlog.dto.ContractLogDto;
import com.nbport.zgwl.contractlog.dto.ContractLogQueryDto;
import com.nbport.zgwl.contractlog.service.ContractLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.RestController;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/contract/log")
public class ContractLogController {
private final ContractLogService contractLogService;
@PostMapping("/list")
public R<PageResult<ContractLogDto>> queryList(@RequestBody ContractLogQueryDto queryDto) {
return R.success(contractLogService.queryPage(queryDto));
}
@PostMapping("/all")
public R<List<ContractLogDto>> queryAll(@RequestBody ContractLogQueryDto queryDto) {
return R.success(contractLogService.queryList(queryDto));
}
@PostMapping("/export/excel")
public ResponseEntity<byte[]> exportExcel(@RequestBody ContractLogQueryDto queryDto) {
byte[] fileBytes = contractLogService.exportExcel(queryDto);
String fileName = "合同日志_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8))
.body(fileBytes);
}
}
@@ -0,0 +1,59 @@
package com.nbport.zgwl.contractlog.dto;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class ContractLogDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("日志ID")
private Long id;
@ExcelProperty("合同ID")
private Long contractId;
@ExcelProperty("合同编码")
private String contractCode;
@ExcelProperty("合同名称")
private String contractName;
@ExcelProperty("合同当前状态")
private String contractStatus;
@ExcelProperty("操作类型")
private String actionType;
@ExcelProperty("操作名称")
private String actionName;
@ExcelProperty("操作结果")
private String actionStatus;
@ExcelProperty("操作人")
private String operatorName;
@ExcelProperty("操作人手机号")
private String operatorPhone;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@ExcelProperty("操作时间")
private LocalDateTime actionTime;
@ExcelProperty("操作内容")
private String actionContent;
@ExcelProperty("原始报文")
private String callbackPayload;
@ExcelProperty("备注")
private String remark;
}
@@ -0,0 +1,30 @@
package com.nbport.zgwl.contractlog.dto;
import com.nbport.zgwl.dto.BasePageDto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class ContractLogQueryDto extends BasePageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long contractId;
private String contractCode;
private String contractName;
private String operatorName;
private String actionType;
private String actionStatus;
private String dateStart;
private String dateEnd;
}
@@ -0,0 +1,16 @@
package com.nbport.zgwl.contractlog.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.contractlog.dto.ContractLogDto;
import com.nbport.zgwl.contractlog.dto.ContractLogQueryDto;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ContractLogMapper {
IPage<ContractLogDto> selectLogPage(Page<ContractLogDto> page, @Param("query") ContractLogQueryDto queryDto);
List<ContractLogDto> selectLogList(@Param("query") ContractLogQueryDto queryDto);
}
@@ -0,0 +1,16 @@
package com.nbport.zgwl.contractlog.service;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.contractlog.dto.ContractLogDto;
import com.nbport.zgwl.contractlog.dto.ContractLogQueryDto;
import java.util.List;
public interface ContractLogService {
PageResult<ContractLogDto> queryPage(ContractLogQueryDto queryDto);
List<ContractLogDto> queryList(ContractLogQueryDto queryDto);
byte[] exportExcel(ContractLogQueryDto queryDto);
}
@@ -0,0 +1,47 @@
package com.nbport.zgwl.contractlog.service;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nbport.zgwl.common.PageResult;
import com.nbport.zgwl.contractlog.dto.ContractLogDto;
import com.nbport.zgwl.contractlog.dto.ContractLogQueryDto;
import com.nbport.zgwl.contractlog.mapper.ContractLogMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ContractLogServiceImpl implements ContractLogService {
private final ContractLogMapper contractLogMapper;
@Override
public PageResult<ContractLogDto> queryPage(ContractLogQueryDto queryDto) {
Page<ContractLogDto> page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize());
Page<ContractLogDto> resultPage = (Page<ContractLogDto>) contractLogMapper.selectLogPage(page, queryDto);
return new PageResult<>(resultPage);
}
@Override
public List<ContractLogDto> queryList(ContractLogQueryDto queryDto) {
return contractLogMapper.selectLogList(queryDto);
}
@Override
public byte[] exportExcel(ContractLogQueryDto queryDto) {
List<ContractLogDto> list = queryList(queryDto);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
EasyExcel.write(outputStream, ContractLogDto.class)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet("合同日志")
.doWrite(list);
return outputStream.toByteArray();
} catch (Exception ex) {
throw new RuntimeException("导出日志失败", ex);
}
}
}
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nbport.zgwl.contractlog.mapper.ContractLogMapper">
<resultMap id="ContractLogResult" type="com.nbport.zgwl.contractlog.dto.ContractLogDto">
<id property="id" column="ID"/>
<result property="contractId" column="CONTRACT_ID"/>
<result property="contractCode" column="CONTRACT_CODE"/>
<result property="contractName" column="CONTRACT_NAME"/>
<result property="contractStatus" column="CONTRACT_STATUS"/>
<result property="actionType" column="ACTION_TYPE"/>
<result property="actionName" column="ACTION_NAME"/>
<result property="actionStatus" column="ACTION_STATUS"/>
<result property="operatorName" column="OPERATOR_NAME"/>
<result property="operatorPhone" column="OPERATOR_PHONE"/>
<result property="actionTime" column="ACTION_TIME"/>
<result property="actionContent" column="ACTION_CONTENT"/>
<result property="callbackPayload" column="CALLBACK_PAYLOAD"/>
<result property="remark" column="REMARK"/>
</resultMap>
<sql id="logQuerySql">
from SEAL_CONTRACT_LAUNCH_LOG log
left join SEAL_CONTRACT_LAUNCH_MAIN main on main.ID = log.CONTRACT_ID
<where>
<if test="query.contractId != null">
and log.CONTRACT_ID = #{query.contractId}
</if>
<if test="query.contractCode != null and query.contractCode != ''">
and main.CONTRACT_CODE like CONCAT(CONCAT('%', #{query.contractCode}), '%')
</if>
<if test="query.contractName != null and query.contractName != ''">
and main.CONTRACT_NAME like CONCAT(CONCAT('%', #{query.contractName}), '%')
</if>
<if test="query.operatorName != null and query.operatorName != ''">
and log.OPERATOR_NAME like CONCAT(CONCAT('%', #{query.operatorName}), '%')
</if>
<if test="query.actionType != null and query.actionType != ''">
and log.ACTION_TYPE = #{query.actionType}
</if>
<if test="query.actionStatus != null and query.actionStatus != ''">
and log.ACTION_STATUS = #{query.actionStatus}
</if>
<if test="query.dateStart != null and query.dateStart != ''">
and log.ACTION_TIME >= TO_DATE(#{query.dateStart} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
</if>
<if test="query.dateEnd != null and query.dateEnd != ''">
and log.ACTION_TIME &lt;= TO_DATE(#{query.dateEnd} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
</if>
</where>
</sql>
<select id="selectLogPage" resultMap="ContractLogResult">
select log.ID, log.CONTRACT_ID, main.CONTRACT_CODE, main.CONTRACT_NAME, main.STATUS as CONTRACT_STATUS,
log.ACTION_TYPE, log.ACTION_NAME, log.ACTION_STATUS, log.OPERATOR_NAME, log.OPERATOR_PHONE,
log.ACTION_TIME, log.ACTION_CONTENT, log.CALLBACK_PAYLOAD, log.REMARK
<include refid="logQuerySql"/>
order by log.ACTION_TIME desc, log.ID desc
</select>
<select id="selectLogList" resultMap="ContractLogResult">
select log.ID, log.CONTRACT_ID, main.CONTRACT_CODE, main.CONTRACT_NAME, main.STATUS as CONTRACT_STATUS,
log.ACTION_TYPE, log.ACTION_NAME, log.ACTION_STATUS, log.OPERATOR_NAME, log.OPERATOR_PHONE,
log.ACTION_TIME, log.ACTION_CONTENT, log.CALLBACK_PAYLOAD, log.REMARK
<include refid="logQuerySql"/>
order by log.ACTION_TIME desc, log.ID desc
</select>
</mapper>