Gqingci hai 15 horas
pai
achega
9c2d2e7b6a

BIN=BIN
report_template2.docx


+ 53 - 119
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java

@@ -1138,23 +1138,7 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
             placeholders.put("surveyTime", safe(resolveSurveyTime(context)));
         }
 
-        // 学历核实(新表有)
-        if (useCheckData && StringUtils.isNotBlank(cd.getGradSchool())) {
-            placeholders.put("school", safe(cd.getGradSchool()));
-        } else {
-            placeholders.put("school", safe(resolveSchoolName(context)));
-        }
-        if (useCheckData && StringUtils.isNotBlank(cd.getEduCertNo())) {
-            placeholders.put("certNo", safe(cd.getEduCertNo()));
-        } else {
-            placeholders.put("certNo", " "); // 旧表 certNo 已删
-        }
-        // eduVerification - 学信网核实结果(上家输入),新表有,直接从新表取
-        if (useCheckData) {
-            placeholders.put("eduVerification", safe(cd.getEduVerifyStatus()));
-        } else {
-            placeholders.put("eduVerification", " "); // 旧表 eduVerification 已删
-        }
+        // 学历核实(template2 无 school/certNo/eduVerification 占位符,无需填充)
 
         // 最近工作单位(新表有)
         if (useCheckData && StringUtils.isNotBlank(cd.getCompanyName())) {
@@ -1172,31 +1156,8 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         } else {
             placeholders.put("jobTitle", safe(resolveLatestJobTitle(context)));
         }
-        if (useCheckData && StringUtils.isNotBlank(cd.getLastSalary())) {
-            placeholders.put("lastSalary", safe(cd.getLastSalary()));
-        } else {
-            placeholders.put("lastSalary", " "); // 旧表 lastSalary 已删
-        }
-        if (useCheckData && StringUtils.isNotBlank(cd.getLeaveReasonVerify())) {
-            placeholders.put("leaveReason", safe(cd.getLeaveReasonVerify()));
-        } else {
-            placeholders.put("leaveReason", " "); // 旧表 leaveReason 已删,无数据来源
-        }
-
-        // 工作表现评估 - 新表有的字段直接从新表取
-        // supervisorEval - 旧表字段,始终从旧表取
-        placeholders.put("supervisorEval", safe(context.review == null ? null : context.review.getSupervisorEval()));
 
-        if (useCheckData) {
-            placeholders.put("strength", safe(cd.getLeaderEvalAdvantage()));
-        } else {
-            placeholders.put("strength", " "); // 旧表 strength 已删
-        }
-        if (useCheckData) {
-            placeholders.put("improvement", safe(cd.getLeaderEvalImprove()));
-        } else {
-            placeholders.put("improvement", " "); // 旧表 improvement 已删
-        }
+        // 工作表现评估 - template2 仅有 professionalAbility / workAttitude / teamwork / ethics
         if (useCheckData) {
             placeholders.put("professionalAbility", safe(cd.getLeaderEvalProf()));
         } else {
@@ -1218,81 +1179,50 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
             placeholders.put("ethics", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityDName(), context.review == null ? null : context.review.getAbilityDRate(), null)));
         }
 
-        // colleagueEval - 旧表字段,始终从旧表取
-        placeholders.put("colleagueEval", safe(context.review == null ? null : context.review.getColleagueEval()));
-
-        if (useCheckData) {
-            placeholders.put("cooperation", safe(cd.getColleagueEvalTeamwork()));
-        } else {
-            placeholders.put("cooperation", " "); // 旧表 cooperation 已删
-        }
-        if (useCheckData) {
-            placeholders.put("colleagueAbility", safe(cd.getColleagueEvalProf()));
-        } else {
-            placeholders.put("colleagueAbility", " "); // 旧表 colleagueAbility 已删
-        }
-
-        // hrEval - 旧表字段,始终从旧表取
-        placeholders.put("hrEval", safe(context.review == null ? null : context.review.getHrEval()));
-
+        // violation - 违规记录(HR评价-劳动争议/违规),新表有 hrEvalDispute
         if (useCheckData) {
             placeholders.put("violation", safe(cd.getHrEvalDispute()));
         } else {
-            placeholders.put("violation", " "); // 旧表 violation 已删
-        }
-        if (useCheckData) {
-            placeholders.put("handover", safe(cd.getHrEvalTransfer()));
-        } else {
-            placeholders.put("handover", " "); // 旧表 handover 已删
+            placeholders.put("violation", " ");
         }
 
         // 调查结论(新表有)
         if (useCheckData) {
             placeholders.put("conclusionReason", safe(cd.getConclusionReason()));
         } else {
-            placeholders.put("conclusionReason", " "); // 旧表 totalRemark 已删
+            placeholders.put("conclusionReason", " ");
         }
 
