diff --git a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java index 8c7faad2..7e4dc6dc 100644 --- a/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java +++ b/safety-eval-app/src/main/java/org/qinan/safetyeval/app/support/OrgPersonnelExcelImporter.java @@ -1,6 +1,7 @@ package org.qinan.safetyeval.app.support; import cn.hutool.core.lang.Pair; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdcardUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.excel.EasyExcel; @@ -11,11 +12,10 @@ import com.zcloud.gbscommon.utils.MD5; import com.zcloud.gbscommon.utils.Sm2Util; import org.qinan.safetyeval.client.co.OrgPersonnelImportErrorCO; import org.qinan.safetyeval.client.co.OrgPersonnelImportResultCO; -import org.qinan.safetyeval.domain.constant.QualificationLevelEnum; import org.qinan.safetyeval.domain.entity.OrgDepartmentEntity; +import org.qinan.safetyeval.domain.entity.OrgPersonnelCertEntity; import org.qinan.safetyeval.domain.entity.OrgPersonnelEntity; import org.qinan.safetyeval.domain.entity.OrgPositionEntity; -import org.qinan.safetyeval.domain.entity.TitleCodeCo; import org.qinan.safetyeval.domain.exception.BizException; import org.qinan.safetyeval.domain.exception.ErrorCode; import org.qinan.safetyeval.domain.gateway.OrgDepartmentGateway; @@ -38,14 +38,15 @@ import javax.annotation.Resource; import javax.validation.ConstraintViolation; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.util.*; import java.util.stream.Collectors; /** * 基于 EasyExcel 的人员信息批量导入器。 *
- * 解析 Excel -> 批量校验部门/岗位名称并回填ID -> 校验账号重复并收集异常 -> 逐行落库。 + * 模板包含两个 sheet:sheet1「专职人员基础信息」、sheet2「专职人员能力信息」, + * 解析后按 身份证号(为空时按姓名) 将能力信息关联到基础信息行; + * 再批量校验部门/岗位名称并回填ID -> 校验账号重复并收集异常 -> 逐行落库。 *
* * @author safety-eval @@ -55,10 +56,6 @@ public class OrgPersonnelExcelImporter { private static final Logger LOGGER = LoggerFactory.getLogger(OrgPersonnelExcelImporter.class); - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - private static final DateTimeFormatter SLASH_DATE_FORMATTER = - new DateTimeFormatterBuilder().appendPattern("yyyy/M/d").toFormatter(); - @Resource private OrgDepartmentGateway orgDepartmentGateway; @@ -80,6 +77,8 @@ public class OrgPersonnelExcelImporter { @Resource private SystemRemote systemRemote; + public static final String YES = "是"; + @Value("${def.password:a123456}") private String defPassword; @Value("${def.publicKey:0402df2195296d4062ac85ad766994d73e871b887e18efb9a9a06b4cebc72372869b7da6c347c129dee2b46a0f279ff066b01c76208c2a052af75977c722a2ccee}") @@ -137,29 +136,32 @@ public class OrgPersonnelExcelImporter { // 5. 逐行转换、落库 int successCount = 0; + for (OrgPersonnelImportRow row : rows) { - String account = trim(row.getAccount()); + String account = trim(row.getBasic().getAccount()); // 账号在库中已存在 -> 跳过该行,记录异常 if (existingAccounts.contains(account)) { - errors.add(new OrgPersonnelImportErrorCO(row.getRowIndex(), account, trim(row.getUserName()), + errors.add(new OrgPersonnelImportErrorCO(row.getBasic().getRowIndex(), account, trim(row.getBasic().getUserName()), "账号已存在,跳过该行")); continue; } try { - OrgPersonnelEntity entity = buildEntity(row, orgId, errors); - if (entity == null) { + OrgPersonnelEntity personnelEntity = buildOrgPersonnel(row, orgId, errors); + if (personnelEntity == null) { // 行内枚举/数据校验未通过,已记录异常 continue; } - Long userId = addUserIfAbsentAndAppendRole(entity); - entity.setId(userId); - orgPersonnelDomainService.add(entity); + List* 文件内手机号重复的行在此剔除,并生成提示放入 errors(空账号不参与去重)。 *
*/ private List基础信息取自 sheet1;毕业院校/所学专业/学历/注册安全工程师取自 sheet2 能力信息(可为空)。
*/ - private OrgPersonnelEntity buildEntity(OrgPersonnelImportRow row, Long orgId, List- * 列顺序:一级部门 / 二级部门 / 三级部门 / 岗位 / 账号(手机号) / 姓名 / 性别 / - * 身份证号 / 学历 / 毕业院校 / 所学专业 / 职称 / 参加工作时间 / - * 是否注册安全工程师 / 现住地址 / 办公地址 / 主要学习工作经历 / - * 出版学术专著、专利、获奖、发表学术论文等 / 自我申报的专业能力及认定方式。 - *
出生日期不再通过Excel导入,统一由身份证号解析。
- * + * 模板包含两个 sheet: + *出生日期不通过Excel导入,统一由身份证号解析。
** 部门按三级层级填写:一级部门为根,二级部门 parentId 指向一级部门, * 三级部门 parentId 指向二级部门;导入时校验层级关系,取最底层不为空的一级作为 deptId。 @@ -28,120 +31,253 @@ import javax.validation.constraints.Size; @Data public class OrgPersonnelImportRow { - @ExcelProperty(value = "一级部门", index = 0) - private String deptNameLevel1; - - @ExcelProperty(value = "二级部门", index = 1) - private String deptNameLevel2; - - @ExcelProperty(value = "三级部门", index = 2) - private String deptNameLevel3; - - @ExcelProperty(value = "岗位", index = 3) - private String positionName; - - @ExcelProperty(value = "账号", index = 4) - @NotBlank(message = "账号不能为空") - @Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确,应为11位数字且以1开头") - @Size(max = 50, message = "账号长度不能超过50个字符") - private String account; - - @ExcelProperty(value = "姓名", index = 5) - @NotBlank(message = "姓名不能为空") - @Size(max = 50, message = "姓名长度不能超过50个字符") - private String userName; - - @ExcelProperty(value = "性别", index = 6) - @Size(max = 10, message = "性别长度不能超过10个字符") - private String gender; - - @ExcelProperty(value = "身份证号", index = 7) - @Size(max = 18, message = "身份证号长度不能超过18个字符") - private String idCardNo; - - @ExcelProperty(value = "学历", index = 8) - @Size(max = 50, message = "学历长度不能超过50个字符") - private String education; - - @ExcelProperty(value = "毕业院校", index = 9) - @Size(max = 200, message = "毕业院校长度不能超过200个字符") - private String graduateSchool; - - @ExcelProperty(value = "所学专业", index = 10) - @Size(max = 100, message = "所学专业长度不能超过100个字符") - private String major; - - @ExcelProperty(value = "职称", index = 11) - private String title; - - @ExcelProperty(value = "参加工作时间", index = 12) - private String joinWorkDate; - - @ExcelProperty(value = "是否注册安全工程师", index = 13) - private String registerEngineer; - - @ExcelProperty(value = "现住地址", index = 14) - @Size(max = 500, message = "现住地址长度不能超过500个字符") - private String currentAddress; - - @ExcelProperty(value = "办公地址", index = 15) - @Size(max = 500, message = "办公地址长度不能超过500个字符") - private String officeAddress; - - @ExcelProperty(value = "主要学习工作经历", index = 16) - private String workExperience; - - @ExcelProperty(value = "出版学术专著、专利、获奖、发表学术论文等", index = 17) - @Size(max = 1000, message = "出版学术专著等信息长度不能超过1000个字符") - private String publications; - - @ExcelProperty(value = "自我申报的专业能力及认定方式", index = 18) - @Size(max = 1000, message = "自我申报的专业能力信息长度不能超过1000个字符") - private String abilityDeclaration; + /** + * sheet1「专职人员基础信息」行数据。 + */ + private BasicRow basic; /** - * EasyExcel 解析时记录的物理行号(从1开始,含表头),由导入器填充。 + * sheet2「专职人员能力信息」行数据(按身份证号/姓名关联,未匹配到时为空)。 */ - @ExcelIgnore - private Integer rowIndex; + private CapabilityRow capability; /** * 校验通过后回填的部门ID(非Excel列)。 */ - @ExcelIgnore private Long deptId; /** * 校验通过后回填的岗位ID(非Excel列)。 */ - @ExcelIgnore private Long postId; /** * 校验通过后回填的学科基础专业对照表ID(非Excel列)。 */ - @ExcelIgnore private Long basicDisciplineMajorId; /** * 校验失败 true 失败 ,false 未失败 */ - @ExcelIgnore private boolean validateFail; /** * 返回最底层(最深一级)不为空的部门名称,用于错误提示。 */ public String getDeptDisplayName() { - if (StringUtils.hasText(deptNameLevel3)) { - return deptNameLevel3.trim(); + if (basic == null) { + return null; } - if (StringUtils.hasText(deptNameLevel2)) { - return deptNameLevel2.trim(); + if (StringUtils.hasText(basic.getDeptNameLevel3())) { + return basic.getDeptNameLevel3().trim(); } - if (StringUtils.hasText(deptNameLevel1)) { - return deptNameLevel1.trim(); + if (StringUtils.hasText(basic.getDeptNameLevel2())) { + return basic.getDeptNameLevel2().trim(); + } + if (StringUtils.hasText(basic.getDeptNameLevel1())) { + return basic.getDeptNameLevel1().trim(); } return null; } + + /** + * Sheet1「专职人员基础信息」行模型。 + *
+ * 列顺序:一级部门 / 二级部门 / 三级部门 / 岗位 / 姓名 / 性别 / 本行业工作年限 / + * 账号(系统登录账号) / 身份证号 / 现住地址 / 办公地址 / 是否技术负责人 / + * 是否过程控制负责人 / 主要学习工作经历 / + * 出版学术专著、专利、获奖、发表学术论文等 / 自我申报的专业能力及认定方式。 + *
+ */ + @Data + public static class BasicRow { + + @ExcelProperty(value = "一级部门", index = 0) + @NotBlank(message = "一级部门不能为空") + @Size(max = 128, message = "一级部门名称最多120个字符") + private String deptNameLevel1; + + @ExcelProperty(value = "二级部门", index = 1) + @Size(max = 128, message = "二级部门名称最多120个字符") + private String deptNameLevel2; + + @ExcelProperty(value = "三级部门", index = 2) + @Size(max = 128, message = "三级部门最多120个字符") + private String deptNameLevel3; + + @ExcelProperty(value = "岗位", index = 3) + @Size(max = 128, message = "岗位最多120个字符") + private String positionName; + + @ExcelProperty(value = "姓名", index = 4) + @NotBlank(message = "姓名不能为空") + @Size(max = 50, message = "姓名长度不能超过50个字符") + private String userName; + + @ExcelProperty(value = "性别", index = 5) + @Size(max = 10, message = "性别长度不能超过10个字符") + @NotBlank(message = "性别不能为空") + private String gender; + + @ExcelProperty(value = "本行业工作年限", index = 6) + @NotNull(message = "本行业工作年限不能为空") + @Range(min = 1, max = 200, message = "本行业工作年限1-200之间") + private Integer industryWorkYears; + + @ExcelProperty(value = "账号(系统登录账号)", index = 7) + @NotBlank(message = "账号不能为空") + @Pattern(regexp = "^1\\d{10}$", message = "手机号格式不正确,应为11位数字且以1开头") + @Size(max = 50, message = "账号长度不能超过50个字符") + private String account; + + @ExcelProperty(value = "身份证号", index = 8) + @Size(max = 18, message = "身份证号长度不能超过18个字符") + @NotBlank(message = "身份证号不能为空") + private String idCardNo; + + @ExcelProperty(value = "现住地址", index = 9) + @Size(max = 500, message = "现住地址长度不能超过500个字符") + private String currentAddress; + + @ExcelProperty(value = "办公地址", index = 10) + @Size(max = 500, message = "办公地址长度不能超过500个字符") + private String officeAddress; + + @ExcelProperty(value = "是否技术负责人", index = 11) + @Pattern(regexp = "^([是否])?$", message = "是否技术负责人仅允许:是、否") + private String technicalDirector; + + @ExcelProperty(value = "是否过程控制负责人", index = 12) + @Pattern(regexp = "^([是否])?$", message = "是否过程控制负责人仅允许:是、否") + private String processControlLeader; + + @ExcelProperty(value = "主要学习工作经历", index = 13) + @Size(max = 1024, message = "办公地址长度不能超过1024个字符") + private String workExperience; + + @ExcelProperty(value = "出版学术专著、专利、获奖、发表学术论文等", index = 14) + @Size(max = 1000, message = "出版学术专著等信息长度不能超过1000个字符") + private String publications; + + @ExcelProperty(value = "自我申报的专业能力及认定方式", index = 15) + @Size(max = 1000, message = "自我申报的专业能力信息长度不能超过1000个字符") + private String abilityDeclaration; + + /** + * sheet1 的物理行号(从1开始,含表头),由导入器填充,用于错误提示。 + */ + @ExcelIgnore + private Integer rowIndex; + } + + /** + * Sheet2「专职人员能力信息」行模型。 + *+ * 列顺序:姓名 / 身份证号 / 能力认证一(认证行业、专业能力、来源) / + * 能力认证二(认证行业、专业能力、来源) / 学历类型 / 毕业院校 / 学历层次 / 所学专业 / + * 是否注册安全工程师 / 注安专业类别 / 注安等级 / 证书编号 / 执业证书编号 / + * 是否评价师 / 评价师等级 / 证书编号 / 是否高级工程师 / 系列/专业。 + *
+ */ + @Data + public static class CapabilityRow { + + @ExcelProperty(value = "姓名", index = 0) + private String userName; + + @ExcelProperty(value = "身份证号", index = 1) + @Size(max = 18, message = "身份证号长度不能超过18个字符") + @NotBlank(message = "身份证号不能为空") + private String idCardNo; + + @ExcelProperty(value = "能力认证一-认证行业", index = 2) + @NotBlank(message = "能力认证一-认证行业不能为空") + @Size(max = 64, message = "能力认证一-认证行业最多64个字符") + private String cert1Industry; + + @ExcelProperty(value = "能力认证一-专业能力", index = 3) + @NotBlank(message = "能力认证一-专业能力不能为空") + @Size(max = 64, message = "能力认证一-专业能力最多64个字符") + private String cert1Ability; + + @ExcelProperty(value = "能力认证一-来源", index = 4) + @NotBlank(message = "能力认证一-来源不能为空") + @Size(max = 128, message = "能力认证一-来源最多64个字符") + private String cert1Source; + + @ExcelProperty(value = "能力认证二-认证行业", index = 5) + @Size(max = 64, message = "能力认证一-认证行业最多64个字符") + private String cert2Industry; + + @ExcelProperty(value = "能力认证二-专业能力", index = 6) + @Size(max = 64, message = "能力认证一-专业能力最多64个字符") + private String cert2Ability; + + @ExcelProperty(value = "能力认证二-来源", index = 7) + @Size(max = 128, message = "能力认证二-来源最多128个字符") + private String cert2Source; + + @ExcelProperty(value = "学历类型", index = 8) + @NotBlank(message = "学历类型不能为空") + @Size(max = 64, message ="学历类型最多64个字符") + private String educationType; + + @ExcelProperty(value = "毕业院校", index = 9) + @Size(max = 200, message = "毕业院校长度不能超过200个字符") + @NotBlank(message = "毕业院校不能为空") + private String graduateSchool; + + @ExcelProperty(value = "学历层次", index = 10) + @NotBlank(message = "学历层次不能为空") + private String educationLevel; + + @ExcelProperty(value = "所学专业", index = 11) + @Size(max = 100, message = "所学专业长度不能超过100个字符") + @NotBlank(message = "所学专业不能为空") + private String major; + + @ExcelProperty(value = "是否注册安全工程师", index = 12) + @Pattern(regexp = "^([是否])?$", message = "是否注册安全工程师仅允许:是、否") + @NotBlank(message = "是否注册安全工程师不能为空") + private String registerEngineer; + + @ExcelProperty(value = "注安专业类别", index = 13) + @Size(max = 100, message = "注安专业类别长度不能超过100个字符") + private String registerEngineerCategory; + + @ExcelProperty(value = "注安等级", index = 14) + @Size(max = 100, message = "注安等级长度不能超过100个字符") + private String registerEngineerLevel; + + @ExcelProperty(value = "注安证书编号", index = 15) + @Size(max = 64, message = "注安证书编号长度不能超过100个字符") + private String registerEngineerCertNo; + + @ExcelProperty(value = "执业证书编号", index = 16) + @Size(max = 64, message = "执业证书编号长度不能超过100个字符") + private String practiceCertNo; + + @ExcelProperty(value = "是否评价师", index = 17) + @Pattern(regexp = "^([是否])?$", message = "是否评价师仅允许:是、否") + @NotBlank(message = "是否是否评价师不能为空") + private String evaluator; + + @ExcelProperty(value = "评价师等级", index = 18) + @Size(max = 64, message = "评价师等级长度不能超过100个字符") + private String evaluatorLevel; + + @ExcelProperty(value = "评价师证书编号", index = 19) + @Size(max = 64, message = "评价师证书编号长度不能超过100个字符") + private String evaluatorCertNo; + + @ExcelProperty(value = "是否高级工程师", index = 20) + @Pattern(regexp = "^([是否])?$", message = "是否高级工程师仅允许:是、否") + private String seniorEngineer; + + @ExcelProperty(value = "高级工程师系列/专业", index = 21) + private String seniorEngineerSeries; + + @ExcelIgnore + private Integer rowIndex; + } }