修改审计报告生成逻辑

This commit is contained in:
2026-03-27 18:03:27 +08:00
parent c18fb054e0
commit 5dde003ab3
8 changed files with 866 additions and 118 deletions

BIN
doc/尾页.docx Normal file

Binary file not shown.

View File

@@ -16,14 +16,16 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
/**
* AI审计历史记录表控制器
*
* @author yc
*/
@Tag(name = "AI审计历史记录表管理")
@RestController
@RequestMapping("/api/ai/history")
@Slf4j
public class AiHistoryController extends BaseController {
@Resource
private AiHistoryService aiHistoryService;
@@ -116,7 +118,7 @@ public class AiHistoryController extends BaseController {
}
//@PreAuthorize("hasAuthority('ai:aiHistory:remove')")
@Operation(summary = "批量删除AI审计历史记录表")
@Operation(summary = "批量删除 AI审计历史记录表")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Long> ids) {
if (aiHistoryService.removeByIds(ids)) {
@@ -124,4 +126,32 @@ public class AiHistoryController extends BaseController {
}
return fail("删除失败");
}
/**
* 根据接口名称和项目 ID 查询最新的历史记录
*/
@Operation(summary = "查询最新的历史记录")
@GetMapping("/latest")
public ApiResult<AiHistory> getLatestHistory(
@RequestParam String interfaceName,
@RequestParam Long projectId) {
try {
log.info("===== 查询最新历史记录 =====");
log.info("interfaceName: {}", interfaceName);
log.info("projectId: {}", projectId);
AiHistory latestHistory = aiHistoryService.getLatestHistory(interfaceName, projectId);
if (latestHistory != null) {
log.info("查询到历史记录 - id: {}, interfaceName: {}", latestHistory.getId(), latestHistory.getInterfaceName());
} else {
log.warn("未查询到历史记录 - interfaceName: {}, projectId: {}", interfaceName, projectId);
}
return success(latestHistory);
} catch (Exception e) {
log.error("查询最新历史记录异常", e);
return fail("查询最新历史记录异常:" + e.getMessage(), null);
}
}
}

View File