-        // 调查人 / 日期(新表有)
-        if (useCheckData && StringUtils.isNotBlank(cd.getInvestigatorName())) {
-            placeholders.put("investigator", safe(cd.getInvestigatorName()));
-        } else {
-            placeholders.put("investigator", safe(context.companyName));
-        }
-        if (useCheckData && cd.getInvestigatorDate() != null) {
-            placeholders.put("investigateDate", safe(new java.text.SimpleDateFormat("yyyy-MM-dd").format(cd.getInvestigatorDate())));
-        } else {
-            placeholders.put("investigateDate", safe(resolveSurveyTime(context)));
-        }
+        // 访谈记录 - template2 有 interviewee1Relation / interviewee2Relation / interviewee3Relation
+        int interviewCount = fillInterviewPlaceholdersForTemplate2(placeholders, context);
 
-        // 访谈记录 Q&A 填充
-        fillInterviewPlaceholders(placeholders, context);
+        // template2 新增占位符
+        // count - 访谈对象人数
+        placeholders.put("count", String.valueOf(interviewCount));
+        // currentTime - 当前日期(报告出具日期)
+        placeholders.put("currentTime", new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date()));
+        // currentCompany1 / currentCompany2 - 调查单位 / 出具单位,都是当前企业名称
+        placeholders.put("currentCompany1", safe(context.companyName));
+        placeholders.put("currentCompany2", safe(context.companyName));
 
         // 确保所有空值替换为空格,保证模板格式不被破坏
         placeholders.replaceAll((k, v) -> (v == null || v.isEmpty()) ? " " : v);
 
         // ========== Checkbox 勾选框 ==========
-        // CB_eduResult - 学历核实结果(总控核实),新表有 eduCheckResult
-        if (useCheckData && StringUtils.isNotBlank(cd.getEduCheckResult())) {
-            placeholders.put("CB_eduResult", "属实,不属实,无法核实|SELECTED:" + safe(cd.getEduCheckResult()));
-        } else {
-            placeholders.put("CB_eduResult", "属实,不属实,无法核实|SELECTED:"); // 旧表 checkResult 已删
-        }
-        // CB_companyResult - 工作单位核实结果(总控核实),新表有 companyCheckResult
+        // CB_companyResult - 工作单位核实结果
         if (useCheckData && StringUtils.isNotBlank(cd.getCompanyCheckResult())) {
             placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:" + safe(cd.getCompanyCheckResult()));
         } else {
-            placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:"); // 旧表 checkResult 已删
+            placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:");
         }
-        // CB_performResult - 表现核实结果(总控核实),新表有 performCheckResult
+        // CB_performResult - 表现核实结果
         if (useCheckData && StringUtils.isNotBlank(cd.getPerformCheckResult())) {
             placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:" + safe(cd.getPerformCheckResult()));
         } else {
-            placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:"); // 旧表 performCheckResult 已删
+            placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:");
         }
 
-        // CB_restrictAgreement - 新表有 hasNonCompete / hasNda
-        // SELECTED 支持多选,用逗号分隔
+        // CB_restrictAgreement - 竞业禁止/保密协议
         String agreeSelected = "";
         if (useCheckData) {
             if (Integer.valueOf(1).equals(cd.getHasNonCompete())) agreeSelected = "竞业禁止协议:是";
@@ -1302,47 +1232,21 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
             } else if (Integer.valueOf(0).equals(cd.getHasNda())) {
                 agreeSelected += (agreeSelected.isEmpty() ? "" : ",") + "保密协议:否";
             }
-        } else {
-            // 旧表 nonCompeteAgreement / confidentialityAgreement 已删,无数据来源
         }
         placeholders.put("CB_restrictAgreement", "竞业禁止协议:是,竞业禁止协议:否,保密协议:是,保密协议:否|SELECTED:" + agreeSelected);
 
