From d4ac165723ab39124af6077810b34368701bbb68 Mon Sep 17 00:00:00 2001 From: wjj <2069666735@qq.com> Date: Mon, 20 Apr 2026 14:52:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=91=98=E5=B7=A5?= =?UTF-8?q?=E6=8B=9B=E8=81=98=E5=8E=86=E5=8F=B2=E5=B7=A5=E4=BD=9C=E7=BB=8F?= =?UTF-8?q?=E5=8E=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CcdiStaffRecruitmentController.java | 35 ++ .../domain/CcdiStaffRecruitment.java | 5 +- .../domain/CcdiStaffRecruitmentWork.java | 76 +++ .../dto/CcdiStaffRecruitmentAddDTO.java | 12 +- .../dto/CcdiStaffRecruitmentEditDTO.java | 9 +- .../dto/CcdiStaffRecruitmentQueryDTO.java | 3 + .../excel/CcdiStaffRecruitmentWorkExcel.java | 95 +++ .../domain/vo/CcdiStaffRecruitmentVO.java | 12 +- .../domain/vo/CcdiStaffRecruitmentWorkVO.java | 46 ++ .../domain/vo/RecruitmentImportFailureVO.java | 9 + .../info/collection/enums/RecruitType.java | 49 ++ .../CcdiStaffRecruitmentWorkMapper.java | 13 + .../ICcdiStaffRecruitmentImportService.java | 12 + .../service/ICcdiStaffRecruitmentService.java | 9 + ...CcdiStaffRecruitmentImportServiceImpl.java | 221 ++++++- .../impl/CcdiStaffRecruitmentServiceImpl.java | 73 ++- .../collection/CcdiStaffRecruitmentMapper.xml | 48 +- ...uitment-work-experience-self-acceptance.md | 64 ++ ruoyi-ui/src/permission.js | 2 +- ruoyi-ui/src/router/index.js | 7 + .../src/views/ccdiStaffRecruitment/index.vue | 559 ++++++++++++++++-- ...-staff-recruitment-social-work-summary.sql | 44 ++ 22 files changed, 1299 insertions(+), 104 deletions(-) create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitmentWork.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffRecruitmentWorkExcel.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentWorkVO.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/RecruitType.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffRecruitmentWorkMapper.java create mode 100644 docs/tests/records/2026-04-20-staff-recruitment-work-experience-self-acceptance.md create mode 100644 sql/migration/2026-04-15-add-staff-recruitment-social-work-summary.sql diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java index 3a4b7351..4e800f6c 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java @@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; +import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel; import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO; import com.ruoyi.info.collection.domain.vo.ImportResultVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO; @@ -128,6 +129,15 @@ public class CcdiStaffRecruitmentController extends BaseController { EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentExcel.class, "员工招聘信息"); } + /** + * 下载历史工作经历导入模板 + */ + @Operation(summary = "下载历史工作经历导入模板") + @PostMapping("/workImportTemplate") + public void workImportTemplate(HttpServletResponse response) { + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentWorkExcel.class, "历史工作经历"); + } + /** * 异步导入招聘信息 */ @@ -155,6 +165,31 @@ public class CcdiStaffRecruitmentController extends BaseController { return AjaxResult.success("导入任务已提交,正在后台处理", result); } + /** + * 异步导入历史工作经历 + */ + @Operation(summary = "异步导入历史工作经历") + @Parameter(name = "file", description = "导入文件", required = true) + @PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:import')") + @Log(title = "员工招聘历史工作经历", businessType = BusinessType.IMPORT) + @PostMapping("/importWorkData") + public AjaxResult importWorkData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { + List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffRecruitmentWorkExcel.class); + + if (list == null || list.isEmpty()) { + return error("至少需要一条数据"); + } + + String taskId = recruitmentService.importRecruitmentWork(list); + + ImportResultVO result = new ImportResultVO(); + result.setTaskId(taskId); + result.setStatus("PROCESSING"); + result.setMessage("历史工作经历导入任务已提交,正在后台处理"); + + return AjaxResult.success("历史工作经历导入任务已提交,正在后台处理", result); + } + /** * 查询导入状态 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitment.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitment.java index 4fc163a2..cd8b71d8 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitment.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitment.java @@ -22,7 +22,7 @@ public class CcdiStaffRecruitment implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 招聘项目编号 */ + /** 招聘记录编号 */ @TableId(type = IdType.INPUT) private String recruitId; @@ -41,6 +41,9 @@ public class CcdiStaffRecruitment implements Serializable { /** 应聘人员姓名 */ private String candName; + /** 招聘类型:SOCIAL-社招,CAMPUS-校招 */ + private String recruitType; + /** 应聘人员学历 */ private String candEdu; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitmentWork.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitmentWork.java new file mode 100644 index 00000000..c78fa10c --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiStaffRecruitmentWork.java @@ -0,0 +1,76 @@ +package com.ruoyi.info.collection.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 招聘记录历史工作经历对象 ccdi_staff_recruitment_work + * + * @author ruoyi + * @date 2026-04-15 + */ +@Data +@TableName("ccdi_staff_recruitment_work") +public class CcdiStaffRecruitmentWork implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 主键 */ + @TableId(type = IdType.AUTO) + private Long id; + + /** 关联招聘记录编号 */ + private String recruitId; + + /** 排序号 */ + private Integer sortOrder; + + /** 工作单位 */ + private String companyName; + + /** 所属部门 */ + private String departmentName; + + /** 岗位名称 */ + private String positionName; + + /** 入职年月 */ + private String jobStartMonth; + + /** 离职年月 */ + private String jobEndMonth; + + /** 离职原因 */ + private String departureReason; + + /** 主要工作内容 */ + private String workContent; + + /** 备注 */ + private String remark; + + /** 创建人 */ + @TableField(fill = FieldFill.INSERT) + private String createdBy; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** 更新人 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updatedBy; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentAddDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentAddDTO.java index 7eed1ce1..b9338ffa 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentAddDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentAddDTO.java @@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto; import com.ruoyi.info.collection.annotation.EnumValid; import com.ruoyi.info.collection.enums.AdmitStatus; +import com.ruoyi.info.collection.enums.RecruitType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; @@ -22,9 +23,9 @@ public class CcdiStaffRecruitmentAddDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 招聘项目编号 */ - @NotBlank(message = "招聘项目编号不能为空") - @Size(max = 32, message = "招聘项目编号长度不能超过32个字符") + /** 招聘记录编号 */ + @NotBlank(message = "招聘记录编号不能为空") + @Size(max = 32, message = "招聘记录编号长度不能超过32个字符") private String recruitId; /** 招聘项目名称 */ @@ -51,6 +52,11 @@ public class CcdiStaffRecruitmentAddDTO implements Serializable { @Size(max = 20, message = "应聘人员姓名长度不能超过20个字符") private String candName; + /** 招聘类型 */ + @NotBlank(message = "招聘类型不能为空") + @EnumValid(enumClass = RecruitType.class, message = "招聘类型状态值不合法") + private String recruitType; + /** 应聘人员学历 */ @NotBlank(message = "应聘人员学历不能为空") @Size(max = 20, message = "应聘人员学历长度不能超过20个字符") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentEditDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentEditDTO.java index 8c050f97..ded9c315 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentEditDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentEditDTO.java @@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto; import com.ruoyi.info.collection.annotation.EnumValid; import com.ruoyi.info.collection.enums.AdmitStatus; +import com.ruoyi.info.collection.enums.RecruitType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; @@ -23,8 +24,8 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 招聘项目编号 */ - @NotNull(message = "招聘项目编号不能为空") + /** 招聘记录编号 */ + @NotNull(message = "招聘记录编号不能为空") private String recruitId; /** 招聘项目名称 */ @@ -46,6 +47,10 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable { @Size(max = 20, message = "应聘人员姓名长度不能超过20个字符") private String candName; + /** 招聘类型 */ + @EnumValid(enumClass = RecruitType.class, message = "招聘类型状态值不合法") + private String recruitType; + /** 应聘人员学历 */ @Size(max = 20, message = "应聘人员学历长度不能超过20个字符") private String candEdu; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentQueryDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentQueryDTO.java index dac83e21..719b74cf 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentQueryDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffRecruitmentQueryDTO.java @@ -26,6 +26,9 @@ public class CcdiStaffRecruitmentQueryDTO implements Serializable { /** 候选人姓名(模糊查询) */ private String candName; + /** 招聘类型(精确查询) */ + private String recruitType; + /** 证件号码(精确查询) */ private String candId; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffRecruitmentWorkExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffRecruitmentWorkExcel.java new file mode 100644 index 00000000..2ee6ee9b --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffRecruitmentWorkExcel.java @@ -0,0 +1,95 @@ +package com.ruoyi.info.collection.domain.excel; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.ruoyi.common.annotation.Required; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 招聘记录历史工作经历Excel导入对象 + * + * @author ruoyi + * @date 2026-04-20 + */ +@Data +public class CcdiStaffRecruitmentWorkExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 招聘记录编号 */ + @ExcelProperty(value = "招聘记录编号", index = 0) + @ColumnWidth(20) + @Required + private String recruitId; + + /** 候选人姓名 */ + @ExcelProperty(value = "候选人姓名", index = 1) + @ColumnWidth(15) + @Required + private String candName; + + /** 招聘项目名称 */ + @ExcelProperty(value = "招聘项目名称", index = 2) + @ColumnWidth(25) + @Required + private String recruitName; + + /** 职位名称 */ + @ExcelProperty(value = "职位名称", index = 3) + @ColumnWidth(20) + @Required + private String posName; + + /** 排序号 */ + @ExcelProperty(value = "排序号", index = 4) + @ColumnWidth(10) + @Required + private Integer sortOrder; + + /** 工作单位 */ + @ExcelProperty(value = "工作单位", index = 5) + @ColumnWidth(25) + @Required + private String companyName; + + /** 所属部门 */ + @ExcelProperty(value = "所属部门", index = 6) + @ColumnWidth(18) + private String departmentName; + + /** 岗位 */ + @ExcelProperty(value = "岗位", index = 7) + @ColumnWidth(20) + @Required + private String positionName; + + /** 入职年月 */ + @ExcelProperty(value = "入职年月", index = 8) + @ColumnWidth(12) + @Required + private String jobStartMonth; + + /** 离职年月 */ + @ExcelProperty(value = "离职年月", index = 9) + @ColumnWidth(12) + private String jobEndMonth; + + /** 离职原因 */ + @ExcelProperty(value = "离职原因", index = 10) + @ColumnWidth(30) + private String departureReason; + + /** 工作内容 */ + @ExcelProperty(value = "工作内容", index = 11) + @ColumnWidth(35) + private String workContent; + + /** 备注 */ + @ExcelProperty(value = "备注", index = 12) + @ColumnWidth(25) + private String remark; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentVO.java index 78bee51c..3afe0cbc 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentVO.java @@ -5,6 +5,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; +import java.util.List; /** * 员工招聘信息VO @@ -18,7 +19,7 @@ public class CcdiStaffRecruitmentVO implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 招聘项目编号 */ + /** 招聘记录编号 */ private String recruitId; /** 招聘项目名称 */ @@ -36,6 +37,9 @@ public class CcdiStaffRecruitmentVO implements Serializable { /** 应聘人员姓名 */ private String candName; + /** 招聘类型 */ + private String recruitType; + /** 应聘人员学历 */ private String candEdu; @@ -57,6 +61,12 @@ public class CcdiStaffRecruitmentVO implements Serializable { /** 录用情况描述 */ private String admitStatusDesc; + /** 历史工作经历条数 */ + private Long workExperienceCount; + + /** 历史工作经历列表 */ + private List workExperienceList; + /** 面试官1姓名 */ private String interviewerName1; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentWorkVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentWorkVO.java new file mode 100644 index 00000000..6605238a --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffRecruitmentWorkVO.java @@ -0,0 +1,46 @@ +package com.ruoyi.info.collection.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 招聘记录历史工作经历VO + * + * @author ruoyi + * @date 2026-04-15 + */ +@Data +public class CcdiStaffRecruitmentWorkVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 排序号 */ + private Integer sortOrder; + + /** 工作单位 */ + private String companyName; + + /** 所属部门 */ + private String departmentName; + + /** 岗位名称 */ + private String positionName; + + /** 入职年月 */ + private String jobStartMonth; + + /** 离职年月 */ + private String jobEndMonth; + + /** 离职原因 */ + private String departureReason; + + /** 主要工作内容 */ + private String workContent; + + /** 备注 */ + private String remark; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java index 4f48e27f..4775dfff 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java @@ -19,6 +19,9 @@ public class RecruitmentImportFailureVO { @Schema(description = "招聘项目名称") private String recruitName; + @Schema(description = "职位名称") + private String posName; + @Schema(description = "应聘人员姓名") private String candName; @@ -28,6 +31,12 @@ public class RecruitmentImportFailureVO { @Schema(description = "录用情况") private String admitStatus; + @Schema(description = "工作单位") + private String companyName; + + @Schema(description = "岗位") + private String positionName; + @Schema(description = "错误信息") private String errorMessage; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/RecruitType.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/RecruitType.java new file mode 100644 index 00000000..33e0741a --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/RecruitType.java @@ -0,0 +1,49 @@ +package com.ruoyi.info.collection.enums; + +import com.ruoyi.common.utils.StringUtils; + +/** + * 招聘类型枚举 + * + * @author ruoyi + */ +public enum RecruitType { + + /** 社招 */ + SOCIAL("SOCIAL", "社招"), + + /** 校招 */ + CAMPUS("CAMPUS", "校招"); + + private final String code; + private final String desc; + + RecruitType(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + + public static String getDescByCode(String code) { + for (RecruitType type : values()) { + if (type.code.equals(code)) { + return type.desc; + } + } + return null; + } + + public static String inferCode(String recruitName) { + if (StringUtils.isNotEmpty(recruitName) && recruitName.contains("校园")) { + return CAMPUS.code; + } + return SOCIAL.code; + } +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffRecruitmentWorkMapper.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffRecruitmentWorkMapper.java new file mode 100644 index 00000000..ca54dcbd --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffRecruitmentWorkMapper.java @@ -0,0 +1,13 @@ +package com.ruoyi.info.collection.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork; + +/** + * 招聘记录历史工作经历 数据层 + * + * @author ruoyi + * @date 2026-04-15 + */ +public interface CcdiStaffRecruitmentWorkMapper extends BaseMapper { +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java index cc250511..cb1f251e 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java @@ -1,6 +1,7 @@ package com.ruoyi.info.collection.service; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; +import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel; import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO; @@ -25,6 +26,17 @@ public interface ICcdiStaffRecruitmentImportService { String taskId, String userName); + /** + * 异步导入招聘记录历史工作经历数据 + * + * @param excelList Excel数据列表 + * @param taskId 任务ID + * @param userName 用户名 + */ + void importRecruitmentWorkAsync(List excelList, + String taskId, + String userName); + /** * 查询导入状态 * diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java index bbf8a694..eaa09ab3 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java @@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; +import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel; import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO; import java.util.List; @@ -81,4 +82,12 @@ public interface ICcdiStaffRecruitmentService { * @return 结果 */ String importRecruitment(List excelList); + + /** + * 导入招聘记录历史工作经历数据(异步) + * + * @param excelList Excel实体列表 + * @return 任务ID + */ + String importRecruitmentWork(List excelList); } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java index c43c2373..697c3a84 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java @@ -3,13 +3,17 @@ package com.ruoyi.info.collection.service.impl; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.info.collection.domain.CcdiStaffRecruitment; +import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; +import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel; import com.ruoyi.info.collection.domain.vo.ImportResult; import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO; import com.ruoyi.info.collection.enums.AdmitStatus; +import com.ruoyi.info.collection.enums.RecruitType; import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper; +import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper; import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService; import com.ruoyi.info.collection.utils.ImportLogUtils; import com.ruoyi.common.utils.IdCardUtil; @@ -43,6 +47,9 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm @Resource private CcdiStaffRecruitmentMapper recruitmentMapper; + @Resource + private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper; + @Resource private RedisTemplate redisTemplate; @@ -60,10 +67,10 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm List newRecords = new ArrayList<>(); List failures = new ArrayList<>(); - // 批量查询已存在的招聘项目编号 - ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的招聘项目编号", excelList.size()); + // 批量查询已存在的招聘记录编号 + ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的招聘记录编号", excelList.size()); Set existingRecruitIds = getExistingRecruitIds(excelList); - ImportLogUtils.logBatchQueryComplete(log, taskId, "招聘项目编号", existingRecruitIds.size()); + ImportLogUtils.logBatchQueryComplete(log, taskId, "招聘记录编号", existingRecruitIds.size()); // 用于检测Excel内部的重复ID Set excelProcessedIds = new HashSet<>(); @@ -76,19 +83,21 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm // 转换为AddDTO进行验证 CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO(); BeanUtils.copyProperties(excel, addDTO); + addDTO.setRecruitType(RecruitType.inferCode(addDTO.getRecruitName())); // 验证数据 validateRecruitmentData(addDTO, existingRecruitIds); CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment(); BeanUtils.copyProperties(excel, recruitment); + recruitment.setRecruitType(addDTO.getRecruitType()); if (existingRecruitIds.contains(excel.getRecruitId())) { - // 招聘项目编号在数据库中已存在,直接报错 - throw new RuntimeException(String.format("招聘项目编号[%s]已存在,请勿重复导入", excel.getRecruitId())); + // 招聘记录编号在数据库中已存在,直接报错 + throw new RuntimeException(String.format("招聘记录编号[%s]已存在,请勿重复导入", excel.getRecruitId())); } else if (excelProcessedIds.contains(excel.getRecruitId())) { - // 招聘项目编号在Excel文件内部重复 - throw new RuntimeException(String.format("招聘项目编号[%s]在导入文件中重复,已跳过此条记录", excel.getRecruitId())); + // 招聘记录编号在Excel文件内部重复 + throw new RuntimeException(String.format("招聘记录编号[%s]在导入文件中重复,已跳过此条记录", excel.getRecruitId())); } else { recruitment.setCreatedBy(userName); recruitment.setUpdatedBy(userName); @@ -107,7 +116,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm failures.add(failure); // 记录验证失败日志 - String keyData = String.format("招聘项目编号=%s, 项目名称=%s, 应聘人员=%s", + String keyData = String.format("招聘记录编号=%s, 项目名称=%s, 应聘人员=%s", excel.getRecruitId(), excel.getRecruitName(), excel.getCandName()); ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData); } @@ -142,7 +151,85 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm // 记录导入完成 long duration = System.currentTimeMillis() - startTime; - ImportLogUtils.logImportComplete(log, taskId, "招聘信息", + ImportLogUtils.logImportComplete(log, taskId, "招聘信息", + excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); + } + + @Override + @Async + @Transactional + public void importRecruitmentWorkAsync(List excelList, + String taskId, + String userName) { + long startTime = System.currentTimeMillis(); + ImportLogUtils.logImportStart(log, taskId, "招聘历史工作经历", excelList.size(), userName); + + List failures = new ArrayList<>(); + List validRecords = new ArrayList<>(); + Set failedRecruitIds = new HashSet<>(); + Set processedRecruitSortKeys = new HashSet<>(); + + Map recruitmentMap = getRecruitmentMap(excelList); + + for (int i = 0; i < excelList.size(); i++) { + CcdiStaffRecruitmentWorkExcel excel = excelList.get(i); + try { + CcdiStaffRecruitment recruitment = recruitmentMap.get(trim(excel.getRecruitId())); + validateRecruitmentWorkData(excel, recruitment, processedRecruitSortKeys); + + CcdiStaffRecruitmentWork work = new CcdiStaffRecruitmentWork(); + BeanUtils.copyProperties(excel, work); + work.setRecruitId(trim(excel.getRecruitId())); + work.setCreatedBy(userName); + work.setUpdatedBy(userName); + validRecords.add(work); + + ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(), + validRecords.size(), failures.size()); + } catch (Exception e) { + failedRecruitIds.add(trim(excel.getRecruitId())); + failures.add(buildWorkFailure(excel, e.getMessage())); + String keyData = String.format("招聘记录编号=%s, 候选人=%s, 工作单位=%s", + excel.getRecruitId(), excel.getCandName(), excel.getCompanyName()); + ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData); + } + } + + List importRecords = validRecords.stream() + .filter(work -> !failedRecruitIds.contains(work.getRecruitId())) + .toList(); + appendSkippedFailures(validRecords, failedRecruitIds, failures); + + if (!importRecords.isEmpty()) { + Set importRecruitIds = importRecords.stream() + .map(CcdiStaffRecruitmentWork::getRecruitId) + .collect(Collectors.toSet()); + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>(); + deleteWrapper.in(CcdiStaffRecruitmentWork::getRecruitId, importRecruitIds); + recruitmentWorkMapper.delete(deleteWrapper); + + importRecords.forEach(recruitmentWorkMapper::insert); + } + + if (!failures.isEmpty()) { + try { + String failuresKey = "import:recruitment:" + taskId + ":failures"; + redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); + ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size()); + } catch (Exception e) { + ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e); + } + } + + ImportResult result = new ImportResult(); + result.setTotalCount(excelList.size()); + result.setSuccessCount(importRecords.size()); + result.setFailureCount(failures.size()); + String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; + updateImportStatus(taskId, finalStatus, result); + + long duration = System.currentTimeMillis() - startTime; + ImportLogUtils.logImportComplete(log, taskId, "招聘历史工作经历", excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); } @@ -184,7 +271,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm } /** - * 批量查询已存在的招聘项目编号 + * 批量查询已存在的招聘记录编号 */ private Set getExistingRecruitIds(List excelList) { List recruitIds = excelList.stream() @@ -212,7 +299,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm Set existingRecruitIds) { // 验证必填字段 if (StringUtils.isEmpty(addDTO.getRecruitId())) { - throw new RuntimeException("招聘项目编号不能为空"); + throw new RuntimeException("招聘记录编号不能为空"); } if (StringUtils.isEmpty(addDTO.getRecruitName())) { throw new RuntimeException("招聘项目名称不能为空"); @@ -247,6 +334,9 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm if (StringUtils.isEmpty(addDTO.getAdmitStatus())) { throw new RuntimeException("录用情况不能为空"); } + if (StringUtils.isEmpty(addDTO.getRecruitType())) { + throw new RuntimeException("招聘类型不能为空"); + } // 验证证件号码格式 String idCardError = IdCardUtil.getErrorMessage(addDTO.getCandId()); @@ -263,6 +353,115 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm if (AdmitStatus.getDescByCode(addDTO.getAdmitStatus()) == null) { throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'"); } + + if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) { + throw new RuntimeException("招聘类型只能填写'SOCIAL'或'CAMPUS'"); + } + } + + private Map getRecruitmentMap(List excelList) { + List recruitIds = excelList.stream() + .map(CcdiStaffRecruitmentWorkExcel::getRecruitId) + .map(this::trim) + .filter(StringUtils::isNotEmpty) + .distinct() + .toList(); + if (recruitIds.isEmpty()) { + return Collections.emptyMap(); + } + List recruitments = recruitmentMapper.selectBatchIds(recruitIds); + return recruitments.stream() + .collect(Collectors.toMap(CcdiStaffRecruitment::getRecruitId, item -> item)); + } + + private void validateRecruitmentWorkData(CcdiStaffRecruitmentWorkExcel excel, + CcdiStaffRecruitment recruitment, + Set processedRecruitSortKeys) { + if (StringUtils.isEmpty(trim(excel.getRecruitId()))) { + throw new RuntimeException("招聘记录编号不能为空"); + } + if (StringUtils.isEmpty(trim(excel.getCandName()))) { + throw new RuntimeException("候选人姓名不能为空"); + } + if (StringUtils.isEmpty(trim(excel.getRecruitName()))) { + throw new RuntimeException("招聘项目名称不能为空"); + } + if (StringUtils.isEmpty(trim(excel.getPosName()))) { + throw new RuntimeException("职位名称不能为空"); + } + if (excel.getSortOrder() == null || excel.getSortOrder() <= 0) { + throw new RuntimeException("排序号不能为空且必须大于0"); + } + if (StringUtils.isEmpty(trim(excel.getCompanyName()))) { + throw new RuntimeException("工作单位不能为空"); + } + if (StringUtils.isEmpty(trim(excel.getPositionName()))) { + throw new RuntimeException("岗位不能为空"); + } + if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) { + throw new RuntimeException("入职年月不能为空"); + } + validateMonth(excel.getJobStartMonth(), "入职年月"); + if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) { + validateMonth(excel.getJobEndMonth(), "离职年月"); + } + if (recruitment == null) { + throw new RuntimeException("招聘记录编号不存在,请先维护招聘主信息"); + } + if (!"SOCIAL".equals(recruitment.getRecruitType())) { + throw new RuntimeException("该招聘记录不是社招,不允许导入历史工作经历"); + } + if (!sameText(excel.getCandName(), recruitment.getCandName())) { + throw new RuntimeException("招聘记录编号与候选人姓名不匹配"); + } + if (!sameText(excel.getRecruitName(), recruitment.getRecruitName())) { + throw new RuntimeException("招聘记录编号与招聘项目名称不匹配"); + } + if (!sameText(excel.getPosName(), recruitment.getPosName())) { + throw new RuntimeException("招聘记录编号与职位名称不匹配"); + } + String duplicateKey = trim(excel.getRecruitId()) + "#" + excel.getSortOrder(); + if (!processedRecruitSortKeys.add(duplicateKey)) { + throw new RuntimeException("同一招聘记录编号下排序号重复"); + } + } + + private void validateMonth(String value, String fieldName) { + String month = trim(value); + if (!month.matches("^((19|20)\\d{2})-(0[1-9]|1[0-2])$")) { + throw new RuntimeException(fieldName + "格式不正确,应为YYYY-MM"); + } + } + + private boolean sameText(String first, String second) { + return Objects.equals(trim(first), trim(second)); + } + + private String trim(String value) { + return value == null ? null : value.trim(); + } + + private RecruitmentImportFailureVO buildWorkFailure(CcdiStaffRecruitmentWorkExcel excel, String errorMessage) { + RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO(); + BeanUtils.copyProperties(excel, failure); + failure.setErrorMessage(errorMessage); + return failure; + } + + private void appendSkippedFailures(List validRecords, + Set failedRecruitIds, + List failures) { + Set appendedRecruitIds = new HashSet<>(); + for (CcdiStaffRecruitmentWork work : validRecords) { + if (failedRecruitIds.contains(work.getRecruitId()) && appendedRecruitIds.add(work.getRecruitId())) { + RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO(); + failure.setRecruitId(work.getRecruitId()); + failure.setCompanyName(work.getCompanyName()); + failure.setPositionName(work.getPositionName()); + failure.setErrorMessage("同一招聘记录编号存在失败行,已跳过该编号下全部工作经历,避免覆盖旧数据"); + failures.add(failure); + } + } } /** diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java index a8c18b86..336dd1cb 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java @@ -1,13 +1,18 @@ package com.ruoyi.info.collection.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.info.collection.domain.CcdiStaffRecruitment; +import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; +import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel; import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO; +import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentWorkVO; import com.ruoyi.info.collection.enums.AdmitStatus; +import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper; import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper; import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService; import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentService; @@ -19,6 +24,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,6 +43,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer @Resource private CcdiStaffRecruitmentMapper recruitmentMapper; + @Resource + private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper; + @Resource private ICcdiStaffRecruitmentImportService recruitmentImportService; @@ -96,7 +105,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer /** * 查询招聘信息详情 * - * @param recruitId 招聘项目编号 + * @param recruitId 招聘记录编号 * @return 招聘信息VO */ @Override @@ -104,6 +113,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer CcdiStaffRecruitmentVO vo = recruitmentMapper.selectRecruitmentById(recruitId); if (vo != null) { vo.setAdmitStatusDesc(AdmitStatus.getDescByCode(vo.getAdmitStatus())); + vo.setWorkExperienceList(selectWorkExperienceList(recruitId)); } return vo; } @@ -117,9 +127,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer @Override @Transactional public int insertRecruitment(CcdiStaffRecruitmentAddDTO addDTO) { - // 检查招聘项目编号唯一性 + // 检查招聘记录编号唯一性 if (recruitmentMapper.selectById(addDTO.getRecruitId()) != null) { - throw new RuntimeException("该招聘项目编号已存在"); + throw new RuntimeException("该招聘记录编号已存在"); } CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment(); @@ -148,12 +158,15 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer /** * 批量删除招聘信息 * - * @param recruitIds 需要删除的招聘项目编号 + * @param recruitIds 需要删除的招聘记录编号 * @return 结果 */ @Override @Transactional public int deleteRecruitmentByIds(String[] recruitIds) { + LambdaQueryWrapper workWrapper = new LambdaQueryWrapper<>(); + workWrapper.in(CcdiStaffRecruitmentWork::getRecruitId, List.of(recruitIds)); + recruitmentWorkMapper.delete(workWrapper); return recruitmentMapper.deleteBatchIds(List.of(recruitIds)); } @@ -197,4 +210,56 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer return taskId; } + + /** + * 导入招聘记录历史工作经历数据(异步) + * + * @param excelList Excel实体列表 + * @return 任务ID + */ + @Override + @Transactional + public String importRecruitmentWork(List excelList) { + if (StringUtils.isNull(excelList) || excelList.isEmpty()) { + throw new RuntimeException("至少需要一条数据"); + } + + String taskId = UUID.randomUUID().toString(); + long startTime = System.currentTimeMillis(); + String userName = SecurityUtils.getUsername(); + + String statusKey = "import:recruitment:" + taskId; + Map statusData = new HashMap<>(); + statusData.put("taskId", taskId); + statusData.put("status", "PROCESSING"); + statusData.put("totalCount", excelList.size()); + statusData.put("successCount", 0); + statusData.put("failureCount", 0); + statusData.put("progress", 0); + statusData.put("startTime", startTime); + statusData.put("message", "正在处理历史工作经历..."); + + redisTemplate.opsForHash().putAll(statusKey, statusData); + redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); + + recruitmentImportService.importRecruitmentWorkAsync(excelList, taskId, userName); + + return taskId; + } + + private List selectWorkExperienceList(String recruitId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, recruitId) + .orderByAsc(CcdiStaffRecruitmentWork::getSortOrder) + .orderByDesc(CcdiStaffRecruitmentWork::getId); + List workList = recruitmentWorkMapper.selectList(wrapper); + if (workList == null || workList.isEmpty()) { + return new ArrayList<>(); + } + return workList.stream().map(work -> { + CcdiStaffRecruitmentWorkVO vo = new CcdiStaffRecruitmentWorkVO(); + BeanUtils.copyProperties(work, vo); + return vo; + }).toList(); + } } diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffRecruitmentMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffRecruitmentMapper.xml index d5482838..452bf01b 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffRecruitmentMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffRecruitmentMapper.xml @@ -12,12 +12,14 @@ + + @@ -31,44 +33,53 @@