diff --git a/ruoyi-admin/src/main/resources/application-test.yml b/ruoyi-admin/src/main/resources/application-test.yml index 377fd78..5eeeb96 100644 --- a/ruoyi-admin/src/main/resources/application-test.yml +++ b/ruoyi-admin/src/main/resources/application-test.yml @@ -282,6 +282,6 @@ justauth: # maxConnectNumPerRoute: 100 chat: # 聊天机器人配置 - filePath: /guoYanXinXi/data/software/minio/data/ruoyi/ - tempfilePath: /guoYanXinXi/data/software/app/tempfile/ + filePath: /guoYanXinXi/data/software/minio/data/ruoyi + tempfilePath: /guoYanXinXi/data/software/app/tempfile chatUrl: http://127.0.0.1:8081 diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualRegulationNamesController.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualRegulationNamesController.java index 302758d..3c60d17 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualRegulationNamesController.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualRegulationNamesController.java @@ -135,4 +135,16 @@ public class ContractualRegulationNamesController extends BaseController { List articles = contractualRegulationNamesService.getRegulationArticles(id); return R.ok(articles); } + + /** + * 获取有效法规名称列表(用于合规性审查) + */ + @SaCheckPermission("productManagement:ContractualRegulationNames:list") + @GetMapping("/effective/list") + public R> getEffectiveRegulationsList() { + ContractualRegulationNamesBo bo = new ContractualRegulationNamesBo(); + bo.setIsEffective("Y"); // 只查询有效的法规 + List list = contractualRegulationNamesService.queryList(bo); + return R.ok(list); + } } diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualTaskResultsController.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualTaskResultsController.java index 5dcb5b4..18068b5 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualTaskResultsController.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualTaskResultsController.java @@ -65,6 +65,18 @@ public class ContractualTaskResultsController extends BaseController { contractualTaskResultsService.getPdfStream(taskId, response); } + /** + * 获取招投标文件PDF文件流 + * + * @param taskId 任务ID + */ + @GetMapping("/getBidPdfStream/{taskId}") + public void getBidPdfStream(@NotNull(message = "任务ID不能为空") + @PathVariable String taskId, + HttpServletResponse response) { + contractualTaskResultsService.getBidPdfStream(taskId, response); + } + /** * 更新合同任务结果项的状态(已读/采纳) * diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/ContractualTasks.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/ContractualTasks.java index a0e0dc1..8b823e4 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/ContractualTasks.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/ContractualTasks.java @@ -29,16 +29,6 @@ public class ContractualTasks extends TenantEntity { @TableId(value = "id") private Long id; - /** - * 模型所属行业 - */ - private String taskIndustry; - - /** - * 模型所属区域 - */ - private String taskRegion; - /** * 合同任务名称 */ @@ -60,6 +50,11 @@ public class ContractualTasks extends TenantEntity { */ private String progressStatus; + /** + * 审查类型(实质性审查,合规性审查,一致性审查) + */ + private String reviewTypes; + /** * */ @@ -114,4 +109,9 @@ public class ContractualTasks extends TenantEntity { */ private String reviewData; private String pdfPath; + + /** + * 招投标文件路径 + */ + private String bidPath; } diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/StartContractReviewRequest.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/StartContractReviewRequest.java index 7e9ca70..f031683 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/StartContractReviewRequest.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/StartContractReviewRequest.java @@ -85,24 +85,19 @@ public class StartContractReviewRequest { @Data public static class ComplianceData { /** - * 关注要点 + * 法规检查方式(ai: AI自动选择, manual: 人工选择法规) */ - private List focusPoints; + private String regulationMethod; /** - * 行业类型 + * 选择的法规名称ID列表(当regulationMethod为manual时使用) */ - private String industry; + private List regulationIds; /** - * 合规级别 - */ - private String level; - - /** - * 适用法规列表 + * 特别说明 */ - private List regulations; + private String specialNote; /** * 审查类型 diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/bo/ContractualTasksBo.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/bo/ContractualTasksBo.java index 4687ae2..b9d6ab5 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/bo/ContractualTasksBo.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/bo/ContractualTasksBo.java @@ -60,36 +60,15 @@ public class ContractualTasksBo extends BaseTaskBo { */ private String reviewData; private String pdfPath; -// /** -// * 模型所属行业 -// */ -// @NotBlank(message = "模型所属行业不能为空", groups = { AddGroup.class, EditGroup.class }) -// private String taskIndustry; -// -// /** -// * 模型所属区域 -// */ -// @NotBlank(message = "模型所属区域不能为空", groups = { AddGroup.class, EditGroup.class }) -// private String taskRegion; -// -// -// @NotEmpty(message = "任务名称列表不能为空", groups = { AddGroup.class, EditGroup.class }) -// private List<@NotBlank(message = "任务名称不能为空") String> taskNameList; -// /** -// * -// */ -// private String documentName; -// -// /** -// * -// */ -// @NotNull(message = "不能为空", groups = { AddGroup.class, EditGroup.class }) -// private Long ossId; -// -// /** -// * -// */ -// private String progressStatus; - + + /** + * 招投标文件路径 + */ + private String bidPath; + + /** + * 审查类型(实质性审查,合规性审查,一致性审查) + */ + private String reviewTypes; } diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/ContractualTasksVo.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/ContractualTasksVo.java index 793af9e..5e7de85 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/ContractualTasksVo.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/ContractualTasksVo.java @@ -75,4 +75,14 @@ public class ContractualTasksVo extends BaseTaskVo implements Serializable { private String reviewData; private Long groupId; private String pdfPath; + + /** + * 招投标文件路径 + */ + private String bidPath; + + /** + * 审查类型(实质性审查,合规性审查,一致性审查) + */ + private String reviewTypes; } diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/DocumentTaskResultVO.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/DocumentTaskResultVO.java index 597a1ce..163fd02 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/DocumentTaskResultVO.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/DocumentTaskResultVO.java @@ -13,9 +13,9 @@ public class DocumentTaskResultVO { @Data public static class ResultItem { - + private String id; // 唯一标识ID - + private Integer serialNumber; // 序号 private String issueName; // 问题点名称 @@ -30,18 +30,11 @@ public class DocumentTaskResultVO { private String existingIssues; // 存在的问题 - private ReviewBasis reviewBasis; // 审查依据 + private String reviewBasis; // 审查依据 private String isRead; // 是否已读 private String isAdopted; // 是否采纳 - @Data - public static class ReviewBasis { - - private String reviewContent; // 审查内容 - - private List reviewPoints; // 审查点 - } } } diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/IContractualTaskResultsService.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/IContractualTaskResultsService.java index e55f5b4..833fb46 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/IContractualTaskResultsService.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/IContractualTaskResultsService.java @@ -37,6 +37,14 @@ public interface IContractualTaskResultsService { */ void getPdfStream(String taskId, HttpServletResponse response); + /** + * 获取招投标文件PDF文件流 + * + * @param taskId 任务ID + * @param response HTTP响应对象 + */ + void getBidPdfStream(String taskId, HttpServletResponse response); + /** * 更新合同任务结果项的状态(已读/采纳) * diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTaskResultsServiceImpl.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTaskResultsServiceImpl.java index 4553c08..b51729a 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTaskResultsServiceImpl.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTaskResultsServiceImpl.java @@ -4,15 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension; -import com.vladsch.flexmark.ext.tables.TablesExtension; -import com.vladsch.flexmark.html.HtmlRenderer; -import com.vladsch.flexmark.parser.Parser; -import com.vladsch.flexmark.util.ast.Node; -import com.vladsch.flexmark.util.data.MutableDataSet; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.xwpf.usermodel.*; import org.dromara.common.core.service.DictService; import org.dromara.common.core.utils.StringUtils; import org.dromara.productManagement.domain.ContractualTaskResultDetail; @@ -24,13 +19,9 @@ import org.dromara.productManagement.service.IContractualTaskResultsService; import org.dromara.productManagement.service.IContractualTasksService; import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.service.ISysOssService; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.safety.Safelist; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.xhtmlrenderer.pdf.ITextRenderer; import javax.sql.DataSource; import java.io.*; @@ -39,6 +30,7 @@ import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; @@ -60,10 +52,10 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult private final IContractualTasksService contractualTasksService; private final DictService dictTypeService; protected final ISysOssService ossService; - + @Value("${chat.tempfilePath}") protected String tempfilePath; - + @Value("${chat.filePath}") protected String fileRootPath; @@ -161,7 +153,6 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult @Override public void downloadResult(Long[] ids, HttpServletResponse response) { try { - // 使用新的导出逻辑,基于getDetailResultsByTaskId获取结果 if (ids.length > 0) { if (ids.length > 1) { // 多文件压缩下载 @@ -181,43 +172,22 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult // 获取详细的任务结果 List resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); - if (resultDetails != null && !resultDetails.isEmpty()) { - // 第一个是"全部"分类 - ContractualTaskResultVO allCategory = resultDetails.get(0); - if (allCategory.getResults() != null && !allCategory.getResults().isEmpty()) { - // 获取任务类型对应的字段配置 - List fieldConfigs = getFieldConfigsByTaskType(taskName); - - // 将结果转换为Markdown - StringBuilder markdown = new StringBuilder(); - for (ContractualTaskResultVO.ResultItem item : allCategory.getResults()) { - markdown.append("## ").append(item.getSerialNumber()).append(". ").append(item.getExistingIssues()).append("\n\n"); - - // 根据配置添加各字段内容 - for (FieldConfig config : fieldConfigs) { - String fieldValue = getFieldValue(item, config.getField()); - if (StringUtils.isNotBlank(fieldValue)) { - markdown.append("### ").append(config.getTitle()).append("\n\n").append(fieldValue).append("\n\n"); - } - } - - // 添加分隔线 - markdown.append("---\n\n"); - } - - // 为每个结果创建PDF文件 - String pdfFileName = label + "_" + documentName + "_" + ".pdf"; - String pdfPath = tempDir + File.separator + pdfFileName; - - // 转换Markdown为PDF - convertMarkdownToPdf(markdown.toString(), pdfPath); + if (resultDetails != null && resultDetails.size() > 1) { + // 跳过第一个"全部"分类,从第二个开始处理 + List categorizedResults = resultDetails.subList(1, resultDetails.size()); + + // 为每个结果创建docx文件 + String docxFileName = label + "_" + documentName + "_" + ".docx"; + String docxPath = tempDir + File.separator + docxFileName; + + // 生成docx文档 + generateDocxDocument(categorizedResults, docxPath); // 添加到ZIP - addToZip(pdfPath, pdfFileName, zos); + addToZip(docxPath, docxFileName, zos); - // 删除临时PDF文件 - new File(pdfPath).delete(); - } + // 删除临时docx文件 + new File(docxPath).delete(); } } } @@ -242,46 +212,25 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult // 获取详细的任务结果 List resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); - if (resultDetails == null || resultDetails.isEmpty() || - resultDetails.get(0).getResults() == null || resultDetails.get(0).getResults().isEmpty()) { + if (resultDetails == null || resultDetails.size() <= 1) { throw new RuntimeException("未找到相关结果"); } - // 第一个是"全部"分类 - ContractualTaskResultVO allCategory = resultDetails.get(0); + // 跳过第一个"全部"分类,从第二个开始处理 + List categorizedResults = resultDetails.subList(1, resultDetails.size()); - // 获取任务类型对应的字段配置 - List fieldConfigs = getFieldConfigsByTaskType(taskName); - - // 将结果转换为Markdown - StringBuilder markdown = new StringBuilder(); - for (ContractualTaskResultVO.ResultItem item : allCategory.getResults()) { - markdown.append("## ").append(item.getSerialNumber()).append(". ").append(item.getExistingIssues()).append("\n\n"); - - // 根据配置添加各字段内容 - for (FieldConfig config : fieldConfigs) { - String fieldValue = getFieldValue(item, config.getField()); - if (StringUtils.isNotBlank(fieldValue)) { - markdown.append("### ").append(config.getTitle()).append("\n\n").append(fieldValue).append("\n\n"); - } - } - - // 添加分隔线 - markdown.append("---\n\n"); - } - - String pdfFileName = label + "_" + documentName + "_" + ".pdf"; + String docxFileName = label + "_" + documentName + "_" + ".docx"; String tempDir = System.getProperty("java.io.tmpdir"); - String pdfPath = tempDir + File.separator + pdfFileName; + String docxPath = tempDir + File.separator + docxFileName; - // 转换Markdown为PDF - convertMarkdownToPdf(markdown.toString(), pdfPath); + // 生成docx文档 + generateDocxDocument(categorizedResults, docxPath); - // 下载PDF文件 - downloadFile(response, pdfPath, pdfFileName); + // 下载docx文件 + downloadFile(response, docxPath, docxFileName); - // 删除临时PDF文件 - new File(pdfPath).delete(); + // 删除临时docx文件 + new File(docxPath).delete(); } } else { throw new RuntimeException("未找到相关文件"); @@ -329,7 +278,7 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult previewDir.mkdirs(); } - filerotPath = fileRootPath + fileInfo.getFileName(); + filerotPath = fileRootPath +File.separator+ fileInfo.getFileName(); // 如果不是pdf文件,需要转换 if (!fileName.toLowerCase().endsWith(".pdf")) { @@ -392,86 +341,133 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult } } - // Markdown转PDF方法 - private void convertMarkdownToPdf(String markdown, String outputPath) throws Exception { - MutableDataSet options = new MutableDataSet(); - // 启用表格解析 - options.set(Parser.EXTENSIONS, Arrays.asList( - TablesExtension.create(), - StrikethroughExtension.create() - )); - - Parser parser = Parser.builder(options).build(); - HtmlRenderer renderer = HtmlRenderer.builder(options) - .extensions(Arrays.asList( - TablesExtension.create(), - StrikethroughExtension.create() - )) - .build(); - - Node document = parser.parse(markdown); - String htmlContent = renderer.render(document); - // 使用 JSoup 清理 - Document.OutputSettings settings = new Document.OutputSettings(); - settings.prettyPrint(false); - - // 配置允许的标签 - Safelist safelist = Safelist.relaxed() - .addTags("em", "strong", "div", "span", "br", "table", "thead", "tbody", "tr", "th", "td", "img") - .addAttributes(":all", "style", "class") - .addAttributes("table", "border", "cellpadding", "cellspacing") - .addAttributes("img", "src", "alt", "width", "height"); // 允许img标签及其属性 - htmlContent = Jsoup.clean(htmlContent, "", safelist, settings); - - // 创建完整的HTML文档,使用微软雅黑 - String html = String.format(""" - - - - - - - - %s - - - """, htmlContent); - - try (OutputStream outputStream = new FileOutputStream(outputPath)) { - ITextRenderer renderer2 = new ITextRenderer(); - renderer2.setDocumentFromString(html); - renderer2.layout(); - renderer2.createPDF(outputStream); + @Override + public void getBidPdfStream(String taskId, HttpServletResponse response) { + try { + // 查询任务信息 + ContractualTasksVo task = contractualTasksService.queryById(Long.valueOf(taskId)); + if (task == null) { + throw new RuntimeException("任务不存在"); + } + + String outputPath = tempfilePath + File.separator + "pdf_preview" + File.separator; + String bidPath = task.getBidPath(); + String filePath = ""; + String filerotPath = ""; + + if (StringUtils.isNotEmpty(bidPath)) { + // 如果bidPath存在,直接使用该路径 + filePath = bidPath; + } else { + // 如果bidPath不存在,从reviewData中解析bidDocumentPath + String reviewData = task.getReviewData(); + if (StringUtils.isEmpty(reviewData)) { + throw new RuntimeException("未找到审查数据"); + } + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(reviewData); + JsonNode consistencyNode = rootNode.get("consistency"); + + if (consistencyNode == null || consistencyNode.isNull()) { + throw new RuntimeException("未找到一致性审查配置"); + } + + JsonNode bidDocumentPathNode = consistencyNode.get("bidDocumentPath"); + if (bidDocumentPathNode == null || bidDocumentPathNode.isNull()) { + throw new RuntimeException("未找到招投标文件路径"); + } + + String bidDocumentPath = bidDocumentPathNode.asText(); + if (StringUtils.isEmpty(bidDocumentPath)) { + throw new RuntimeException("招投标文件路径为空"); + } + + log.info("从reviewData中获取到招投标文件路径: " + bidDocumentPath); + + // 创建pdf预览目录 + File previewDir = new File(outputPath); + if (!previewDir.exists()) { + previewDir.mkdirs(); + } + + filerotPath = bidDocumentPath; + + // 检查文件是否为PDF格式 + if (!bidDocumentPath.toLowerCase().endsWith(".pdf")) { + // 需要转换为PDF + String fileName = new File(bidDocumentPath).getName(); + String fileNameWithoutExt = fileName.substring(0, fileName.lastIndexOf(".")); + filePath = outputPath + fileNameWithoutExt + ".pdf"; + log.info("需要转换招投标文件: " + bidDocumentPath + " -> " + filePath); + } else { + // 已经是PDF格式,直接使用 + filePath = bidDocumentPath; + } + + } catch (Exception e) { + log.error("解析reviewData失败", e); + throw new RuntimeException("解析审查数据失败: " + e.getMessage()); + } + } + + log.info("开始输出招投标文件: " + filePath); + + // 读取文件并输出到response + File file = new File(filePath); + if (!file.exists()) { + log.info("招投标文件不存在,开始转换: " + filePath); + + // 使用libreoffice转换为pdf + ProcessBuilder builder = new ProcessBuilder("libreoffice", "--headless", "--convert-to", "pdf", filerotPath, "--outdir", outputPath); + Process process = builder.start(); + int completed = process.waitFor(); + if (completed != 0) { + InputStream errorStream = process.getErrorStream(); + String errorMessage = new String(errorStream.readAllBytes()); + process.destroyForcibly(); + throw new RuntimeException("招投标文件转换失败: " + errorMessage); + } + process.destroyForcibly(); + + // 重新检查转换后的文件 + if (!file.exists()) { + throw new RuntimeException("招投标文件转换后仍不存在: " + filePath); + } + } + + log.info("招投标文件存在,开始输出: " + filePath); + + // 使用JDBC更新数据库中的bid_path + try (Connection conn = dataSource.getConnection()) { + String sql = "UPDATE contractual_tasks SET bid_path = ? WHERE id = ?"; + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, filePath); + pstmt.setLong(2, Long.parseLong(taskId)); + pstmt.executeUpdate(); + } + } catch (SQLException e) { + log.error("更新招投标PDF路径失败", e); + throw new RuntimeException("更新招投标PDF路径失败: " + e.getMessage()); + } + + response.setContentType("application/pdf"); + response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8.name())); + + try (FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis, 8192); + OutputStream os = response.getOutputStream()) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = bis.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.flush(); + } + } catch (Exception e) { + log.error("获取招投标PDF文件流失败", e); + throw new RuntimeException("获取招投标PDF文件流失败: " + e.getMessage()); } } @@ -519,40 +515,251 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult } } - private static class FieldConfig { - private String field; - private String title; + /** + * 生成docx文档 + * + * @param categorizedResults 分类结果列表 + * @param outputPath 输出路径 + * @throws Exception 异常 + */ + private void generateDocxDocument(List categorizedResults, String outputPath) throws Exception { + try (XWPFDocument document = new XWPFDocument()) { + int categoryIndex = 1; // 一级标题序号 + + for (ContractualTaskResultVO category : categorizedResults) { + if (category.getResults() == null || category.getResults().isEmpty()) { + continue; + } + + // 添加一级标题(带序号) + XWPFParagraph title = document.createParagraph(); + XWPFRun titleRun = title.createRun(); + titleRun.setText(categoryIndex + ". " + category.getName()); + titleRun.setBold(true); + titleRun.setFontSize(16); + titleRun.setFontFamily("Microsoft YaHei"); + + // 根据分类名称获取表格配置 + TableConfiguration tableConfig = getTableConfiguration(category.getName()); + + int issueIndex = 1; // 二级标题序号 + for (ContractualTaskResultVO.ResultItem item : category.getResults()) { + // 添加二级标题 - 问题点(带序号) + XWPFParagraph issueTitle = document.createParagraph(); + XWPFRun issueTitleRun = issueTitle.createRun(); + issueTitleRun.setText(categoryIndex + "." + issueIndex + ". 问题点:" + item.getExistingIssues()); + issueTitleRun.setBold(true); + issueTitleRun.setFontSize(12); + issueTitleRun.setFontFamily("Microsoft YaHei"); + + // 创建表格 + createTableForItem(document, tableConfig, item, category.getName()); + + // 添加段落间距 + XWPFParagraph spacer = document.createParagraph(); + spacer.createRun().addBreak(); + + issueIndex++; + } + + categoryIndex++; + } + + // 写入文件 + try (FileOutputStream out = new FileOutputStream(outputPath)) { + document.write(out); + } + } + } - public FieldConfig(String field, String title) { - this.field = field; - this.title = title; + /** + * 为特定项目创建表格 - 使用新的坐标配置系统 + */ + private void createTableForItem(XWPFDocument document, TableConfiguration config, ContractualTaskResultVO.ResultItem item, String categoryName) { + XWPFTable table = document.createTable(); + setTableStyle(table); + + // 创建表格的所有行 + for (int i = 1; i < config.getTotalRows(); i++) { + table.createRow(); } - public String getField() { - return field; + // 确保表格有足够的列 + for (int i = 0; i < config.getTotalRows(); i++) { + XWPFTableRow row = table.getRow(i); + while (row.getTableCells().size() < config.getTotalColumns()) { + row.addNewTableCell(); + } } - public String getTitle() { - return title; + // 根据配置填充单元格 + for (CellConfiguration cellConfig : config.getCellConfigs()) { + XWPFTableRow row = table.getRow(cellConfig.getRow()); + XWPFTableCell cell = row.getCell(cellConfig.getColumn()); + + String content = ""; + if (cellConfig.isHeader()) { + content = cellConfig.getHeaderText(); + } else { + content = getFieldValue(item, cellConfig.getFieldName()); + } + + setupTableCell(cell, content, cellConfig.isHeader()); + + // 处理合并 + if (cellConfig.getMergeType() != MergeType.NONE) { + applyCellMerge(row, cellConfig); + } } } /** - * 根据任务类型获取字段配置 - * - * @param taskName 任务名称 - * @return 字段配置列表 + * 应用单元格合并 */ - private List getFieldConfigsByTaskType(String taskName) { - // 这里可以根据不同的任务类型返回不同的字段配置 - // 目前返回通用的配置 - return Arrays.asList( - new FieldConfig("originalText", "原文"), - new FieldConfig("comparedText", "比对原文"), - new FieldConfig("modifiedContent", "修改后的内容"), - new FieldConfig("modificationDisplay", "展示修改情况"), - new FieldConfig("reviewBasis", "审查依据") - ); + private void applyCellMerge(XWPFTableRow row, CellConfiguration cellConfig) { + switch (cellConfig.getMergeType()) { + case HORIZONTAL: + int endCol = cellConfig.getColumn() + cellConfig.getMergeSpanColumns() - 1; + mergeCellsHorizontally(row, cellConfig.getColumn(), endCol); + break; + case VERTICAL: + // 垂直合并的实现(如果需要的话) + // mergeVertically(table, cellConfig.getRow(), cellConfig.getColumn(), cellConfig.getMergeSpanRows()); + break; + case BOTH: + // 双向合并的实现(如果需要的话) + break; + default: + break; + } + } + + /** + * 格式化审查依据字段 + */ + private String formatReviewBasisField(String reviewBasis, String fieldName) { + if (StringUtils.isBlank(reviewBasis)) { + return ""; + } + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(reviewBasis); + + if (fieldName.equals("review_points[0]")) { + if (jsonNode.has("review_points") && jsonNode.get("review_points").isArray()) { + JsonNode arrayNode = jsonNode.get("review_points"); + if (arrayNode.size() > 0) { + return arrayNode.get(0).asText(); + } + } + } else if (fieldName.equals("review_content")) { + if (jsonNode.has("review_content") && !jsonNode.get("review_content").isNull()) { + return jsonNode.get("review_content").asText(); + } + } + } catch (Exception e) { + log.warn("解析审查依据字段失败: " + fieldName, e); + } + + return ""; + } + + /** + * 设置表格单元格 - 简化版 + */ + private void setupTableCell(XWPFTableCell cell, String text, boolean isHeader) { + // 清除默认内容 + if (cell.getParagraphs().size() > 0) { + cell.removeParagraph(0); + } + + XWPFParagraph paragraph = cell.addParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText(text != null ? text : ""); + run.setFontFamily("Microsoft YaHei"); + run.setFontSize(12); + + if (isHeader) { + run.setBold(true); + // 设置表头背景色 + cell.setColor("E6E6E6"); + } + } + + /** + * 设置表格样式 - 简化版 + */ + private void setTableStyle(XWPFTable table) { + // 设置表格宽度为100% + table.setWidth("100%"); + } + + /** + * 根据分类名称获取表格配置 + */ + private TableConfiguration getTableConfiguration(String categoryName) { + TableConfiguration config; + + switch (categoryName) { + case "实质性审查": + // 4行2列:表头行 + 数据行 + 审查依据标签行 + 审查依据内容行 + config = new TableConfiguration(categoryName, 4, 2); + // 表头行 + config.addCellConfig(new CellConfiguration(0, 0).header("原文")); + config.addCellConfig(new CellConfiguration(0, 1).header("修改建议")); + // 数据行 + config.addCellConfig(new CellConfiguration(1, 0).field("originalText")); + config.addCellConfig(new CellConfiguration(1, 1).field("modifiedContent")); + // 审查依据标签行(跨两列合并) + config.addCellConfig(new CellConfiguration(2, 0).header("审查依据").mergeHorizontal(2)); + // 审查依据内容行(跨两列合并) + config.addCellConfig(new CellConfiguration(3, 0).field("review_basis_combined").mergeHorizontal(2)); + break; + + case "合规性审查": + // 4行2列:表头行 + 数据行 + 法规依据标签行 + 法规依据内容行 + config = new TableConfiguration(categoryName, 4, 1); + // 表头行 + config.addCellConfig(new CellConfiguration(0, 0).header("原文")); +// config.addCellConfig(new CellConfiguration(0, 1).header("修改后的内容")); + // 数据行 + config.addCellConfig(new CellConfiguration(1, 0).field("originalText")); +// config.addCellConfig(new CellConfiguration(1, 1).field("modifiedContent")); + // 法规依据标签行(跨两列合并) + config.addCellConfig(new CellConfiguration(2, 0).header("法规依据").mergeHorizontal(2)); + // 法规依据内容行(跨两列合并) + config.addCellConfig(new CellConfiguration(3, 0).field("review_points[0]").mergeHorizontal(2)); + break; + + case "一致性审查": + // 4行3列:表头行 + 数据行 + 修改后内容标签行 + 修改后内容数据行 + config = new TableConfiguration(categoryName, 4, 2); + // 表头行 + config.addCellConfig(new CellConfiguration(0, 0).header("合同原文")); + config.addCellConfig(new CellConfiguration(0, 1).header("招标文件原文")); + // 数据行 + config.addCellConfig(new CellConfiguration(1, 0).field("originalText")); + config.addCellConfig(new CellConfiguration(1, 1).field("comparedText")); + // 修改后内容标签行(跨三列合并) + config.addCellConfig(new CellConfiguration(2, 0).header("问题描述").mergeHorizontal(2)); + // 修改后内容数据行(跨三列合并) + config.addCellConfig(new CellConfiguration(3, 0).field("existingIssues").mergeHorizontal(2)); + break; + + default: + // 默认配置:4行2列 + config = new TableConfiguration(categoryName, 4, 2); + config.addCellConfig(new CellConfiguration(0, 0).header("原文")); + config.addCellConfig(new CellConfiguration(0, 1).header("修改建议")); + config.addCellConfig(new CellConfiguration(1, 0).field("originalText")); + config.addCellConfig(new CellConfiguration(1, 1).field("modifiedContent")); + config.addCellConfig(new CellConfiguration(2, 0).header("审查依据").mergeHorizontal(2)); + config.addCellConfig(new CellConfiguration(3, 0).field("review_basis_combined").mergeHorizontal(2)); + break; + } + + return config; } /** @@ -563,6 +770,10 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult * @return 字段值 */ private String getFieldValue(ContractualTaskResultVO.ResultItem item, String fieldName) { + if (fieldName == null || fieldName.isEmpty()) { + return ""; + } + switch (fieldName) { case "originalText": return item.getOriginalText(); @@ -573,36 +784,235 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult case "modificationDisplay": return item.getModificationDisplay(); case "reviewBasis": - if (item.getReviewBasis() != null && StringUtils.isNotBlank(item.getReviewBasis())) { - // reviewBasis现在是JSON字符串,可以直接返回 - // 或者解析后格式化显示 + return item.getReviewBasis(); + case "existingIssues": + return item.getExistingIssues(); + case "review_basis_combined": + // 组合审查依据:review_points[0] + review_content + return formatReviewBasisCombined(item.getReviewBasis()); + case "review_points[0]": + return formatReviewBasisField(item.getReviewBasis(), "review_points[0]"); + case "review_content": + return formatReviewBasisField(item.getReviewBasis(), "review_content"); + case "": + return ""; + default: + return ""; + } + } + + /** + * 格式化组合的审查依据 + */ + private String formatReviewBasisCombined(String reviewBasis) { + if (StringUtils.isBlank(reviewBasis)) { + return ""; + } + try { ObjectMapper objectMapper = new ObjectMapper(); - JsonNode jsonNode = objectMapper.readTree(item.getReviewBasis()); - - StringBuilder sb = new StringBuilder(); - if (jsonNode.has("reviewContent") && !jsonNode.get("reviewContent").isNull()) { - sb.append("审查内容: ").append(jsonNode.get("reviewContent").asText()).append("\n"); - } - if (jsonNode.has("reviewPoints") && jsonNode.get("reviewPoints").isArray()) { - sb.append("审查点: "); - for (JsonNode point : jsonNode.get("reviewPoints")) { - sb.append(point.asText()).append(", "); - } - // 移除最后的逗号和空格 - if (sb.length() > 4) { - sb.setLength(sb.length() - 2); - } - } - return sb.toString(); + JsonNode jsonNode = objectMapper.readTree(reviewBasis); + + StringBuilder result = new StringBuilder(); + + // 添加review_points[0] + if (jsonNode.has("review_points") && jsonNode.get("review_points").isArray()) { + JsonNode arrayNode = jsonNode.get("review_points"); + if (arrayNode.size() > 0) { + result.append(arrayNode.get(0).asText()); + } + } + + // 添加review_content + if (jsonNode.has("review_content") && !jsonNode.get("review_content").isNull()) { + String content = jsonNode.get("review_content").asText(); + if (StringUtils.isNotBlank(content)) { + if (result.length() > 0) { + result.append(" "); + } + result.append(content); + } + } + + return result.toString(); } catch (Exception e) { - // 如果JSON解析失败,直接返回原始字符串 - return item.getReviewBasis(); + log.warn("解析组合审查依据失败", e); + return reviewBasis; + } + } + + /** + * 水平合并单元格 - 使用POI真正合并功能 + * @param row 表格行 + * @param fromCol 起始列 + * @param toCol 结束列 + */ + private void mergeCellsHorizontally(XWPFTableRow row, int fromCol, int toCol) { + if (fromCol >= toCol) return; + + try { + // 使用POI的真正合并功能 + for (int i = fromCol; i <= toCol; i++) { + XWPFTableCell cell = row.getCell(i); + if (cell != null) { + if (i == fromCol) { + // 第一个单元格设置为合并开始 + cell.getCTTc().addNewTcPr().addNewHMerge().setVal( + org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART); + } else { + // 其他单元格设置为合并继续 + cell.getCTTc().addNewTcPr().addNewHMerge().setVal( + org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE); + // 清空被合并单元格的内容 + while (cell.getParagraphs().size() > 0) { + cell.removeParagraph(0); + } + cell.addParagraph(); } } - return null; - default: - return null; + } + } catch (Exception e) { + log.warn("合并单元格失败: fromCol=" + fromCol + ", toCol=" + toCol, e); + } + } + + private boolean isEmpty(String str) { + return StringUtils.isBlank(str); + } + + /** + * 表格配置类 - 基于坐标的配置系统 + */ + private static class TableConfiguration { + private String categoryName; + private int totalRows; + private int totalColumns; + private List cellConfigs; + + public TableConfiguration(String categoryName, int totalRows, int totalColumns) { + this.categoryName = categoryName; + this.totalRows = totalRows; + this.totalColumns = totalColumns; + this.cellConfigs = new ArrayList<>(); + } + + public void addCellConfig(CellConfiguration cellConfig) { + this.cellConfigs.add(cellConfig); + } + + // Getters + public String getCategoryName() { return categoryName; } + public int getTotalRows() { return totalRows; } + public int getTotalColumns() { return totalColumns; } + public List getCellConfigs() { return cellConfigs; } + } + + /** + * 单元格配置类 + */ + private static class CellConfiguration { + private int row; + private int column; + private String fieldName; // 对应ResultItem的字段名 + private String headerText; // 表头文本 + private boolean isHeader; // 是否为表头 + private MergeType mergeType; // 合并类型 + private int mergeSpanRows; // 合并的行数 + private int mergeSpanColumns; // 合并的列数 + + public CellConfiguration(int row, int column) { + this.row = row; + this.column = column; + this.mergeType = MergeType.NONE; + this.mergeSpanRows = 1; + this.mergeSpanColumns = 1; + } + + // Builder模式的方法 + public CellConfiguration field(String fieldName) { + this.fieldName = fieldName; + return this; + } + + public CellConfiguration header(String headerText) { + this.headerText = headerText; + this.isHeader = true; + return this; + } + + public CellConfiguration mergeHorizontal(int spanColumns) { + this.mergeType = MergeType.HORIZONTAL; + this.mergeSpanColumns = spanColumns; + return this; + } + + public CellConfiguration mergeVertical(int spanRows) { + this.mergeType = MergeType.VERTICAL; + this.mergeSpanRows = spanRows; + return this; + } + + public CellConfiguration mergeBoth(int spanRows, int spanColumns) { + this.mergeType = MergeType.BOTH; + this.mergeSpanRows = spanRows; + this.mergeSpanColumns = spanColumns; + return this; + } + + // Getters + public int getRow() { return row; } + public int getColumn() { return column; } + public String getFieldName() { return fieldName; } + public String getHeaderText() { return headerText; } + public boolean isHeader() { return isHeader; } + public MergeType getMergeType() { return mergeType; } + public int getMergeSpanRows() { return mergeSpanRows; } + public int getMergeSpanColumns() { return mergeSpanColumns; } + } + + /** + * 合并类型枚举 + */ + private enum MergeType { + NONE, // 不合并 + HORIZONTAL, // 水平合并 + VERTICAL, // 垂直合并 + BOTH // 双向合并 + } + + /** + * 审查依据配置类 - 简化版 + */ + private static class BasisConfiguration { + private String label; + private List fieldCombinations; + + public BasisConfiguration(String label, List fieldCombinations) { + this.label = label; + this.fieldCombinations = fieldCombinations; + } + + public String getLabel() { return label; } + public List getFieldCombinations() { return fieldCombinations; } + } + + /** + * 字段组合配置类 + */ + private static class FieldCombination { + private String fieldPath; + private String separator; + + public FieldCombination(String fieldPath, String separator) { + this.fieldPath = fieldPath; + this.separator = separator; } + + public FieldCombination(String fieldPath) { + this(fieldPath, ""); + } + + public String getFieldPath() { return fieldPath; } + public String getSeparator() { return separator; } } } diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java index 6dde960..8b9716f 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java @@ -124,6 +124,9 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor buildQueryWrapper(ContractualTasksBo bo) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(ContractualTasks::getTaskType, TaskEnum.TaskType.CONTRACT_REVIEW.getValue()); + lqw.like(bo.getDocumentName() != null, ContractualTasks::getDocumentName, bo.getDocumentName()); + lqw.like(bo.getReviewTypes() != null, ContractualTasks::getReviewTypes, bo.getReviewTypes()); + lqw.eq(bo.getProgressStatus() != null, ContractualTasks::getProgressStatus, bo.getProgressStatus()); return lqw; } @@ -265,7 +268,13 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor result = new HashMap<>(); result.put("success", true); result.put("message", "合同审查任务已创建,正在后台处理"); diff --git a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTaskResultsServiceImpl.java b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTaskResultsServiceImpl.java index cb7a772..dd996d0 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTaskResultsServiceImpl.java +++ b/zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTaskResultsServiceImpl.java @@ -721,7 +721,7 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi previewDir.mkdirs(); } - filerotPath = fileRootPath + fileInfo.getFileName(); + filerotPath = fileRootPath +File.separator+ fileInfo.getFileName(); // 如果不是pdf文件,需要转换 if (!fileName.toLowerCase().endsWith(".pdf")) { diff --git a/zaojiaManagement/zaojia-productManagement/src/main/resources/mapper/productManagement/DocumentTaskResultsMapper.xml b/zaojiaManagement/zaojia-productManagement/src/main/resources/mapper/productManagement/DocumentTaskResultsMapper.xml index 29e0ba8..4225cb4 100644 --- a/zaojiaManagement/zaojia-productManagement/src/main/resources/mapper/productManagement/DocumentTaskResultsMapper.xml +++ b/zaojiaManagement/zaojia-productManagement/src/main/resources/mapper/productManagement/DocumentTaskResultsMapper.xml @@ -12,6 +12,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + +