|
|
@@ -0,0 +1,296 @@
|
|
|
+package com.usky.vpp.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.usky.common.core.bean.CommonPage;
|
|
|
+import com.usky.common.core.exception.BusinessException;
|
|
|
+import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.vpp.domain.VppContractTemplate;
|
|
|
+import com.usky.vpp.mapper.VppContractTemplateMapper;
|
|
|
+import com.usky.vpp.service.VppContractTemplateService;
|
|
|
+import com.usky.vpp.service.vo.VppContractTemplateRequestVO;
|
|
|
+import com.usky.vpp.service.vo.VppContractTemplateResponseVO;
|
|
|
+import com.usky.vpp.util.VppFieldValidator;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import javax.servlet.ServletOutputStream;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * VppContractTemplateService 默认实现
|
|
|
+ * @author fyc
|
|
|
+ * @email yuchuan.fu@chinausky.com
|
|
|
+ * @date 2026/7/4
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class VppContractTemplateServiceImpl implements VppContractTemplateService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private VppContractTemplateMapper vppContractTemplateMapper;
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate = new RestTemplate();
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean create(VppContractTemplateRequestVO vo) {
|
|
|
+ String username = SecurityUtils.getUsername();
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+
|
|
|
+ // ========== templateCode 格式校验 ==========
|
|
|
+ VppFieldValidator.validateTemplateCode(vo.getTemplateCode());
|
|
|
+
|
|
|
+ // ========== templateCode 全租户唯一性校验 ==========
|
|
|
+ checkTemplateCodeUnique(vo.getTemplateCode(), null, tenantId);
|
|
|
+
|
|
|
+ VppContractTemplate template = new VppContractTemplate();
|
|
|
+ template.setTemplateCode(vo.getTemplateCode());
|
|
|
+ template.setTemplateName(vo.getTemplateName());
|
|
|
+ template.setContractType(vo.getContractType());
|
|
|
+ template.setVersion("V1.0");
|
|
|
+ template.setFileUrl(vo.getFileUrl());
|
|
|
+ template.setVariablesJson(VppFieldValidator.validateJson(vo.getVariablesJson(), "占位符变量定义"));
|
|
|
+ template.setIsEnabled(vo.getIsEnabled() != null ? vo.getIsEnabled() : 1);
|
|
|
+ template.setCreateTime(LocalDateTime.now());
|
|
|
+ template.setCreatedBy(username);
|
|
|
+ template.setTenantId(tenantId);
|
|
|
+ template.setDeleteFlag(0);
|
|
|
+
|
|
|
+ int insert = vppContractTemplateMapper.insert(template);
|
|
|
+ return insert > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean update(VppContractTemplateRequestVO vo) {
|
|
|
+ if (vo.getId() == null) {
|
|
|
+ throw new BusinessException("主键ID不能为空!");
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppContractTemplate::getId, vo.getId())
|
|
|
+ .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId())
|
|
|
+ .eq(VppContractTemplate::getDeleteFlag, 0);
|
|
|
+ VppContractTemplate existingRecord = vppContractTemplateMapper.selectOne(queryWrapper);
|
|
|
+
|
|
|
+ if (existingRecord == null) {
|
|
|
+ throw new BusinessException("合同模板不存在或已被删除!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仅更新非空字段,追踪是否仅变更 isEnabled
|
|
|
+ boolean hasNonEnabledChange = false;
|
|
|
+
|
|
|
+ if (StringUtils.isNotBlank(vo.getTemplateCode())) {
|
|
|
+ // ========== templateCode 格式校验 ==========
|
|
|
+ VppFieldValidator.validateTemplateCode(vo.getTemplateCode());
|
|
|
+
|
|
|
+ // ========== 仅当与现有值不同时做唯一性校验 ==========
|
|
|
+ if (!vo.getTemplateCode().equals(existingRecord.getTemplateCode())) {
|
|
|
+ checkTemplateCodeUnique(vo.getTemplateCode(), vo.getId(), SecurityUtils.getTenantId());
|
|
|
+ }
|
|
|
+
|
|
|
+ existingRecord.setTemplateCode(vo.getTemplateCode());
|
|
|
+ hasNonEnabledChange = true;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(vo.getTemplateName())) {
|
|
|
+ existingRecord.setTemplateName(vo.getTemplateName());
|
|
|
+ hasNonEnabledChange = true;
|
|
|
+ }
|
|
|
+ if (vo.getContractType() != null) {
|
|
|
+ existingRecord.setContractType(vo.getContractType());
|
|
|
+ hasNonEnabledChange = true;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(vo.getFileUrl())) {
|
|
|
+ existingRecord.setFileUrl(vo.getFileUrl());
|
|
|
+ hasNonEnabledChange = true;
|
|
|
+ }
|
|
|
+ if (vo.getVariablesJson() != null) {
|
|
|
+ existingRecord.setVariablesJson(VppFieldValidator.validateJson(vo.getVariablesJson(), "占位符变量定义"));
|
|
|
+ hasNonEnabledChange = true;
|
|
|
+ }
|
|
|
+ // isEnabled 单独变更不触发版本号累加
|
|
|
+ if (vo.getIsEnabled() != null) {
|
|
|
+ existingRecord.setIsEnabled(vo.getIsEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 版本号自动累加:仅当非 isEnabled 字段有变更时 +0.1
|
|
|
+ if (hasNonEnabledChange) {
|
|
|
+ String currentVersion = existingRecord.getVersion();
|
|
|
+ if (StringUtils.isNotBlank(currentVersion) && currentVersion.startsWith("V")) {
|
|
|
+ try {
|
|
|
+ double versionNum = Double.parseDouble(currentVersion.substring(1));
|
|
|
+ versionNum += 0.1;
|
|
|
+ existingRecord.setVersion("V" + String.format("%.1f", versionNum));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ existingRecord.setVersion(currentVersion + ".1");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ existingRecord.setUpdateTime(LocalDateTime.now());
|
|
|
+ existingRecord.setUpdatedBy(SecurityUtils.getUsername());
|
|
|
+
|
|
|
+ int result = vppContractTemplateMapper.updateById(existingRecord);
|
|
|
+ return result > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommonPage<VppContractTemplateResponseVO> page(Long id, String templateName, Integer contractType,
|
|
|
+ Integer isEnabled, Integer pageNum, Integer pageSize) {
|
|
|
+ IPage<VppContractTemplate> page = new Page<>(pageNum, pageSize);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppContractTemplate::getDeleteFlag, 0)
|
|
|
+ .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId())
|
|
|
+ .eq(id != null, VppContractTemplate::getId, id)
|
|
|
+ .eq(contractType != null, VppContractTemplate::getContractType, contractType)
|
|
|
+ .eq(isEnabled != null, VppContractTemplate::getIsEnabled, isEnabled)
|
|
|
+ .like(StringUtils.isNotBlank(templateName), VppContractTemplate::getTemplateName, templateName)
|
|
|
+ .orderByDesc(VppContractTemplate::getCreateTime);
|
|
|
+ page = vppContractTemplateMapper.selectPage(page, queryWrapper);
|
|
|
+
|
|
|
+ List<VppContractTemplateResponseVO> list = page.getRecords().stream().map(entity -> {
|
|
|
+ VppContractTemplateResponseVO responseVO = new VppContractTemplateResponseVO();
|
|
|
+ responseVO.setId(entity.getId());
|
|
|
+ responseVO.setTemplateCode(entity.getTemplateCode());
|
|
|
+ responseVO.setTemplateName(entity.getTemplateName());
|
|
|
+ responseVO.setContractType(entity.getContractType());
|
|
|
+ responseVO.setVersion(entity.getVersion());
|
|
|
+ responseVO.setFileUrl(entity.getFileUrl());
|
|
|
+ responseVO.setVariablesJson(entity.getVariablesJson());
|
|
|
+ responseVO.setIsEnabled(entity.getIsEnabled());
|
|
|
+ responseVO.setCreatedBy(entity.getCreatedBy());
|
|
|
+ responseVO.setCreateTime(entity.getCreateTime());
|
|
|
+ responseVO.setUpdatedBy(entity.getUpdatedBy());
|
|
|
+ responseVO.setUpdateTime(entity.getUpdateTime());
|
|
|
+ responseVO.setTenantId(entity.getTenantId());
|
|
|
+ return responseVO;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean delete(Long id) {
|
|
|
+ LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppContractTemplate::getId, id)
|
|
|
+ .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId());
|
|
|
+ VppContractTemplate template = vppContractTemplateMapper.selectOne(queryWrapper);
|
|
|
+
|
|
|
+ if (template == null) {
|
|
|
+ throw new BusinessException("合同模板不存在!");
|
|
|
+ }
|
|
|
+
|
|
|
+ template.setDeleteFlag(1);
|
|
|
+ template.setIsEnabled(0);
|
|
|
+ template.setDeletedAt(LocalDateTime.now());
|
|
|
+ template.setUpdatedBy(SecurityUtils.getUsername());
|
|
|
+ int i = vppContractTemplateMapper.updateById(template);
|
|
|
+ return i > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void download(Long id, HttpServletResponse response) {
|
|
|
+ // ========== 查询模板记录 ==========
|
|
|
+ LambdaQueryWrapper<VppContractTemplate> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppContractTemplate::getId, id)
|
|
|
+ .eq(VppContractTemplate::getDeleteFlag, 0)
|
|
|
+ .eq(VppContractTemplate::getTenantId, SecurityUtils.getTenantId());
|
|
|
+ VppContractTemplate template = vppContractTemplateMapper.selectOne(queryWrapper);
|
|
|
+
|
|
|
+ if (template == null) {
|
|
|
+ writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "合同模板不存在或已被删除!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String fileUrl = template.getFileUrl();
|
|
|
+ String fileName = template.getTemplateName();
|
|
|
+ if (StringUtils.isBlank(fileUrl)) {
|
|
|
+ writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "模板文件URL为空,无法下载!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 设置响应头 ==========
|
|
|
+ // 根据 fileUrl 推断原始文件名(优先使用模板名称 + 扩展名)
|
|
|
+ String downloadFileName = fileName;
|
|
|
+ int dotIndex = fileUrl.lastIndexOf('.');
|
|
|
+ if (dotIndex > 0 && !fileName.contains(".")) {
|
|
|
+ downloadFileName = fileName + fileUrl.substring(dotIndex);
|
|
|
+ }
|
|
|
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
|
|
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
|
|
|
+ "attachment; filename=" + encodeFileName(downloadFileName));
|
|
|
+
|
|
|
+ // ========== 下载文件并写入响应流 ==========
|
|
|
+ try {
|
|
|
+ byte[] fileBytes = restTemplate.getForObject(fileUrl, byte[].class);
|
|
|
+ if (fileBytes == null || fileBytes.length == 0) {
|
|
|
+ writeErrorResponse(response, HttpStatus.NOT_FOUND.value(), "模板文件内容为空!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ServletOutputStream sos = response.getOutputStream();
|
|
|
+ sos.write(fileBytes);
|
|
|
+ sos.flush();
|
|
|
+ } catch (Exception e) {
|
|
|
+ if (!response.isCommitted()) {
|
|
|
+ writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
|
|
+ "下载合同模板失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 向响应中写入 JSON 错误信息
|
|
|
+ */
|
|
|
+ private void writeErrorResponse(HttpServletResponse response, int status, String message) {
|
|
|
+ response.setStatus(status);
|
|
|
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
|
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
|
|
+ try {
|
|
|
+ response.getWriter().write("{\"code\":\"" + status + "\",\"msg\":\"" + message + "\"}");
|
|
|
+ } catch (IOException e) {
|
|
|
+ // 忽略写入异常
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 编码文件名,兼容各浏览器
|
|
|
+ */
|
|
|
+ private String encodeFileName(String fileName) {
|
|
|
+ try {
|
|
|
+ return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
+ return fileName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 全租户唯一性校验:检查 templateCode 是否已被占用
|
|
|
+ *
|
|
|
+ * @param templateCode 模板编码
|
|
|
+ * @param excludeId 排除的记录ID(更新时传入当前记录ID,创建时传 null)
|
|
|
+ * @throws BusinessException 编码已被占用时抛出
|
|
|
+ */
|
|
|
+ private void checkTemplateCodeUnique(String templateCode, Long excludeId, Integer tenantId) {
|
|
|
+ LambdaQueryWrapper<VppContractTemplate> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(VppContractTemplate::getTemplateCode, templateCode)
|
|
|
+ .eq(VppContractTemplate::getDeleteFlag, 0)
|
|
|
+ .eq(VppContractTemplate::getTenantId, tenantId)
|
|
|
+ .ne(excludeId != null, VppContractTemplate::getId, excludeId);
|
|
|
+ Integer count = vppContractTemplateMapper.selectCount(wrapper);
|
|
|
+ if (count != null && count > 0) {
|
|
|
+ throw new BusinessException("模板编码【" + templateCode + "】已存在,请使用其他编码!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|