| | |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | // 同步教育经历 |
| | | syncEducationList(employeeId, updateReqVO.getEducations()); |
| | | if (updateReqVO.getEducations() != null) syncEducationList(employeeId, updateReqVO.getEducations()); |
| | | // 同步工作经历 |
| | | syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories()); |
| | | if (updateReqVO.getWorkHistories() != null) syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories()); |
| | | // 同步紧急联系人 |
| | | syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts()); |
| | | if (updateReqVO.getEmergencyContacts() != null) syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts()); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * 只同步:姓名↔昵称、手机号↔mobile、部门、性别、邮箱。 |
| | | * 登录名 username、岗位、角色、状态不同步。 |
| | | * 同步失败只记录日志,不阻断员工修改主流程。 |
| | | * 同步失败时回滚员工更新,避免员工和系统账号不一致。 |
| | | */ |
| | | private void syncSystemUserForEmployee(HrmEmployeeDO employee, HrmEmployeeSaveReqVO reqVO) { |
| | | try { |
| | |
| | | dto.setEmail(reqVO.getEmail() != null ? reqVO.getEmail() : employee.getEmail()); |
| | | adminUserApi.updateUserForEmployee(dto); |
| | | } catch (Exception ex) { |
| | | log.warn("[updateEmployee][员工({}) 同步账号({}) 失败,已忽略]", employee.getId(), employee.getUserId(), ex); |
| | | log.warn("[updateEmployee] 员工账号同步失败,员工编号={}", employee.getId()); |
| | | throw exception(EMPLOYEE_USER_SYNC_FAILED); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> importEmployees, |
| | | HrmEmployeeImportReqVO importReqVO) { |
| | | // 1. 校验非空 |
| | | importEmployees = filterList(importEmployees, item -> Objects.nonNull(item.getName())); |
| | | if (CollUtil.isEmpty(importEmployees)) { |
| | | throw exception(EMPLOYEE_IMPORT_LIST_IS_EMPTY); |
| | | } |
| | | // 2. 预加载部门/岗位/角色 名称 -> 实体 映射 |
| | | Map<String, DeptRespDTO> deptMap = deptApi.getDeptList().stream() |
| | | .collect(Collectors.toMap(DeptRespDTO::getName, dept -> dept, (v1, v2) -> v1)); |
| | | Map<String, PostRespDTO> postMap = postApi.getPostList().stream() |
| | | .collect(Collectors.toMap(PostRespDTO::getName, post -> post, (v1, v2) -> v1)); |
| | | Map<String, RoleRespDTO> roleMap = roleApi.getRoleList().stream() |
| | | .collect(Collectors.toMap(RoleRespDTO::getName, role -> role, (v1, v2) -> v1)); |
| | | // 3. 逐条处理 |
| | | HrmEmployeeImportRespVO respVO = HrmEmployeeImportRespVO.builder() |
| | | .createNames(new ArrayList<>()) |
| | | .updateNames(new ArrayList<>()) |
| | | .failureNames(new LinkedHashMap<>()) |
| | | .build(); |
| | | for (HrmEmployeeImportExcelVO importEmployee : importEmployees) { |
| | | String name = importEmployee.getName(); |
| | | public HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> rows, HrmEmployeeImportReqVO req) { |
| | | if (CollUtil.isEmpty(rows)) throw exception(EMPLOYEE_IMPORT_LIST_IS_EMPTY); |
| | | Map<String, List<DeptRespDTO>> depts = deptApi.getDeptList().stream() |
| | | .filter(v -> StrUtil.isNotBlank(v.getName())).collect(Collectors.groupingBy(v -> v.getName().trim())); |
| | | Map<String, List<PostRespDTO>> posts = postApi.getPostList().stream() |
| | | .filter(v -> StrUtil.isNotBlank(v.getName())).collect(Collectors.groupingBy(v -> v.getName().trim())); |
| | | Map<String, List<RoleRespDTO>> roles = roleApi.getRoleList().stream() |
| | | .filter(v -> StrUtil.isNotBlank(v.getName())).collect(Collectors.groupingBy(v -> v.getName().trim())); |
| | | HrmEmployeeImportRespVO result = HrmEmployeeImportRespVO.builder() |
| | | .createNames(new ArrayList<>()).updateNames(new ArrayList<>()).failureNames(new LinkedHashMap<>()).build(); |
| | | Set<String> phones = new LinkedHashSet<>(); |
| | | boolean hasData = false; |
| | | for (int i = 0; i < rows.size(); i++) { |
| | | HrmEmployeeImportExcelVO row = rows.get(i); |
| | | if (row == null || cn.hutool.core.bean.BeanUtil.beanToMap(row).values().stream() |
| | | .noneMatch(v -> v != null && (!(v instanceof String) || StrUtil.isNotBlank((String) v)))) continue; |
| | | hasData = true; |
| | | String label = "第" + (i + 2) + "行" + (StrUtil.isBlank(row.getName()) ? "" : "(" + row.getName().trim() + ")"); |
| | | try { |
| | | // 3.1 名称转 ID |
| | | Long deptId = parseDeptId(importEmployee.getDeptName(), deptMap); |
| | | Long postId = parsePostId(importEmployee.getPostName(), postMap); |
| | | Set<Long> roleIds = parseRoleIds(importEmployee.getRoleNames(), roleMap); |
| | | // 3.2 构建保存请求 |
| | | HrmEmployeeSaveReqVO saveReqVO = buildImportSaveReqVO(importEmployee, deptId, postId, roleIds); |
| | | // 3.3 判断存在性:按手机号 |
| | | HrmEmployeeDO existEmployee = employeeMapper.selectByPhone(importEmployee.getPhone()); |
| | | if (existEmployee == null) { |
| | | self.createEmployee(saveReqVO); |
| | | respVO.getCreateNames().add(name); |
| | | } else { |
| | | if (!Boolean.TRUE.equals(importReqVO.getUpdateSupport())) { |
| | | respVO.getFailureNames().put(name, EMPLOYEE_PHONE_EXISTS.getMsg()); |
| | | continue; |
| | | if (StrUtil.isBlank(row.getName()) || StrUtil.isBlank(row.getPhone()) || row.getHireDate() == null) { |
| | | throw exception(EMPLOYEE_IMPORT_INVALID, "员工姓名、手机号和入职日期不能为空"); |
| | | } |
| | | saveReqVO.setId(existEmployee.getId()); |
| | | self.updateEmployee(saveReqVO); |
| | | respVO.getUpdateNames().add(name); |
| | | row.setName(row.getName().trim()); row.setPhone(row.getPhone().trim()); |
| | | if (!phones.add(row.getPhone())) throw exception(EMPLOYEE_IMPORT_INVALID, "文件内手机号重复"); |
| | | if (StrUtil.isBlank(row.getDeptName()) || StrUtil.isBlank(row.getPostName())) { |
| | | throw exception(EMPLOYEE_IMPORT_INVALID, "部门和岗位不能为空"); |
| | | } |
| | | List<DeptRespDTO> deptMatches = depts.get(row.getDeptName().trim()); |
| | | List<PostRespDTO> postMatches = posts.get(row.getPostName().trim()); |
| | | if (deptMatches == null || deptMatches.size() != 1) throw exception(EMPLOYEE_IMPORT_INVALID, "部门名称不存在或不唯一"); |
| | | if (postMatches == null || postMatches.size() != 1) throw exception(EMPLOYEE_IMPORT_INVALID, "岗位名称不存在或不唯一"); |
| | | Set<Long> roleIds = new LinkedHashSet<>(); |
| | | if (StrUtil.isNotBlank(row.getRoleNames())) { |
| | | for (String value : row.getRoleNames().split("[,,]")) { |
| | | String name = value.trim(); |
| | | if (name.isEmpty()) continue; |
| | | List<RoleRespDTO> matches = roles.get(name); |
| | | if (matches == null || matches.size() != 1) throw exception(EMPLOYEE_IMPORT_INVALID, "角色名称不存在或不唯一:" + name); |
| | | roleIds.add(matches.get(0).getId()); |
| | | } |
| | | } |
| | | HrmEmployeeSaveReqVO save = buildImportSaveReqVO(row, deptMatches.get(0).getId(), postMatches.get(0).getId(), roleIds); |
| | | HrmEmployeeDO existing = employeeMapper.selectByPhone(row.getPhone()); |
| | | if (existing == null) { |
| | | if (row.getGender() == null) throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "性别"); |
| | | self.createEmployee(save); |
| | | result.getCreateNames().add(label); |
| | | } else { |
| | | if (!Boolean.TRUE.equals(req.getUpdateSupport())) throw exception(EMPLOYEE_PHONE_EXISTS); |
| | | if (StrUtil.isNotBlank(row.getPassword())) throw exception(EMPLOYEE_IMPORT_INVALID, "覆盖已有员工时初始密码须留空,密码请在账号管理中修改"); |
| | | if (!roleIds.isEmpty()) throw exception(EMPLOYEE_IMPORT_INVALID, "覆盖员工时不支持修改用户角色,请留空并在账号管理中维护"); |
| | | if (!Objects.equals(existing.getPostId(), save.getPostId())) throw exception(EMPLOYEE_IMPORT_INVALID, "覆盖员工时暂不支持变更岗位,请先处理账号岗位配置"); |
| | | save.setId(existing.getId()); |
| | | if (row.getGender() == null) save.setGender(existing.getGender()); |
| | | if (row.getEmployeeStatus() == null) save.setEmployeeStatus(existing.getEmployeeStatus()); |
| | | self.updateEmployee(save); |
| | | result.getUpdateNames().add(label); |
| | | } |
| | | } catch (ServiceException ex) { |
| | | respVO.getFailureNames().put(name, ex.getMessage()); |
| | | } catch (Exception ex) { |
| | | respVO.getFailureNames().put(name, ex.getMessage()); |
| | | result.getFailureNames().put(label, ex.getMessage()); |
| | | } catch (RuntimeException ex) { |
| | | log.warn("[importEmployeeList] 员工导入失败,行号={}", i + 2); |
| | | result.getFailureNames().put(label, "保存或账号同步失败,请检查该行数据及账号配置"); |
| | | } |
| | | } |
| | | return respVO; |
| | | if (!hasData) throw exception(EMPLOYEE_IMPORT_LIST_IS_EMPTY); |
| | | return result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 解析员工初始密码: |
| | |
| | | return reqVO; |
| | | } |
| | | |
| | | private Long parseDeptId(String deptName, Map<String, DeptRespDTO> deptMap) { |
| | | if (StrUtil.isBlank(deptName)) { |
| | | throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "部门名称"); |
| | | } |
| | | DeptRespDTO dept = deptMap.get(deptName.trim()); |
| | | if (dept == null) { |
| | | throw exception(EMPLOYEE_DEPT_NAME_NOT_EXISTS, deptName.trim()); |
| | | } |
| | | return dept.getId(); |
| | | } |
| | | |
| | | private Long parsePostId(String postName, Map<String, PostRespDTO> postMap) { |
| | | if (StrUtil.isBlank(postName)) { |
| | | throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "岗位名称"); |
| | | } |
| | | PostRespDTO post = postMap.get(postName.trim()); |
| | | if (post == null) { |
| | | throw exception(EMPLOYEE_POST_NAME_NOT_EXISTS, postName.trim()); |
| | | } |
| | | return post.getId(); |
| | | } |
| | | |
| | | private Set<Long> parseRoleIds(String roleNames, Map<String, RoleRespDTO> roleMap) { |
| | | if (StrUtil.isBlank(roleNames)) { |
| | | return Collections.emptySet(); |
| | | } |
| | | Set<Long> roleIds = new LinkedHashSet<>(); |
| | | for (String roleName : roleNames.split("[,,]")) { |
| | | String trimmed = roleName.trim(); |
| | | if (StrUtil.isBlank(trimmed)) { |
| | | continue; |
| | | } |
| | | RoleRespDTO role = roleMap.get(trimmed); |
| | | if (role == null) { |
| | | throw exception(EMPLOYEE_ROLE_NAME_NOT_EXISTS, trimmed); |
| | | } |
| | | roleIds.add(role.getId()); |
| | | } |
| | | return roleIds; |
| | | } |
| | | |
| | | } |