From c18fb054e0ea4f9ee5ea420a516fe6b0601f05f2 Mon Sep 17 00:00:00 2001 From: genggengtang Date: Tue, 10 Mar 2026 22:48:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=A1=E8=AE=A1=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E7=94=9F=E6=88=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/sql/update_260308.sql | 61 + .../controller/AuditContent1Controller.java | 11 +- .../controller/AuditEvidenceController.java | 78 +- .../ai/controller/AuditReportController.java | 1013 +++++++++++++++++ .../ai/dto/AuditEvidenceRequest.java | 8 + .../ai/dto/AuditReportGenRequest.java | 39 + .../ai/dto/AuditReportSaveRequest.java | 36 + .../gxwebsoft/ai/entity/AuditEvidence.java | 114 ++ .../com/gxwebsoft/ai/entity/AuditReport.java | 75 ++ .../ai/enums/AuditMatterTypeEnum.java | 57 + .../gxwebsoft/ai/enums/AuditReportEnum.java | 5 +- .../ai/mapper/AuditEvidenceMapper.java | 35 + .../ai/mapper/AuditReportMapper.java | 12 + .../ai/mapper/xml/AuditEvidenceMapper.xml | 100 ++ .../ai/param/AuditEvidenceParam.java | 65 ++ .../ai/service/AuditEvidenceService.java | 17 +- .../ai/service/AuditReportService.java | 32 + .../AuditContent9PersonnelServiceImpl.java | 2 +- .../impl/AuditEvidenceServiceImpl.java | 301 ++++- .../service/impl/AuditReportServiceImpl.java | 348 +++++- .../com/gxwebsoft/pwl/entity/PwlProject.java | 6 + src/main/resources/application.yml | 6 + 22 files changed, 2375 insertions(+), 46 deletions(-) create mode 100644 doc/sql/update_260308.sql create mode 100644 src/main/java/com/gxwebsoft/ai/dto/AuditReportGenRequest.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/AuditReportSaveRequest.java create mode 100644 src/main/java/com/gxwebsoft/ai/entity/AuditEvidence.java create mode 100644 src/main/java/com/gxwebsoft/ai/entity/AuditReport.java create mode 100644 src/main/java/com/gxwebsoft/ai/enums/AuditMatterTypeEnum.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AuditEvidenceMapper.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AuditReportMapper.java create mode 100644 src/main/java/com/gxwebsoft/ai/mapper/xml/AuditEvidenceMapper.xml create mode 100644 src/main/java/com/gxwebsoft/ai/param/AuditEvidenceParam.java diff --git a/doc/sql/update_260308.sql b/doc/sql/update_260308.sql new file mode 100644 index 0000000..565d10d --- /dev/null +++ b/doc/sql/update_260308.sql @@ -0,0 +1,61 @@ +ALTER TABLE `cms_demo`.`pwl_project` + ADD COLUMN `person_name` varchar(50) NULL COMMENT '针对用户名称' AFTER `company_id`, +ADD COLUMN `position` varchar(50) NULL COMMENT '用户职务' AFTER `person_name`; + +CREATE TABLE `audit_evidence` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `case_index` varchar(255) DEFAULT NULL COMMENT '案件编号', + `project_id` int(11) NOT NULL COMMENT '项目编号', + `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称', + `content_type` int(10) unsigned NOT NULL COMMENT '内容类型,1到11', + `audited_target` varchar(255) DEFAULT NULL COMMENT '被审计单位或个人', + `audit_matter_type` varchar(50) DEFAULT NULL COMMENT '审计事项类型', + `audit_matter` varchar(100) DEFAULT NULL COMMENT '审计事项描述', + `summary_title` varchar(255) DEFAULT NULL COMMENT '核心问题标题', + `audit_record` varchar(2000) DEFAULT NULL COMMENT '客观的审计核查事实记录', + `audit_finding` varchar(255) DEFAULT NULL COMMENT '审计发现的具体问题', + `evidence_basis` varchar(255) DEFAULT NULL COMMENT '定性依据', + `handling` varchar(255) DEFAULT NULL COMMENT '拟采取的处理措施', + `suggestion` varchar(255) DEFAULT NULL COMMENT '改进或整改建议', + `attachment` varchar(255) DEFAULT NULL COMMENT '随附的证明材料', + `auditors` varchar(50) DEFAULT NULL COMMENT '审计人员姓名', + `compile_date` varchar(20) DEFAULT NULL COMMENT '编制日期', + `provider_opinion` varchar(255) DEFAULT NULL COMMENT '证据提供单位或个人意见', + `provider_date` varchar(20) DEFAULT NULL COMMENT '证据提供日期', + `attachment_pages` int(11) DEFAULT NULL COMMENT '附件页数', + `feedback_deadline` varchar(100) DEFAULT NULL COMMENT '反馈期限', + `data` longtext COMMENT '取证单数据', + `user_id` int(11) DEFAULT NULL COMMENT '用户ID', + `username` varchar(30) DEFAULT NULL COMMENT '用户名', + `status` int(11) DEFAULT NULL COMMENT '状态, 0正常, 1冻结', + `deleted` int(11) DEFAULT NULL COMMENT '是否删除, 0否, 1是', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户id', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='审计取证单表'; + + +-- 审计报告表 +CREATE TABLE IF NOT EXISTS `audit_report` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键 ID', + `project_id` INT(11) DEFAULT NULL COMMENT '项目编号', + `project_name` VARCHAR(255) DEFAULT NULL COMMENT '项目名称', + `case_index` VARCHAR(100) DEFAULT NULL COMMENT '案引号', + `audited_target` VARCHAR(255) DEFAULT NULL COMMENT '被审计单位', + `report_content` LONGTEXT COMMENT '报告内容(JSON 格式,包含 sections 和 previewHtml)', + `preview_html` LONGTEXT COMMENT '报告预览 HTML', + `section_count` INT(11) DEFAULT NULL COMMENT '章节数量', + `user_id` INT(11) DEFAULT NULL COMMENT '用户 ID', + `username` VARCHAR(100) DEFAULT NULL COMMENT '用户名', + `status` INT(1) DEFAULT 0 COMMENT '状态,0 正常,1 冻结', + `deleted` INT(1) DEFAULT 0 COMMENT '是否删除,0 否,1 是', + `tenant_id` INT(11) DEFAULT NULL COMMENT '租户 id', + `form_commit` INT(11) DEFAULT NULL COMMENT '表单提交标识,用于判断记录是否存在', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + KEY `idx_project_id` (`project_id`), + KEY `idx_form_commit` (`form_commit`), + KEY `idx_project_form` (`project_id`, `form_commit`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计报告表'; \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java index 8c63b36..bfe7c44 100644 --- a/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent1Controller.java @@ -12,6 +12,7 @@ import com.gxwebsoft.common.core.web.ApiResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -26,19 +27,17 @@ import java.util.stream.Collectors; * 审计内容1控制器 - 八项规定对比分析 */ @Slf4j +@RequiredArgsConstructor @Tag(name = "审计内容1-八项规定") @RestController @RequestMapping("/api/ai/auditContent1") public class AuditContent1Controller extends BaseAuditContentController { - @Autowired - private AuditContent1LeaderListService auditContent1LeaderListService; + private final AuditContent1LeaderListService auditContent1LeaderListService; - @Autowired - private AuditContent1ExpenseService auditContent1ExpenseService; + private final AuditContent1ExpenseService auditContent1ExpenseService; - @Autowired - private AuditContent1EightRegService auditContent1EightRegService; + private final AuditContent1EightRegService auditContent1EightRegService; /** * 生成领导班子名单数据 diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java b/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java index 0bec5e0..59dafc6 100644 --- a/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditEvidenceController.java @@ -2,18 +2,25 @@ package com.gxwebsoft.ai.controller; import com.alibaba.fastjson.JSONObject; import com.gxwebsoft.ai.dto.AuditEvidenceRequest; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.param.AuditEvidenceParam; import com.gxwebsoft.ai.service.AuditEvidenceService; import com.gxwebsoft.common.core.web.ApiResult; import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; import com.gxwebsoft.common.system.entity.User; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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 javax.servlet.http.HttpServletResponse; @@ -40,36 +47,83 @@ import cn.afterturn.easypoi.word.WordExportUtil; */ @Slf4j @Tag(name = "审计取证单") +@RequiredArgsConstructor @RestController @RequestMapping("/api/ai/auditEvidence") public class AuditEvidenceController extends BaseController { - @Autowired - private AuditEvidenceService auditEvidenceService; - - @Autowired - private TemplateConfig templateConfig; + private final AuditEvidenceService auditEvidenceService; + + private final TemplateConfig templateConfig; /** * 生成审计取证单 */ @Operation(summary = "生成审计取证单") @PostMapping("/generate") - public ApiResult generateAuditEvidence(@RequestBody AuditEvidenceRequest request) { + public ApiResult generateAuditEvidence(@RequestBody AuditEvidenceRequest request) { try { - final User loginUser = getLoginUser(); - request.setUserName(loginUser.getUsername()); - + final User loginUser = getLoginUser(); + request.setUserName(loginUser.getUsername()); + log.info("接收到审计取证单生成请求 - 用户: {}, 项目: {}", request.getUserName(), request.getProjectName()); - JSONObject result = auditEvidenceService.generateAuditEvidence(request); + return auditEvidenceService.generateAuditEvidence(request); + /*JSONObject result = auditEvidenceService.generateAuditEvidence(request); if (Boolean.TRUE.equals(result.getBoolean("success"))) { return success(result); } else { return fail(result.getString("error") != null ? result.getString("error") : "生成审计取证单失败"); - } + }*/ } catch (Exception e) { log.error("生成审计取证单异常", e); - return fail("生成审计取证单异常: " + e.getMessage()); + return fail("生成审计取证单异常: " + e.getMessage(), null); + } + } + + /** + * 分页查询审计取证单记录 + */ + @Operation(summary = "分页查询审计取证单记录") + @PostMapping("/page") + public ApiResult> getAuditEvidencePage( + @Parameter(description = "页码,默认为 1") @RequestParam(defaultValue = "1") Integer current, + @Parameter(description = "页面大小,默认为 10") @RequestParam(defaultValue = "10") Integer size, + @RequestBody(required = false) AuditEvidenceParam param) { + try { + // 创建分页对象 + param.setPage(current.longValue()); + param.setLimit(size.longValue()); + PageParam page = + new PageParam<>(param); + + // 调用服务层分页查询方法 + PageResult resultPage = + auditEvidenceService.getAuditEvidencePage(page, param); + + return success(resultPage); + } catch (Exception e) { + log.error("查询审计取证单分页记录失败", e); + return fail("查询审计取证单分页记录失败:" + e.getMessage(), null); + } + } + + /** + * 保存审计取证单到数据库 + */ + @Operation(summary = "保存审计取证单") + @PostMapping("/save") + public ApiResult saveAuditEvidence(@RequestBody AuditEvidenceRequest request) { + try { + final User loginUser = getLoginUser(); + request.setUserName(loginUser.getUsername()); + request.setUserId(loginUser.getUserId()); + request.setTenantId(loginUser.getTenantId()); + + log.info("接收到审计取证单保存请求 - 用户:{}, 项目:{}", request.getUserName(), request.getProjectName()); + return auditEvidenceService.saveAuditEvidence(request); + } catch (Exception e) { + log.error("保存审计取证单异常", e); + return fail("保存审计取证单异常:" + e.getMessage(), null); } } diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java index 760175b..4d10585 100644 --- a/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditReportController.java @@ -1,30 +1,59 @@ package com.gxwebsoft.ai.controller; +import java.io.IOException; import java.io.OutputStream; +import java.math.BigInteger; +import java.net.URLEncoder; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.mapper.AuditEvidenceMapper; +import com.gxwebsoft.pwl.entity.PwlProject; +import com.gxwebsoft.pwl.service.PwlProjectService; +import lombok.extern.slf4j.Slf4j; import org.apache.poi.openxml4j.util.ZipSecureFile; +import org.apache.poi.xwpf.usermodel.BreakType; +import org.apache.poi.xwpf.usermodel.ParagraphAlignment; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; +import org.apache.poi.xwpf.usermodel.XWPFTable; +import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.apache.poi.xwpf.usermodel.XWPFTableRow; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc; import org.springframework.beans.factory.annotation.Autowired; +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.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; import com.alibaba.fastjson.JSONObject; import com.gxwebsoft.ai.config.TemplateConfig; import com.gxwebsoft.ai.dto.AuditReportRequest; +import com.gxwebsoft.ai.dto.AuditReportSaveRequest; import com.gxwebsoft.ai.dto.KnowledgeBaseRequest; +import com.gxwebsoft.ai.entity.AuditReport; import com.gxwebsoft.ai.enums.AuditReportEnum; +import com.gxwebsoft.ai.service.AuditReportService; import com.gxwebsoft.ai.service.KnowledgeBaseService; import com.gxwebsoft.ai.util.AuditReportUtil; import com.gxwebsoft.common.core.web.ApiResult; @@ -45,6 +74,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Tag(name = "审计报告") @RestController @RequestMapping("/api/ai/auditReport") +@Slf4j public class AuditReportController extends BaseController { @Autowired @@ -53,6 +83,15 @@ public class AuditReportController extends BaseController { @Autowired private KnowledgeBaseService knowledgeBaseService; + @Autowired + private AuditReportService auditReportService; + + @Autowired + private PwlProjectService pwlProjectService; + + @Autowired + private AuditEvidenceMapper auditEvidenceMapper; + private String invok(String query, String knowledge, String history, String suggestion, String title, String userName) { // 构建请求体 JSONObject requestBody = new JSONObject(); @@ -170,6 +209,946 @@ public class AuditReportController extends BaseController { } } + /** + * 根据项目 ID 查询审计报告和取证单数据 + */ + @Operation(summary = "根据项目 ID 查询审计数据") + @PostMapping("/queryAuditDataByProjectId") + public ApiResult> queryAuditDataByProjectId( + @RequestParam Integer projectId, + @RequestBody(required = false) Map requestBody) { + try { + log.info("根据项目 ID 查询审计数据和取证单数据 - 项目 ID: {}, 请求体:{}", projectId, requestBody); + + // 从请求体中获取选中的取证单 ID 列表 + List evidenceIds = new ArrayList<>(); + if (requestBody != null && requestBody.containsKey("evidenceIds")) { + Object evidenceIdsObj = requestBody.get("evidenceIds"); + if (evidenceIdsObj instanceof List) { + List evidenceIdList = (List) evidenceIdsObj; + for (Object id : evidenceIdList) { + if (id instanceof Integer) { + evidenceIds.add(((Integer) id).longValue()); + } else if (id instanceof Long) { + evidenceIds.add((Long) id); + } else if (id instanceof Number) { + evidenceIds.add(((Number) id).longValue()); + } + } + log.info("提取到勾选取证单 IDs: {}", evidenceIds); + } + } + + // 调用 Service 方法,传入证据 ID 列表 + Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, evidenceIds); + return success(data); + } catch (Exception e) { + log.error("查询审计数据异常", e); + return fail("查询审计数据异常:" + e.getMessage(), null); + } + } + + /** + * 根据项目 ID 生成并下载审计报告(从数据库查询所有数据) + */ + @Operation(summary = "根据项目 ID 生成审计报告") + @PostMapping("/generateByProjectId") + public void generateAuditReportByProjectId( + @RequestParam Integer projectId, + HttpServletResponse response) { + double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio(); + + try { + ZipSecureFile.setMinInflateRatio(0.001); + + // 1. 查询该项目的所有审计数据(包括报告和取证单),并对所有取证单进行 AI 分析 + List allEvidences = new ArrayList<>(); + LambdaQueryWrapper evidenceWrapper = new LambdaQueryWrapper<>(); + evidenceWrapper.eq(AuditEvidence::getProjectId, projectId) + .eq(AuditEvidence::getDeleted, 0); + allEvidences = auditEvidenceMapper.selectList(evidenceWrapper); + + log.info("=== 生成 Word 报告 ==="); + log.info("项目 ID: {}", projectId); + log.info("查询到的取证单总数:{}", allEvidences.size()); + + // 提取所有证据 ID + List allEvidenceIds = allEvidences.stream() + .map(AuditEvidence::getId) + .collect(Collectors.toList()); + + log.info("勾选取证单 IDs: {}", allEvidenceIds); + + // 调用带分析的接口 + Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, allEvidenceIds); + List reports = (List) data.get("reports"); + String evaluate = (String) data.get("evaluate"); // 审计总体评价 + String suggestion = (String) data.get("suggestion"); // 改进意见和建议 + + log.info("=== Service 返回的数据 ==="); + log.info("evaluate 字数:{}", evaluate != null ? evaluate.length() : 0); + log.info("suggestion 字数:{}", suggestion != null ? suggestion.length() : 0); + log.info("evaluate 内容:{}", evaluate); + log.info("suggestion 内容:{}", suggestion); + + // 2. 构建内容映射 + Map contentMap = new HashMap<>(); + for (AuditReport report : reports) { + if (report.getFormCommit() != null && StrUtil.isNotBlank(report.getReportContent())) { + String content = extractTextFromReportContent(report.getReportContent()); +// log.info("formCommit={}, 提取的内容={}", report.getFormCommit(), content); + contentMap.put(report.getFormCommit(), content); + } + } + + // 3. 获取项目信息 + PwlProject project = pwlProjectService.getById(projectId); + String projectName = project != null ? (project.getName() != null ? project.getName() : "") : ""; + + // 4. 使用 XWPFDocument 直接生成 Word 文档(不使用模板) + XWPFDocument document = new XWPFDocument(); + + // 5. 添加封面页 + addCoverPage(document, project); + + // 添加分页符 + addPageBreak(document); + + // 6. 添加正文标题(居中) + addBodyTitle(document, "经济责任审计报告"); + + // 7. 添加文号(右对齐,蓝色)- 从 pwl_project.case_index 获取 + if (project != null && StrUtil.isNotBlank(project.getCaseIndex())) { + addDocumentNumber(document, project.getCaseIndex()); + } else { + addDocumentNumber(document, "XX 专字 [202X]4501Z000X 号"); + } + + // 8. 添加委托单位名称 + addClientUnit(document, project); + + // 9. 添加审计引言段落(从 form_commit=30 获取) + addAuditIntro(document, contentMap, project); + + // 10. 按照前端页面的固定目录结构生成内容(带标题,支持子章节) + addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project); + + addSectionWithTitle(document, String.format("二、%s主要业绩", project.getPersonName()), null, contentMap, project); + + addSectionWithTitle(document, "三、履行经济责任的主要情况", + Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); + + // 四、审计总体评价(从接口获取 evaluate 字段) + if (StrUtil.isNotBlank(evaluate)) { + XWPFParagraph sectionPara4 = document.createParagraph(); + sectionPara4.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun4 = sectionPara4.createRun(); + sectionRun4.setText("四、审计总体评价"); + sectionRun4.setBold(true); + sectionRun4.setFontSize(18); + sectionRun4.setFontFamily("仿宋_GB2312"); + sectionRun4.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 2 字符 + addParagraphWithIndent(document, evaluate); + } + + // 五、改进意见和建议(从接口获取 suggestion 字段) + if (StrUtil.isNotBlank(suggestion)) { + XWPFParagraph sectionPara5 = document.createParagraph(); + sectionPara5.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun5 = sectionPara5.createRun(); + sectionRun5.setText("五、改进意见和建议"); + sectionRun5.setBold(true); + sectionRun5.setFontSize(18); + sectionRun5.setFontFamily("仿宋_GB2312"); + sectionRun5.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 2 字符 + addParagraphWithIndent(document, suggestion); + } + + // 六、其他事项说明(先添加标题) + XWPFParagraph sectionPara6 = document.createParagraph(); + sectionPara6.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun6 = sectionPara6.createRun(); + sectionRun6.setText("六、其他事项说明"); + sectionRun6.setBold(true); + sectionRun6.setFontSize(18); + sectionRun6.setFontFamily("仿宋_GB2312"); + sectionRun6.setColor("2c3e50"); + document.createParagraph(); + + // 添加固定内容(两段话) + addOtherMattersSectionFixedContent(document, project); + + // 添加分页符 + addPageBreak(document); + + // 11. 添加尾页(签名页) + addSignaturePage(document); + + // 10. 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + String fileName = "经济责任审计报告_" + projectName + ".docx"; + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8")); + + // 11. 输出文档 + try (OutputStream out = response.getOutputStream()) { + document.write(out); + out.flush(); + } + + } catch (Exception e) { + throw new RuntimeException("生成审计报告失败", e); + } finally { + ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); + } + } + + /** + * 根据项目 ID 和选中的取证单生成并下载审计报告 + */ + @Operation(summary = "根据项目 ID 和选中的取证单生成审计报告") + @PostMapping("/generateWithEvidences") + public void generateAuditReportWithEvidences( + @RequestParam Integer projectId, + @RequestBody(required = false) Map requestBody, + HttpServletResponse response) { + double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio(); + + try { + ZipSecureFile.setMinInflateRatio(0.001); + + // 1. 从请求体中获取选中的取证单 ID 列表 + List evidenceIds = new ArrayList<>(); + if (requestBody != null && requestBody.containsKey("evidenceIds")) { + List evidenceIdInts = (List) requestBody.get("evidenceIds"); +// for (Integer id : evidenceIdInts) { +// evidenceIds.add(id.longValue()); +// } + evidenceIds = evidenceIdInts.stream().distinct().map(Long::valueOf).collect(Collectors.toList()); + } + + log.info("根据项目 ID 和选中的取证单生成审计报告 - 项目 ID: {}, 取证单 IDs: {}", projectId, evidenceIds); + + // 2. 查询该项目的所有审计数据(包括报告和取证单),并对选中的取证单进行 AI 分析 + Map data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, evidenceIds); + List reports = (List) data.get("reports"); + String evaluate = (String) data.get("evaluate"); // 审计总体评价 + String suggestion = (String) data.get("suggestion"); // 改进意见和建议 + + log.info("=== 生成 Word 报告(带勾选)==="); + log.info("项目 ID: {}", projectId); + log.info("勾选取证单 IDs: {}", evidenceIds); + log.info("evaluate 字数:{}", evaluate != null ? evaluate.length() : 0); + log.info("suggestion 字数:{}", suggestion != null ? suggestion.length() : 0); + + // 3. 筛选出用户选中的取证单 +// List selectedEvidences = new ArrayList<>(); +// if (!evidenceIds.isEmpty() && allEvidences != null) { +// for (AuditEvidence evidence : allEvidences) { +// if (evidenceIds.contains(evidence.getId())) { +// selectedEvidences.add(evidence); +// log.info("选中取证单:ID={}, summaryTitle={}", evidence.getId(), evidence.getSummaryTitle()); +// } +// } +// } +// log.info("共选中 {} 条取证单", selectedEvidences.size()); + + // 4. 构建内容映射 + Map contentMap = new HashMap<>(); + for (AuditReport report : reports) { + if (report.getFormCommit() != null && StrUtil.isNotBlank(report.getReportContent())) { + String content = extractTextFromReportContent(report.getReportContent()); +// log.info("formCommit={}, 提取的内容={}", report.getFormCommit(), content); + contentMap.put(report.getFormCommit(), content); + } + } + + // 5. 获取项目信息 + PwlProject project = pwlProjectService.getById(projectId); + String projectName = project != null ? (project.getName() != null ? project.getName() : "") : ""; + + // 6. 使用 XWPFDocument 直接生成 Word 文档(不使用模板) + XWPFDocument document = new XWPFDocument(); + + // 7. 添加封面页 + addCoverPage(document, project); + + // 添加分页符 + addPageBreak(document); + + // 8. 添加正文标题(居中) + addBodyTitle(document, "经济责任审计报告"); + + // 9. 添加文号(右对齐,蓝色)- 从 pwl_project.case_index 获取 + if (project != null && StrUtil.isNotBlank(project.getCaseIndex())) { + addDocumentNumber(document, project.getCaseIndex()); + } else { + addDocumentNumber(document, "XX 专字 [202X]4501Z000X 号"); + } + + // 10. 添加委托单位名称 + addClientUnit(document, project); + + // 11. 添加审计引言段落(从 form_commit=30 获取) + addAuditIntro(document, contentMap, project); + + // 12. 按照前端页面的固定目录结构生成内容(带标题,支持子章节) + addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project); + + addSectionWithTitle(document, String.format("二、%s主要业绩", project.getPersonName()), null, contentMap, project); + + addSectionWithTitle(document, "三、履行经济责任的主要情况", + Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project); + + // 四、审计总体评价(从接口获取 evaluate 字段) + if (StrUtil.isNotBlank(evaluate)) { + XWPFParagraph sectionPara4 = document.createParagraph(); + sectionPara4.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun4 = sectionPara4.createRun(); + sectionRun4.setText("四、审计总体评价"); + sectionRun4.setBold(true); + sectionRun4.setFontSize(18); + sectionRun4.setFontFamily("仿宋_GB2312"); + sectionRun4.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 2 字符 + addParagraphWithIndent(document, evaluate); + } + + // 五、改进意见和建议(从接口获取 suggestion 字段) + if (StrUtil.isNotBlank(suggestion)) { + XWPFParagraph sectionPara5 = document.createParagraph(); + sectionPara5.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun5 = sectionPara5.createRun(); + sectionRun5.setText("五、改进意见和建议"); + sectionRun5.setBold(true); + sectionRun5.setFontSize(18); + sectionRun5.setFontFamily("仿宋_GB2312"); + sectionRun5.setColor("2c3e50"); + document.createParagraph(); + + // 添加内容,首行缩进 2 字符 + addParagraphWithIndent(document, suggestion); + } + + // 六、其他事项说明(先添加标题) + XWPFParagraph sectionPara6 = document.createParagraph(); + sectionPara6.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun6 = sectionPara6.createRun(); + sectionRun6.setText("六、其他事项说明"); + sectionRun6.setBold(true); + sectionRun6.setFontSize(18); + sectionRun6.setFontFamily("仿宋_GB2312"); + sectionRun6.setColor("2c3e50"); + document.createParagraph(); + + // 添加固定内容(两段话) + addOtherMattersSectionFixedContent(document, project); + + // 如果选中了取证单,添加取证单详情部分 +// if (!selectedEvidences.isEmpty()) { +// addPageBreak(document); +// addEvidenceSection(document, selectedEvidences); +// } + + // 添加分页符 + addPageBreak(document); + + // 13. 添加尾页(签名页) + addSignaturePage(document); + + // 14. 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + String fileName = "经济责任审计报告_" + projectName + ".docx"; + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8")); + + // 15. 输出文档 + try (OutputStream out = response.getOutputStream()) { + document.write(out); + out.flush(); + } + + } catch (Exception e) { + throw new RuntimeException("生成审计报告失败", e); + } finally { + ZipSecureFile.setMinInflateRatio(originalMinInflateRatio); + } + } + + /** + * 添加封面页 + */ + private void addCoverPage(XWPFDocument document, PwlProject project) { + // 添加多个空行,使内容垂直居中 + for (int i = 0; i < 8; i++) { + document.createParagraph(); + } + + // 主标题:ABC 有限公司原董事长××同志(黑体,二号,加粗) + XWPFParagraph titlePara1 = document.createParagraph(); + titlePara1.setAlignment(ParagraphAlignment.CENTER); + XWPFRun titleRun1 = titlePara1.createRun(); + titleRun1.setText(String.format("%s原%s %s", project.getName(), project.getPosition(), project.getPersonName())); + titleRun1.setBold(true); + titleRun1.setFontSize(22); // 二号 + titleRun1.setFontFamily("黑体"); + + // 副标题:经济责任审计报告(黑体,二号,加粗) + XWPFParagraph titlePara2 = document.createParagraph(); + titlePara2.setAlignment(ParagraphAlignment.CENTER); + XWPFRun titleRun2 = titlePara2.createRun(); + titleRun2.setText("经济责任审计报告"); + titleRun2.setBold(true); + titleRun2.setFontSize(22); // 二号 + titleRun2.setFontFamily("黑体"); + + // 添加空行 + for (int i = 0; i < 6; i++) { + document.createParagraph(); + } + + // 文号(黑体,小二,加粗) + String docNumber = (project != null && StrUtil.isNotBlank(project.getCaseIndex())) + ? project.getCaseIndex() : "XX 专字 [202X]4501Z000X 号"; + XWPFParagraph docNumberPara = document.createParagraph(); + docNumberPara.setAlignment(ParagraphAlignment.CENTER); + XWPFRun docNumberRun = docNumberPara.createRun(); + docNumberRun.setText(docNumber); + docNumberRun.setBold(true); + docNumberRun.setFontSize(18); // 小二 + docNumberRun.setFontFamily("黑体"); + + // 添加多个空行到底部 + for (int i = 0; i < 15; i++) { + document.createParagraph(); + } + + // 底部署名:XX 会计师事务所(黑体,小二,加粗) + XWPFParagraph footerPara = document.createParagraph(); + footerPara.setAlignment(ParagraphAlignment.CENTER); + XWPFRun footerRun = footerPara.createRun(); + footerRun.setText("XX 会计师事务所"); + footerRun.setBold(true); + footerRun.setFontSize(18); // 小二 + footerRun.setFontFamily("黑体"); + } + + /** + * 添加分页符 + */ + private void addPageBreak(XWPFDocument document) { + XWPFParagraph para = document.createParagraph(); + XWPFRun run = para.createRun(); + run.addBreak(BreakType.PAGE); + } + + /** + * 添加带首行缩进的段落(支持多段文本) + * @param document Word 文档对象 + * @param text 文本内容(可能包含多个段落,用换行符分隔) + */ + private void addParagraphWithIndent(XWPFDocument document, String text) { + if (StrUtil.isBlank(text)) { + return; + } + + // 按换行符分割成多个段落 + String[] paragraphs = text.split("\n"); + + for (String paragraph : paragraphs) { + if (StrUtil.isNotBlank(paragraph.trim())) { + XWPFParagraph para = document.createParagraph(); + // 设置首行缩进 2 字符(1 字符约等于 240 二十分之一磅) + para.setFirstLineIndent(480); // 2 字符 = 480 单位 + + XWPFRun run = para.createRun(); + run.setText(paragraph.trim()); + run.setFontSize(12); // 小四 + run.setFontFamily("仿宋_GB2312"); + } + } + } + + /** + * 添加签名页(尾页) + */ + private void addSignaturePage(XWPFDocument document) { + // 创建一个 3 行 3 列的表格 + XWPFTable table = document.createTable(3, 3); + + // 设置表格宽度为 100%(自适应文档宽度) + // 表格宽度单位:二十分之一磅,A4 纸默认宽度约 9696(约 485 磅) + CTTbl ctTbl = table.getCTTbl(); + CTTblPr tblPr = ctTbl.getTblPr(); + CTTblWidth tblWidth = tblPr.getTblW(); + if (tblWidth == null) { + tblWidth = tblPr.addNewTblW(); + } + tblWidth.setW(java.math.BigInteger.valueOf(9696)); // 表格宽度(约 100% 文档宽度) + tblWidth.setType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth.DXA); + + // 设置表格右对齐 + tblPr.addNewJc().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc.RIGHT); + + // 第一行 + XWPFTableRow row1 = table.getRow(0); + // 设置第一行高度为 5 行字(12px * 5 = 60 磅,1 磅=1/72 英寸) + row1.setHeight(4320); // 单位是二十分之一磅,60 磅 * 72 = 4320 + + // 第一行第一列:XX 会计师事务所(仿宋_GB2312,小四)- 跨两行合并 + XWPFTableCell cell1_1 = row1.getCell(0); + // 设置垂直合并(向下合并) + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr1_1 = cell1_1.getCTTc().getTcPr(); + if (tcPr1_1 == null) { + tcPr1_1 = cell1_1.getCTTc().addNewTcPr(); + } + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance(); + vMerge.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART); + tcPr1_1.setVMerge(vMerge); + XWPFParagraph para1_1 = cell1_1.getParagraphs().get(0); + XWPFRun run1_1 = para1_1.createRun(); + run1_1.setText("XX 会计师事务所"); + run1_1.setFontSize(12); // 小四 + run1_1.setFontFamily("仿宋_GB2312"); + para1_1.setIndentationFirstLine(480); + + // 第一行第二列:中国注册会计师(仿宋_GB2312,小四) + XWPFTableCell cell1_2 = row1.getCell(1); + XWPFParagraph para1_2 = cell1_2.getParagraphs().get(0); + XWPFRun run1_2 = para1_2.createRun(); + run1_2.setText("中国注册会计师"); + run1_2.setFontSize(12); // 小四 + run1_2.setFontFamily("仿宋_GB2312"); + + // 第一行第三列:签名线(仿宋_GB2312,小四) + XWPFTableCell cell1_3 = row1.getCell(2); + XWPFParagraph para1_3 = cell1_3.getParagraphs().get(0); + XWPFRun run1_3 = para1_3.createRun(); + run1_3.setText("XXX"); + run1_3.setFontSize(12); // 小四 + run1_3.setFontFamily("仿宋_GB2312"); + para1_3.setAlignment(ParagraphAlignment.CENTER); + + // 第二行 + XWPFTableRow row2 = table.getRow(1); + // 设置第二行高度为 5 行字(12px * 5 = 60 磅) + row2.setHeight(4320); // 单位是二十分之一磅,60 磅 * 72 = 4320 + + // 第二行第一列:被合并(不显示内容) + XWPFTableCell cell2_1 = row2.getCell(0); + // 设置垂直合并(继续上一行的合并) + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr2_1 = cell2_1.getCTTc().getTcPr(); + if (tcPr2_1 == null) { + tcPr2_1 = cell2_1.getCTTc().addNewTcPr(); + } + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge2 = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance(); + vMerge2.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE); + tcPr2_1.setVMerge(vMerge2); + + // 第二行第二列:中国注册会计师(仿宋_GB2312,小四) + XWPFTableCell cell2_2 = row2.getCell(1); + XWPFParagraph para2_2 = cell2_2.getParagraphs().get(0); + XWPFRun run2_2 = para2_2.createRun(); + run2_2.setText("中国注册会计师"); + run2_2.setFontSize(12); // 小四 + run2_2.setFontFamily("仿宋_GB2312"); + + // 第二行第三列:签名(仿宋_GB2312,小四) + XWPFTableCell cell2_3 = row2.getCell(2); + XWPFParagraph para2_3 = cell2_3.getParagraphs().get(0); + XWPFRun run2_3 = para2_3.createRun(); + run2_3.setText("XXX"); + run2_3.setFontSize(12); // 小四 + run2_3.setFontFamily("仿宋_GB2312"); + para2_3.setAlignment(ParagraphAlignment.CENTER); + + // 第三行 - 使用 gridAfter 来正确合并单元格 + XWPFTableRow row3 = table.getRow(2); + + // 第三行第一列:中国·南宁(仿宋_GB2312,小四) + XWPFTableCell cell3_1 = row3.getCell(0); + XWPFParagraph para3_1 = cell3_1.getParagraphs().get(0); + XWPFRun run3_1 = para3_1.createRun(); + run3_1.setText("中国·南宁"); + run3_1.setFontSize(12); // 小四 + run3_1.setFontFamily("仿宋_GB2312"); + para3_1.setIndentationFirstLine(480); + + // 第三行第二列和第三列合并:二〇二 X 年 X 月 XX 日(仿宋_GB2312,小四) + XWPFTableCell cell3_2 = row3.getCell(1); + // 设置合并单元格的属性 + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr = cell3_2.getCTTc().getTcPr(); + if (tcPr == null) { + tcPr = cell3_2.getCTTc().addNewTcPr(); + } + // 设置 gridSpan 为 2(合并 2 列,即第 2 列和第 3 列) + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber gridSpan = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber.Factory.newInstance(); + gridSpan.setVal(java.math.BigInteger.valueOf(2)); + tcPr.setGridSpan(gridSpan); + + XWPFParagraph para3_2 = cell3_2.getParagraphs().get(0); + XWPFRun run3_2 = para3_2.createRun(); + run3_2.setText("二〇二 X 年 X 月 XX 日"); + run3_2.setFontSize(12); // 小四 + run3_2.setFontFamily("仿宋_GB2312"); + // 右对齐 + para3_2.setAlignment(ParagraphAlignment.RIGHT); + + // 关键:删除第 3 列的 XML 节点,避免边框问题 + // 通过表格的 CTTbl 获取第三行,然后删除第三个单元格 + org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow ctRow3 = ctTbl.getTrList().get(2); + // 删除第三个单元格 (索引从 0 开始,所以删除索引 2) + if (ctRow3.getTcList().size() > 2) { + ctRow3.removeTc(2); + } + } + + /** + * 添加正文标题(居中) + */ + private void addBodyTitle(XWPFDocument document, String title) throws IOException { + XWPFParagraph titlePara = document.createParagraph(); + titlePara.setAlignment(ParagraphAlignment.CENTER); + XWPFRun titleRun = titlePara.createRun(); + titleRun.setText(title); + titleRun.setBold(true); + titleRun.setFontSize(16); // 三号 + titleRun.setFontFamily("仿宋_GB2312"); + + // 添加 2 个空行 + document.createParagraph(); + document.createParagraph(); + } + + /** + * 添加主标题 + */ + private void addMainTitle(XWPFDocument document, String title) throws IOException { + XWPFParagraph titlePara = document.createParagraph(); + titlePara.setAlignment(ParagraphAlignment.CENTER); + XWPFRun titleRun = titlePara.createRun(); + titleRun.setText(title); + titleRun.setBold(true); + titleRun.setFontSize(16); // 三号 + titleRun.setFontFamily("仿宋_GB2312"); + + // 添加 2 个空行 + document.createParagraph(); + document.createParagraph(); + } + + /** + * 添加文号(右对齐,蓝色) + */ + private void addDocumentNumber(XWPFDocument document, String docNumber) throws IOException { + XWPFParagraph para = document.createParagraph(); + para.setAlignment(ParagraphAlignment.RIGHT); + XWPFRun run = para.createRun(); + run.setText(docNumber); + run.setFontSize(12); // 小四 + run.setFontFamily("仿宋_GB2312"); + run.setColor("449ED4"); // 蓝色 + + // 添加 2 个空行 + document.createParagraph(); + document.createParagraph(); + } + + /** + * 添加委托单位名称 + */ + private void addClientUnit(XWPFDocument document, PwlProject project) throws IOException { + XWPFParagraph para = document.createParagraph(); + para.setAlignment(ParagraphAlignment.LEFT); + XWPFRun run = para.createRun(); + run.setText(project.getName()+":"); + run.setFontSize(12); // 小四 + run.setFontFamily("仿宋_GB2312"); + run.setBold(true); + run.setColor("449ED4"); // 蓝色 + + // 添加 1 个空行 + document.createParagraph(); + } + + /** + * 添加审计引言段落 + */ + private void addAuditIntro(XWPFDocument document, Map contentMap, PwlProject project) throws IOException { + // 从 form_commit=30 获取审计引言内容 + /*String intro = contentMap.get(30); + + if (intro == null || intro.trim().isEmpty()) { + // 如果数据库中没有数据,使用默认内容 + intro = "我们接受委托,自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日,对 XX 同志自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任公司董事长(具体职务)期间的经济责任履行情况进行了就地审计。公司及 XX 同志的责任是提供与本次审计相关的全部资料,并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下:"; + }*/ + String intro = "我们接受%s委托,自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日,对%s自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任公司%s期间的经济责任履行情况进行了就地审计。公司及%s责任是提供与本次审计相关的全部资料,并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下:"; + + // 整段话作为一个段落(仿宋_GB2312,小四) + XWPFParagraph para = document.createParagraph(); + para.setAlignment(ParagraphAlignment.LEFT); + XWPFRun run = para.createRun(); + run.setText(String.format(intro, project.getName(), project.getPersonName(), project.getPosition(), project.getPersonName())); + run.setFontSize(12); // 小四 + run.setFontFamily("仿宋_GB2312"); + + // 设置首行缩进 2 个中文字符(POI 中使用半字符单位,1 中文字符=2 英文字符=480 单位) + para.setIndentationFirstLine(480); // 1 个中文字符 + + // 添加 2 个空行 + document.createParagraph(); + document.createParagraph(); + } + + /** + * 添加标题 + */ + private void addTitle(XWPFDocument document, String title) throws IOException { + XWPFParagraph titlePara = document.createParagraph(); + titlePara.setAlignment(ParagraphAlignment.CENTER); + XWPFRun titleRun = titlePara.createRun(); + titleRun.setText(title); + titleRun.setBold(true); + titleRun.setFontSize(16); // 三号 + titleRun.setFontFamily("仿宋_GB2312"); + + // 添加空行 + document.createParagraph(); + } + + /** + * 添加章节 + */ + private void addSection(XWPFDocument document, String sectionTitle, List subSections) throws IOException { + // 一级标题(仿宋_GB2312,三号,加粗) + XWPFParagraph sectionPara = document.createParagraph(); + sectionPara.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun = sectionPara.createRun(); + sectionRun.setText(sectionTitle); + sectionRun.setBold(true); + sectionRun.setFontSize(16); // 三号 + sectionRun.setFontFamily("仿宋_GB2312"); + + // 添加空行 + document.createParagraph(); + + // 添加子章节 + if (subSections != null && !subSections.isEmpty()) { + for (String subSection : subSections) { + if (subSection != null && !subSection.trim().isEmpty()) { + // 按换行符分割内容 + String[] lines = subSection.split("\\n"); + for (String line : lines) { + if (!line.trim().isEmpty()) { + XWPFParagraph contentPara = document.createParagraph(); + contentPara.setAlignment(ParagraphAlignment.LEFT); + XWPFRun contentRun = contentPara.createRun(); + contentRun.setText(line.trim()); + contentRun.setFontSize(12); // 小四 + contentRun.setFontFamily("仿宋_GB2312"); + + // 设置首行缩进 1 个中文字符 + contentPara.setIndentationFirstLine(480); + } + } + } + } + } + + // 添加空行 + document.createParagraph(); + } + + /** + * 添加章节内容(只显示内容,不显示标题) + */ + private void addSectionContent(XWPFDocument document, String content) throws IOException { + if (content == null || content.trim().isEmpty()) { + return; + } + + // 按换行符分割内容 + String[] lines = content.split("\\n"); + for (String line : lines) { + if (!line.trim().isEmpty()) { + XWPFParagraph contentPara = document.createParagraph(); + contentPara.setAlignment(ParagraphAlignment.LEFT); + XWPFRun contentRun = contentPara.createRun(); + contentRun.setText(line.trim()); + contentRun.setFontSize(12); // 小四 + contentRun.setFontFamily("仿宋_GB2312"); + + // 设置首行缩进 2 个中文字符 + contentPara.setIndentationFirstLine(480); + } + } + + // 添加空行 + document.createParagraph(); + } + + /** + * 添加章节(带标题和前缀,支持子章节) + */ + private void addSectionWithTitle(XWPFDocument document, String sectionTitle, + List formCommits, Map contentMap, PwlProject project) { + log.info("添加章节标题:{}", sectionTitle); + + // 一级标题(仿宋_GB2312,三号,加粗) + XWPFParagraph sectionPara = document.createParagraph(); + sectionPara.setAlignment(ParagraphAlignment.LEFT); + XWPFRun sectionRun = sectionPara.createRun(); + sectionRun.setText(sectionTitle); + sectionRun.setBold(true); + sectionRun.setFontSize(16); // 三号 + sectionRun.setFontFamily("仿宋_GB2312"); + + // 添加空行 + document.createParagraph(); + + // 添加子章节内容(根据固定的子章节结构) + if (formCommits != null && !formCommits.isEmpty()) { + for (Integer formCommit : formCommits) { + // 获取子章节标题(从 AuditReportEnum 中获取) + String subsectionTitle = getSubsectionTitle(formCommit, project); + + // 添加子章节标题(仿宋_GB2312,小四) + XWPFParagraph subsectionPara = document.createParagraph(); + subsectionPara.setAlignment(ParagraphAlignment.LEFT); + XWPFRun subsectionRun = subsectionPara.createRun(); + subsectionRun.setText(subsectionTitle); + subsectionRun.setFontSize(12); // 小四 + subsectionRun.setFontFamily("仿宋_GB2312"); + subsectionPara.setIndentationFirstLine(480); + + // 添加空行 + document.createParagraph(); + + // 添加子章节内容(如果有) + String content = contentMap.get(formCommit); + if (content != null && !content.trim().isEmpty()) { + // 按换行符分割内容 + String[] lines = content.split("\\n"); + for (String line : lines) { + if (!line.trim().isEmpty()) { + XWPFParagraph contentPara = document.createParagraph(); + contentPara.setAlignment(ParagraphAlignment.LEFT); + XWPFRun contentRun = contentPara.createRun(); + contentRun.setText(line.trim()); + contentRun.setFontSize(12); // 小四 + contentRun.setFontFamily("仿宋_GB2312"); + + // 设置首行缩进 1 个中文字符 + contentPara.setIndentationFirstLine(480); + } + } + + // 添加空行 + document.createParagraph(); + } + } + } + + // 添加空行 + document.createParagraph(); + } + + /** + * 根据 formCommit 获取子章节标题 + */ + private String getSubsectionTitle(Integer formCommit, PwlProject project) { + switch (formCommit) { + case 10: return "(一)审计依据"; + case 41: return String.format("(二)%s概况", project.getName()); + case 42: return String.format("(三)%s任职及分工情况", project.getPersonName()); + case 43: return "(四)实施审计的基本情况"; + case 51: return "(一)贯彻执行党和国家经济方针政策、决策部署情况"; + case 52: return "(二)企业发展战略规划的制定、执行和效果情况"; + case 53: return "(三)重大经济事项的决策、执行和效果情况"; + case 54: return "(四)企业法人治理结构的建立、健全和运行情况,内部控制制度的制定和执行情况"; + case 55: return "(五)企业财务的真实合法效益情况,风险管控情况,境外资产管理情况,生态环境保护情况"; + case 56: return "(六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况"; + case 57: return "(七)以往审计发现问题的整改情况"; + default: return ""; + } + } + + /** + * 添加其他事项说明的固定内容(两段话) + */ + private void addOtherMattersSectionFixedContent(XWPFDocument document, PwlProject project) { + // (一)本报告依据委托方及 ABC 公司提供的审计相关资料出具,审计意见受所提供资料真实性及完整性的影响。(仿宋_GB2312,小四) + XWPFParagraph para1 = document.createParagraph(); + para1.setAlignment(ParagraphAlignment.LEFT); + XWPFRun run1 = para1.createRun(); + run1.setText(String.format("(一)本报告依据委托方及%s提供的审计相关资料出具,审计意见受所提供资料真实性及完整性的影响。", project.getName())); + run1.setFontSize(12); // 小四 + run1.setFontFamily("仿宋_GB2312"); + para1.setIndentationFirstLine(480); + + // 添加空行 + document.createParagraph(); + + // (二)本报告仅供委托方 XX 单位了解 XX 同志经济责任履行情况之用,未经本所书面同意不得用作其他用途,因使用不当造成的后果,与执行本次专项审计业务的注册会计师及会计师事务所无关。(仿宋_GB2312,小四) + XWPFParagraph para2 = document.createParagraph(); + para2.setAlignment(ParagraphAlignment.LEFT); + XWPFRun run2 = para2.createRun(); + run2.setText(String.format("(二)本报告仅供委托方%s了解%s经济责任履行情况之用,未经本所书面同意不得用作其他用途,因使用不当造成的后果,与执行本次专项审计业务的注册会计师及会计师事务所无关。", project.getName(), project.getPersonName())); + run2.setFontSize(12); // 小四 + run2.setFontFamily("仿宋_GB2312"); + para2.setIndentationFirstLine(480); + + // 添加空行 + document.createParagraph(); + } + + /** + * 从 reportContent 中提取纯文本内容 + */ + private String extractTextFromReportContent(String reportContent) { + try { + JSONObject json = JSONObject.parseObject(reportContent); + StringBuilder sb = new StringBuilder(); + + if (json.containsKey("sections")) { + JSONArray sections = json.getJSONArray("sections"); + for (int i = 0; i < sections.size(); i++) { + JSONObject section = sections.getJSONObject(i); + + // 提取章节内容 + if (section.containsKey("content") && StrUtil.isNotBlank(section.getString("content"))) { + sb.append(section.getString("content")).append("\n"); + } + + // 提取小节内容 + if (section.containsKey("records")) { + JSONArray records = section.getJSONArray("records"); + for (int j = 0; j < records.size(); j++) { + JSONObject record = records.getJSONObject(j); +// if (record.containsKey("name") && StrUtil.isNotBlank(record.getString("name"))) { +// sb.append(record.getString("name")).append("\n"); // 子章节标题 +// } + if (record.containsKey("content") && StrUtil.isNotBlank(record.getString("content"))) { + sb.append(record.getString("content")).append("\n"); + } + } + } + } + } + + return sb.toString().trim(); + } catch (Exception e) { + // JSON 解析失败,直接返回原内容 + return reportContent; + } + } + private void processParagraphs(XWPFDocument document) { List originalParas = new ArrayList<>(document.getParagraphs()); @@ -205,4 +1184,38 @@ public class AuditReportController extends BaseController { } } + /** + * 保存审计报告到数据库 + */ + @Operation(summary = "保存审计报告") + @PostMapping("/save") + public ApiResult saveAuditReport(@RequestBody AuditReportSaveRequest request) { + try { + final User loginUser = getLoginUser(); + + log.info("接收到审计报告保存请求 - 用户:{}, 项目:{}", loginUser.getUsername(), request.getProjectName()); + return auditReportService.saveAuditReport(request); + } catch (Exception e) { + log.error("保存审计报告异常", e); + return fail("保存审计报告异常:" + e.getMessage(), null); + } + } + + /** + * 根据projectId 和 formCommit 查询审计报告 + */ + @Operation(summary = "查询审计报告") + @PostMapping("/query") + public ApiResult queryAuditReport( + @RequestParam Integer projectId, + @RequestParam Integer formCommit) { + try { + log.info("查询审计报告 - 项目 ID: {}, formCommit: {}", projectId, formCommit); + return auditReportService.queryAuditReport(projectId, formCommit); + } catch (Exception e) { + log.error("查询审计报告异常", e); + return fail("查询审计报告异常:" + e.getMessage(), null); + } + } + } diff --git a/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java index d32d1ec..2562cc1 100644 --- a/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java +++ b/src/main/java/com/gxwebsoft/ai/dto/AuditEvidenceRequest.java @@ -1,13 +1,19 @@ package com.gxwebsoft.ai.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data public class AuditEvidenceRequest { // 基础信息 private String caseIndex; // 案引号 + private Integer projectId; // 项目名称 private String projectName; // 项目名称 + @Schema(description = "内容类型,1到11") + private Integer contentType; private String auditedTarget; // 被审计单位或个人 + @Schema(description = "审计事项,对应AuditMatterTypeEnum") + private String auditMatterType; private String auditMatter; // 审计事项 private String summaryTitle; // 标题 private String auditRecord; // 审计记录 @@ -32,4 +38,6 @@ public class AuditEvidenceRequest { // 用户信息 private String userName; + private Integer userId; + private Integer tenantId; } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/AuditReportGenRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AuditReportGenRequest.java new file mode 100644 index 0000000..63e0397 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/AuditReportGenRequest.java @@ -0,0 +1,39 @@ +package com.gxwebsoft.ai.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "审计报告生成请求") +@Data +public class AuditReportGenRequest { + // 基础信息 + private Integer projectId; // 项目名称 + @Schema(description = "内容类型,1到11") + private Integer contentType; + private String auditedTarget; // 被审计单位或个人 + private String summaryTitle; // 标题 + private String auditRecord; // 审计记录 + private String auditFinding; // 审计发现 + private String evidenceBasis; // 定性依据 + private String handling; // 处理 + private String suggestion; // 建议 + private String attachment; // 附件 + private String auditors; // 审计人员 + private String compileDate; // 编制日期 + private String providerOpinion; // 证据提供单位意见 + private String providerDate; // 证据提供日期 + private String attachmentPages; // 附件页数 + private String feedbackDeadline; // 反馈期限 + + // 导出取证单使用 + private String pageIndex; + private String pageTotal; + + // 历史内容(用于工作流生成) + private String history; + + // 用户信息 + private String userName; + private Integer userId; + private Integer tenantId; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/AuditReportSaveRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AuditReportSaveRequest.java new file mode 100644 index 0000000..6360fcc --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/AuditReportSaveRequest.java @@ -0,0 +1,36 @@ +package com.gxwebsoft.ai.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 审计报告保存请求 DTO + */ +@Data +@Schema(description = "审计报告保存请求") +public class AuditReportSaveRequest { + + @Schema(description = "项目编号") + private Integer projectId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "案引号") + private String caseIndex; + + @Schema(description = "被审计单位") + private String auditedTarget; + + @Schema(description = "报告内容(JSON 格式)") + private String reportContent; + + @Schema(description = "报告预览 HTML") + private String previewHtml; + + @Schema(description = "章节数量") + private Integer sectionCount; + + @Schema(description = "表单提交标识") + private Integer formCommit; +} diff --git a/src/main/java/com/gxwebsoft/ai/entity/AuditEvidence.java b/src/main/java/com/gxwebsoft/ai/entity/AuditEvidence.java new file mode 100644 index 0000000..af5de85 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AuditEvidence.java @@ -0,0 +1,114 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 审计取证单表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AuditEvidence对象", description = "审计取证单表") +@TableName("audit_evidence") +public class AuditEvidence implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "案件编号") + private String caseIndex; + + @Schema(description = "项目编号") + private Integer projectId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "内容类型,1到11") + private Integer contentType; + + @Schema(description = "被审计单位或个人") + private String auditedTarget; + + @Schema(description = "审计事项类型,对应AuditMatterTypeEnum") + private String auditMatterType; + + @Schema(description = "审计事项描述") + private String auditMatter; + + @Schema(description = "核心问题标题") + private String summaryTitle; + + @Schema(description = "客观的审计核查事实记录") + private String auditRecord; + + @Schema(description = "审计发现的具体问题") + private String auditFinding; + + @Schema(description = "定性依据") + private String evidenceBasis; + + @Schema(description = "拟采取的处理措施") + private String handling; + + @Schema(description = "改进或整改建议") + private String suggestion; + + @Schema(description = "随附的证明材料") + private String attachment; + + @Schema(description = "审计人员姓名") + private String auditors; + + @Schema(description = "编制日期") + private String compileDate; + + @Schema(description = "证据提供单位或个人意见") + private String providerOpinion; + + @Schema(description = "证据提供日期") + private String providerDate; + + @Schema(description = "附件页数") + private Integer attachmentPages; + + @Schema(description = "反馈期限") + private String feedbackDeadline; + + @Schema(description = "取证单数据") + private String data; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + + @TableField(exist = false) + private Boolean success; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/entity/AuditReport.java b/src/main/java/com/gxwebsoft/ai/entity/AuditReport.java new file mode 100644 index 0000000..0af90cc --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AuditReport.java @@ -0,0 +1,75 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 审计报告表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AuditReport 对象", description = "审计报告表") +@TableName("audit_report") +public class AuditReport implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "主键 ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @Schema(description = "项目编号") + private Integer projectId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "案引号") + private String caseIndex; + + @Schema(description = "被审计单位") + private String auditedTarget; + + @Schema(description = "报告内容(JSON 格式,包含 sections 和 previewHtml)") + private String reportContent; + + @Schema(description = "报告预览 HTML") + private String previewHtml; + + @Schema(description = "章节数量") + private Integer sectionCount; + + @Schema(description = "用户 ID") + private Integer userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "状态,0 正常,1 冻结") + private Integer status; + + @Schema(description = "是否删除,0 否,1 是") + @TableLogic + private Integer deleted; + + @Schema(description = "租户 id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + + @Schema(description = "表单提交标识,用于判断记录是否存在") + private Integer formCommit; + + @TableField(exist = false) + private Boolean success; +} diff --git a/src/main/java/com/gxwebsoft/ai/enums/AuditMatterTypeEnum.java b/src/main/java/com/gxwebsoft/ai/enums/AuditMatterTypeEnum.java new file mode 100644 index 0000000..33c7e61 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/enums/AuditMatterTypeEnum.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.ai.enums; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +@Schema(description = "审计表类型") +public enum AuditMatterTypeEnum { + EIGHT_REGULATION(1,"eightReg", "八项规定"), + EXPENSE(1,"expense", "决策支出表"), + LEADER_LIST(1,"leaderList", "领导班子名单"), + STRATEGY(2,"strategyAudit", "单位发展战略执行"), + DECISION_TABLE(3,"decisionTable", "重大经济决策调查表"), + TRIPLE_ONE(3,"tripleOne", "三重一大"), + TARGET(4,"target", "目标责任制完成情况表"), + BUDGET_EXECUTION(5,"budgetExecution", "预算执行情况审计"), + BUDGET_MANAGE(5,"budgetManage", "预算管理审计"), + STATE_ASSETS(6,"assets", "国有资产管理审计"), + INVESTMENT_SITUATION(7,"investmentSituation", "重大投资情况"), + INTERNAL_CONTROL(8,"internalControl", "内部控制测试表"), + PERSONNEL_ESTABLISHMENT(9,"personnel", "人员编制管理审计"), + PARTY_CONDUCT(10,"partyConduct", "党风廉政建设责任制审计"), + HISTORY(11,"history", "历史审计问题整改"); + + private final Integer value; + private final String code; + private final String desc; + + public static AuditMatterTypeEnum getByCode(String code) { + for (AuditMatterTypeEnum type : AuditMatterTypeEnum.values()) { + if (type.getCode().equals(code)) { + return type; + } + } + return null; + } + + public static AuditMatterTypeEnum getByValue(Integer value) { + for (AuditMatterTypeEnum type : AuditMatterTypeEnum.values()) { + if (type.getValue().equals(value)) { + return type; + } + } + return null; + } + + public static AuditMatterTypeEnum getByDesc(String desc) { + for (AuditMatterTypeEnum type : AuditMatterTypeEnum.values()) { + if (type.getDesc().equals(desc)) { + return type; + } + } + return null; + } +} diff --git a/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java b/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java index 0cec26b..14e4872 100644 --- a/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java +++ b/src/main/java/com/gxwebsoft/ai/enums/AuditReportEnum.java @@ -24,11 +24,12 @@ public enum AuditReportEnum { INTEGRITY_COMPLIANCE(56, "五、审计内容和重点及审计方法-(六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况"), PREV_AUDIT_ISSUES(57, "五、审计内容和重点及审计方法-(七)对以往审计中发现问题的整改情况"), OTHER_MATTERS(58, "五、审计内容和重点及审计方法-(八)其他需要审计的事项"), - + + // 重要风险的识别及应对 RISK_IDENTIFY(61, "六、重要风险的识别及应对-(一)重要风险的识别"), RISK_RESPONSE(62, "六、重要风险的识别及应对-(二)风险的应对策略"), - + // 其他部分 TECHNIQUES(70, "七、审计技术方法"), SCHEDULE(80, "八、工作步骤与时间安排"), diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AuditEvidenceMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AuditEvidenceMapper.java new file mode 100644 index 0000000..6a6fc56 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AuditEvidenceMapper.java @@ -0,0 +1,35 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.param.AuditEvidenceParam; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 审计取证单表Mapper + * + * @author yc + */ +public interface AuditEvidenceMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return IPage + */ + IPage selectPageRel(@Param("page") IPage page, + @Param("param") AuditEvidenceParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") AuditEvidenceParam param); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AuditReportMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AuditReportMapper.java new file mode 100644 index 0000000..e09505d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AuditReportMapper.java @@ -0,0 +1,12 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gxwebsoft.ai.entity.AuditReport; +import org.apache.ibatis.annotations.Mapper; + +/** + * 审计报告 Mapper 接口 + */ +@Mapper +public interface AuditReportMapper extends BaseMapper { +} diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AuditEvidenceMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AuditEvidenceMapper.xml new file mode 100644 index 0000000..5928074 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/xml/AuditEvidenceMapper.xml @@ -0,0 +1,100 @@ + + + + + + + SELECT a.* + FROM audit_evidence a + + + AND a.id = #{param.id} + + + AND a.case_index = #{param.caseIndex} + + + AND a.project_name LIKE CONCAT('%', #{param.projectName}, '%') + + + AND a.audited_target LIKE CONCAT('%', #{param.auditedTarget}, '%') + + + AND a.audit_matter LIKE CONCAT('%', #{param.auditMatter}, '%') + + + AND a.summary_title LIKE CONCAT('%', #{param.summaryTitle}, '%') + + + AND a.content_type = #{param.contentType} + + + AND a.user_id = #{param.userId} + + + AND a.username LIKE CONCAT('%', #{param.username}, '%') + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.tenant_id = #{param.tenantId} + + + AND a.create_time >= #{param.startTime} + + + AND a.create_time <= #{param.endTime} + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/param/AuditEvidenceParam.java b/src/main/java/com/gxwebsoft/ai/param/AuditEvidenceParam.java new file mode 100644 index 0000000..08690d9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/param/AuditEvidenceParam.java @@ -0,0 +1,65 @@ +package com.gxwebsoft.ai.param; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import com.gxwebsoft.common.core.web.BaseParam; + +import java.time.LocalDateTime; + +/** + * 审计取证单查询参数 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AuditEvidenceParam对象", description = "审计取证单查询参数") +public class AuditEvidenceParam extends BaseParam { + + @Schema(description = "主键ID") + private Long id; + + @Schema(description = "案件编号") + private String caseIndex; + + @Schema(description = "项目编号") + private String projectId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "被审计单位或个人") + private String auditedTarget; + + @Schema(description = "审计事项类型") + private String auditMatterType; + + @Schema(description = "审计事项描述") + private String auditMatter; + + @Schema(description = "核心问题标题") + private String summaryTitle; + + @Schema(description = "内容类型,1到11") + private Integer contentType; + + @Schema(description = "用户ID") + private Integer userId; + + @Schema(description = "用户名") + private String username; + + @Schema(description = "删除状态, 0未删除, 1已删除") + private Integer deleted; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "开始时间") + private LocalDateTime startTime; + + @Schema(description = "结束时间") + private LocalDateTime endTime; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java b/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java index d711f32..5fe2c5a 100644 --- a/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java +++ b/src/main/java/com/gxwebsoft/ai/service/AuditEvidenceService.java @@ -2,10 +2,25 @@ package com.gxwebsoft.ai.service; import com.alibaba.fastjson.JSONObject; import com.gxwebsoft.ai.dto.AuditEvidenceRequest; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.param.AuditEvidenceParam; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; public interface AuditEvidenceService { /** * 生成审计取证单 */ - JSONObject generateAuditEvidence(AuditEvidenceRequest request); + ApiResult generateAuditEvidence(AuditEvidenceRequest request); + + /** + * 分页查询审计取证单记录 + */ + PageResult getAuditEvidencePage(PageParam page, AuditEvidenceParam param); + + /** + * 保存审计取证单到数据库(新增或更新) + */ + ApiResult saveAuditEvidence(AuditEvidenceRequest request); } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java b/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java index 40efa9c..76b68ac 100644 --- a/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java +++ b/src/main/java/com/gxwebsoft/ai/service/AuditReportService.java @@ -1,6 +1,13 @@ package com.gxwebsoft.ai.service; import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.dto.AuditReportSaveRequest; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.entity.AuditReport; +import com.gxwebsoft.common.core.web.ApiResult; + +import java.util.List; +import java.util.Map; public interface AuditReportService { @@ -11,4 +18,29 @@ public interface AuditReportService { String kbId, String libraryIds, String analysisLibrary, String projectLibrary, String userName); + + /** + * 保存审计报告到数据库(新增或更新) + */ + ApiResult saveAuditReport(AuditReportSaveRequest request); + + /** + * 根据 projectId 和 formCommit 查询审计报告 + */ + ApiResult queryAuditReport(Integer projectId, Integer formCommit); + + /** + * 根据项目 ID 查询所有审计报告和取证单数据 + * @param projectId 项目 ID + * @return Map,包含 reports (AuditReport 列表) 和 evidences (AuditEvidence 列表) + */ + Map queryAuditDataByProjectId(Integer projectId, List selectedEvidenceIds); + + /** + * 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析 + * @param projectId 项目 ID + * @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null) + * @return 包含 reports、evidences、evaluate、suggestion 的 Map + */ + Map queryAuditDataByProjectIdWithAnalysis(Integer projectId, List selectedEvidenceIds); } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java index ef5c1e7..d5d82cf 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent9PersonnelServiceImpl.java @@ -65,7 +65,7 @@ public class AuditContent9PersonnelServiceImpl extends AbstractAuditContentServi } catch (Exception e) { log.error("生成人员编制管理审计表失败", e); - return buildErrorResponse("生成失败: " + e.getMessage()); + return buildErrorResponse("生成人员编制管理审计失败: " + e.getMessage()); } } diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java index 5f44717..0e35eda 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditEvidenceServiceImpl.java @@ -1,27 +1,35 @@ package com.gxwebsoft.ai.service.impl; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gxwebsoft.ai.dto.AuditEvidenceRequest; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.enums.AuditMatterTypeEnum; +import com.gxwebsoft.ai.mapper.AuditEvidenceMapper; +import com.gxwebsoft.ai.param.AuditEvidenceParam; import com.gxwebsoft.ai.service.AuditEvidenceService; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; -import java.util.Date; - @Slf4j @Service -public class AuditEvidenceServiceImpl implements AuditEvidenceService { +public class AuditEvidenceServiceImpl extends ServiceImpl implements AuditEvidenceService { // Dify工作流配置 private static final String DIFY_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-DEhvF537rQfy6MvH9KeBKUVj"; @Override - public JSONObject generateAuditEvidence(AuditEvidenceRequest request) { + public ApiResult generateAuditEvidence(AuditEvidenceRequest request) { log.info("开始生成审计取证单 - 用户: {}, 项目: {}", request.getUserName(), request.getProjectName()); long startTime = System.currentTimeMillis(); @@ -33,21 +41,24 @@ public class AuditEvidenceServiceImpl implements AuditEvidenceService { // 2. 构建工作流请求 JSONObject requestBody = buildWorkflowRequest(knowledgeContext, request.getUserName()); - // 3. 调用Dify工作流 - JSONObject result = callWorkflow(requestBody, "审计取证单"); + // 3. 调用 Dify 工作流 + AuditEvidence result = callWorkflow(requestBody); - // 4. 添加处理时间等信息 - result.put("success", true); - result.put("processing_time", (System.currentTimeMillis() - startTime) + "ms"); - result.put("generated_time", new Date().toString()); + // 4. 保存到数据库 +// saveToDatabase(result, request); - log.info("审计取证单生成成功 - 处理时间: {}ms", (System.currentTimeMillis() - startTime)); - - return result; + // 5. 添加处理时间等信息 + if (result != null) { + result.setSuccess(true); + log.info("审计取证单生成成功 - ID: {}, 处理时间:{}ms", result.getId(), (System.currentTimeMillis() - startTime)); + } + + return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, result); } catch (Exception e) { log.error("生成审计取证单失败", e); - return buildErrorResponse("生成审计取证单失败: " + e.getMessage()); + return new ApiResult<>(1, "生成审计取证单失败: " + e.getMessage()); +// return buildErrorResponse("生成审计取证单失败: " + e.getMessage()); } } @@ -153,7 +164,7 @@ public class AuditEvidenceServiceImpl implements AuditEvidenceService { /** * 调用工作流 */ - private JSONObject callWorkflow(JSONObject requestBody, String workflowName) { + /*private JSONObject callWorkflow(JSONObject requestBody, String workflowName) { try { log.info("调用{}工作流,请求体长度: {}", workflowName, requestBody.toString().length()); @@ -189,16 +200,260 @@ public class AuditEvidenceServiceImpl implements AuditEvidenceService { log.error("调用{}工作流失败", workflowName, e); throw new RuntimeException("调用" + workflowName + "工作流失败: " + e.getMessage(), e); } + }*/ + + private AuditEvidence callWorkflow(JSONObject requestBody) { + try { + log.info("调用审计取证单工作流,请求体长度: {}", requestBody.toString().length()); + + String result = HttpUtil.createPost(DIFY_WORKFLOW_URL) + .header("Authorization", DIFY_WORKFLOW_TOKEN) + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .timeout(5 * 60 * 1000) // 5分钟 + .execute() + .body(); + + log.info("审计取证单工作流返回结果长度: {}", result.length()); + + JSONObject jsonResponse = JSONObject.parseObject(result); + + // 提取输出文本 + String outputText = jsonResponse.getJSONObject("data") + .getJSONObject("outputs") + .getString("result"); + + if (StrUtil.isBlank(outputText)) { + log.warn("审计取证单工作流返回结果为空"); + return null; + } + + // 解析为JSON对象 + AuditEvidence auditEvidence = JSONObject.parseObject(outputText, AuditEvidence.class); + + log.info("成功解析审计取证单工作流返回数据"); + return auditEvidence; + + } catch (Exception e) { + log.error("调用审计取证单工作流失败", e); + throw new RuntimeException("调用审计取证单工作流失败: " + e.getMessage(), e); + } } /** - * 构建错误响应 + * 保存审计取证单到数据库 */ - private JSONObject buildErrorResponse(String errorMessage) { - JSONObject result = new JSONObject(); - result.put("success", false); - result.put("error", errorMessage); - result.put("timestamp", System.currentTimeMillis()); - return result; + /*private void saveToDatabase(AuditEvidence auditEvidence, AuditEvidenceRequest request) { + try { +// AuditEvidence auditEvidence = new AuditEvidence(); + + // 基本信息 + auditEvidence.setAuditMatterType(request.getAuditMaterType()); + auditEvidence.setContentType(request.getContentType()); + auditEvidence.setCaseIndex(request.getCaseIndex()); + auditEvidence.setProjectId(request.getProjectId()); + auditEvidence.setProjectName(request.getProjectName()); + auditEvidence.setData(request.getHistory()); +// auditEvidence.setAuditedTarget(result.getString("auditedTarget")); +// auditEvidence.setAuditMatter(result.getString("auditMatter")); +// auditEvidence.setSummaryTitle(result.getString("summaryTitle")); + + // 审计内容 +// auditEvidence.setAuditRecord(result.getString("auditRecord")); +// auditEvidence.setAuditFinding(result.getString("auditFinding")); +// auditEvidence.setEvidenceBasis(result.getString("evidenceBasis")); +// auditEvidence.setHandling(result.getString("handling")); +// auditEvidence.setSuggestion(result.getString("suggestion")); +// auditEvidence.setAttachment(result.getString("attachment")); + + // 时间和人员信息 +// auditEvidence.setAuditors(result.getString("auditors")); +// auditEvidence.setCompileDate(result.getString("compileDate")); +// auditEvidence.setProviderOpinion(result.getString("providerOpinion")); +// auditEvidence.setProviderDate(result.getString("providerDate")); +// auditEvidence.setAttachmentPages(result.getString("attachmentPages")); +// auditEvidence.setFeedbackDeadline(result.getString("feedbackDeadline")); + + // 用户信息 + auditEvidence.setUserId(request.getUserId()); + auditEvidence.setUsername(request.getUserName()); + auditEvidence.setTenantId(request.getTenantId()); + + // 状态信息 + auditEvidence.setStatus(0); // 正常状态 + auditEvidence.setDeleted(0); // 未删除 + + // 保存到数据库 + baseMapper.insert(auditEvidence); + +// log.info("审计取证单保存成功 - ID: {}", auditEvidence.getId()); +// return auditEvidence; + + } catch (Exception e) { + log.error("保存审计取证单到数据库失败", e); + throw new RuntimeException("保存审计取证单到数据库失败: " + e.getMessage(), e); + } + }*/ + + @Override + public PageResult getAuditEvidencePage(PageParam page, AuditEvidenceParam param) { + log.info("查询审计取证单分页列表 - 页码: {}, 页面大小: {}", page.getCurrent(), page.getSize()); + + try { + // 调用Mapper的分页查询方法 + IPage list = baseMapper.selectPageRel(page, param); + + log.info("审计取证单分页查询成功 - 总记录数: {}, 当前页记录数: {}", + page.getTotal(), list.getTotal()); + + // 返回分页结果 + return new PageResult<>(list.getRecords(), page.getTotal()); + + } catch (Exception e) { + log.error("查询审计取证单分页列表失败", e); + throw new RuntimeException("查询审计取证单分页列表失败:" + e.getMessage(), e); + } + } + + @Override + public ApiResult saveAuditEvidence(AuditEvidenceRequest request) { + log.info("开始保存审计取证单 - 用户:{}, 项目:{}", request.getUserName(), request.getProjectName()); + + try { + // 1. 验证并转换 auditMatterType + if (StrUtil.isBlank(request.getAuditMatterType())) { + return new ApiResult<>(1, "审计事项类型不能为空"); + } + + AuditMatterTypeEnum matterType = AuditMatterTypeEnum.getByCode(request.getAuditMatterType()); + if (matterType == null) { + return new ApiResult<>(1, "无效的审计事项类型:" + request.getAuditMatterType()); + } + + // 2. 检查是否已存在(根据 projectId + auditMatterType) + if (request.getProjectId() == null) { + return new ApiResult<>(1, "项目编号不能为空"); + } + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AuditEvidence::getProjectId, request.getProjectId()) + .eq(AuditEvidence::getAuditMatterType, request.getAuditMatterType()) + .eq(AuditEvidence::getDeleted, 0); + + AuditEvidence existingEvidence = baseMapper.selectOne(queryWrapper); + + AuditEvidence auditEvidence; + if (existingEvidence != null) { + // 3. 更新现有记录 + log.info("发现已存在的记录,执行更新操作 - ID: {}", existingEvidence.getId()); + auditEvidence = existingEvidence; + + // 更新字段 + auditEvidence.setCaseIndex(request.getCaseIndex()); + auditEvidence.setProjectName(request.getProjectName()); + auditEvidence.setContentType(request.getContentType()); + + // 审计内容 + auditEvidence.setSummaryTitle(request.getSummaryTitle()); + auditEvidence.setAuditRecord(request.getAuditRecord()); + auditEvidence.setAuditFinding(request.getAuditFinding()); + auditEvidence.setEvidenceBasis(request.getEvidenceBasis()); + auditEvidence.setHandling(request.getHandling()); + auditEvidence.setSuggestion(request.getSuggestion()); + auditEvidence.setAttachment(request.getAttachment()); + + // 时间和人员信息 + auditEvidence.setAuditors(request.getAuditors()); + auditEvidence.setCompileDate(request.getCompileDate()); + auditEvidence.setProviderOpinion(request.getProviderOpinion()); + auditEvidence.setProviderDate(request.getProviderDate()); + + // 处理附件页数(转换为 Integer) + if (request.getAttachmentPages() != null && !request.getAttachmentPages().isEmpty()) { + try { + auditEvidence.setAttachmentPages(Integer.parseInt(request.getAttachmentPages())); + } catch (NumberFormatException e) { + log.warn("附件页数转换失败,使用默认值 0", e); + auditEvidence.setAttachmentPages(0); + } + } else { + auditEvidence.setAttachmentPages(0); + } + + auditEvidence.setFeedbackDeadline(request.getFeedbackDeadline()); + + // 将原始数据保存为 JSON 字符串 + auditEvidence.setData(com.alibaba.fastjson.JSONObject.toJSONString(request)); + + // 更新数据库 + baseMapper.updateById(auditEvidence); + + log.info("审计取证单更新成功 - ID: {}", auditEvidence.getId()); + } else { + // 4. 新增记录 + log.info("未发现已存在的记录,执行新增操作"); + auditEvidence = new AuditEvidence(); + + // 基本信息 + auditEvidence.setCaseIndex(request.getCaseIndex()); + auditEvidence.setProjectId(request.getProjectId()); + auditEvidence.setProjectName(request.getProjectName()); + auditEvidence.setContentType(request.getContentType()); + auditEvidence.setAuditMatterType(request.getAuditMatterType()); + auditEvidence.setAuditMatter(request.getAuditMatter()); + + // 审计内容 + auditEvidence.setSummaryTitle(request.getSummaryTitle()); + auditEvidence.setAuditRecord(request.getAuditRecord()); + auditEvidence.setAuditFinding(request.getAuditFinding()); + auditEvidence.setEvidenceBasis(request.getEvidenceBasis()); + auditEvidence.setHandling(request.getHandling()); + auditEvidence.setSuggestion(request.getSuggestion()); + auditEvidence.setAttachment(request.getAttachment()); + + // 时间和人员信息 + auditEvidence.setAuditors(request.getAuditors()); + auditEvidence.setCompileDate(request.getCompileDate()); + auditEvidence.setProviderOpinion(request.getProviderOpinion()); + auditEvidence.setProviderDate(request.getProviderDate()); + + // 处理附件页数(转换为 Integer) + if (request.getAttachmentPages() != null && !request.getAttachmentPages().isEmpty()) { + try { + auditEvidence.setAttachmentPages(Integer.parseInt(request.getAttachmentPages())); + } catch (NumberFormatException e) { + log.warn("附件页数转换失败,使用默认值 0", e); + auditEvidence.setAttachmentPages(0); + } + } else { + auditEvidence.setAttachmentPages(0); + } + + auditEvidence.setFeedbackDeadline(request.getFeedbackDeadline()); + + // 将原始数据保存为 JSON 字符串 + auditEvidence.setData(com.alibaba.fastjson.JSONObject.toJSONString(request)); + + // 用户信息 + auditEvidence.setUserId(request.getUserId()); + auditEvidence.setUsername(request.getUserName()); + auditEvidence.setTenantId(request.getTenantId()); + + // 状态信息 + auditEvidence.setStatus(0); // 正常状态 + auditEvidence.setDeleted(0); // 未删除 + + // 保存到数据库 + baseMapper.insert(auditEvidence); + + log.info("审计取证单保存成功 - ID: {}", auditEvidence.getId()); + } + + return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, auditEvidence); + + } catch (Exception e) { + log.error("保存审计取证单到数据库失败", e); + return new ApiResult<>(1, "保存审计取证单失败:" + e.getMessage()); + } } } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java index e7e1836..75e33ed 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditReportServiceImpl.java @@ -10,29 +10,48 @@ import com.gxwebsoft.ai.config.KnowledgeBaseConfig; import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; import com.gxwebsoft.ai.service.AuditReportService; import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import com.gxwebsoft.pwl.entity.PwlProject; import com.gxwebsoft.pwl.entity.PwlProjectLibrary; import com.gxwebsoft.pwl.service.PwlProjectLibraryService; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; +import com.gxwebsoft.pwl.service.PwlProjectService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.ai.dto.AuditReportSaveRequest; +import com.gxwebsoft.ai.entity.AuditEvidence; +import com.gxwebsoft.ai.entity.AuditReport; +import com.gxwebsoft.ai.mapper.AuditEvidenceMapper; +import com.gxwebsoft.ai.mapper.AuditReportMapper; +import com.gxwebsoft.common.core.Constants; +import com.gxwebsoft.common.core.web.ApiResult; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @Service -public class AuditReportServiceImpl implements AuditReportService { +public class AuditReportServiceImpl extends ServiceImpl implements AuditReportService { @Autowired private KnowledgeBaseClientFactory clientFactory; @Autowired private KnowledgeBaseConfig config; + + @Autowired + private PwlProjectService pwlProjectService; @Autowired private PwlProjectLibraryService pwlProjectLibraryService; + + @Autowired + private AuditEvidenceMapper auditEvidenceMapper; // 工作流配置 private static final String QUESTION_GENERATION_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; @@ -344,4 +363,331 @@ public class AuditReportServiceImpl implements AuditReportService { String[] keywords = {"制度", "办法", "规定", "流程", "标准", "规范", "要求"}; return String.join(" ", keywords); } + + @Override + public ApiResult saveAuditReport(AuditReportSaveRequest request) { + log.info("开始保存审计报告 - 用户:{}, 项目:{}", request.getAuditedTarget(), request.getProjectName()); + + try { + // 1. 验证必填字段 + if (request.getProjectId() == null) { + return new ApiResult<>(1, "项目编号不能为空"); + } + + if (request.getFormCommit() == null) { + return new ApiResult<>(1, "表单提交标识不能为空"); + } + + // 2. 检查是否已存在(根据projectId + formCommit) + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AuditReport::getProjectId, request.getProjectId()) + .eq(AuditReport::getFormCommit, request.getFormCommit()) + .eq(AuditReport::getDeleted, 0); + + AuditReport existingReport = baseMapper.selectOne(queryWrapper); + + AuditReport auditReport; + if (existingReport != null) { + // 3. 更新现有记录 + log.info("发现已存在的记录,执行更新操作 - ID: {}", existingReport.getId()); + auditReport = existingReport; + + // 更新字段 + auditReport.setProjectName(request.getProjectName()); + auditReport.setCaseIndex(request.getCaseIndex()); + auditReport.setAuditedTarget(request.getAuditedTarget()); + auditReport.setReportContent(request.getReportContent()); + auditReport.setPreviewHtml(request.getPreviewHtml()); + auditReport.setSectionCount(request.getSectionCount()); + + // 更新数据库 + baseMapper.updateById(auditReport); + + log.info("审计报告更新成功 - ID: {}", auditReport.getId()); + } else { + // 4. 新增记录 + log.info("未发现已存在的记录,执行新增操作"); + auditReport = new AuditReport(); + + // 基本信息 + auditReport.setProjectId(request.getProjectId()); + auditReport.setProjectName(request.getProjectName()); + auditReport.setCaseIndex(request.getCaseIndex()); + auditReport.setAuditedTarget(request.getAuditedTarget()); + auditReport.setReportContent(request.getReportContent()); + auditReport.setPreviewHtml(request.getPreviewHtml()); + auditReport.setSectionCount(request.getSectionCount()); + auditReport.setFormCommit(request.getFormCommit()); + + // 保存到数据库 + baseMapper.insert(auditReport); + + log.info("审计报告保存成功 - ID: {}", auditReport.getId()); + } + + // 设置 success 标志 + auditReport.setSuccess(true); + + return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, auditReport); + + } catch (Exception e) { + log.error("保存审计报告到数据库失败", e); + return new ApiResult<>(1, "保存审计报告失败:" + e.getMessage()); + } + } + + @Override + public ApiResult queryAuditReport(Integer projectId, Integer formCommit) { + log.info("查询审计报告 - 项目 ID: {}, formCommit: {}", projectId, formCommit); + + try { + if (projectId == null || formCommit == null) { + return new ApiResult<>(1, "项目编号和表单提交标识不能为空"); + } + + // 根据projectId + formCommit 查询 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AuditReport::getProjectId, projectId) + .eq(AuditReport::getFormCommit, formCommit) + .eq(AuditReport::getDeleted, 0); + + AuditReport auditReport = baseMapper.selectOne(queryWrapper); + + if (auditReport != null) { + log.info("查询到审计报告 - ID: {}", auditReport.getId()); + auditReport.setSuccess(true); + return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, auditReport); + } else { + log.info("未查询到审计报告"); + return new ApiResult<>(Constants.RESULT_OK_CODE, "未查询到记录", null); + } + + } catch (Exception e) { + log.error("查询审计报告失败", e); + return new ApiResult<>(1, "查询审计报告失败:" + e.getMessage()); + } + } + + @Override + public Map queryAuditDataByProjectId(Integer projectId, List selectedEvidenceIds) { + // 调用新方法,不传入 selectedEvidenceIds(兼容旧接口) + return queryAuditDataByProjectIdWithAnalysis(projectId, selectedEvidenceIds); + } + + /** + * 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析 + * @param projectId 项目 ID + * @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null) + * @return 包含 reports、evidences、evaluate、suggestion 的 Map + */ + @Override + public Map queryAuditDataByProjectIdWithAnalysis(Integer projectId, List selectedEvidenceIds) { + log.info("=== 开始查询审计数据 ==="); + log.info("项目 ID: {}, 勾选取证单 IDs: {}", projectId, selectedEvidenceIds); + + Map result = new HashMap<>(); + + try { + if (projectId == null) { + log.warn("项目 ID 为 null,返回空结果"); + result.put("reports", new ArrayList<>()); + result.put("evidences", new ArrayList<>()); + result.put("evaluate", ""); + result.put("suggestion", ""); + return result; + } + + // 1. 查询审计报告数据 + log.info("正在查询审计报告..."); + LambdaQueryWrapper reportWrapper = new LambdaQueryWrapper<>(); + reportWrapper.eq(AuditReport::getProjectId, projectId) + .eq(AuditReport::getDeleted, 0) + .orderByAsc(AuditReport::getFormCommit); + + List reports = baseMapper.selectList(reportWrapper); + log.info("查询到 {} 条审计报告记录", reports.size()); + + // 2. 查询审计取证单数据 - 查询该项目下所有的取证单 + log.info("正在查询审计取证单..."); + LambdaQueryWrapper evidenceWrapper = new LambdaQueryWrapper<>(); + evidenceWrapper.eq(AuditEvidence::getProjectId, projectId) + .eq(AuditEvidence::getDeleted, 0) + .orderByAsc(AuditEvidence::getId); + + List evidences = auditEvidenceMapper.selectList(evidenceWrapper); + log.info("查询到 {} 条审计取证单记录", evidences.size()); + + result.put("reports", reports); + result.put("evidences", evidences); + + // 3. 获取项目信息(用于获取项目名称) + PwlProject project = pwlProjectService.getById(projectId); + String projectName = project != null ? (project.getName() != null ? project.getName() : "") : ""; + result.put("projectName", projectName); // 添加项目名称到返回结果 + log.info("项目名称:{}", projectName); + + // 生成审计引言 + String auditIntro = generateAuditIntro(project); + result.put("auditIntro", auditIntro); // 添加审计引言到返回结果 + + // 生成其他事项说明 + String otherMatters = generateOtherMatters(project); + result.put("otherMatters", otherMatters); // 添加其他事项说明到返回结果 + + // 4. 如果传入了勾选的取证单 ID,进行字段叠加 + if (selectedEvidenceIds != null && !selectedEvidenceIds.isEmpty()) { + log.info("开始对 {} 条勾选的取证单进行字段叠加", selectedEvidenceIds.size()); + + // 筛选出勾选的取证单 + List selectedEvidences = evidences.stream() + .filter(e -> selectedEvidenceIds.contains(e.getId())) + .collect(Collectors.toList()); + + log.info("实际筛选出 {} 条取证单进行叠加", selectedEvidences.size()); + + if (!selectedEvidences.isEmpty()) { + // 叠加字段内容 + String evaluate = concatenateField(selectedEvidences, "auditFinding"); + String suggestion = concatenateField(selectedEvidences, "suggestion"); + result.put("evaluate", evaluate); + result.put("suggestion", suggestion); + + log.info("字段叠加完成,评价字数:{},建议字数:{}", + evaluate.length(), + suggestion.length()); + } else { + log.info("没有筛选到勾选的取证单,返回空的评价和建议"); + result.put("evaluate", ""); + result.put("suggestion", ""); + } + } else { + // 没有勾选,返回空值 + log.info("未传入勾选的取证单 ID,返回空的评价和建议"); + result.put("evaluate", ""); + result.put("suggestion", ""); + } + + log.info("=== 审计数据查询完成 ==="); + return result; + + } catch (Exception e) { + log.error("根据项目 ID 查询审计数据失败,异常信息:{}", e.getMessage(), e); + result.put("reports", new ArrayList<>()); + result.put("evidences", new ArrayList<>()); + result.put("evaluate", ""); + result.put("suggestion", ""); + return result; + } + } + + /** + * 生成审计引言段落 + * @param project 项目信息 + * @return 审计引言文本 + */ + private String generateAuditIntro(PwlProject project) { + if (project == null) { + return "我们接受委托,自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日,对 XX 同志自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任公司董事长(具体职务)期间的经济责任履行情况进行了就地审计。公司及 XX 同志的责任是提供与本次审计相关的全部资料,并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下:"; + } + + String intro = "我们接受%s委托,自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日,对%s自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任公司%s期间的经济责任履行情况进行了就地审计。公司及%s责任是提供与本次审计相关的全部资料,并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下:"; + + return String.format(intro, project.getName(), project.getPersonName(), project.getPosition(), project.getPersonName()); + } + + /** + * 生成其他事项说明内容 + * @param project 项目信息 + * @return 其他事项说明文本 + */ + private String generateOtherMatters(PwlProject project) { + StringBuilder otherMatters = new StringBuilder(); + + // 第一段 + otherMatters.append("(一)本报告依据委托方及"); + if (project != null && StrUtil.isNotBlank(project.getName())) { + otherMatters.append(project.getName()); + } else { + otherMatters.append("ABC 公司"); + } + otherMatters.append("提供的审计相关资料出具,审计意见受所提供资料真实性及完整性的影响。"); + + // 第二段 + otherMatters.append("(二)本报告仅供委托方 XX 单位了解 XX 同志经济责任履行情况之用,未经本所书面同意不得用作其他用途,因使用不当造成的后果,与执行本次专项审计业务的注册会计师及会计师事务所无关。"); + + return otherMatters.toString(); + } + + /** + * 叠加取证单字段内容 + * @param selectedEvidences 勾选的取证单列表 + * @param fieldName 字段名称(auditFinding 或 suggestion) + * @return 叠加后的字符串(包含标题和内容) + */ + private String concatenateField(List selectedEvidences, String fieldName) { + log.info("开始叠加字段:{}", fieldName); + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < selectedEvidences.size(); i++) { + AuditEvidence evidence = selectedEvidences.get(i); + String fieldValue = null; + + if ("auditFinding".equals(fieldName)) { + fieldValue = evidence.getAuditFinding(); + } else if ("suggestion".equals(fieldName)) { + fieldValue = evidence.getSuggestion(); + } + + log.info("取证单 ID={}, 序号={}, 字段={}, 值长度={}", + evidence.getId(), i+1, fieldName, fieldValue != null ? fieldValue.length() : 0); + + if (StrUtil.isNotBlank(fieldValue)) { + // 添加序号和审计事项作为标题 + String auditMatter = evidence.getAuditMatter(); + String numText = getChineseNumber(i + 1); + + if (StrUtil.isNotBlank(auditMatter)) { + result.append("(").append(numText).append(") ").append(auditMatter).append("\n"); + } else { + result.append("(").append(numText).append(") 事项").append(i + 1).append("\n"); + } + + // 将内容按换行符分割,每段都首行缩进 + String[] paragraphs = fieldValue.split("\\n+"); + for (String paragraph : paragraphs) { + if (StrUtil.isNotBlank(paragraph.trim())) { + result.append(" ").append(paragraph.trim()).append("\n"); + } + } + + // 如果不是最后一条,添加空行分隔 + if (i < selectedEvidences.size() - 1) { + result.append("\n"); + } + } else { + log.warn("取证单 ID={}, 字段 {} 为空或空字符串", evidence.getId(), fieldName); + } + } + + log.info("字段 {} 叠加完成,总长度={}", fieldName, result.length()); + return result.toString(); + } + + /** + * 获取中文序号(一、二、三...十) + * @param number 数字(1-10) + * @return 中文序号字符串 + */ + private String getChineseNumber(int number) { + String[] chineseNumbers = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"}; + + if (number >= 1 && number <= 10) { + return chineseNumbers[number]; + } else if (number > 10 && number <= 20) { + return "十" + (number == 10 ? "" : chineseNumbers[number - 10]); + } else { + // 超过 20 的数字,直接返回阿拉伯数字 + return String.valueOf(number); + } + } } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java b/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java index d174e00..1b946f4 100644 --- a/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java +++ b/src/main/java/com/gxwebsoft/pwl/entity/PwlProject.java @@ -41,6 +41,12 @@ public class PwlProject implements Serializable { @Schema(description = "项目标识") private String code; + @Schema(description = "针对用户名称") + private String personName; + + @Schema(description = "职务") + private String position; + @Schema(description = "案引号") private String caseIndex; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 369923b..fd37e1a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -235,3 +235,9 @@ payment: notify-url: "https://cms-api.websoft.top/api/shop/shop-order/notify" # 生产环境是否启用环境感知 environment-aware: false + +# AI 审计报告模板配置 +ai: + template: + word-template-path: classpath:templates/audit_report_template.docx + evidence-template-path: classpath:templates/audit_evidence_template.docx