@@ -1,8 +1,8 @@
package com.gxwebsoft.ai.controller;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -15,6 +15,7 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.ai.entity.AuditEvidence;
@@ -31,20 +32,15 @@ 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;
@@ -55,6 +51,7 @@ 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.service.AiHistoryService;
import com.gxwebsoft.ai.util.AuditReportUtil;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
@@ -92,6 +89,12 @@ public class AuditReportController extends BaseController {
@Autowired
private AuditEvidenceMapper auditEvidenceMapper;
// @Autowired
// private AuditEvidenceService auditEvidenceService;
@Autowired
private AiHistoryService aiHistoryService;
private String invok(String query, String knowledge, String history, String suggestion, String title, String userName) {
// 构建请求体
JSONObject requestBody = new JSONObject();
@@ -121,8 +124,7 @@ public class AuditReportController extends BaseController {
// 获取outputs字段
JSONObject outputs = data.getJSONObject("outputs");
// 获取outputs中的result字符串
String resultStr = outputs.getString("result");
return resultStr;
return outputs.getString("result");
}
/**
@@ -240,7 +242,8 @@ public class AuditReportController extends BaseController {
}
// 调用 Service 方法,传入证据 ID 列表
Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, evidenceIds);
PwlProject project = pwlProjectService.getById(projectId);
Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, evidenceIds);
return success(data);
} catch (Exception e) {
log.error("查询审计数据异常", e);
@@ -280,7 +283,8 @@ public class AuditReportController extends BaseController {
log.info("勾选取证单 IDs: {}", allEvidenceIds);
// 调用带分析的接口
Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, allEvidenceIds);
PwlProject project = pwlProjectService.getById(projectId);
Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, allEvidenceIds);
List<AuditReport> reports = (List<AuditReport>) data.get("reports");
String evaluate = (String) data.get("evaluate"); // 审计总体评价
String suggestion = (String) data.get("suggestion"); // 改进意见和建议
@@ -302,8 +306,7 @@ public class AuditReportController extends BaseController {
}
// 3. 获取项目信息
PwlProject project = pwlProjectService.getById(projectId);
String projectName = project != null ? (project.getName() != null ? project.getName() : "") : "";
String projectName = project.getName() != null ? project.getName() : "";
// 4. 使用 XWPFDocument 直接生成 Word 文档(不使用模板)
XWPFDocument document = new XWPFDocument();
@@ -333,40 +336,40 @@ public class AuditReportController extends BaseController {
// 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 字段)
// 二、总体评价(从接口获取 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");
XWPFParagraph sectionPara2 = document.createParagraph();
sectionPara2.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun2 = sectionPara2.createRun();
sectionRun2.setText("二、总体评价");
sectionRun2.setBold(true);
sectionRun2.setFontSize(18);
sectionRun2.setFontFamily("仿宋_GB2312");
sectionRun2.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进 2 字符
addParagraphWithIndent(document, evaluate);
// 添加内容,首行缩进
addParagraphWithoutIndent(document, evaluate);
}
// 五、改进意见和建议(从接口获取 suggestion 字段)
addSectionWithTitle(document, String.format("三、%s主要业绩", project.getPersonName()), null, contentMap, project);
addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定",
Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project);
// 五、审计建议(从接口获取 suggestion 字段)
if (StrUtil.isNotBlank(suggestion)) {
XWPFParagraph sectionPara5 = document.createParagraph();
sectionPara5.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun5 = sectionPara5.createRun();
sectionRun5.setText("五、改进意见和建议");
sectionRun5.setText("五、审计建议");
sectionRun5.setBold(true);
sectionRun5.setFontSize(18);
sectionRun5.setFontFamily("仿宋_GB2312");
sectionRun5.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进 2 字符
// 添加内容,首行缩进
addParagraphWithIndent(document, suggestion);
}
@@ -435,7 +438,8 @@ public class AuditReportController extends BaseController {
log.info("根据项目 ID 和选中的取证单生成审计报告 - 项目 ID: {}, 取证单 IDs: {}", projectId, evidenceIds);
// 2. 查询该项目的所有审计数据(包括报告和取证单),并对选中的取证单进行 AI 分析
Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(projectId, evidenceIds);
PwlProject project = pwlProjectService.getById(projectId);
Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, evidenceIds);
List<AuditReport> reports = (List<AuditReport>) data.get("reports");
String evaluate = (String) data.get("evaluate"); // 审计总体评价
String suggestion = (String) data.get("suggestion"); // 改进意见和建议
@@ -469,8 +473,7 @@ public class AuditReportController extends BaseController {
}
// 5. 获取项目信息
PwlProject project = pwlProjectService.getById(projectId);
String projectName = project != null ? (project.getName() != null ? project.getName() : "") : "";
String projectName = project.getName() != null ? project.getName() : "";
// 6. 使用 XWPFDocument 直接生成 Word 文档(不使用模板)
XWPFDocument document = new XWPFDocument();
@@ -500,40 +503,54 @@ public class AuditReportController extends BaseController {
// 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 字段)
// 二、总体评价(从接口获取 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");
XWPFParagraph sectionPara2 = document.createParagraph();
sectionPara2.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun2 = sectionPara2.createRun();
sectionRun2.setText("二、总体评价");
sectionRun2.setBold(true);
sectionRun2.setFontSize(18);
sectionRun2.setFontFamily("仿宋_GB2312");
sectionRun2.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进 2 字符
// 添加内容,首行缩进
addParagraphWithIndent(document, evaluate);
}
// 五、改进意见和建议(从接口获取 suggestion 字段
// 三、主要业绩(从 contentMap 获取 formCommit=30 的数据
if (contentMap.containsKey(30) && StrUtil.isNotBlank(contentMap.get(30))) {
XWPFParagraph sectionPara3 = document.createParagraph();
sectionPara3.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun3 = sectionPara3.createRun();
sectionRun3.setText(String.format("三、%s主要业绩", project.getPersonName()));
sectionRun3.setBold(true);
sectionRun3.setFontSize(18);
sectionRun3.setFontFamily("仿宋_GB2312");
sectionRun3.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进
addParagraphWithIndent(document, contentMap.get(30));
}
addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定",
Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project);
// 五、审计建议(从接口获取 suggestion 字段)
if (StrUtil.isNotBlank(suggestion)) {
XWPFParagraph sectionPara5 = document.createParagraph();
sectionPara5.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun5 = sectionPara5.createRun();
sectionRun5.setText("五、改进意见和建议");
sectionRun5.setText("五、审计建议");
sectionRun5.setBold(true);
sectionRun5.setFontSize(18);
sectionRun5.setFontFamily("仿宋_GB2312");
sectionRun5.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进 2 字符
// 添加内容,首行缩进
addParagraphWithIndent(document, suggestion);
}
@@ -581,6 +598,175 @@ public class AuditReportController extends BaseController {
}
}
/**
* 根据项目 ID、选中的取证单和章节内容生成审计报告使用前端传递的内容不查数据库
*/
@Operation(summary = "根据项目 ID 和章节内容生成审计报告")
@PostMapping("/generateWithContent")
public void generateAuditReportWithContent(
@RequestParam Integer projectId,
@RequestBody Map<String, Object> requestBody,
HttpServletResponse response) {
double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();
try {
ZipSecureFile.setMinInflateRatio(0.001);
// 1. 从请求体中获取选中的取证单 ID 列表
List<Long> evidenceIds = new ArrayList<>();
if (requestBody.containsKey("evidenceIds")) {
List<Integer> evidenceIdInts = (List<Integer>) requestBody.get("evidenceIds");
evidenceIds = evidenceIdInts.stream().distinct().map(Long::valueOf).collect(Collectors.toList());
}
// 2. 获取前端传递的章节内容
List<Map<String, Object>> chapters = (List<Map<String, Object>>) requestBody.get("chapters");
log.info("=== 生成 Word 报告(使用前端传递内容)===");
log.info("项目 ID: {}", projectId);
log.info("勾选取证单 IDs: {}", evidenceIds);
log.info("章节数量:{}", chapters != null ? chapters.size() : 0);
// 3. 构建内容映射(直接使用前端传递的内容)
Map<Integer, String> contentMap = new HashMap<>();
if (chapters != null) {
for (Map<String, Object> chapter : chapters) {
Integer formCommit = (Integer) chapter.get("formCommit");
String reportContent = (String) chapter.get("reportContent");
if (formCommit != null && StrUtil.isNotBlank(reportContent)) {
// 直接使用前端传递的内容,不需要从数据库读取
contentMap.put(formCommit, reportContent);
log.info("加载章节formCommit={}, content 长度={}", formCommit, reportContent.length());
}
}
}
// 4. 获取项目信息
PwlProject project = pwlProjectService.getById(projectId);
String projectName = project.getName() != null ? project.getName() : "";
// 5. 获取 evaluate 和 suggestion从 request body 获取)
String evaluate = requestBody.containsKey("evaluate") ? (String) requestBody.get("evaluate") : "";
String suggestion = requestBody.containsKey("suggestion") ? (String) requestBody.get("suggestion") : "";
// 6. 使用 XWPFDocument 直接生成 Word 文档(不使用模板)
XWPFDocument document = new XWPFDocument();
// 7. 添加封面页
addCoverPage(document, project);
// 添加分页符
addPageBreak(document);
// 8. 添加正文标题(居中)
addBodyTitle(document, "经济责任审计报告");
// 9. 添加文号(右对齐,蓝色)
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);
// 二、总体评价
if (StrUtil.isNotBlank(evaluate)) {
XWPFParagraph sectionPara2 = document.createParagraph();
sectionPara2.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun2 = sectionPara2.createRun();
sectionRun2.setText("二、总体评价");
sectionRun2.setBold(true);
sectionRun2.setFontSize(18);
sectionRun2.setFontFamily("仿宋_GB2312");
sectionRun2.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进
addParagraphWithIndent(document, evaluate);
}
// 三、主要业绩(从前端传递的 contentMap 获取)
if (contentMap.containsKey(30) && StrUtil.isNotBlank(contentMap.get(30))) {
XWPFParagraph sectionPara3 = document.createParagraph();
sectionPara3.setAlignment(ParagraphAlignment.LEFT);
XWPFRun sectionRun3 = sectionPara3.createRun();
sectionRun3.setText(String.format("三、%s同志主要业绩", project.getPersonName()));
sectionRun3.setBold(true);
sectionRun3.setFontSize(18);
sectionRun3.setFontFamily("仿宋_GB2312");
sectionRun3.setColor("2c3e50");
document.createParagraph();
// 添加内容,首行缩进
addParagraphWithIndent(document, contentMap.get(30));
}
addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定",
Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project);
// 五、审计建议
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();
// 添加内容,首行缩进
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);
// 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);
}
}
/**
* 添加封面页
*/
@@ -676,9 +862,192 @@ public class AuditReportController extends BaseController {
}
/**
* 添加签名页(尾页
* 添加无首行缩进的段落(支持多段文本,用于总体评价和审计建议
* @param document Word 文档对象
* @param text 文本内容(可能包含多个段落,用换行符分隔)
*/
private void addParagraphWithoutIndent(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();
// 不设置首行缩进
XWPFRun run = para.createRun();
run.setText(paragraph.trim());
run.setFontSize(12); // 小四
run.setFontFamily("仿宋_GB2312");
}
}
}
/**
* 添加签名页(尾页)- 从 doc/尾页.docx 读取
*/
private void addSignaturePage(XWPFDocument document) {
try (InputStream is = this.getClass().getResourceAsStream("/doc/尾页.docx")) {
if (is == null) {
log.warn("未找到尾页模板文件,使用默认表格方式创建");
createSignaturePageWithTable(document);
return;
}
XWPFDocument signatureDoc = new XWPFDocument(is);
// 使用底层 XML 复制方式,确保样式 100% 保留
// 1. 复制所有段落 - 直接追加到文档末尾
for (XWPFParagraph srcPara : signatureDoc.getParagraphs()) {
document.createParagraph();
int paraPos = document.getParagraphPos(document.getParagraphs().size() - 1);
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP ctp = (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP)srcPara.getCTP().copy();
document.getDocument().getBody().setPArray(paraPos, ctp);
}
// 2. 复制所有表格
for (XWPFTable srcTable : signatureDoc.getTables()) {
document.createTable();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl cttbl = (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl)srcTable.getCTTbl().copy();
// 获取文档中所有表格的 XML 数组并替换最后一个
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody body = document.getDocument().getBody();
// 添加新的 tbl 元素
body.addNewTbl();
body.setTblArray(body.getTblArray().length - 1, cttbl);
}
log.info("成功添加尾页(从 doc/尾页.docx 读取,使用底层 XML 复制)");
} catch (IOException e) {
log.error("读取尾页文档失败", e);
createSignaturePageWithTable(document);
}
}
/**
* 复制段落内容和样式
*/
private void copyParagraph(XWPFParagraph src, XWPFParagraph dest) {
// 复制段落属性
if (src.getAlignment() != null) {
dest.setAlignment(src.getAlignment());
}
if (src.getIndentationFirstLine() != -1) {
dest.setIndentationFirstLine(src.getIndentationFirstLine());
}
if (src.getIndentationLeft() != -1) {
dest.setIndentationLeft(src.getIndentationLeft());
}
if (src.getIndentationRight() != -1) {
dest.setIndentationRight(src.getIndentationRight());
}
if (src.getSpacingBefore() != -1) {
dest.setSpacingBefore(src.getSpacingBefore());
}
if (src.getSpacingAfter() != -1) {
dest.setSpacingAfter(src.getSpacingAfter());
}
// 复制 Run文本片段
for (XWPFRun srcRun : src.getRuns()) {
XWPFRun destRun = dest.createRun();
String text = srcRun.getText(0);
if (text != null) {
destRun.setText(text);
}
// 复制所有样式属性
try {
// 字体相关
if (srcRun.getFontFamily() != null) {
destRun.setFontFamily(srcRun.getFontFamily());
}
// 尝试使用 setFontName某些版本的 POI 支持)
try {
java.lang.reflect.Method setFontNameMethod = destRun.getClass().getMethod("setFontName", String.class);
if (srcRun.getFontName() != null) {
setFontNameMethod.invoke(destRun, srcRun.getFontName());
}
} catch (Exception e) {
// 忽略,不使用 setFontName
}
// 字体大小
if (srcRun.getFontSize() != -1) {
destRun.setFontSize(srcRun.getFontSize());
}
// 字体样式
destRun.setBold(srcRun.isBold());
destRun.setItalic(srcRun.isItalic());
// 下划线
if (srcRun.getUnderline() != null) {
destRun.setUnderline(srcRun.getUnderline());
}
// 删除线
destRun.setStrike(srcRun.isStrike());
// 颜色
if (srcRun.getColor() != null) {
destRun.setColor(srcRun.getColor());
}
// 上标/下标
if (srcRun.getSubscript() != null) {
destRun.setSubscript(srcRun.getSubscript());
}
} catch (Exception e) {
log.warn("复制 Run 样式时出错", e);
}
}
}
/**
* 复制表格结构和内容
*/
private void copyTable(XWPFTable src, XWPFTable dest) {
for (XWPFTableRow srcRow : src.getRows()) {
// 创建新行并复制高度
XWPFTableRow destRow = dest.createRow();
destRow.setHeight(srcRow.getHeight());
List<XWPFTableCell> srcCells = srcRow.getTableCells();
// 先确保目标行有足够数量的单元格
while (destRow.getTableCells().size() < srcCells.size()) {
destRow.addNewTableCell();
}
List<XWPFTableCell> destCells = destRow.getTableCells();
// 复制每个单元格的内容
for (int i = 0; i < srcCells.size(); i++) {
XWPFTableCell srcCell = srcCells.get(i);
XWPFTableCell destCell = destCells.get(i);
// 清空目标单元格的默认段落
if (destCell.getParagraphs().size() > 0) {
destCell.removeParagraph(0);
}
// 复制源单元格的所有段落
for (XWPFParagraph srcPara : srcCell.getParagraphs()) {
XWPFParagraph destPara = destCell.addParagraph();
copyParagraph(srcPara, destPara);
}
}
}
}
/**
* 使用表格方式创建尾页(备用方案)
*/
private void createSignaturePageWithTable(XWPFDocument document) {
// 创建一个 3 行 3 列的表格
XWPFTable table = document.createTable(3, 3);
@@ -1202,7 +1571,7 @@ public class AuditReportController extends BaseController {
}
/**
* 根据projectId 和 formCommit 查询审计报告
* 根据 projectId 和 formCommit 查询审计报告
*/
@Operation(summary = "查询审计报告")
@PostMapping("/query")
@@ -1218,4 +1587,304 @@ public class AuditReportController extends BaseController {
}
}
/**
* AI 生成默认话术(根据章节标题)
*/
@Operation(summary = "AI 生成默认话术")
@PostMapping("/generateDefaultText")
public ApiResult<?> generateDefaultText(
@RequestParam Integer projectId,
@RequestParam Integer formCommit,
@RequestParam(required = false) String chapterTitle) {
try {
final User loginUser = getLoginUser();
log.info("AI 生成默认话术 - formCommit: {}, chapterTitle: {}", formCommit, chapterTitle);
// 获取章节标题
String title = chapterTitle;
if (title == null || title.trim().isEmpty()) {
title = AuditReportEnum.getByCode(formCommit) != null
? AuditReportEnum.getByCode(formCommit).getDesc()
: "审计内容";
}
// 构建提示词,根据 formCommit 使用不同的默认 prompt
PwlProject project = pwlProjectService.getById(projectId);
String companyName = project.getName();
String personName = project.getPersonName();
String prompt;
if (formCommit == 20) {
// 二、总体评价的特殊提示词模板
// 查询该项目的所有取证单,判断是否有发现问题
LambdaQueryWrapper<AuditEvidence> evidenceWrapper = new LambdaQueryWrapper<>();
evidenceWrapper.eq(AuditEvidence::getProjectId, projectId)
.eq(AuditEvidence::getDeleted, 0);
List<AuditEvidence> allEvidences = auditEvidenceMapper.selectList(evidenceWrapper);
// 判断是否有问题(检查是否存在 auditFinding 不为空或有问题的记录)
boolean hasIssues = allEvidences.stream()
.anyMatch(e -> e.getAuditFinding() != null && !e.getAuditFinding().trim().isEmpty());
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append(String.format("综合本次经济责任审计情况,我们对%s 任职期间经济责任履行情况的总体评价如下(本部分应基于审计事实,作出客观、公正的概括;既要肯定成绩,也需指出不足,并与第三、%s 主要业绩、四部分履行经济责任的主要情况及审计发现的问题与责任认定相呼应):\n", personName, personName));
// 如果审计没有问题,添加特定段落
if (!hasIssues) {
promptBuilder.append("\n审计期间未收到涉及").append(personName).append("的举报信息;根据现有审计程序与所获证据,也未发现其存在违反党风廉政及廉洁从业规定的明显问题或行为线索。");
}
prompt = promptBuilder.toString();
} else if (formCommit == 30) {
// 三、XX 同志主要业绩的特殊提示词模板
prompt = String.format("请根据传入的取证单及相关资料库的数据,总结%s 任职期间的主要业绩。要求1.基于审计事实和数据2.突出重要贡献和成绩3.内容客观真实4.条理清晰。", personName);
} else if (formCommit == 41) {
// XX 公司概况的特殊提示词模板
prompt = String.format("请生成关于'%s'的详细说明。要求按以下格式返回:\n" +
"%s 为(说明企业性质,如:国有独资/控股公司),成立于 XXXX 年 X 月 X 日,注册资本**元,法定代表人 XX统一社会信用代码XXX。\n" +
"公司主营业务为……(简述主营业务)。\n" +
"%s 下设……(组织架构)。", title, companyName, companyName);
} else if (formCommit == 42) {
// XX 同志任职及分工情况的特殊提示词模板
prompt = String.format("请生成关于《%s任职及分工情况》的详细说明。按以下格式返回\n" +
"%s(同本章节标题人名) 自 XXXX 年 X 月至 XXXX 年 X 月任 XXXX单位名称XXX职务名称其主要职责是...(描述主要工作职责)。", personName, personName);
} else if (formCommit == 43) {
// (四)实施审计的基本情况,要求返回两段内容
prompt = String.format("请生成关于'%s'的详细说明。要求返回两段内容:\n" +
"第一段格式:本次审计的时间范围是 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日。本次审计以 %s 会计报表、账簿、凭证及相关经济活动资料为基础,对 %s 任职期间履行经济责任的情况进行审查,主要包括:...(具体内容)。\n" +
"第二段格式:%s 及 %s 对所提供的与审计相关的会计资料以及其他证明材料作出了书面承诺,对其真实性和完整性负责。我们按照审计实施方案确定的范围和内容,实施了在当时情况下认为有必要采取的审计程序和方法,包括(具体的程序和方案)等,并对重要事项进行了必要的延伸和追溯。",
title, companyName, personName, companyName, personName);
} else if (formCommit >= 51 && formCommit <= 57) {
// 四、履行经济责任的主要情况及审计发现的问题与责任认定的所有子章节
// 要求返回三段:第一段概述和问题列示,第二段原因分析,第三段责任界定
prompt = String.format("请生成关于'%s'的详细说明。要求返回三段内容:\n" +
"第一段(概述和问题列示):概述%s 任职期间履行经济责任所涉及的主要工作,重点说明其在职责范围内开展的经济活动和管理行为的核心内容;列示审计中发现的问题,例如:未严格落实国家相关政策要求、在某类业务操作中违反国有资产监管程序等,需列明具体事实、涉及金额以及所违反的具体规定条文;并对问题进行责任认定。\n" +
"第二段标题'原因分析:',针对上述问题,深入分析问题产生的原因,包括主观原因和客观原因,从制度机制、内部管理、人员素质等多个角度进行剖析。\n" +
"第三段标题'责任界定:',根据问题性质和情节轻重,结合%s 的职责分工,对其应承担的责任进行界定,明确是直接责任、主管责任还是领导责任,并说明认定依据。",
title, personName, personName);
}
else {
// 其他章节使用通用 prompt - 查询审计相关法规
prompt = String.format("请查询与'%s'相关的所有审计法规、制度和政策文件。要求1.列出完整的法规名称2.注明颁布单位3.注明发文字号如有4.列出相关条款5.按重要性排序6.使用规范的格式。", title);
}
prompt += "(请注意:数据源都必须来自资料库或者数据库相关数据,不能从其他地方获取数据。)";
log.info("生成审计报告标题:{}AI提示词: {}", chapterTitle, prompt);
// 调用 AI 接口
String result = invokeDefaultTextGeneration(prompt, loginUser.getUsername());
// 保存历史记录到数据库
try {
// 构建请求数据
JSONObject requestData = new JSONObject();
requestData.put("projectId", projectId);
requestData.put("formCommit", formCommit);
requestData.put("chapterTitle", chapterTitle);
// 根据 formCommit 生成不同的接口名称
String interfaceName = "/api/ai/auditReport/generateDefaultText_" + formCommit;
// 生成请求哈希
String requestHash = DigestUtil.md5Hex(interfaceName + ":" + requestData.toJSONString());
// 保存历史记录
aiHistoryService.saveHistory(
Long.valueOf(projectId),
requestHash,
interfaceName,
requestData.toJSONString(),
result,
loginUser.getUserId(),
loginUser.getUsername(),
loginUser.getTenantId()
);
log.info("保存 AI 生成历史记录成功 - projectId: {}, interfaceName: {}", projectId, interfaceName);
} catch (Exception e) {
log.warn("保存 AI 生成历史记录失败", e);
}
return success(result);
} catch (Exception e) {
log.error("AI 生成默认话术异常", e);
return fail("AI 生成默认话术异常:" + e.getMessage(), null);
}
}
/**
* AI 分析用户自定义输入
*/
@Operation(summary = "AI 分析用户自定义输入")
@PostMapping("/analyzeUserInput")
public ApiResult<?> analyzeUserInput(
@RequestParam Integer formCommit,
@RequestParam String userQuestion,
@RequestParam(required = false) String chapterContent) {
try {
final User loginUser = getLoginUser();
log.info("AI 分析用户自定义输入 - formCommit: {}, userQuestion: {}", formCommit, userQuestion);
// 获取章节标题
String chapterTitle = AuditReportEnum.getByCode(formCommit) != null
? AuditReportEnum.getByCode(formCommit).getDesc()
: "审计内容";
// 构建提示词
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append(String.format("当前章节:%s\n", chapterTitle));
if (chapterContent != null && !chapterContent.trim().isEmpty()) {
promptBuilder.append(String.format("当前章节内容:%s\n\n", chapterContent));
}
promptBuilder.append(String.format("用户问题:%s\n\n", userQuestion));
promptBuilder.append("请根据用户的问题为该审计报告章节提供专业、详细的分析内容。要求1.回答要针对用户问题2.使用审计专业术语3.内容要有实际价值4.字数在 300-500 字左右。");
// 调用 AI 接口
String result = invokeAIAnalysis(promptBuilder.toString(), loginUser.getUsername());
return success(result);
} catch (Exception e) {
log.error("AI 分析用户自定义输入异常", e);
return fail("AI 分析用户自定义输入异常:" + e.getMessage(), null);
}
}
/**
* 调用 AI 生成默认话术
*/
private String invokeDefaultTextGeneration(String prompt, String userName) {
// 构建请求体
JSONObject requestBody = new JSONObject();
JSONObject inputs = new JSONObject();
inputs.put("query", prompt);
inputs.put("knowledge", "");
inputs.put("history", "");
inputs.put("suggestion", "");
inputs.put("title", "审计报告默认话术生成");
requestBody.put("inputs", inputs);
requestBody.put("response_mode", "blocking");
requestBody.put("user", userName);
// 发送 POST 请求
String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run")
.header("Authorization", "Bearer app-d7Ok9FECVZG2Ybw9wpg7tGu9")
.header("Content-Type", "application/json")
.body(requestBody.toString())
.timeout(600000)
.execute()
.body();
// 解析返回的 JSON 字符串
JSONObject jsonResponse = JSONObject.parseObject(result);
JSONObject data = jsonResponse.getJSONObject("data");
JSONObject outputs = data.getJSONObject("outputs");
String resultStr = outputs.getString("result");
return resultStr != null ? resultStr : "";
}
/**
* 调用 AI 分析用户输入
*/
private String invokeAIAnalysis(String prompt, String userName) {
// 构建请求体
JSONObject requestBody = new JSONObject();
JSONObject inputs = new JSONObject();
inputs.put("query", prompt);
inputs.put("knowledge", "");
inputs.put("history", "");
inputs.put("suggestion", "");
inputs.put("title", "审计报告 AI 分析");
requestBody.put("inputs", inputs);
requestBody.put("response_mode", "blocking");
requestBody.put("user", userName);
// 发送 POST 请求
String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run")
.header("Authorization", "Bearer app-d7Ok9FECVZG2Ybw9wpg7tGu9")
.header("Content-Type", "application/json")
.body(requestBody.toString())
.timeout(600000)
.execute()
.body();
// 解析返回的 JSON 字符串
JSONObject jsonResponse = JSONObject.parseObject(result);
JSONObject data = jsonResponse.getJSONObject("data");
JSONObject outputs = data.getJSONObject("outputs");
String resultStr = outputs.getString("result");
return resultStr != null ? resultStr : "";
}
/**
* 根据取证单生成审计建议
*/
@Operation(summary = "根据取证单生成审计建议")
@PostMapping("/generateAuditSuggestion")
public ApiResult<?> generateAuditSuggestion(
@RequestParam Integer projectId,
@RequestBody List<Integer> evidenceIds) {
try {
final User loginUser = getLoginUser();
log.info("根据取证单生成审计建议 - projectId: {}, evidenceIds: {}", projectId, evidenceIds);
if (evidenceIds == null || evidenceIds.isEmpty()) {
return fail("请提供取证单 ID 列表", null);
}
// 查询项目信息
PwlProject project = pwlProjectService.getById(projectId);
if (project == null) {
return fail("项目不存在", null);
}
String personName = project.getPersonName();
// 根据取证单 ID 查询所有取证单数据
List<AuditEvidence> evidences = auditEvidenceMapper.selectBatchIds(evidenceIds);
if (evidences == null || evidences.isEmpty()) {
return fail("未找到对应的取证单", null);
}
// 收集所有取证单的改进或整改建议
StringBuilder suggestionsBuilder = new StringBuilder();
suggestionsBuilder.append("以下是各个取证单中的改进或整改建议:\n\n");
int index = 1;
for (AuditEvidence evidence : evidences) {
String suggestion = evidence.getSuggestion();
if (suggestion != null && !suggestion.trim().isEmpty()) {
suggestionsBuilder.append(String.format("%d. %s取证单 ID%d\n", index, suggestion, evidence.getId()));
index++;
}
}
if (index == 1) {
return fail("所选取证单中没有改进或整改建议内容", null);
}
// 构建提示词
String prompt = String.format(
"请根据以下各个取证单中的改进或整改建议,总结生成一份完整的审计建议。\n\n" +
"要求:\n" +
"1.对提供的建议进行分类整理和归纳;\n" +
"2.使用规范的审计报告语言;\n" +
"3.建议要具体、可操作;\n" +
"4.结构清晰,层次分明;\n" +
"5.字数控制在 800-1500 字。\n\n" +
"%s\n\n" +
"请根据以上内容,生成审计建议:",
suggestionsBuilder.toString()
);
// 调用 AI 接口
String result = invokeDefaultTextGeneration(prompt, loginUser.getUsername());
return success(result);
} catch (Exception e) {
log.error("根据取证单生成审计建议异常", e);
return fail("根据取证单生成审计建议异常:" + e.getMessage(), null);
}
}
}

