tuqin
7 小时以前 52feee3e55403d2858da59605173ec055f601bfa
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -6,7 +6,7 @@
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEducationSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEmergencyContactSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportExcelVO;
@@ -17,10 +17,12 @@
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSimpleRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeWorkHistorySaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeContractDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEducationDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEmergencyContactDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeWorkHistoryDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeContractMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
@@ -32,18 +34,21 @@
import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -56,7 +61,6 @@
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.*;
import static cn.iocoder.yudao.module.hrm.enums.LogRecordConstants.*;
/**
 * 员工信息 Service 实现类
@@ -69,6 +73,8 @@
    @Resource
    private HrmEmployeeMapper employeeMapper;
    @Resource
    private HrmEmployeeContractMapper contractMapper;
    @Resource
    private HrmEmployeeEducationMapper educationMapper;
    @Resource
@@ -85,6 +91,8 @@
    private AdminUserApi adminUserApi;
    @Resource
    private RoleApi roleApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    @Lazy // 自注入需延迟加载,否则 bean 被代理包装后注入原始版本导致启动失败
    private HrmEmployeeService self;
@@ -132,6 +140,8 @@
        }
        employeeMapper.insert(employee);
        Long employeeId = employee.getId();
        // 绑定附件
        storageAttachmentApi.bindAttachments("file", "hrm_employee", employeeId, createReqVO.getBlobIds());
        // 7. 同步添加教育经历
        if (CollUtil.isNotEmpty(createReqVO.getEducations())) {
            createReqVO.getEducations().forEach(educationVO -> {
@@ -213,6 +223,8 @@
        syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories());
        // 同步紧急联系人
        syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts());
        // 更新附件
        storageAttachmentApi.updateAttachments("file", "hrm_employee", employeeId, updateReqVO.getBlobIds());
    }
    private void syncEducationList(Long employeeId, List<HrmEmployeeEducationSaveReqVO> newList) {
@@ -260,6 +272,8 @@
        educationMapper.deleteByEmployeeId(id);
        workHistoryMapper.deleteByEmployeeId(id);
        emergencyContactMapper.deleteByEmployeeId(id);
        // 删除附件
        storageAttachmentApi.deleteAttachmentsByRecord("hrm_employee", id);
        // 删除员工
        employeeMapper.deleteById(id);
    }
@@ -314,6 +328,8 @@
                respVO.setRoleNames(roles.stream().map(RoleRespDTO::getName).collect(Collectors.joining(",")));
            }
        }
        // 填充附件
        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_employee", id));
        return respVO;
    }
@@ -355,18 +371,67 @@
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
        // 解析岗位ID(post_ids 存的是 JSON 数组字符串,如 [38]),并批量加载岗位名称
        Map<Long, List<Long>> empPostIdsMap = new LinkedHashMap<>();
        Set<Long> allPostIds = new LinkedHashSet<>();
        for (HrmEmployeeDO emp : list) {
            List<Long> postIdList = parseEmployeePostIds(emp.getPostIds());
            empPostIdsMap.put(emp.getId(), postIdList);
            allPostIds.addAll(postIdList);
        }
        Map<Long, PostRespDTO> postMap = CollUtil.isEmpty(allPostIds)
                ? Collections.emptyMap() : postApi.getPostMap(allPostIds);
        return list.stream().map(emp -> {
            HrmEmployeeSimpleRespVO vo = new HrmEmployeeSimpleRespVO();
            vo.setId(emp.getId());
            vo.setEmployeeNo(emp.getEmployeeNo());
            vo.setName(emp.getName());
            vo.setEmployeeStatus(emp.getEmployeeStatus());
            vo.setDeptId(emp.getDeptId());
            if (emp.getDeptId() != null && deptMap.containsKey(emp.getDeptId())) {
                vo.setDeptName(deptMap.get(emp.getDeptId()).getName());
            }
            // 岗位信息(用于调岗等场景自动带出原岗位)
            List<Long> postIdList = empPostIdsMap.getOrDefault(emp.getId(), Collections.emptyList());
            vo.setPostIds(postIdList);
            if (CollUtil.isNotEmpty(postIdList)) {
                vo.setPostName(postIdList.stream()
                        .map(postMap::get)
                        .filter(Objects::nonNull)
                        .map(PostRespDTO::getName)
                        .collect(Collectors.joining(",")));
            }
            return vo;
        }).collect(Collectors.toList());
    }
    /**
     * 解析员工的岗位ID字符串(JSON 数组格式,如 [38] 或 [1,2])
     */
    /**
     * 解析员工岗位ID字符串(post_ids 存 JSON 数组,如 [38] 或 [1,2])
     */
    private java.util.List<Long> parseEmployeePostIds(String postIds) {
        if (StrUtil.isBlank(postIds)) {
            return Collections.emptyList();
        }
        // post_ids 形如 "[38]" 或 "[1,2]":去掉中括号后按逗号解析
        return Arrays.stream(postIds.replace("[", "").replace("]", "").split(","))
                .map(String::trim)
                .filter(StrUtil::isNotBlank)
                .map(s -> {
                    try {
                        return Long.valueOf(s);
                    } catch (NumberFormatException ex) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
    @Override
    public HrmEmployeeDO getEmployeeByUserId(Long userId) {
@@ -384,8 +449,6 @@
    }
    @Override
    @LogRecord(type = HRM_EMPLOYEE_TYPE, subType = HRM_EMPLOYEE_IMPORT_SUB_TYPE, bizNo = "0",
            success = HRM_EMPLOYEE_IMPORT_SUCCESS)
    public HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> importEmployees,
                                                      HrmEmployeeImportReqVO importReqVO) {
        // 1. 校验非空
@@ -435,9 +498,6 @@
                respVO.getFailureNames().put(name, ex.getMessage());
            }
        }
        // 记录导入结果汇总,供 @LogRecord 的 success 文案引用
        OperateLogUtils.putImportSummary(respVO.getCreateNames(), respVO.getUpdateNames(),
                respVO.getFailureNames());
        return respVO;
    }
