Gqingci 8 часов назад
Родитель
Сommit
afc5cff952
16 измененных файлов с 722 добавлено и 266 удалено
  1. 50 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsQuickPhraseController.java
  2. 39 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/CsQuickPhrase.java
  3. 85 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackCheckData.java
  4. 4 30
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPostCandidateReview.java
  5. 0 4
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainStudentEducation.java
  6. 0 4
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainStudentExperience.java
  7. 16 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/CsQuickPhraseBo.java
  8. 48 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/CsQuickPhraseVo.java
  9. 9 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/CsQuickPhraseMapper.java
  10. 7 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainBackCheckDataMapper.java
  11. 24 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsQuickPhraseService.java
  12. 19 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainBackCheckDataService.java
  13. 70 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsQuickPhraseServiceImpl.java
  14. 38 0
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackCheckDataServiceImpl.java
  15. 298 224
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java
  16. 15 4
      ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/WordReportService.java

+ 50 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/controller/CsQuickPhraseController.java

@@ -0,0 +1,50 @@
+package org.dromara.main.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.main.domain.bo.CsQuickPhraseBo;
+import org.dromara.main.domain.vo.CsQuickPhraseVo;
+import org.dromara.main.service.ICsQuickPhraseService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/main/quickPhrase")
+public class CsQuickPhraseController extends BaseController {
+
+    private final ICsQuickPhraseService quickPhraseService;
+
+    /**
+     * 获取当前用户的快捷短语列表
+     */
+    @GetMapping("/list")
+    public R<List<CsQuickPhraseVo>> list() {
+        return R.ok(quickPhraseService.listByCurrentUser());
+    }
+
+    /**
+     * 添加快捷短语
+     */
+    @Log(title = "添加快捷短语", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public R<CsQuickPhraseVo> add(@Validated @RequestBody CsQuickPhraseBo bo) {
+        return R.ok(quickPhraseService.addPhrase(bo));
+    }
+
+    /**
+     * 删除快捷短语
+     */
+    @Log(title = "删除快捷短语", businessType = BusinessType.DELETE)
+    @DeleteMapping("/delete")
+    public R<Void> delete(@RequestBody Map<String, Long> params) {
+        return toAjax(quickPhraseService.deletePhrase(params.get("id")));
+    }
+}

+ 39 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/CsQuickPhrase.java

@@ -0,0 +1,39 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 客服快捷短语
+ */
+@Data
+@TableName("cs_quick_phrase")
+public class CsQuickPhrase {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 短语内容
+     */
+    private String content;
+
+    /**
+     * 排序序号
+     */
+    private Integer sortOrder;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 85 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainBackCheckData.java

@@ -0,0 +1,85 @@
+package org.dromara.main.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.util.Date;
+
+/**
+ * 背景调查表单数据(独立存储,不与当前公司评价冲突)
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("main_back_check_data")
+public class MainBackCheckData extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long recordId;
+    private Long candidateId;
+    private String tenantId;
+
+    // 1. 候选人基本信息
+    private String candidateName;
+    private String applyPosition;
+    private Date checkTime;
+
+    // 2. 学历核实
+    private String gradSchool;
+    private String eduCertNo;
+    private String eduVerifyStatus;
+    /** 学历核实结果(总控填写): 属实/不属实/无法核实 */
+    private String eduCheckResult;
+
+    // 3. 最近工作单位
+    private String companyName;
+    private String workStartTime;
+    private String workEndTime;
+    private String jobTitle;
+    private String lastSalary;
+    private String leaveReasonVerify;
+    private String companyCheckRemark;
+    /** 工作单位核实结果(总控填写): 属实/部分属实/不属实 */
+    private String companyCheckResult;
+
+    // 4. 工作表现 - 上级
+    private String leaderEvalAdvantage;
+    private String leaderEvalImprove;
+    private String leaderEvalProf;
+    private String leaderEvalAttitude;
+    private String leaderEvalTeam;
+    private String leaderEvalMorals;
+
+    // 4. 工作表现 - 同事
+    private String colleagueEvalTeamwork;
+    private String colleagueEvalProf;
+
+    // 4. 工作表现 - HR
+    private String hrEvalDispute;
+    private String hrEvalTransfer;
+    private String performCheckRemark;
+    /** 表现核实结果(总控填写): 良好/一般/需关注 */
+    private String performCheckResult;
+
+    // 5. 法务风险
+    private Integer hasNonCompete;
+    private Integer hasNda;
+    private String agreementRemark;
+    private Integer hasDispute;
+    private String disputeRemark;
+
+    // 7. 调查结论
+    private String conclusion;
+    private String conclusionReason;
+    private String investigatorName;
+    private Date investigatorDate;
+
+    @TableLogic
+    private String delFlag;
+}

+ 4 - 30
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainPostCandidateReview.java

@@ -26,50 +26,24 @@ public class MainPostCandidateReview extends BaseEntity {
     private String employmentStatus;
     private Date entryTime;
     private Date leaveTime;
-    private String leaveReason;
+
+    private String supervisorEval;
+    private String colleagueEval;
+    private String hrEval;
 
     private BigDecimal totalRate;
-    private String totalRemark;
 
     private String abilityAName;
     private BigDecimal abilityARate;
-    private String abilityARemark;
 
     private String abilityBName;
     private BigDecimal abilityBRate;
-    private String abilityBRemark;
 
     private String abilityCName;
     private BigDecimal abilityCRate;
-    private String abilityCRemark;
-
-    private String supervisorEval;
-    private String strength;
-    private String improvement;
 
     private String abilityDName;
     private BigDecimal abilityDRate;
-    private String abilityDRemark;
-
-    private String colleagueEval;
-    private String cooperation;
-    private String colleagueAbility;
-
-    private String hrEval;
-    private String violation;
-    private String handover;
-
-    private String performCheckResult;
-    private String performRemark;
-
-    private Integer nonCompeteAgreement;
-    private Integer confidentialityAgreement;
-    private String agreementRemark;
-
-    private Integer laborDispute;
-    private String disputeRemark;
-
-    private String conclusionResult;
 
     private String reviewStatus;
 

+ 0 - 4
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainStudentEducation.java

@@ -24,10 +24,6 @@ public class MainStudentEducation extends BaseEntity {
     private String major;
     private String campusExperience;
 
-    private String certNo;
-    private String eduVerification;
-    private String checkResult;
-
     @TableLogic
     private String delFlag;
 }

+ 0 - 4
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/MainStudentExperience.java

@@ -27,10 +27,6 @@ public class MainStudentExperience extends BaseEntity {
 
     private Integer isHidden;
 
-    private String lastSalary;
-    private String checkResult;
-    private String checkRemark;
-
     @TableLogic
     private String delFlag;
 }

+ 16 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/bo/CsQuickPhraseBo.java

@@ -0,0 +1,16 @@
+package org.dromara.main.domain.bo;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Data
+public class CsQuickPhraseBo {
+
+    /**
+     * 短语内容
+     */
+    @NotBlank(message = "短语内容不能为空")
+    @Size(max = 30, message = "短语内容最多30个字符")
+    private String content;
+}

+ 48 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/domain/vo/CsQuickPhraseVo.java

@@ -0,0 +1,48 @@
+package org.dromara.main.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.main.domain.CsQuickPhrase;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@AutoMapper(target = CsQuickPhrase.class)
+public class CsQuickPhraseVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @JsonProperty("id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long userId;
+
+    /**
+     * 短语内容
+     */
+    private String content;
+
+    /**
+     * 排序序号
+     */
+    private Integer sortOrder;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 9 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/CsQuickPhraseMapper.java

@@ -0,0 +1,9 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.CsQuickPhrase;
+import org.dromara.main.domain.vo.CsQuickPhraseVo;
+
+public interface CsQuickPhraseMapper extends BaseMapperPlus<CsQuickPhrase, CsQuickPhraseVo> {
+
+}

+ 7 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/mapper/MainBackCheckDataMapper.java

@@ -0,0 +1,7 @@
+package org.dromara.main.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.main.domain.MainBackCheckData;
+
+public interface MainBackCheckDataMapper extends BaseMapperPlus<MainBackCheckData, MainBackCheckData> {
+}

+ 24 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/ICsQuickPhraseService.java

@@ -0,0 +1,24 @@
+package org.dromara.main.service;
+
+import org.dromara.main.domain.bo.CsQuickPhraseBo;
+import org.dromara.main.domain.vo.CsQuickPhraseVo;
+
+import java.util.List;
+
+public interface ICsQuickPhraseService {
+
+    /**
+     * 获取当前用户的快捷短语列表
+     */
+    List<CsQuickPhraseVo> listByCurrentUser();
+
+    /**
+     * 添加快捷短语(每人最多3个)
+     */
+    CsQuickPhraseVo addPhrase(CsQuickPhraseBo bo);
+
+    /**
+     * 删除快捷短语
+     */
+    Boolean deletePhrase(Long id);
+}

+ 19 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/IMainBackCheckDataService.java

@@ -0,0 +1,19 @@
+package org.dromara.main.service;
+
+import org.dromara.main.domain.MainBackCheckData;
+
+/**
+ * 背景调查表单数据Service接口
+ */
+public interface IMainBackCheckDataService {
+
+    /**
+     * 根据recordId查询背调表单数据
+     */
+    MainBackCheckData getByRecordId(Long recordId);
+
+    /**
+     * 新增或更新背调表单数据(按recordId去重)
+     */
+    void saveOrUpdateByRecordId(MainBackCheckData data);
+}

+ 70 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/CsQuickPhraseServiceImpl.java

@@ -0,0 +1,70 @@
+package org.dromara.main.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import io.github.linpeilie.Converter;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.main.domain.CsQuickPhrase;
+import org.dromara.main.domain.bo.CsQuickPhraseBo;
+import org.dromara.main.domain.vo.CsQuickPhraseVo;
+import org.dromara.main.mapper.CsQuickPhraseMapper;
+import org.dromara.main.service.ICsQuickPhraseService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class CsQuickPhraseServiceImpl implements ICsQuickPhraseService {
+
+    private final CsQuickPhraseMapper baseMapper;
+    private final Converter converter;
+
+    private static final int MAX_PHRASE_COUNT = 3;
+
+    @Override
+    public List<CsQuickPhraseVo> listByCurrentUser() {
+        Long userId = LoginHelper.getUserId();
+        LambdaQueryWrapper<CsQuickPhrase> lqw = Wrappers.lambdaQuery();
+        lqw.eq(CsQuickPhrase::getUserId, userId);
+        lqw.orderByAsc(CsQuickPhrase::getSortOrder, CsQuickPhrase::getId);
+        List<CsQuickPhrase> list = baseMapper.selectList(lqw);
+        return converter.convert(list, CsQuickPhraseVo.class);
+    }
+
+    @Override
+    public CsQuickPhraseVo addPhrase(CsQuickPhraseBo bo) {
+        Long userId = LoginHelper.getUserId();
+
+        // 校验数量上限
+        Long count = baseMapper.selectCount(
+            Wrappers.lambdaQuery(CsQuickPhrase.class)
+                .eq(CsQuickPhrase::getUserId, userId)
+        );
+        if (count >= MAX_PHRASE_COUNT) {
+            throw new ServiceException("最多只能添加" + MAX_PHRASE_COUNT + "个快捷短语");
+        }
+
+        CsQuickPhrase entity = new CsQuickPhrase();
+        entity.setUserId(userId);
+        entity.setContent(bo.getContent());
+        entity.setSortOrder(count.intValue() + 1);
+        entity.setCreateTime(LocalDateTime.now());
+        baseMapper.insert(entity);
+
+        return converter.convert(entity, CsQuickPhraseVo.class);
+    }
+
+    @Override
+    public Boolean deletePhrase(Long id) {
+        Long userId = LoginHelper.getUserId();
+        CsQuickPhrase existing = baseMapper.selectById(id);
+        if (existing == null || !existing.getUserId().equals(userId)) {
+            throw new ServiceException("快捷短语不存在或无权删除");
+        }
+        return baseMapper.deleteById(id) > 0;
+    }
+}

+ 38 - 0
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackCheckDataServiceImpl.java

@@ -0,0 +1,38 @@
+package org.dromara.main.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.main.domain.MainBackCheckData;
+import org.dromara.main.mapper.MainBackCheckDataMapper;
+import org.dromara.main.service.IMainBackCheckDataService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 背景调查表单数据Service实现
+ */
+@RequiredArgsConstructor
+@Service
+public class MainBackCheckDataServiceImpl implements IMainBackCheckDataService {
+
+    private final MainBackCheckDataMapper baseMapper;
+
+    @Override
+    public MainBackCheckData getByRecordId(Long recordId) {
+        return baseMapper.selectOne(
+            Wrappers.<MainBackCheckData>lambdaQuery()
+                .eq(MainBackCheckData::getRecordId, recordId)
+                .last("limit 1")
+        );
+    }
+
+    @Override
+    public void saveOrUpdateByRecordId(MainBackCheckData data) {
+        MainBackCheckData existing = getByRecordId(data.getRecordId());
+        if (existing == null) {
+            baseMapper.insert(data);
+        } else {
+            data.setId(existing.getId());
+            baseMapper.updateById(data);
+        }
+    }
+}

+ 298 - 224
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/MainBackOrderServiceImpl.java

@@ -25,6 +25,7 @@ import org.dromara.main.domain.MainBackRecord;
 import org.dromara.main.domain.MainOrder;
 import org.dromara.main.domain.MainPosition;
 import org.dromara.main.domain.MainPostCandidateReview;
+import org.dromara.main.domain.MainBackCheckData;
 import org.dromara.main.domain.MainBackInterview;
 import org.dromara.main.domain.bo.BackgroundCheckFormBo;
 import org.dromara.main.domain.bo.MainBackOrderBo;
@@ -40,6 +41,7 @@ import org.dromara.main.mapper.MainBackCandidateMapper;
 import org.dromara.main.mapper.MainBackCategoryMapper;
 import org.dromara.main.mapper.MainBackClauseMapper;
 import org.dromara.main.mapper.MainBackInterviewMapper;
+import org.dromara.main.mapper.MainBackCheckDataMapper;
 import org.dromara.main.mapper.MainBackOrderMapper;
 import org.dromara.main.mapper.MainBackRecordMapper;
 import org.dromara.main.mapper.MainOrderMapper;
@@ -50,6 +52,7 @@ import org.dromara.main.mapper.MainStudentExperienceMapper;
 import org.dromara.main.mapper.MainStudentMapper;
 import org.dromara.main.mapper.PaymentMapper;
 import org.dromara.main.service.ICompanyAccountService;
+import org.dromara.main.service.IMainBackCheckDataService;
 import org.dromara.main.service.IMainBackOrderService;
 import org.dromara.system.domain.SysTenant;
 import org.dromara.system.domain.bo.SysTenantBo;
@@ -99,11 +102,13 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
     private final MainStudentExperienceMapper mainStudentExperienceMapper;
     private final MainPostCandidateReviewMapper mainPostCandidateReviewMapper;
     private final MainBackInterviewMapper mainBackInterviewMapper;
+    private final MainBackCheckDataMapper mainBackCheckDataMapper;
     private final MainPositionMapper mainPositionMapper;
     private final PaymentMapper paymentMapper;
     private final SysTenantMapper sysTenantMapper;
     private final ISysTenantService tenantService;
     private final ICompanyAccountService companyAccountService;
+    private final IMainBackCheckDataService mainBackCheckDataService;
     private final WordReportService wordReportService;
 
     @Override
@@ -1058,6 +1063,11 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
     private ReportContext buildReportContext(MainBackRecord record, MainBackOrder backOrder) {
         MainBackCandidate candidate = mainBackCandidateMapper.selectById(record.getCandidateId());
         MainStudent student = candidate == null ? null : mainStudentMapper.selectById(candidate.getStudentId());
+
+        // 新表数据(新表有值的字段优先用,新表没有的字段仍从旧表取)
+        MainBackCheckData checkData = mainBackCheckDataService.getByRecordId(record.getId());
+
+        // 旧表数据始终查询,用于补充新表不存在的字段
         MainPostCandidateReview review = mainPostCandidateReviewMapper.selectOne(
             Wrappers.<MainPostCandidateReview>lambdaQuery()
                 .eq(MainPostCandidateReview::getCandidateId, record.getCandidateId())
@@ -1084,12 +1094,14 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
                 .orderByAsc(MainBackInterview::getId)
         );
 
-        Long postId = review != null && review.getPostId() != null ? review.getPostId() : candidate == null ? null : candidate.getPostId();
+        Long postId = (review != null && review.getPostId() != null) ? review.getPostId()
+            : (candidate == null ? null : candidate.getPostId());
         MainPosition position = postId == null ? null : mainPositionMapper.selectById(postId);
         SysTenantVo tenant = tenantService.queryByTenantId(backOrder.getTenantId());
 
         ReportContext context = new ReportContext();
         context.student = student;
+        context.checkData = checkData;
         context.review = review;
         context.education = education;
         context.experience = experience;
@@ -1103,45 +1115,155 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
 
     private byte[] buildReportPdf(ReportContext context) {
         Map<String, String> placeholders = new LinkedHashMap<>();
+        MainBackCheckData cd = context.checkData;
+        boolean useCheckData = cd != null;
 
-        // 候选人基本信息
-        placeholders.put("candidateName", safe(context.student == null ? null : context.student.getName()));
-        placeholders.put("position", safe(resolvePositionName(context)));
-        placeholders.put("surveyTime", safe(resolveSurveyTime(context)));
+        // 策略:新表有的字段直接从新表取,新表没有的字段从旧表取
+        // 注:旧表 review 已删的字段不再走 else,直接从新表取或给默认值
 
-        // 学历核实
-        placeholders.put("school", safe(resolveSchoolName(context)));
-        placeholders.put("certNo", safe(context.education == null ? null : context.education.getCertNo()));
-        placeholders.put("eduVerification", safe(context.education == null ? null : context.education.getEduVerification()));
+        // 候选人基本信息(新表有)
+        if (useCheckData && StringUtils.isNotBlank(cd.getCandidateName())) {
+            placeholders.put("candidateName", safe(cd.getCandidateName()));
+        } else {
+            placeholders.put("candidateName", safe(context.student == null ? null : context.student.getName()));
+        }
+        if (useCheckData && StringUtils.isNotBlank(cd.getApplyPosition())) {
+            placeholders.put("position", safe(cd.getApplyPosition()));
+        } else {
+            placeholders.put("position", safe(resolvePositionName(context)));
+        }
+        if (useCheckData && cd.getCheckTime() != null) {
+            placeholders.put("surveyTime", safe(new java.text.SimpleDateFormat("yyyy-MM-dd").format(cd.getCheckTime())));
+        } else {
+            placeholders.put("surveyTime", safe(resolveSurveyTime(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", safe(context.experience == null ? null : context.experience.getLastSalary()));
-        placeholders.put("leaveReason", safe(context.review == null ? null : context.review.getLeaveReason()));
+        // 学历核实(新表有)
+        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 已删
+        }
 
-        // 工作表现评估
+        // 最近工作单位(新表有)
+        if (useCheckData && StringUtils.isNotBlank(cd.getCompanyName())) {
+            placeholders.put("company", safe(cd.getCompanyName()));
+        } else {
+            placeholders.put("company", safe(context.experience == null ? null : context.experience.getCompany()));
+        }
+        if (useCheckData && (StringUtils.isNotBlank(cd.getWorkStartTime()) || StringUtils.isNotBlank(cd.getWorkEndTime()))) {
+            placeholders.put("employmentPeriod", safe(cd.getWorkStartTime()) + " ~ " + safe(cd.getWorkEndTime()));
+        } else {
+            placeholders.put("employmentPeriod", safe(resolveEmploymentPeriod(context)));
+        }
+        if (useCheckData && StringUtils.isNotBlank(cd.getJobTitle())) {
+            placeholders.put("jobTitle", safe(cd.getJobTitle()));
+        } 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()));
-        placeholders.put("strength", safe(context.review == null ? null : context.review.getStrength()));
-        placeholders.put("improvement", safe(context.review == null ? null : context.review.getImprovement()));
-        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", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityDName(), context.review == null ? null : context.review.getAbilityDRate(), context.review == null ? null : context.review.getAbilityDRemark())));
+
+        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 已删
+        }
+        if (useCheckData) {
+            placeholders.put("professionalAbility", safe(cd.getLeaderEvalProf()));
+        } else {
+            placeholders.put("professionalAbility", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityAName(), context.review == null ? null : context.review.getAbilityARate(), null)));
+        }
+        if (useCheckData) {
+            placeholders.put("workAttitude", safe(cd.getLeaderEvalAttitude()));
+        } else {
+            placeholders.put("workAttitude", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityBName(), context.review == null ? null : context.review.getAbilityBRate(), null)));
+        }
+        if (useCheckData) {
+            placeholders.put("teamwork", safe(cd.getLeaderEvalTeam()));
+        } else {
+            placeholders.put("teamwork", safe(buildAbilityLine(context.review == null ? null : context.review.getAbilityCName(), context.review == null ? null : context.review.getAbilityCRate(), null)));
+        }
+        if (useCheckData) {
+            placeholders.put("ethics", safe(cd.getLeaderEvalMorals()));
+        } else {
+            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()));
-        placeholders.put("cooperation", safe(context.review == null ? null : context.review.getCooperation()));
-        placeholders.put("colleagueAbility", safe(context.review == null ? null : context.review.getColleagueAbility()));
+
+        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()));
-        placeholders.put("violation", safe(context.review == null ? null : context.review.getViolation()));
-        placeholders.put("handover", safe(context.review == null ? null : context.review.getHandover()));
 