View File

@@ -1,5 +1,8 @@
package com.gxwebsoft.ai.enums;
import lombok.Getter;
@Getter
public enum AuditReportEnum {
// 基础信息
@@ -43,14 +46,6 @@ public enum AuditReportEnum {
this.desc = desc;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
public String getCodeStr() {
return String.format("%02d", code);
}

View File

@@ -51,13 +51,22 @@ public interface AiHistoryService extends IService<AiHistory> {
/**
* 保存历史记录
*
* @param projectId 项目ID
* @param projectId 项目 ID
* @param requestHash 请求哈希值
* @param interfaceName 接口名称
* @param requestData 请求数据
* @param responseData 响应数据
* @param userId 用户ID
* @param userId 用户 ID
* @param username 用户名
*/
void saveHistory(Long projectId, String requestHash, String interfaceName, String requestData, String responseData, Integer userId, String username, Integer tenantId);
/**
* 根据接口名称和项目 ID 查询最新的历史记录
*
* @param interfaceName 接口名称
* @param projectId 项目 ID
* @return AiHistory
*/
AiHistory getLatestHistory(String interfaceName, Long projectId);
}

View File

@@ -5,6 +5,7 @@ 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 com.gxwebsoft.pwl.entity.PwlProject;
import java.util.List;
import java.util.Map;
@@ -31,16 +32,16 @@ public interface AuditReportService {
/**
* 根据项目 ID 查询所有审计报告和取证单数据
* @param projectId 项目 ID
* @param project 项目
* @return Map包含 reports (AuditReport 列表) 和 evidences (AuditEvidence 列表)
*/
Map<String, Object> queryAuditDataByProjectId(Integer projectId, List<Long> selectedEvidenceIds);
Map<String, Object> queryAuditDataByProjectId(PwlProject project, List<Long> selectedEvidenceIds);
/**
* 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析
* @param projectId 项目 ID
* @param project 项目
* @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null
* @return 包含 reports、evidences、evaluate、suggestion 的 Map
*/
Map<String, Object> queryAuditDataByProjectIdWithAnalysis(Integer projectId, List<Long> selectedEvidenceIds);
Map<String, Object> queryAuditDataByProjectIdWithAnalysis(PwlProject project, List<Long> selectedEvidenceIds);
}

View File

@@ -72,4 +72,17 @@ public class AiHistoryServiceImpl extends ServiceImpl<AiHistoryMapper, AiHistory
save(history);
}
@Override
public AiHistory getLatestHistory(String interfaceName, Long projectId) {
// 使用 LambdaQueryWrapper 查询最新的记录
LambdaQueryWrapper<AiHistory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AiHistory::getInterfaceName, interfaceName)
.eq(AiHistory::getProjectId, projectId)
.eq(AiHistory::getDeleted, 0)
.orderByDesc(AiHistory::getCreateTime)
.last("LIMIT 1");
return getOne(wrapper);
}
}

