From ef3cbc1c6882786e8218b08be3e52ea44587f03b Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期日, 08 十一月 2026 09:45:42 +0800
Subject: [PATCH] feat(hrm): 请假批量导入、加班代申请人、批量排班与节假日区间联动

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java |  134 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 133 insertions(+), 1 deletions(-)

diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java
index 36eb169..2c597c6 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java
+++ b/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;
+    }
+
 }

--
Gitblit v1.9.3