-        // 调查结论
-        placeholders.put("conclusionReason", safe(context.review == null ? null : context.review.getTotalRemark()));
+        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 已删
+        }
+
+        // 调查结论(新表有)
+        if (useCheckData) {
+            placeholders.put("conclusionReason", safe(cd.getConclusionReason()));
+        } else {
+            placeholders.put("conclusionReason", " "); // 旧表 totalRemark 已删
+        }
 
-        // 调查人 / 日期
-        placeholders.put("investigator", safe(context.companyName));
-        placeholders.put("investigateDate", safe(resolveSurveyTime(context)));
+        // 调查人 / 日期(新表有)
+        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)));
+        }
 
         // 访谈记录 Q&A 填充
         fillInterviewPlaceholders(placeholders, context);
@@ -1150,39 +1272,78 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         placeholders.replaceAll((k, v) -> (v == null || v.isEmpty()) ? " " : v);
 
         // ========== Checkbox 勾选框 ==========
-        placeholders.put("CB_eduResult", "属实,不属实,无法核实|SELECTED:" + safe(context.education == null ? null : context.education.getCheckResult()));
-        placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:" + safe(context.experience == null ? null : context.experience.getCheckResult()));
-        placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:" + safe(context.review == null ? null : context.review.getPerformCheckResult()));
-        
-        String agreeSelect = "";
-        if (context.review != null) {
-            if (Integer.valueOf(1).equals(context.review.getNonCompeteAgreement())) agreeSelect = "竞业禁止协议:是";
-            else if (Integer.valueOf(0).equals(context.review.getNonCompeteAgreement())) agreeSelect = "竞业禁止协议:否";
-            
-            if (Integer.valueOf(1).equals(context.review.getConfidentialityAgreement())) {
-                 agreeSelect += (agreeSelect.isEmpty() ? "" : "|SELECTED:") + "保密协议:是"; // NOT FULLY SUPPORTED by current CB logic to select multiple in one line natively, let's just format it as text if needed, but our WordReportService only supports ONE SELECTED. Wait, our word report service checks if option.equals(selected).
-                 // Actually, the placeholder supports one selected. Since they are separate options in the string, if we want multiple, we need to improve WordReportService.
-                 // For now, let's map them to 2 CB groups? No, word has them on one line.
+        // 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
+        if (useCheckData && StringUtils.isNotBlank(cd.getCompanyCheckResult())) {
+            placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:" + safe(cd.getCompanyCheckResult()));
+        } else {
+            placeholders.put("CB_companyResult", "属实,部分属实,不属实|SELECTED:"); // 旧表 checkResult 已删
+        }
+        // CB_performResult - 表现核实结果(总控核实),新表有 performCheckResult
+        if (useCheckData && StringUtils.isNotBlank(cd.getPerformCheckResult())) {
+            placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:" + safe(cd.getPerformCheckResult()));
+        } else {
+            placeholders.put("CB_performResult", "良好,一般,需关注|SELECTED:"); // 旧表 performCheckResult 已删
+        }
+
+        // CB_restrictAgreement - 新表有 hasNonCompete / hasNda
+        // SELECTED 支持多选,用逗号分隔
+        String agreeSelected = "";
+        if (useCheckData) {
+            if (Integer.valueOf(1).equals(cd.getHasNonCompete())) agreeSelected = "竞业禁止协议:是";
+            else if (Integer.valueOf(0).equals(cd.getHasNonCompete())) agreeSelected = "竞业禁止协议:否";
+            if (Integer.valueOf(1).equals(cd.getHasNda())) {
+                agreeSelected += (agreeSelected.isEmpty() ? "" : ",") + "保密协议:是";
+            } else if (Integer.valueOf(0).equals(cd.getHasNda())) {
+                agreeSelected += (agreeSelected.isEmpty() ? "" : ",") + "保密协议:否";
             }
+        } else {
+            // 旧表 nonCompeteAgreement / confidentialityAgreement 已删,无数据来源
         }
-        
-        // Better approach for restriction agreements: it's better to use two different CB placeholders if they are multiple choices, 
-        // but the word template only has one CB_restrictAgreement. Let's fix that dynamically by creating a specialized string or just use the DB value.
-        // I will just use text replacing for now or keep it simple. Let's pass the raw string if needed.
-        placeholders.put("CB_restrictAgreement", "竞业禁止协议:是,竞业禁止协议:否,保密协议:是,保密协议:否|SELECTED:" + agreeSelect);
-        
+        placeholders.put("CB_restrictAgreement", "竞业禁止协议:是,竞业禁止协议:否,保密协议:是,保密协议:否|SELECTED:" + agreeSelected);
+
+        // CB_disputeRecord - 新表有 hasDispute
         String disputeSelect = "";
-        if (context.review != null && context.review.getLaborDispute() != null) {
-             disputeSelect = context.review.getLaborDispute() == 1 ? "是" : "否";
+        if (useCheckData) {
+            disputeSelect = cd.getHasDispute() != null && cd.getHasDispute() == 1 ? "是" : "否";
+        } else {
+            // 旧表 laborDispute 已删,无数据来源
         }
         placeholders.put("CB_disputeRecord", "是,否|SELECTED:" + disputeSelect);
-        placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:" + safe(context.review == null ? null : context.review.getConclusionResult()));
+
+        // CB_conclusionResult - 新表有 conclusion
+        if (useCheckData && StringUtils.isNotBlank(cd.getConclusion())) {
+            placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:" + safe(cd.getConclusion()));
+        } else {
+            placeholders.put("CB_conclusionResult", "推荐入职,有条件推荐,不推荐|SELECTED:"); // 旧表 conclusionResult 已删
+        }
 
         // ========== 备注列 ==========
-        placeholders.put("companyRemark", safe(context.experience == null ? null : context.experience.getCheckRemark()));
-        placeholders.put("performRemark", safe(context.review == null ? null : context.review.getPerformRemark()));
-        placeholders.put("restrictRemark", safe(context.review == null ? null : context.review.getAgreementRemark()));
-        placeholders.put("disputeRemark", safe(context.review == null ? null : context.review.getDisputeRemark()));
+        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 已删
+        }
 
         return wordReportService.generatePdf(placeholders);
     }
@@ -1191,7 +1352,7 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         try (ByteArrayInputStream inputStream = new ByteArrayInputStream(pdfBytes)) {
             OssClient ossClient = OssFactory.instance();
             String key = "report/check/" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM"))
-                + "/" + record.getId() + "-" + sanitizeFileName(safe(context.student == null ? null : context.student.getName()), "candidate")
+                + "/" + record.getId() + "-" + System.currentTimeMillis() + "-" + sanitizeFileName(safe(context.student == null ? null : context.student.getName()), "candidate")
                 + ".pdf";
             UploadResult uploadResult = ossClient.upload(inputStream, key, (long) pdfBytes.length, "application/pdf");
             return uploadResult.getUrl();
@@ -1337,7 +1498,8 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
 
     /**
      * 提交背景调查表单(总控填写后提交)
-     * 将前端表单数据拆分写入 education / experience / review / interview 四张表,
+     * 将前端表单数据整体写入 main_back_check_data 表(不再拆分写入旧表),
+     * 同时保留访谈记录写入 main_back_interview,
      * 并将 record 的 report_status 设为 1(已出具)。
      */
     @Override
@@ -1348,25 +1510,92 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
             throw new ServiceException("背调记录不存在");
         }
 
+        // 2. 校验是否已提交过,防止重复提交
+        if (record.getReportStatus() != null && record.getReportStatus() == 1) {
+            throw new ServiceException("该背调记录已提交过表单,不可重复提交");
+        }
+
         MainBackCandidate candidate = mainBackCandidateMapper.selectById(record.getCandidateId());
         if (candidate == null) {
             throw new ServiceException("候选人记录不存在");
         }
-        Long studentId = candidate.getStudentId();
 
-        // 2. 更新学历信息
-        saveEducation(studentId, bo);
+        // 2. 组装 MainBackCheckData 并保存(按recordId去重更新)
+        MainBackCheckData checkData = new MainBackCheckData();
+        checkData.setRecordId(record.getId());
+        checkData.setCandidateId(candidate.getId());
+        MainBackOrder backOrderForTenantId = baseMapper.selectById(record.getOrderId());
+        checkData.setTenantId(backOrderForTenantId == null ? null : backOrderForTenantId.getTenantId());
 
-        // 3. 更新工作经历
-        saveExperience(studentId, bo);
+        // 候选人基本信息
+        checkData.setCandidateName(bo.getCandidateName());
+        checkData.setApplyPosition(bo.getApplyPosition());
+        if (StringUtils.isNotBlank(bo.getCheckTime())) {
+            try {
+                checkData.setCheckTime(new java.text.SimpleDateFormat("yyyy-MM-dd").parse(bo.getCheckTime()));
+            } catch (Exception ignored) {
+                // 日期格式不合法则忽略
+            }
+        }
 
-        // 4. 更新/创建评价记录
-        saveReview(candidate, bo);
+        // 学历核实
+        checkData.setGradSchool(bo.getGradSchool());
+        checkData.setEduCertNo(bo.getEduCertNo());
+        checkData.setEduVerifyStatus(bo.getEduVerifyStatus());
 
-        // 5. 保存访谈记录(先删旧的,再插入新的)
+        // 最近工作单位
+        checkData.setCompanyName(bo.getCompanyName());
+        if (bo.getWorkPeriod() != null && bo.getWorkPeriod().size() >= 2) {
+            checkData.setWorkStartTime(bo.getWorkPeriod().get(0));
+            checkData.setWorkEndTime(bo.getWorkPeriod().get(1));
+        }
+        checkData.setJobTitle(bo.getPosition());
+        checkData.setLastSalary(bo.getLastSalary());
+        checkData.setLeaveReasonVerify(bo.getLeaveReasonVerify());
+        checkData.setCompanyCheckRemark(bo.getAttachment1Remark());
+
+        // 工作表现评估 - 上级评价
+        checkData.setLeaderEvalAdvantage(bo.getLeaderEvalAdvantage());
+        checkData.setLeaderEvalImprove(bo.getLeaderEvalImprove());
+        checkData.setLeaderEvalProf(bo.getLeaderEvalProf());
+        checkData.setLeaderEvalAttitude(bo.getLeaderEvalAttitude());
+        checkData.setLeaderEvalTeam(bo.getLeaderEvalTeam());
+        checkData.setLeaderEvalMorals(bo.getLeaderEvalMorals());
+
+        // 工作表现评估 - 同事评价
+        checkData.setColleagueEvalTeamwork(bo.getColleagueEvalTeamwork());
+        checkData.setColleagueEvalProf(bo.getColleagueEvalProf());
+
+        // 工作表现评估 - HR评价
+        checkData.setHrEvalDispute(bo.getHrEvalDispute());
+        checkData.setHrEvalTransfer(bo.getHrEvalTransfer());
+        checkData.setPerformCheckRemark(bo.getAttachment2Remark());
+
+        // 法务风险
+        checkData.setHasNonCompete(bo.getHasNonCompete() != null && bo.getHasNonCompete() ? 1 : 0);
+        checkData.setHasNda(bo.getHasNda() != null && bo.getHasNda() ? 1 : 0);
+        checkData.setAgreementRemark(bo.getAgreementRemark());
+        checkData.setHasDispute(bo.getHasDisputeStatus() != null && bo.getHasDisputeStatus() ? 1 : 0);
+        checkData.setDisputeRemark(bo.getDisputeRemark());
+
+        // 调查结论
+        checkData.setConclusion(bo.getConclusion());
+        checkData.setConclusionReason(bo.getConclusionReason());
+        checkData.setInvestigatorName(bo.getInvestigatorName());
+        if (StringUtils.isNotBlank(bo.getInvestigatorDate())) {
+            try {
+                checkData.setInvestigatorDate(new java.text.SimpleDateFormat("yyyy-MM-dd").parse(bo.getInvestigatorDate()));
+            } catch (Exception ignored) {
+                // 日期格式不合法则忽略
+            }
+        }
+
+        mainBackCheckDataService.saveOrUpdateByRecordId(checkData);
+
+        // 3. 保存访谈记录(先删旧的,再插入新的)
         saveInterviews(record, candidate, bo);
 
-        // 6. 更新 record 状态为已出具,清除缓存的报告URL
+        // 4. 更新 record 状态为已出具,清除缓存的报告URL
         MainBackRecord update = new MainBackRecord();
         update.setId(record.getId());
         update.setReportStatus(1);
@@ -1375,162 +1604,6 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
         recordMapper.updateById(update);
     }
 
