| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmTransferApplicationMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmTransferTypeEnum; |
| | | import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.PostApi; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | /** |
| | | * 调岗申请 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service |
| | | @Validated |
| | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private HrmEmployeeService employeeService; |
| | | @Resource |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | @Resource |
| | | private BpmCategoryService bpmCategoryService; |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createTransferApplication(HrmTransferApplicationSaveReqVO createReqVO) { |
| | | // 1. 校验目标部门与原部门不能相同 |
| | | // 1. 解析申请人 & 补全原部门/原岗位 |
| | | // 前端表单只让用户选择「被调岗员工」,不直接提交 currentDeptId/currentPostId, |
| | | // 而这两个字段在库里是 NOT NULL,缺省会直接抛 SQL 异常(Field 'current_dept_id' doesn't have a default value) |
| | | Long userId = createReqVO.getUserId(); |
| | | Long employeeId = createReqVO.getEmployeeId(); |
| | | HrmEmployeeDO employee = null; |
| | | if (employeeId != null) { |
| | | employee = employeeService.getEmployee(employeeId); |
| | | } |
| | | if (employee == null && userId != null) { |
| | | // 约定 user_id 存 system_users.id;但历史上前端也有把「员工主键」塞进 userId 的情况,两种都兼容 |
| | | employee = employeeService.getEmployeeByUserId(userId); |
| | | if (employee == null) { |
| | | employee = employeeService.getEmployee(userId); |
| | | } |
| | | } |
| | | // 以员工档案上的 user_id 为准(前端可能把员工主键塞在 userId 字段里,直接用会写错申请人) |
| | | if (employee != null && employee.getUserId() != null) { |
| | | userId = employee.getUserId(); |
| | | } |
| | | if (userId == null && employee != null) { |
| | | userId = employee.getUserId(); |
| | | } |
| | | if (userId == null) { |
| | | userId = getCurrentUserId(); |
| | | if (employee == null) { |
| | | employee = employeeService.getEmployeeByUserId(userId); |
| | | } |
| | | } |
| | | if (adminUserApi.getUser(userId) == null) { |
| | | userId = getCurrentUserId(); |
| | | } |
| | | if (createReqVO.getCurrentDeptId() == null && employee != null) { |
| | | createReqVO.setCurrentDeptId(employee.getDeptId()); |
| | | } |
| | | if (createReqVO.getCurrentPostId() == null && employee != null) { |
| | | createReqVO.setCurrentPostId(parseFirstPostId(employee.getPostIds())); |
| | | } |
| | | if (createReqVO.getCurrentDeptId() == null) { |
| | | throw exception(TRANSFER_APPLICATION_CURRENT_DEPT_REQUIRED); |
| | | } |
| | | if (createReqVO.getCurrentPostId() == null) { |
| | | throw exception(TRANSFER_APPLICATION_CURRENT_POST_REQUIRED); |
| | | } |
| | | // 2. 校验目标部门与原部门不能相同(放在补全之后,否则补全出来的原部门绕过了校验) |
| | | if (Objects.equals(createReqVO.getTargetDeptId(), createReqVO.getCurrentDeptId())) { |
| | | throw exception(TRANSFER_APPLICATION_SAME_DEPT); |
| | | } |
| | | // 2. 校验目标岗位与原岗位不能相同 |
| | | // 3. 校验目标岗位与原岗位不能相同 |
| | | if (Objects.equals(createReqVO.getTargetPostId(), createReqVO.getCurrentPostId())) { |
| | | throw exception(TRANSFER_APPLICATION_SAME_POST); |
| | | } |
| | | // 3. 生成申请单号 |
| | | // 4. 生成申请单号 |
| | | String no = generateUniqueNo(); |
| | | // 4. 设置申请人信息 |
| | | Long userId = createReqVO.getUserId(); |
| | | if (userId == null) { |
| | | userId = getCurrentUserId(); |
| | | } |
| | | AdminUserRespDTO user = adminUserApi.getUser(userId); |
| | | if (user == null) { |
| | | userId = getCurrentUserId(); |
| | | } |
| | | // 5. 插入 |
| | | HrmTransferApplicationDO application = BeanUtils.toBean(createReqVO, HrmTransferApplicationDO.class); |
| | | application.setNo(no); |
| | |
| | | storageAttachmentApi.bindAttachments("file", "hrm_transfer_application", |
| | | application.getId(), createReqVO.getBlobIds()); |
| | | return application.getId(); |
| | | } |
| | | |
| | | /** |
| | | * 取员工岗位列表中的第一个作为「原岗位」 |
| | | * |
| | | * <p>hrm_employee.post_ids 以 JSON 数组字符串存储,例如 {@code [20,21]} |
| | | * |
| | | * @param postIds 岗位编号 JSON 字符串 |
| | | * @return 第一个岗位编号;解析不出来时返回 {@code null} |
| | | */ |
| | | private Long parseFirstPostId(String postIds) { |
| | | if (StrUtil.isBlank(postIds)) { |
| | | return null; |
| | | } |
| | | try { |
| | | List<Long> ids = JSONUtil.toList(postIds, Long.class); |
| | | return CollUtil.isEmpty(ids) ? null : ids.get(0); |
| | | } catch (Exception ex) { |
| | | log.warn("[parseFirstPostId][postIds({}) 解析失败]", postIds, ex); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Long getCurrentUserId() { |
| | |
| | | } |
| | | HrmTransferApplicationRespVO respVO = BeanUtils.toBean(application, HrmTransferApplicationRespVO.class); |
| | | if (application.getUserId() != null) { |
| | | AdminUserRespDTO user = adminUserApi.getUser(application.getUserId()); |
| | | if (user != null) { |
| | | respVO.setUserName(user.getNickname()); |
| | | // user_id 正常存 system_users.id;历史数据也有存「员工主键」的,两种都兼容 |
| | | HrmEmployeeDO employee = employeeService.getEmployeeByUserId(application.getUserId()); |
| | | if (employee == null) { |
| | | employee = employeeService.getEmployee(application.getUserId()); |
| | | } |
| | | if (employee != null) { |
| | | respVO.setUserName(employee.getName()); |
| | | respVO.setEmployeeId(employee.getId()); |
| | | } |
| | | } |
| | | fillDeptNames(respVO, application); |
| | |
| | | .map(HrmTransferApplicationDO::getUserId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | // user_id 正常存 system_users.id;历史数据也有存「员工主键」的,两种都兼容 |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| | | Map<Long, String> employeeNameMap = new HashMap<>(); |
| | | for (Long userId : userIds) { |
| | | if (!userMap.containsKey(userId)) { |
| | | HrmEmployeeDO employee = employeeService.getEmployee(userId); |
| | | if (employee != null) { |
| | | employeeNameMap.put(userId, employee.getName()); |
| | | } |
| | | } |
| | | } |
| | | Set<Long> deptIds = pageResult.getList().stream() |
| | | .flatMap(app -> Stream.of(app.getCurrentDeptId(), app.getTargetDeptId())) |
| | | .filter(Objects::nonNull) |
| | |
| | | Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds); |
| | | List<HrmTransferApplicationRespVO> list = pageResult.getList().stream().map(app -> { |
| | | HrmTransferApplicationRespVO vo = BeanUtils.toBean(app, HrmTransferApplicationRespVO.class); |
| | | if (app.getUserId() != null && userMap.containsKey(app.getUserId())) { |
| | | vo.setUserName(userMap.get(app.getUserId()).getNickname()); |
| | | if (app.getUserId() != null) { |
| | | if (userMap.containsKey(app.getUserId())) { |
| | | vo.setUserName(userMap.get(app.getUserId()).getNickname()); |
| | | } else if (employeeNameMap.containsKey(app.getUserId())) { |
| | | vo.setUserName(employeeNameMap.get(app.getUserId())); |
| | | } |
| | | } |
| | | if (app.getCurrentDeptId() != null && deptMap.containsKey(app.getCurrentDeptId())) { |
| | | vo.setCurrentDeptName(deptMap.get(app.getCurrentDeptId()).getName()); |
| | |
| | | throw exception(TRANSFER_APPLICATION_SUBMIT_FAIL_NOT_DRAFT); |
| | | } |
| | | validateApproveCategoryAndProcessDefinition(); |
| | | // 前端不传流程定义 Key 时,自动取该审批分类下最新版本的活跃流程, |
| | | // 避免直接把 MissingServletRequestParameterException 抛给用户 |
| | | if (StrUtil.isBlank(processDefinitionKey)) { |
| | | processDefinitionKey = resolveActiveProcessDefinitionKey(); |
| | | } |
| | | ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey); |
| | | if (processDefinition == null) { |
| | | throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() |
| | | .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id))); |
| | | .setProcessDefinitionKey(processDefinition.getKey()).setBusinessKey(String.valueOf(id))); |
| | | transferApplicationMapper.updateById(HrmTransferApplicationDO.builder() |
| | | .id(id) |
| | | .processInstanceId(processInstanceId) |
| | | .status(HrmAuditStatusEnum.PROCESS.getStatus()) |
| | | .build()); |
| | | } |
| | | |
| | | /** |
| | | * 取调岗审批分类下最新版本的活跃流程定义 Key |
| | | */ |
| | | private String resolveActiveProcessDefinitionKey() { |
| | | List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService |
| | | .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE); |
| | | if (CollUtil.isEmpty(definitionInfoList)) { |
| | | throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId); |
| | | List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds); |
| | | return processDefinitions.stream() |
| | | .filter(pd -> !pd.isSuspended()) |
| | | .max(Comparator.comparingInt(ProcessDefinition::getVersion)) |
| | | .map(ProcessDefinition::getKey) |
| | | .orElseThrow(() -> exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS)); |
| | | } |
| | | |
| | | private void validateApproveCategoryAndProcessDefinition() { |
| | |
| | | .status(status) |
| | | .approveTime(java.time.LocalDateTime.now()) |
| | | .build()); |
| | | // 审批通过:把员工档案里的部门/岗位同步成调岗后的目标值 |
| | | if (HrmAuditStatusEnum.APPROVE.getStatus().equals(status)) { |
| | | syncEmployeePosition(application); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 调岗审批通过后,把员工档案的部门/岗位更新为目标部门/岗位 |
| | | * |
| | | * <p>走 {@link HrmEmployeeService#updateEmployee} 而不是直接改表, |
| | | * 这样 system_users 上的部门/岗位关联也会一并同步,员工管理列表能立刻看到新岗位。 |
| | | */ |
| | | private void syncEmployeePosition(HrmTransferApplicationDO application) { |
| | | if (application.getTargetDeptId() == null || application.getTargetPostId() == null) { |
| | | return; |
| | | } |
| | | try { |
| | | HrmEmployeeDO employee = employeeService.getEmployeeByUserId(application.getUserId()); |
| | | if (employee == null) { |
| | | employee = employeeService.getEmployee(application.getUserId()); |
| | | } |
| | | if (employee == null) { |
| | | log.warn("[syncEmployeePosition][调岗申请({}) 找不到员工档案,跳过档案同步]", application.getId()); |
| | | return; |
| | | } |
| | | HrmEmployeeSaveReqVO updateReqVO = BeanUtils.toBean(employee, HrmEmployeeSaveReqVO.class); |
| | | updateReqVO.setDeptId(application.getTargetDeptId()); |
| | | // 调岗按单岗位处理,直接落目标岗位 |
| | | updateReqVO.setPostIds(Collections.singletonList(application.getTargetPostId())); |
| | | employeeService.updateEmployee(updateReqVO); |
| | | log.info("[syncEmployeePosition][调岗申请({}) 已将员工({}) 部门改为 {}、岗位改为 {}]", |
| | | application.getId(), employee.getName(), application.getTargetDeptId(), application.getTargetPostId()); |
| | | } catch (Exception ex) { |
| | | // 档案同步失败(例如目标部门/岗位已被删除)不应该阻断审批状态落库,只记录日志 |
| | | log.error("[syncEmployeePosition][调岗申请({}) 同步员工档案失败]", application.getId(), ex); |
| | | } |
| | | } |
| | | |
| | | @Override |