-        // CB_disputeRecord - 新表有 hasDispute
+        // CB_disputeRecord - 劳动争议
         String disputeSelect = "";
         if (useCheckData) {
             disputeSelect = cd.getHasDispute() != null && cd.getHasDispute() == 1 ? "是" : "否";
-        } else {
-            // 旧表 laborDispute 已删,无数据来源
         }
         placeholders.put("CB_disputeRecord", "是,否|SELECTED:" + disputeSelect);
 
-        // CB_conclusionResult - 新表有 conclusion
+        // CB_conclusionResult - 调查结论
         if (useCheckData && StringUtils.isNotBlank(cd.getConclusion())) {
             placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:" + safe(cd.getConclusion()));
         } else {
-            placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:"); // 旧表 conclusionResult 已删
-        }
-
-        // ========== 备注列 ==========
-        if (useCheckData && StringUtils.isNotBlank(cd.getCompanyCheckRemark())) {
-            placeholders.put("companyRemark", safe(cd.getCompanyCheckRemark()));
-        } else {
-            placeholders.put("companyRemark", " "); // 旧表 checkRemark 已删
-        }
-        if (useCheckData && StringUtils.isNotBlank(cd.getPerformCheckRemark())) {
-            placeholders.put("performRemark", safe(cd.getPerformCheckRemark()));
-        } else {
-            placeholders.put("performRemark", " "); // 旧表 performRemark 已删
-        }
-        if (useCheckData && StringUtils.isNotBlank(cd.getAgreementRemark())) {
-            placeholders.put("restrictRemark", safe(cd.getAgreementRemark()));
-        } else {
-            placeholders.put("restrictRemark", " "); // 旧表 agreementRemark 已删
-        }
-        if (useCheckData && StringUtils.isNotBlank(cd.getDisputeRemark())) {
-            placeholders.put("disputeRemark", safe(cd.getDisputeRemark()));
-        } else {
-            placeholders.put("disputeRemark", " "); // 旧表 disputeRemark 已删
+            placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:");
         }
 
         return wordReportService.generatePdf(placeholders);
@@ -1463,6 +1367,36 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         return source.replaceAll("[\\\\/:*?\"<>|\\s]+", "_");
     }
 
+    /**
+     * 填充访谈记录到占位符(template2 版,interviewee1Relation / interviewee2Relation / interviewee3Relation)
+     * @return 实际有内容的访谈对象人数
+     */
+    private int fillInterviewPlaceholdersForTemplate2(Map<String, String> placeholders, ReportContext context) {
+        // 先设默认空值
+        placeholders.put("interviewee1Relation", " ");
+        placeholders.put("interviewee2Relation", " ");
+        placeholders.put("interviewee3Relation", " ");
+
+        if (context.interviews == null || context.interviews.isEmpty()) {
+            return 0;
+        }
+
+        int count = 0;
+        if (context.interviews.size() >= 1 && StringUtils.isNotBlank(context.interviews.get(0).getIntervieweeRelation())) {
+            placeholders.put("interviewee1Relation", safe(context.interviews.get(0).getIntervieweeRelation()));
+            count++;
+        }
+        if (context.interviews.size() >= 2 && StringUtils.isNotBlank(context.interviews.get(1).getIntervieweeRelation())) {
+            placeholders.put("interviewee2Relation", safe(context.interviews.get(1).getIntervieweeRelation()));
+            count++;
+        }
+        if (context.interviews.size() >= 3 && StringUtils.isNotBlank(context.interviews.get(2).getIntervieweeRelation())) {
+            placeholders.put("interviewee3Relation", safe(context.interviews.get(2).getIntervieweeRelation()));
+            count++;
+        }
+        return count;
+    }
+
     /**
      * 填充访谈记录到占位符
      */

+ 111 - 36
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/WordReportService.java

@@ -19,16 +19,16 @@ import java.util.regex.Pattern;
 /**
  * Word 模板报告服务。
  * <p>
- * 从 classpath 加载 Word 模板 (templates/report_template.docx),
+ * 从 classpath 加载 Word 模板 (templates/report_template2.docx),
  * 将 {{placeholder}} 占位符替换为实际值,
  * 处理 checkbox 勾选框(Wingdings 字体),
- * 然后通过 LibreOffice headless 转换为高保真 PDF。
+ * 支持直接输出 docx 或通过 LibreOffice 转 PDF。
  */
 @Slf4j
 @Component
 public class WordReportService {
 
-    private static final String TEMPLATE_PATH = "templates/report_template.docx";
+    private static final String TEMPLATE_PATH = "templates/report_template2.docx";
     private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{\\s*(\\w+)\\s*}}");
     /** CB_ 前缀的占位符,表示 checkbox 组,值格式为 "选项1|选项2|选项3|SELECTED:选中项" */
     private static final Pattern CB_PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{CB_(\\w+)}}");