-    private void saveEducation(Long studentId, BackgroundCheckFormBo bo) {
-        MainStudentEducation education = mainStudentEducationMapper.selectOne(
-            Wrappers.<MainStudentEducation>lambdaQuery()
-                .eq(MainStudentEducation::getStudentId, studentId)
-                .orderByDesc(MainStudentEducation::getCreateTime)
-                .last("limit 1")
-        );
-        if (education == null) {
-            education = new MainStudentEducation();
-            education.setStudentId(studentId);
-        }
-        if (StringUtils.isNotBlank(bo.getGradSchool())) {
-            education.setSchool(bo.getGradSchool());
-        }
-        if (StringUtils.isNotBlank(bo.getEduCertNo())) {
-            education.setCertNo(bo.getEduCertNo());
-        }
-        if (StringUtils.isNotBlank(bo.getEduVerifyStatus())) {
-            education.setCheckResult(bo.getEduVerifyStatus());
-        }
-        if (education.getId() == null) {
-            mainStudentEducationMapper.insert(education);
-        } else {
-            mainStudentEducationMapper.updateById(education);
-        }
-    }
-
-    private void saveExperience(Long studentId, BackgroundCheckFormBo bo) {
-        MainStudentExperience experience = mainStudentExperienceMapper.selectOne(
-            Wrappers.<MainStudentExperience>lambdaQuery()
-                .eq(MainStudentExperience::getStudentId, studentId)
-                .orderByDesc(MainStudentExperience::getCreateTime)
-                .last("limit 1")
-        );
-        if (experience == null) {
-            experience = new MainStudentExperience();
-            experience.setStudentId(studentId);
-        }
-        if (StringUtils.isNotBlank(bo.getCompanyName())) {
-            experience.setCompany(bo.getCompanyName());
-        }
-        if (bo.getWorkPeriod() != null && bo.getWorkPeriod().size() >= 2) {
-            experience.setStartTime(bo.getWorkPeriod().get(0));
-            experience.setEndTime(bo.getWorkPeriod().get(1));
-        }
-        if (StringUtils.isNotBlank(bo.getPosition())) {
-            experience.setJobTitle(bo.getPosition());
-        }
-        if (StringUtils.isNotBlank(bo.getLastSalary())) {
-            experience.setLastSalary(bo.getLastSalary());
-        }
-        if (StringUtils.isNotBlank(bo.getAttachment1Remark())) {
-            experience.setCheckRemark(bo.getAttachment1Remark());
-        }
-        if (experience.getId() == null) {
-            mainStudentExperienceMapper.insert(experience);
-        } else {
-            mainStudentExperienceMapper.updateById(experience);
-        }
-    }
-
-    private void saveReview(MainBackCandidate candidate, BackgroundCheckFormBo bo) {
-        MainPostCandidateReview review = mainPostCandidateReviewMapper.selectOne(
-            Wrappers.<MainPostCandidateReview>lambdaQuery()
-                .eq(MainPostCandidateReview::getCandidateId, candidate.getId())
-                .last("limit 1")
-        );
-        if (review == null) {
-            review = new MainPostCandidateReview();
-            review.setCandidateId(candidate.getId());
-            review.setStudentId(candidate.getStudentId());
-            review.setPostId(candidate.getPostId());
-        }
-
-        // 离职原因核实
-        if (StringUtils.isNotBlank(bo.getLeaveReasonVerify())) {
-            review.setLeaveReason(bo.getLeaveReasonVerify());
-        }
-
-        // 上级评价
-        if (StringUtils.isNotBlank(bo.getLeaderEvalAdvantage())) {
-            review.setStrength(bo.getLeaderEvalAdvantage());
-        }
-        if (StringUtils.isNotBlank(bo.getLeaderEvalImprove())) {
-            review.setImprovement(bo.getLeaderEvalImprove());
-        }
-        // 能力维度(名称 + 评语)
-        if (StringUtils.isNotBlank(bo.getLeaderEvalProf())) {
-            review.setAbilityAName("专业能力");
-            review.setAbilityARemark(bo.getLeaderEvalProf());
-        }
-        if (StringUtils.isNotBlank(bo.getLeaderEvalAttitude())) {
-            review.setAbilityBName("工作态度");
-            review.setAbilityBRemark(bo.getLeaderEvalAttitude());
-        }
-        if (StringUtils.isNotBlank(bo.getLeaderEvalTeam())) {
-            review.setAbilityCName("团队协作");
-            review.setAbilityCRemark(bo.getLeaderEvalTeam());
-        }
-        if (StringUtils.isNotBlank(bo.getLeaderEvalMorals())) {
-            review.setAbilityDName("职业操守");
-            review.setAbilityDRemark(bo.getLeaderEvalMorals());
-        }
-
-        // 同事评价
-        if (StringUtils.isNotBlank(bo.getColleagueEvalTeamwork())) {
-            review.setCooperation(bo.getColleagueEvalTeamwork());
-        }
-        if (StringUtils.isNotBlank(bo.getColleagueEvalProf())) {
-            review.setColleagueAbility(bo.getColleagueEvalProf());
-        }
-
-        // HR评价
-        if (StringUtils.isNotBlank(bo.getHrEvalDispute())) {
-            review.setViolation(bo.getHrEvalDispute());
-        }
-        if (StringUtils.isNotBlank(bo.getHrEvalTransfer())) {
-            review.setHandover(bo.getHrEvalTransfer());
-        }
-        if (StringUtils.isNotBlank(bo.getAttachment2Remark())) {
-            review.setPerformRemark(bo.getAttachment2Remark());
-        }
-
-        // 法务风险
-        if (bo.getHasNonCompete() != null) {
-            review.setNonCompeteAgreement(bo.getHasNonCompete() ? 1 : 0);
-        }
-        if (bo.getHasNda() != null) {
-            review.setConfidentialityAgreement(bo.getHasNda() ? 1 : 0);
-        }
-        if (StringUtils.isNotBlank(bo.getAgreementRemark())) {
-            review.setAgreementRemark(bo.getAgreementRemark());
-        }
-        if (bo.getHasDisputeStatus() != null) {
-            review.setLaborDispute(bo.getHasDisputeStatus() ? 1 : 0);
-        }
-        if (StringUtils.isNotBlank(bo.getDisputeRemark())) {
-            review.setDisputeRemark(bo.getDisputeRemark());
-        }
-
-        // 调查结论
-        if (StringUtils.isNotBlank(bo.getConclusion())) {
-            review.setConclusionResult(bo.getConclusion());
-        }
-        if (StringUtils.isNotBlank(bo.getConclusionReason())) {
-            review.setTotalRemark(bo.getConclusionReason());
-        }
-
-        review.setReviewStatus("1"); // 已填写
-
-        if (review.getId() == null) {
-            mainPostCandidateReviewMapper.insert(review);
-        } else {
-            mainPostCandidateReviewMapper.updateById(review);
-        }
-    }
 
     private void saveInterviews(MainBackRecord record, MainBackCandidate candidate, BackgroundCheckFormBo bo) {
         // 先删掉该 record 下的旧访谈记录
@@ -1563,6 +1636,7 @@ public class MainBackOrderServiceImpl implements IMainBackOrderService {
 
     private static class ReportContext {
         private MainStudent student;
+        private MainBackCheckData checkData; // 优先使用的新表数据
         private MainPostCandidateReview review;
         private MainStudentEducation education;
         private MainStudentExperience experience;

+ 15 - 4
ruoyi-modules/ruoyi-main/src/main/java/org/dromara/main/service/impl/WordReportService.java

@@ -91,8 +91,8 @@ public class WordReportService {
                 document.write(fos);
             }
 
-            // 调试:保存到项目目录
-            saveDebugDocx(document);
+            // 调试:保存到项目目录(已关闭)
+            // saveDebugDocx(document);
 
             // 4. LibreOffice 转 PDF
             byte[] pdfBytes = convertToPdfWithLibreOffice(docxPath, tempDir);
@@ -125,7 +125,8 @@ public class WordReportService {
             return;
         }
 
-        // 解析 value: "选项1,选项2,选项3|SELECTED:选中项"
+        // 解析 value: "选项1,选项2,选项3|SELECTED:选中项1,选中项2"
+        // 支持多选,多个选中项用逗号分隔
         String optionsPart = value;
         String selected = "";
         if (value.contains("|SELECTED:")) {
@@ -134,6 +135,16 @@ public class WordReportService {
             selected = value.substring(idx + "|SELECTED:".length());
         }
         String[] options = optionsPart.split(",");
+        // 将多个选中项解析为 Set,支持 "选中项1,选中项2" 格式
+        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);
+                }
+            }
+        }
 
         // 清空现有段落
         clearParagraphText(existingParagraph);
@@ -142,7 +153,7 @@ public class WordReportService {
         boolean isFirst = true;
         for (String option : options) {
             option = option.trim();
-            boolean isChecked = option.equals(selected.trim());
+            boolean isChecked = selectedSet.contains(option.trim());
 
             XWPFParagraph para;
             if (isFirst) {