|
|
@@ -6,11 +6,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
-import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
-import org.apache.pdfbox.pdmodel.PDPage;
|
|
|
-import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|
|
-import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|
|
-import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
|
|
import org.dromara.common.core.exception.ServiceException;
|
|
|
import org.dromara.common.core.utils.MapstructUtils;
|
|
|
import org.dromara.common.core.utils.StringUtils;
|
|
|
@@ -60,11 +55,7 @@ import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
-import java.io.ByteArrayOutputStream;
|
|
|
-import java.io.IOException;
|
|
|
import java.math.BigDecimal;
|
|
|
-import java.nio.file.Files;
|
|
|
-import java.nio.file.Path;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.ArrayList;
|
|
|
@@ -86,10 +77,7 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
|
|
|
private static final String RECORD_STATUS_AUTHORIZED = "已授权";
|
|
|
private static final String RECORD_STATUS_UNAUTHORIZED = "未授权";
|
|
|
private static final DateTimeFormatter REPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
|
|
- private static final float REPORT_MARGIN = 52F;
|
|
|
- private static final float REPORT_TITLE_SIZE = 18F;
|
|
|
- private static final float REPORT_HEADING_SIZE = 13F;
|
|
|
- private static final float REPORT_BODY_SIZE = 10.5F;
|
|
|
+
|
|
|
private static final Set<String> AUTHORIZATION_TERMINAL_STATUSES =
|
|
|
new HashSet<>(List.of(RECORD_STATUS_AUTHORIZED, RECORD_STATUS_UNAUTHORIZED));
|
|
|
|
|
|
@@ -108,6 +96,7 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
|
|
|
private final SysTenantMapper sysTenantMapper;
|
|
|
private final ISysTenantService tenantService;
|
|
|
private final ICompanyAccountService companyAccountService;
|
|
|
+ private final WordReportService wordReportService;
|
|
|
|
|
|
@Override
|
|
|
public TableDataInfo<MainBackOrderVo> queryPageList(MainBackOrderBo bo, PageQuery pageQuery) {
|
|
|
@@ -886,78 +875,90 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
|
|
|
}
|
|
|
|
|
|
private byte[] buildReportPdf(ReportContext context) {
|
|
|
- try (PDDocument document = new PDDocument();
|
|
|
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
|
|
- PDType0Font font = loadChineseFont(document);
|
|
|
- ReportWriter writer = new ReportWriter(document, font);
|
|
|
-
|
|
|
- writer.addTitle("背景调查报告");
|
|
|
- writer.addKeyValueSection("候选人基本信息", List.of(
|
|
|
- "姓名:" + safe(context.student == null ? null : context.student.getName()),
|
|
|
- "应聘职位:" + safe(resolvePositionName(context)),
|
|
|
- "调查时间:" + safe(resolveSurveyTime(context))
|
|
|
- ));
|
|
|
- writer.addKeyValueSection("学历核实", List.of(
|
|
|
- "毕业院校:" + safe(resolveSchoolName(context)),
|
|
|
- "学历证书编号:" + "",
|
|
|
- "学信网核实:" + safe(resolveEducationVerification(context))
|
|
|
- ));
|
|
|
- writer.addKeyValueSection("最近工作单位(上家公司)", List.of(
|
|
|
- "公司名称:" + safe(context.experience == null ? null : context.experience.getCompany()),
|
|
|
- "岗位名称:" + safe(resolveLatestJobTitle(context)),
|
|
|
- "在职时间:" + safe(resolveEmploymentPeriod(context)),
|
|
|
- "任职状态:" + safe(resolveEmploymentStatus(context)),
|
|
|
- "离职原因:" + safe(context.review == null ? null : context.review.getLeaveReason())
|
|
|
- ));
|
|
|
- writer.addParagraphSection("工作职责/信息来源", safe(resolveWorkContent(context)));
|
|
|
- writer.addParagraphSection("综合评价", safe(context.review == null ? null : context.review.getTotalRemark()));
|
|
|
- writer.addRatingSection("能力评价", List.of(
|
|
|
- buildAbilityLine(context.review == null ? null : context.review.getAbilityAName(),
|
|
|
- context.review == null ? null : context.review.getAbilityARate(),
|
|
|
- context.review == null ? null : context.review.getAbilityARemark()),
|
|
|
- buildAbilityLine(context.review == null ? null : context.review.getAbilityBName(),
|
|
|
- context.review == null ? null : context.review.getAbilityBRate(),
|
|
|
- context.review == null ? null : context.review.getAbilityBRemark()),
|
|
|
- buildAbilityLine(context.review == null ? null : context.review.getAbilityCName(),
|
|
|
- context.review == null ? null : context.review.getAbilityCRate(),
|
|
|
- context.review == null ? null : context.review.getAbilityCRemark())
|
|
|
- ));
|
|
|
- writer.addQuestionAnswerSection("模板问答整理", List.of(
|
|
|
- new QaLine("请问你和候选人是什么时候认识的?他的职务是什么?具体工作职责有哪些方面?",
|
|
|
- safe(resolveKnownAnswer(context))),
|
|
|
- new QaLine("你觉得他在工作方面有哪些优势?比较擅长哪方面的工作?", ""),
|
|
|
- new QaLine("他在职业道德方面的表现怎么样?你有没有看到或听到关于他不太好的现象和评价?", ""),
|
|
|
- new QaLine("请问他人际关系怎么样?和其他同事相处有没有问题?", ""),
|
|
|
- new QaLine("你对他的总体评价如何?你觉得他哪些方面需要提高和改进呢?",
|
|
|
- safe(context.review == null ? null : context.review.getTotalRemark()))
|
|
|
- ));
|
|
|
- writer.addParagraphSection("备注", safe(context.companyName));
|
|
|
- writer.close();
|
|
|
-
|
|
|
- document.save(outputStream);
|
|
|
- return outputStream.toByteArray();
|
|
|
- } catch (IOException e) {
|
|
|
- throw new ServiceException("生成报告PDF失败: " + e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private PDType0Font loadChineseFont(PDDocument document) throws IOException {
|
|
|
- List<String> fontPaths = List.of(
|
|
|
- "C:/Windows/Fonts/msyh.ttc",
|
|
|
- "C:/Windows/Fonts/simsun.ttc",
|
|
|
- "C:/Windows/Fonts/simhei.ttf"
|
|
|
- );
|
|
|
- for (String fontPath : fontPaths) {
|
|
|
- Path path = Path.of(fontPath);
|
|
|
- if (Files.exists(path)) {
|
|
|
- try {
|
|
|
- return PDType0Font.load(document, Files.newInputStream(path), true);
|
|
|
- } catch (IOException ignored) {
|
|
|
- // 继续尝试下一个可用字体
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- throw new ServiceException("未找到可用的中文字体文件,无法生成报告PDF");
|
|
|
+ Map<String, String> placeholders = new LinkedHashMap<>();
|
|
|
+
|
|
|
+ // 候选人基本信息
|
|
|
+ placeholders.put("candidateName", safe(context.student == null ? null : context.student.getName()));
|
|
|
+ placeholders.put("position", safe(resolvePositionName(context)));
|
|
|
+ placeholders.put("surveyTime", safe(resolveSurveyTime(context)));
|
|
|
+
|
|
|
+ // 学历核实
|
|
|
+ placeholders.put("school", safe(resolveSchoolName(context)));
|
|
|
+ placeholders.put("certNo", " ");
|
|
|
+ placeholders.put("eduVerification", safe(resolveEducationVerification(context)));
|
|
|
+
|
|
|
+ // 最近工作单位
|
|
|
+ placeholders.put("company", safe(context.experience == null ? null : context.experience.getCompany()));
|
|
|
+ placeholders.put("employmentPeriod", safe(resolveEmploymentPeriod(context)));
|
|
|
+ placeholders.put("jobTitle", safe(resolveLatestJobTitle(context)));
|
|
|
+ placeholders.put("lastSalary", " ");
|
|
|
+ placeholders.put("leaveReason", safe(context.review == null ? null : context.review.getLeaveReason()));
|
|
|
+
|
|
|
+ // 工作表现评估
|
|
|
+ placeholders.put("supervisorEval", " ");
|
|
|
+ placeholders.put("strength", safe(resolveWorkContent(context)));
|
|
|
+ placeholders.put("improvement", " ");
|
|
|
+ placeholders.put("professionalAbility",
|
|
|
+ safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityAName(),
|
|
|
+ context.review == null ? null : context.review.getAbilityARate(),
|
|
|
+ context.review == null ? null : context.review.getAbilityARemark())));
|
|
|
+ placeholders.put("workAttitude",
|
|
|
+ safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityBName(),
|
|
|
+ context.review == null ? null : context.review.getAbilityBRate(),
|
|
|
+ context.review == null ? null : context.review.getAbilityBRemark())));
|
|
|
+ placeholders.put("teamwork",
|
|
|
+ safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityCName(),
|
|
|
+ context.review == null ? null : context.review.getAbilityCRate(),
|
|
|
+ context.review == null ? null : context.review.getAbilityCRemark())));
|
|
|
+ placeholders.put("ethics", " ");
|
|
|
+ placeholders.put("colleagueEval", " ");
|
|
|
+ placeholders.put("cooperation", " ");
|
|
|
+ placeholders.put("colleagueAbility", " ");
|
|
|
+ placeholders.put("hrEval", " ");
|
|
|
+ placeholders.put("violation", " ");
|
|
|
+ placeholders.put("handover", " ");
|
|
|
+
|
|
|
+ // 调查结论
|
|
|
+ placeholders.put("conclusionReason", safe(context.review == null ? null : context.review.getTotalRemark()));
|
|
|
+
|
|
|
+ // 调查人 / 日期
|
|
|
+ placeholders.put("investigator", safe(context.companyName));
|
|
|
+ placeholders.put("investigateDate", safe(resolveSurveyTime(context)));
|
|
|
+
|
|
|
+ // 访谈记录(一)—— 使用上家公司数据
|
|
|
+ placeholders.put("interviewee1Name", " ");
|
|
|
+ placeholders.put("interviewee1Relation", "上级主管");
|
|
|
+ placeholders.put("interviewee1Contact", " ");
|
|
|
+ placeholders.put("qa1_a1", safe(resolveKnownAnswer(context)));
|
|
|
+ placeholders.put("qa1_a2", " ");
|
|
|
+ placeholders.put("qa1_a3", " ");
|
|
|
+ placeholders.put("qa1_a4", " ");
|
|
|
+ placeholders.put("qa1_a5", safe(context.review == null ? null : context.review.getTotalRemark()));
|
|
|
+
|
|
|
+ // 访谈记录(二)
|
|
|
+ placeholders.put("interviewee2Name", " ");
|
|
|
+ placeholders.put("interviewee2Relation", "公司HR");
|
|
|
+ placeholders.put("interviewee2Contact", " ");
|
|
|
+ placeholders.put("qa2_a1", " ");
|
|
|
+ placeholders.put("qa2_a2", " ");
|
|
|
+ placeholders.put("qa2_a3", " ");
|
|
|
+ placeholders.put("qa2_a4", " ");
|
|
|
+ placeholders.put("qa2_a5", " ");
|
|
|
+
|
|
|
+ // 访谈记录(三)
|
|
|
+ placeholders.put("interviewee3Name", " ");
|
|
|
+ placeholders.put("interviewee3Relation", "同事");
|
|
|
+ placeholders.put("interviewee3Contact", " ");
|
|
|
+ placeholders.put("qa3_a1", " ");
|
|
|
+ placeholders.put("qa3_a2", " ");
|
|
|
+ placeholders.put("qa3_a3", " ");
|
|
|
+ placeholders.put("qa3_a4", " ");
|
|
|
+ placeholders.put("qa3_a5", " ");
|
|
|
+
|
|
|
+ // 确保所有空值替换为空格,保证模板格式不被破坏
|
|
|
+ placeholders.replaceAll((k, v) -> (v == null || v.isEmpty()) ? " " : v);
|
|
|
+
|
|
|
+ return wordReportService.generatePdf(placeholders);
|
|
|
}
|
|
|
|
|
|
private String uploadReportPdf(MainBackRecord record, ReportContext context, byte[] pdfBytes) {
|
|
|
@@ -1085,147 +1086,6 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
|
|
|
private MainBackOrder backOrder;
|
|
|
private String companyName;
|
|
|
}
|
|
|
-
|
|
|
- private static class QaLine {
|
|
|
- private final String question;
|
|
|
- private final String answer;
|
|
|
-
|
|
|
- private QaLine(String question, String answer) {
|
|
|
- this.question = question;
|
|
|
- this.answer = answer;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static class ReportWriter {
|
|
|
- private final PDDocument document;
|
|
|
- private final PDType0Font font;
|
|
|
- private PDPage page;
|
|
|
- private PDPageContentStream stream;
|
|
|
- private float y;
|
|
|
-
|
|
|
- private ReportWriter(PDDocument document, PDType0Font font) throws IOException {
|
|
|
- this.document = document;
|
|
|
- this.font = font;
|
|
|
- newPage();
|
|
|
- }
|
|
|
-
|
|
|
- private void addTitle(String title) throws IOException {
|
|
|
- writeWrapped(title, REPORT_TITLE_SIZE, true, 1.2F, 10F);
|
|
|
- y -= 8F;
|
|
|
- }
|
|
|
-
|
|
|
- private void addKeyValueSection(String heading, List<String> lines) throws IOException {
|
|
|
- addHeading(heading);
|
|
|
- for (String line : lines) {
|
|
|
- writeWrapped(line, REPORT_BODY_SIZE, false, 1.5F, 4F);
|
|
|
- }
|
|
|
- y -= 4F;
|
|
|
- }
|
|
|
-
|
|
|
- private void addParagraphSection(String heading, String content) throws IOException {
|
|
|
- addHeading(heading);
|
|
|
- writeWrapped(StringUtils.isBlank(content) ? "" : content, REPORT_BODY_SIZE, false, 1.6F, 6F);
|
|
|
- y -= 4F;
|
|
|
- }
|
|
|
-
|
|
|
- private void addRatingSection(String heading, List<String> lines) throws IOException {
|
|
|
- addHeading(heading);
|
|
|
- for (String line : lines) {
|
|
|
- if (StringUtils.isBlank(line)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- writeWrapped("• " + line, REPORT_BODY_SIZE, false, 1.6F, 4F);
|
|
|
- }
|
|
|
- y -= 4F;
|
|
|
- }
|
|
|
-
|
|
|
- private void addQuestionAnswerSection(String heading, List<QaLine> lines) throws IOException {
|
|
|
- addHeading(heading);
|
|
|
- for (QaLine line : lines) {
|
|
|
- writeWrapped("问:" + safeLine(line.question), REPORT_BODY_SIZE, true, 1.5F, 2F);
|
|
|
- writeWrapped("答:" + safeLine(line.answer), REPORT_BODY_SIZE, false, 1.6F, 6F);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void addHeading(String heading) throws IOException {
|
|
|
- ensureSpace(REPORT_HEADING_SIZE * 2.2F);
|
|
|
- writeWrapped(heading, REPORT_HEADING_SIZE, true, 1.3F, 6F);
|
|
|
- }
|
|
|
-
|
|
|
- private void writeWrapped(String text, float fontSize, boolean bold, float lineFactor, float bottomGap) throws IOException {
|
|
|
- List<String> lines = wrapText(safeLine(text), fontSize);
|
|
|
- if (lines.isEmpty()) {
|
|
|
- lines = List.of("");
|
|
|
- }
|
|
|
- float lineHeight = fontSize * lineFactor;
|
|
|
- ensureSpace(lineHeight * lines.size() + bottomGap);
|
|
|
- for (String line : lines) {
|
|
|
- stream.beginText();
|
|
|
- stream.setFont(font, fontSize);
|
|
|
- stream.newLineAtOffset(REPORT_MARGIN, y);
|
|
|
- stream.showText(line);
|
|
|
- stream.endText();
|
|
|
- y -= lineHeight;
|
|
|
- }
|
|
|
- y -= bottomGap;
|
|
|
- }
|
|
|
-
|
|
|
- private List<String> wrapText(String text, float fontSize) throws IOException {
|
|
|
- float width = PDRectangle.A4.getWidth() - REPORT_MARGIN * 2;
|
|
|
- List<String> lines = new ArrayList<>();
|
|
|
- if (StringUtils.isBlank(text)) {
|
|
|
- lines.add("");
|
|
|
- return lines;
|
|
|
- }
|
|
|
- StringBuilder current = new StringBuilder();
|
|
|
- for (char ch : text.toCharArray()) {
|
|
|
- if (ch == '\n') {
|
|
|
- lines.add(current.toString());
|
|
|
- current.setLength(0);
|
|
|
- continue;
|
|
|
- }
|
|
|
- String candidate = current.toString() + ch;
|
|
|
- float candidateWidth = font.getStringWidth(candidate) / 1000 * fontSize;
|
|
|
- if (candidateWidth > width && current.length() > 0) {
|
|
|
- lines.add(current.toString());
|
|
|
- current.setLength(0);
|
|
|
- }
|
|
|
- current.append(ch);
|
|
|
- }
|
|
|
- if (!current.isEmpty()) {
|
|
|
- lines.add(current.toString());
|
|
|
- }
|
|
|
- return lines;
|
|
|
- }
|
|
|
-
|
|
|
- private void ensureSpace(float neededHeight) throws IOException {
|
|
|
- if (y - neededHeight < REPORT_MARGIN) {
|
|
|
- newPage();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void newPage() throws IOException {
|
|
|
- closeCurrentStream();
|
|
|
- page = new PDPage(PDRectangle.A4);
|
|
|
- document.addPage(page);
|
|
|
- stream = new PDPageContentStream(document, page);
|
|
|
- y = PDRectangle.A4.getHeight() - REPORT_MARGIN;
|
|
|
- }
|
|
|
-
|
|
|
- private void closeCurrentStream() throws IOException {
|
|
|
- if (stream != null) {
|
|
|
- stream.close();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void close() throws IOException {
|
|
|
- closeCurrentStream();
|
|
|
- }
|
|
|
-
|
|
|
- private String safeLine(String value) {
|
|
|
- return value == null ? "" : value.replace('\t', ' ');
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
|