@@ -46,6 +46,28 @@ public class WordReportService {
         "/usr/local/bin/soffice",
     };
 
+    /**
+     * 根据占位符映射生成填充后的 Word 文档字节数组(不转 PDF)。
+     *
+     * @param placeholders key=占位符名称, value=要填入的值。
+     *                     对于 CB_ 前缀的 key,value 格式为 "选项1,选项2,选项3|SELECTED:选中项"
+     * @return docx 文件字节数组
+     */
+    public byte[] generateDocx(Map<String, String> placeholders) {
+        try (InputStream templateStream = new ClassPathResource(TEMPLATE_PATH).getInputStream();
+             XWPFDocument document = new XWPFDocument(templateStream)) {
+
+            fillPlaceholders(document, placeholders);
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            document.write(baos);
+            return baos.toByteArray();
+        } catch (Exception e) {
+            log.error("生成报告docx失败", e);
+            throw new ServiceException("生成报告docx失败: " + (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
+        }
+    }
+
     /**
      * 根据占位符映射生成 PDF。
      *
@@ -57,34 +79,9 @@ public class WordReportService {
         try (InputStream templateStream = new ClassPathResource(TEMPLATE_PATH).getInputStream();
              XWPFDocument document = new XWPFDocument(templateStream)) {
 
-            // 1. 替换文档正文段落中的占位符
-            for (XWPFParagraph paragraph : document.getParagraphs()) {
-                replacePlaceholdersInParagraph(paragraph, placeholders);
-            }
+            fillPlaceholders(document, placeholders);
 
-            // 2. 替换所有表格中的占位符(包括 checkbox)
-            for (XWPFTable table : document.getTables()) {
-                for (XWPFTableRow row : table.getRows()) {
-                    for (XWPFTableCell cell : row.getTableCells()) {
-                        for (XWPFParagraph paragraph : new ArrayList<>(cell.getParagraphs())) {
-                            // 检查是否是 checkbox 占位符
-                            String paraText = paragraph.getText();
-                            if (paraText != null) {
-                                Matcher cbMatcher = CB_PLACEHOLDER_PATTERN.matcher(paraText);
-                                if (cbMatcher.find()) {
-                                    String cbKey = "CB_" + cbMatcher.group(1);
-                                    String cbValue = placeholders.getOrDefault(cbKey, "");
-                                    applyCheckboxParagraph(cell, paragraph, cbValue);
-                                    continue;
-                                }
-                            }
-                            replacePlaceholdersInParagraph(paragraph, placeholders);
-                        }
-                    }
-                }
-            }
-
-            // 3. 保存到临时文件
+            // 保存到临时文件
             Path tempDir = Files.createTempDirectory("report_");
             Path docxPath = tempDir.resolve("report.docx");
             try (FileOutputStream fos = new FileOutputStream(docxPath.toFile())) {
@@ -94,10 +91,10 @@ public class WordReportService {
             // 调试:保存到项目目录(已关闭)
             // saveDebugDocx(document);
 
-            // 4. LibreOffice 转 PDF
+            // LibreOffice 转 PDF
             byte[] pdfBytes = convertToPdfWithLibreOffice(docxPath, tempDir);
 
-            // 5. 清理
+            // 清理
             cleanupTempDir(tempDir);
 
             return pdfBytes;
@@ -110,6 +107,29 @@ public class WordReportService {
         }
     }
 
+    /**
+     * 填充文档中的占位符(段落 + 表格)。
+     * 对于 CB_ 前缀的 checkbox 占位符,采用内联纯文本替换(☑/☐),
+     * 以支持 CB_ 占位符与普通文字在同一个段落中的场景。
+     */
+    private void fillPlaceholders(XWPFDocument document, Map<String, String> placeholders) {
+        // 1. 替换文档正文段落中的占位符
+        for (XWPFParagraph paragraph : document.getParagraphs()) {
+            replacePlaceholdersInParagraph(paragraph, placeholders);
+        }
+
+        // 2. 替换所有表格中的占位符(包括 CB_ checkbox 内联替换)
+        for (XWPFTable table : document.getTables()) {
+            for (XWPFTableRow row : table.getRows()) {
+                for (XWPFTableCell cell : row.getTableCells()) {
+                    for (XWPFParagraph paragraph : new ArrayList<>(cell.getParagraphs())) {
+                        replacePlaceholdersInParagraph(paragraph, placeholders);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * 处理 checkbox 组。
      * value 格式: "选项1,选项2,选项3|SELECTED:选中项"
@@ -206,7 +226,8 @@ public class WordReportService {
     }
 
     /**
-     * 替换段落中的普通 {{placeholder}} 占位符。
+     * 替换段落中的普通 {{placeholder}} 占位符和 {{CB_xxx}} checkbox 占位符。
+     * CB_ 占位符会被转换为 ☑/☐ 纯文本内联替换,支持与普通文字在同一段落中。
      */
     private void replacePlaceholdersInParagraph(XWPFParagraph paragraph, Map<String, String> placeholders) {
         String fullText = paragraph.getText();
@@ -228,15 +249,24 @@ public class WordReportService {
         }
 
         String concatenated = sb.toString();
-        Matcher matcher = PLACEHOLDER_PATTERN.matcher(concatenated);
-        if (!matcher.find()) {
+        // 先检查是否有普通占位符或 CB_ 占位符
+        Matcher placeholderMatcher = PLACEHOLDER_PATTERN.matcher(concatenated);
+        if (!placeholderMatcher.find()) {
             return;
         }
 
+        // 替换所有占位符(包括 CB_ 前缀的),CB_ 的值转换为 ☑/☐ 纯文本
         String replaced = PLACEHOLDER_PATTERN.matcher(concatenated).replaceAll(mr -> {
             String key = mr.group(1).trim();
             String value = placeholders.getOrDefault(key, " ");
-            return value.isEmpty() ? " " : Matcher.quoteReplacement(value);
+            if (value.isEmpty()) {
+                return " ";
+            }
+            // 如果是 CB_ 前缀的占位符,将 checkbox 格式转为 ☑/☐ 纯文本
+            if (key.startsWith("CB_")) {
+                return Matcher.quoteReplacement(convertCheckboxToPlainText(value));
+            }
+            return Matcher.quoteReplacement(value);
         });
 
         if (replaced.contains("\n")) {
@@ -257,6 +287,51 @@ public class WordReportService {
         }
     }
 
+    /**
+     * 将 CB_ checkbox 格式的值转换为纯文本。
+     * 输入格式: "选项1,选项2,选项3|SELECTED:选中项"
+     * 输出格式: "☑ 选项1 ☐ 选项2 ☐ 选项3"
+     */
+    private String convertCheckboxToPlainText(String value) {
+        if (value == null || value.isEmpty()) {
+            return " ";
+        }
+
+        String optionsPart = value;
+        String selected = "";
+        if (value.contains("|SELECTED:")) {
+            int idx = value.indexOf("|SELECTED:");
+            optionsPart = value.substring(0, idx);
+            selected = value.substring(idx + "|SELECTED:".length());
+        }
+        String[] options = optionsPart.split(",");
+
+        java.util.Set<String> selectedSet = new java.util.HashSet<>();
+        if (!selected.isEmpty()) {
+            for (String s : selected.split(",")) {
+                String trimmed = s.trim();
+                if (!trimmed.isEmpty()) {
+                    selectedSet.add(trimmed);
+                }
+            }
+        }
+
+        StringBuilder result = new StringBuilder();
+        for (String option : options) {
+            option = option.trim();
+            if (result.length() > 0) {
+                result.append(" ");
+            }
+            if (selectedSet.contains(option)) {
+                result.append("☑ ");
+            } else {
+                result.append("☐ ");
+            }
+            result.append(option);
+        }
+        return result.toString();
+    }
+
     private void setRunTextSafely(XWPFRun run, String text) {
         if (run == null) {
             return;

BIN=BIN
ruoyi-modules/ruoyi-main/src/main/resources/templates/report_template2.docx


BIN=BIN
背景调查报告-供下家参考.docx