@@ -449,8 +509,9 @@
        if (StrUtil.isBlank(importEmployee.getPhone())) {
            throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "手机号");
        }
        if (importEmployee.getHireDate() == null) {
            throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "入职日期");
        LocalDate hireDate = parseImportDate(importEmployee.getHireDate());
        if (hireDate == null) {
            throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "入职日期(格式:2015 或 2015-01-01)");
        }
        HrmEmployeeSaveReqVO reqVO = new HrmEmployeeSaveReqVO();
        reqVO.setName(importEmployee.getName());
@@ -459,11 +520,11 @@
        reqVO.setDeptId(deptId);
        reqVO.setPostIds(CollUtil.isEmpty(postIds) ? null : new ArrayList<>(postIds));
        reqVO.setRoleIds(CollUtil.isEmpty(roleIds) ? null : new ArrayList<>(roleIds));
        reqVO.setHireDate(importEmployee.getHireDate());
        reqVO.setHireDate(hireDate);
        reqVO.setEmployeeStatus(importEmployee.getEmployeeStatus() != null
                ? importEmployee.getEmployeeStatus() : 1); // 默认在职
        reqVO.setEmail(importEmployee.getEmail());
        reqVO.setBirthday(importEmployee.getBirthday());
        reqVO.setBirthday(parseImportDate(importEmployee.getBirthday()));
        reqVO.setIdCard(importEmployee.getIdCard());
        reqVO.setNation(importEmployee.getNation());
        reqVO.setMarriageStatus(importEmployee.getMarriageStatus());
@@ -471,6 +532,11 @@
        reqVO.setEducation(importEmployee.getEducation());
        reqVO.setPoliticalStatus(importEmployee.getPoliticalStatus());
        reqVO.setRemark(importEmployee.getRemark());
        reqVO.setEmployeeCategory(importEmployee.getEmployeeCategory());
        reqVO.setBaseSalary(importEmployee.getBaseSalary());
        reqVO.setBankName(importEmployee.getBankName());
        reqVO.setBankAccount(importEmployee.getBankAccount());
        reqVO.setAddress(importEmployee.getAddress());
        return reqVO;
    }
