diff --git a/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java b/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java index 3a8e11c..84ef586 100644 --- a/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java +++ b/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java @@ -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 { diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java b/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java index bd001ac..8fea330 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java @@ -133,6 +133,19 @@ public class ContractLaunchController { return R.success(contractLaunchService.copyDraft(id)); } + /** + * 基于现有合同发起变更/附加合同草稿。 + * + * 处理原则: + * 1. 复制现有业务内容 + * 2. 自动关联主合同编码 mainContractCode + * 3. 生成新的草稿,供业务在此基础上做变更补充 + */ + @PostMapping("/{id}/change") + public R 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 terminate(@PathVariable("id") Long id, + @RequestBody(required = false) Map 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)); + } + /** * 删除合同草稿(逻辑删除)。 * diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java index ce30df1..1251507 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java @@ -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); } diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java index 1951e7d..5912767 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java @@ -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 copyPartyList(List sourceList) { if (sourceList == null || sourceList.isEmpty()) { return new ArrayList<>(); diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/utils/ContractLaunchConstants.java b/src/main/java/com/nbport/zgwl/contractlaunch/utils/ContractLaunchConstants.java index cecfd59..a418d1f 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/utils/ContractLaunchConstants.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/utils/ContractLaunchConstants.java @@ -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"; diff --git a/src/main/java/com/nbport/zgwl/contractlog/controller/ContractLogController.java b/src/main/java/com/nbport/zgwl/contractlog/controller/ContractLogController.java new file mode 100644 index 0000000..8ba6dfb --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlog/controller/ContractLogController.java @@ -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> queryList(@RequestBody ContractLogQueryDto queryDto) { + return R.success(contractLogService.queryPage(queryDto)); + } + + @PostMapping("/all") + public R> queryAll(@RequestBody ContractLogQueryDto queryDto) { + return R.success(contractLogService.queryList(queryDto)); + } + + @PostMapping("/export/excel") + public ResponseEntity 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); + } +} diff --git a/src/main/java/com/nbport/zgwl/contractlog/dto/ContractLogDto.java b/src/main/java/com/nbport/zgwl/contractlog/dto/ContractLogDto.java new file mode 100644 index 0000000..629ca4f --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlog/dto/ContractLogDto.java @@ -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; +} diff --git a/src/main/java/com/nbport/zgwl/contractlog/dto/ContractLogQueryDto.java b/src/main/java/com/nbport/zgwl/contractlog/dto/ContractLogQueryDto.java new file mode 100644 index 0000000..46114e3 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlog/dto/ContractLogQueryDto.java @@ -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; +} diff --git a/src/main/java/com/nbport/zgwl/contractlog/mapper/ContractLogMapper.java b/src/main/java/com/nbport/zgwl/contractlog/mapper/ContractLogMapper.java new file mode 100644 index 0000000..5de2952 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlog/mapper/ContractLogMapper.java @@ -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 selectLogPage(Page page, @Param("query") ContractLogQueryDto queryDto); + + List selectLogList(@Param("query") ContractLogQueryDto queryDto); +} diff --git a/src/main/java/com/nbport/zgwl/contractlog/service/ContractLogService.java b/src/main/java/com/nbport/zgwl/contractlog/service/ContractLogService.java new file mode 100644 index 0000000..ed4d31f --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlog/service/ContractLogService.java @@ -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 queryPage(ContractLogQueryDto queryDto); + + List queryList(ContractLogQueryDto queryDto); + + byte[] exportExcel(ContractLogQueryDto queryDto); +} diff --git a/src/main/java/com/nbport/zgwl/contractlog/service/ContractLogServiceImpl.java b/src/main/java/com/nbport/zgwl/contractlog/service/ContractLogServiceImpl.java new file mode 100644 index 0000000..db2128a --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlog/service/ContractLogServiceImpl.java @@ -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 queryPage(ContractLogQueryDto queryDto) { + Page page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize()); + Page resultPage = (Page) contractLogMapper.selectLogPage(page, queryDto); + return new PageResult<>(resultPage); + } + + @Override + public List queryList(ContractLogQueryDto queryDto) { + return contractLogMapper.selectLogList(queryDto); + } + + @Override + public byte[] exportExcel(ContractLogQueryDto queryDto) { + List 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); + } + } +} diff --git a/src/main/resources/mapper/contractlog/ContractLogMapper.xml b/src/main/resources/mapper/contractlog/ContractLogMapper.xml new file mode 100644 index 0000000..f8cd8bc --- /dev/null +++ b/src/main/resources/mapper/contractlog/ContractLogMapper.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + from SEAL_CONTRACT_LAUNCH_LOG log + left join SEAL_CONTRACT_LAUNCH_MAIN main on main.ID = log.CONTRACT_ID + + + and log.CONTRACT_ID = #{query.contractId} + + + and main.CONTRACT_CODE like CONCAT(CONCAT('%', #{query.contractCode}), '%') + + + and main.CONTRACT_NAME like CONCAT(CONCAT('%', #{query.contractName}), '%') + + + and log.OPERATOR_NAME like CONCAT(CONCAT('%', #{query.operatorName}), '%') + + + and log.ACTION_TYPE = #{query.actionType} + + + and log.ACTION_STATUS = #{query.actionStatus} + + + and log.ACTION_TIME >= TO_DATE(#{query.dateStart} || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS') + + + and log.ACTION_TIME <= TO_DATE(#{query.dateEnd} || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS') + + + + + + + + +