| | |
| | | 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.*; |
| | |
| | | private HrmEmployeeMapper employeeMapper; |
| | | @Resource |
| | | private HrmShiftMapper shiftMapper; |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | @Resource |
| | | private HrmAttendanceHolidayService attendanceHolidayService; |
| | | |
| | | @Override |
| | | public List<HrmEmployeeScheduleRespVO> getScheduleList(HrmEmployeeScheduleListReqVO reqVO) { |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | |
| | | @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; |
| | | } |
| | | |
| | | } |