gaoluyang
19 小时以前 ef3cbc1c6882786e8218b08be3e52ea44587f03b
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java
@@ -1,21 +1,30 @@
package cn.iocoder.yudao.module.hrm.service.attendance;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchSaveRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleListReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleSaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmEmployeeScheduleDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum;
import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmEmployeeScheduleMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmShiftMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.*;
@@ -40,6 +49,10 @@
    private HrmEmployeeMapper employeeMapper;
    @Resource
    private HrmShiftMapper shiftMapper;
    @Resource
    private DeptApi deptApi;
    @Resource
    private HrmAttendanceHolidayService attendanceHolidayService;
    @Override
    public List<HrmEmployeeScheduleRespVO> getScheduleList(HrmEmployeeScheduleListReqVO reqVO) {
@@ -52,7 +65,9 @@
        if (reqVO.getEmployeeId() != null) {
            list = employeeScheduleMapper.selectListByEmployeeIdAndPeriod(reqVO.getEmployeeId(), startDate, endDate);
        } else if (reqVO.getDeptId() != null) {
            List<HrmEmployeeDO> employees = employeeMapper.selectByDeptId(reqVO.getDeptId());
            // 与员工分页保持一致:选中部门时连带查询所有下级部门的员工
            Set<Long> deptIds = getSelfAndChildDeptIds(reqVO.getDeptId());
            List<HrmEmployeeDO> employees = employeeMapper.selectListByDeptIds(deptIds);
            Set<Long> employeeIds = employees.stream().map(HrmEmployeeDO::getId).collect(Collectors.toSet());
            list = CollUtil.isEmpty(employeeIds) ? Collections.emptyList()
                    : employeeScheduleMapper.selectListByEmployeeIdsAndPeriod(employeeIds, startDate, endDate);
@@ -140,4 +155,121 @@
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public HrmEmployeeScheduleBatchSaveRespVO batchSaveSchedule(HrmEmployeeScheduleBatchSaveReqVO reqVO) {
        // 1. 校验日期范围和班次
        if (reqVO.getEndDate().isBefore(reqVO.getStartDate())) {
            throw exception(SCHEDULE_BATCH_DATE_RANGE_INVALID);
        }
        if (reqVO.getShiftId() != null && shiftMapper.selectById(reqVO.getShiftId()) == null) {
            throw exception(SCHEDULE_SHIFT_NOT_EXISTS);
        }
        // 2. 按部门(含下级)+ 岗位 + 员工状态筛选目标员工
        Set<Long> deptIds = getSelfAndChildDeptIds(reqVO.getDeptId());
        List<HrmEmployeeDO> employees = employeeMapper.selectListByDeptIds(deptIds).stream()
                .filter(employee -> matchEmployeeStatus(employee, reqVO.getEmployeeStatus()))
                .filter(employee -> matchPost(employee, reqVO.getPostId()))
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(employees)) {
            DeptRespDTO dept = deptApi.getDept(reqVO.getDeptId());
            throw exception(SCHEDULE_BATCH_EMPLOYEE_EMPTY, dept == null ? String.valueOf(reqVO.getDeptId()) : dept.getName());
        }
        // 3. 预加载范围内已有排班,避免逐条查询
        Set<Long> employeeIds = employees.stream().map(HrmEmployeeDO::getId).collect(Collectors.toSet());
        Map<String, HrmEmployeeScheduleDO> existingMap = employeeScheduleMapper
                .selectListByEmployeeIdsAndPeriod(employeeIds, reqVO.getStartDate(), reqVO.getEndDate())
                .stream()
                .collect(Collectors.toMap(item -> scheduleKey(item.getEmployeeId(), item.getDate()),
                        Function.identity(), (a, b) -> a));
        // 4. 预加载区间内节假日配置:配置为休息日的当天强制休息,配置为工作日的当天强制排班(可覆盖周末)
        Map<LocalDate, Integer> holidayTypeMap = attendanceHolidayService
                .getAttendanceHolidayListByDateRange(reqVO.getStartDate(), reqVO.getEndDate())
                .stream()
                .collect(Collectors.toMap(HrmAttendanceHolidayDO::getDate, HrmAttendanceHolidayDO::getType,
                        (a, b) -> a));
        // 5. 按日期逐天生成排班:休息日删除原班次,工作日 upsert
        int itemCount = 0;
        for (LocalDate date = reqVO.getStartDate(); !date.isAfter(reqVO.getEndDate()); date = date.plusDays(1)) {
            boolean rest = isRestDay(date, reqVO.getRestMode(), holidayTypeMap.get(date))
                    || reqVO.getShiftId() == null;
            for (HrmEmployeeDO employee : employees) {
                HrmEmployeeScheduleDO existing = existingMap.get(scheduleKey(employee.getId(), date));
                if (rest) {
                    if (existing != null) {
                        employeeScheduleMapper.deleteById(existing.getId());
                        itemCount++;
                    }
                    continue;
                }
                if (existing == null) {
                    HrmEmployeeScheduleDO schedule = new HrmEmployeeScheduleDO();
                    schedule.setEmployeeId(employee.getId());
                    schedule.setDate(date);
                    schedule.setShiftId(reqVO.getShiftId());
                    employeeScheduleMapper.insert(schedule);
                    itemCount++;
                } else if (!Objects.equals(existing.getShiftId(), reqVO.getShiftId())) {
                    existing.setShiftId(reqVO.getShiftId());
                    employeeScheduleMapper.updateById(existing);
                    itemCount++;
                }
            }
        }
        HrmEmployeeScheduleBatchSaveRespVO respVO = new HrmEmployeeScheduleBatchSaveRespVO();
        respVO.setEmployeeCount(employees.size());
        respVO.setItemCount(itemCount);
        return respVO;
    }
    private Set<Long> getSelfAndChildDeptIds(Long deptId) {
        Set<Long> deptIds = new LinkedHashSet<>();
        deptIds.add(deptId);
        List<DeptRespDTO> childDepts = deptApi.getChildDeptList(deptId);
        if (CollUtil.isNotEmpty(childDepts)) {
            childDepts.forEach(dept -> deptIds.add(dept.getId()));
        }
        return deptIds;
    }
    /** employeeStatus 为空时包含在职和试用,排除离职 */
    private boolean matchEmployeeStatus(HrmEmployeeDO employee, Integer employeeStatus) {
        if (employeeStatus != null) {
            return Objects.equals(employee.getEmployeeStatus(), employeeStatus);
        }
        return !Objects.equals(employee.getEmployeeStatus(), 3);
    }
    private boolean matchPost(HrmEmployeeDO employee, Long postId) {
        if (postId == null) {
            return true;
        }
        if (StrUtil.isBlank(employee.getPostIds())) {
            return false;
        }
        return JSONUtil.toList(employee.getPostIds(), Long.class).contains(postId);
    }
    private boolean isRestDay(LocalDate date, String restMode, Integer holidayType) {
        // 节假日配置优先:休息日强制休息,工作日强制上班(含调休上班的周末)
        if (holidayType != null) {
            return HrmAttendanceHolidayTypeEnum.REST_DAY.getType().equals(holidayType);
        }
        if ("double".equals(restMode)) {
            return date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY;
        }
        if ("single".equals(restMode)) {
            return date.getDayOfWeek() == DayOfWeek.SUNDAY;
        }
        return false;
    }
    private String scheduleKey(Long employeeId, LocalDate date) {
        return employeeId + "_" + date;
    }
}