View File

@@ -1,5 +1,6 @@
package com.gxwebsoft.ai.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.aliyun.bailian20231229.Client;
import com.aliyun.bailian20231229.models.RetrieveResponse;
import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyDataNodes;
@@ -17,11 +18,17 @@ 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.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -44,9 +51,6 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
@Autowired
private KnowledgeBaseConfig config;
@Autowired
private PwlProjectService pwlProjectService;
@Autowired
private PwlProjectLibraryService pwlProjectLibraryService;
@@ -60,6 +64,12 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
private static final String QUESTION_ANALYSIS_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run";
private static final String QUESTION_ANALYSIS_TOKEN = "Bearer app-5CQWPxctsPape0HePWA3cP4Y";
private static final String QUOTA_STR = "1. 《自治区国资委监管企业内部审计管理办法》【颁布单位】广西壮族自治区人民政府国有资产监督管理委员会【涉及条款】第五条、第十七条、第二十条、第二十一条、第十四条、第十六条、第十七条\n" +
"2. 《自治区国资委关于深化监管企业内部审计监督工作的实施意见》【颁布单位】广西壮族自治区人民政府国有资产监督管理委员会【发文字号】桂国资发(2020)43号【涉及条款】\n" +
"3. 《广西壮族自治区直属企业审计监督工作办法》【颁布单位】中共广西壮族自治区委员会审计委员会办公室【发文字号】桂审委办202039号【涉及条款】第六条、第二十五条\n" +
"4. 《广西北投公路建设投资集团有限公司内部审计管理办法2021年修订\n" +
"5. 《BTGL公司经济责任审计管理办法2021年修订";
@Override
public JSONObject generateCompleteAuditReport(String auditContent, String fileName,
String kbId, String libraryIds,
@@ -125,8 +135,7 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
JSONObject requestBody = buildQuestionGenerationRequest(knowledgeContext, userName);
JSONArray response = callQuestionGenerationWorkflow(requestBody);
return response;
return callQuestionGenerationWorkflow(requestBody);
}
/**
@@ -469,24 +478,35 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
}
@Override
public Map<String, Object> queryAuditDataByProjectId(Integer projectId, List<Long> selectedEvidenceIds) {
public Map<String, Object> queryAuditDataByProjectId(PwlProject project, List<Long> selectedEvidenceIds) {
// 调用新方法,不传入 selectedEvidenceIds兼容旧接口
return queryAuditDataByProjectIdWithAnalysis(projectId, selectedEvidenceIds);
return queryAuditDataByProjectIdWithAnalysis(project, selectedEvidenceIds);
}
/**
* 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析
* @param projectId 项目 ID
* @param project 项目 ID
* @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null
* @return 包含 reports、evidences、evaluate、suggestion 的 Map
*/
@Override
public Map<String, Object> queryAuditDataByProjectIdWithAnalysis(Integer projectId, List<Long> selectedEvidenceIds) {
public Map<String, Object> queryAuditDataByProjectIdWithAnalysis(PwlProject project, List<Long> selectedEvidenceIds) {
Integer projectId = project.getId();
log.info("=== 开始查询审计数据 ===");
log.info("项目 ID: {}, 勾选取证单 IDs: {}", projectId, selectedEvidenceIds);
Map<String, Object> result = new HashMap<>();
// 添加章节顺序配置,供前端使用
List<Map<String, Object>> sectionOrder = new ArrayList<>();
sectionOrder.add(createSectionInfo(1, "基本情况", Arrays.asList(41, 42, 43)));
sectionOrder.add(createSectionInfo(2, "总体评价", null));
sectionOrder.add(createSectionInfo(3, String.format("%s同志主要业绩", project.getPersonName()), null));
sectionOrder.add(createSectionInfo(4, "履行经济责任的主要情况及审计发现的问题与责任认定", Arrays.asList(51, 52, 53, 54, 55, 56, 57)));
sectionOrder.add(createSectionInfo(5, "审计建议", null));
sectionOrder.add(createSectionInfo(6, "其他事项说明", null));
result.put("sectionOrder", sectionOrder);
try {
if (projectId == null) {
log.warn("项目 ID 为 null返回空结果");
@@ -499,12 +519,23 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
// 1. 查询审计报告数据
log.info("正在查询审计报告...");
List<AuditReport> reports = new ArrayList<>();
AuditReport report10 = new AuditReport();
report10.setFormCommit(10);
report10.setProjectId(projectId);
report10.setReportContent(QUOTA_STR);
reports.add(report10);
LambdaQueryWrapper<AuditReport> reportWrapper = new LambdaQueryWrapper<>();
reportWrapper.eq(AuditReport::getProjectId, projectId)
.eq(AuditReport::getDeleted, 0)
.in(AuditReport::getFormCommit, Arrays.asList(41, 42, 43))
.orderByAsc(AuditReport::getFormCommit);
List<AuditReport> reports = baseMapper.selectList(reportWrapper);
List<AuditReport> reportList = baseMapper.selectList(reportWrapper);
if(CollectionUtil.isNotEmpty(reportList)){
reports.addAll(reportList);
}
log.info("查询到 {} 条审计报告记录", reports.size());
// 2. 查询审计取证单数据 - 查询该项目下所有的取证单
@@ -521,8 +552,7 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
result.put("evidences", evidences);
// 3. 获取项目信息(用于获取项目名称)
PwlProject project = pwlProjectService.getById(projectId);
String projectName = project != null ? (project.getName() != null ? project.getName() : "") : "";
String projectName = project.getName() != null ? project.getName() : "";
result.put("projectName", projectName); // 添加项目名称到返回结果
log.info("项目名称:{}", projectName);
@@ -537,12 +567,16 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
// 4. 如果传入了勾选的取证单 ID进行字段叠加
if (selectedEvidenceIds != null && !selectedEvidenceIds.isEmpty()) {
log.info("开始对 {} 条勾选的取证单进行字段叠加", selectedEvidenceIds.size());
// LambdaQueryWrapper<AuditEvidence> evidenceWrapper = new LambdaQueryWrapper<>();
// evidenceWrapper.eq(AuditEvidence::getProjectId, projectId)
// .eq(AuditEvidence::getDeleted, 0)
// .orderByAsc(AuditEvidence::getId);
// 筛选出勾选的取证单
List<AuditEvidence> selectedEvidences = evidences.stream()
.filter(e -> selectedEvidenceIds.contains(e.getId()))
.collect(Collectors.toList());
// List<AuditEvidence> selectedEvidences = auditEvidenceMapper.selectList(evidenceWrapper);
log.info("实际筛选出 {} 条取证单进行叠加", selectedEvidences.size());
if (!selectedEvidences.isEmpty()) {
@@ -601,29 +635,26 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
* @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();
return String.format("(一)本报告依据委托方及 %s 提供的审计相关资料出具,审计意见受所提供资料真实性及完整性的影响。" +
// 第二段
"(二)本报告仅供委托方 %s 了解 %s 的经济责任履行情况之用,未经本所书面同意不得用作其他用途,因使用不当造成的后果,与执行本次专项审计业务的注册会计师及会计师事务所无关。", project.getName(), project.getName(), project.getPersonName());
}
/**
* 叠加取证单字段内容
* @param selectedEvidences 勾选的取证单列表
* @param fieldName 字段名称auditFinding 或 suggestion
* @return 叠加后的字符串(包含标题和内容)
* 创建章节信息
* @param order 章节顺序
* @param title 章节标题
* @param formCommits 表单提交标识列表
* @return 章节信息Map
*/
private Map<String, Object> createSectionInfo(int order, String title, List<Integer> formCommits) {
Map<String, Object> sectionInfo = new HashMap<>();
sectionInfo.put("order", order);
sectionInfo.put("title", title);
sectionInfo.put("formCommits", formCommits);
return sectionInfo;
}
private String concatenateField(List<AuditEvidence> selectedEvidences, String fieldName) {
log.info("开始叠加字段:{}", fieldName);
StringBuilder result = new StringBuilder();
@@ -684,7 +715,7 @@ public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, Audit
if (number >= 1 && number <= 10) {
return chineseNumbers[number];
} else if (number > 10 && number <= 20) {
return "" + (number == 10 ? "" : chineseNumbers[number - 10]);
return "" + chineseNumbers[number - 10];
} else {
// 超过 20 的数字,直接返回阿拉伯数字
return String.valueOf(number);