@@ -526,4 +592,108 @@
        return roleIds;
    }
    /**
     * 容错解析导入日期文本,支持:2015 / 2015年 / 2015-01 / 2015-01-02 / 2015/1/2 / 2015.1.2 /
     * 2015-01-02 00:00:00 / 20150102,以及 Excel 日期序列号;解析不出返回 null。
     */
    private static LocalDate parseImportDate(String raw) {
        if (StrUtil.isBlank(raw)) {
            return null;
        }
        String s = raw.trim();
        // 去掉时间部分
        int sp = s.indexOf(' ');
        if (sp > 0) {
            s = s.substring(0, sp);
        }
        // 中文年月日 / 斜杠 / 点 / 下划线 统一成 - 分隔
        s = s.replace("年", "-").replace("月", "-").replace("日", "")
                .replace("/", "-").replace(".", "-").replace("_", "-").trim();
        s = s.replaceAll("^-+|-+$", "");
        if (StrUtil.isBlank(s)) {
            return null;
        }
        try {
            if (s.matches("\\d+")) {
                int len = s.length();
                if (len == 4) { // 2015
                    return LocalDate.of(Integer.parseInt(s), 1, 1);
                }
                if (len == 5) { // Excel 日期序列号(1900 日期系统,基准 1899-12-30)
                    return LocalDate.of(1899, 12, 30).plusDays(Long.parseLong(s));
                }
                if (len == 6) { // 201506
                    return LocalDate.of(Integer.parseInt(s.substring(0, 4)), Integer.parseInt(s.substring(4, 6)), 1);
                }
                if (len == 8) { // 20150601
                    return LocalDate.of(Integer.parseInt(s.substring(0, 4)), Integer.parseInt(s.substring(4, 6)), Integer.parseInt(s.substring(6, 8)));
                }
                return null;
            }
            String[] parts = s.split("-");
            if (parts.length == 2) { // 2015-06
                return LocalDate.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), 1);
            }
            if (parts.length >= 3) { // 2015-06-01
                return LocalDate.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }
    // ========== 转正 ==========
    @Override
    public void regularizeEmployee(Long id) {
        validateEmployee(id);
        HrmEmployeeContractDO contract = findCurrentContract(id);
        // 仅「劳动合同」可转正;劳务合同不转正
        if (contract == null || !Integer.valueOf(1).equals(contract.getContractType())) {
            throw exception(EMPLOYEE_REGULARIZE_NEED_LABOR_CONTRACT);
        }
        HrmEmployeeDO updateObj = new HrmEmployeeDO();
        updateObj.setId(id);
        updateObj.setEmployeeStatus(1); // 1-在职
        updateObj.setRegularDate(LocalDate.now());
        employeeMapper.updateById(updateObj);
    }
    @Override
    public List<HrmEmployeeDO> getRegularizeReminderList(Integer days) {
        int aheadDays = days == null ? 7 : days;
        LocalDate limit = LocalDate.now().plusDays(aheadDays);
        // 试用中的员工
        List<HrmEmployeeDO> trials = employeeMapper.selectList(
                new LambdaQueryWrapperX<HrmEmployeeDO>().eq(HrmEmployeeDO::getEmployeeStatus, 2));
        List<HrmEmployeeDO> result = new ArrayList<>();
        for (HrmEmployeeDO employee : trials) {
            HrmEmployeeContractDO contract = findCurrentContract(employee.getId());
            // 仅劳动合同、且试用期即将到期
            if (contract == null || !Integer.valueOf(1).equals(contract.getContractType())) {
                continue;
            }
            LocalDate probationEnd = contract.getProbationEndDate();
            if (probationEnd != null && !probationEnd.isAfter(limit)) {
                result.add(employee);
            }
        }
        return result;
    }
    /** 查询员工当前生效合同 */
    private HrmEmployeeContractDO findCurrentContract(Long employeeId) {
        List<HrmEmployeeContractDO> list = contractMapper.selectListByEmployee(employeeId);
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        for (HrmEmployeeContractDO contract : list) {
            if (Boolean.TRUE.equals(contract.getIsCurrent())) {
                return contract;
            }
        }
        return null;
    }
}