From 52e4ba6897d5d808c4ed08720bbebad9cb006103 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期四, 05 十一月 2026 13:48:56 +0800
Subject: [PATCH] fix(hrm): 修复生成考勤统计报错、实出天数翻倍;调岗/离职审批通过同步人员信息

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java |  185 ++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 171 insertions(+), 14 deletions(-)

diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
index 67eb1ef..ab7ec40 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java
@@ -4,9 +4,11 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceSummaryDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRecordMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper;
 import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
@@ -20,7 +22,16 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.YearMonth;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 鑰冨嫟缁熻姹囨�� Service 瀹炵幇绫�
@@ -32,6 +43,9 @@
 @Slf4j
 public class HrmAttendanceSummaryServiceImpl implements HrmAttendanceSummaryService {
 
+    /** 鍛樺伐鐘舵�侊細绂昏亴锛�1-鍦ㄨ亴锛�2-璇曠敤锛�3-绂昏亴锛� */
+    private static final Integer EMPLOYEE_STATUS_RESIGNED = 3;
+
     @Resource
     private HrmAttendanceSummaryMapper attendanceSummaryMapper;
     @Resource
@@ -40,6 +54,8 @@
     private HrmEmployeeService employeeService;
     @Resource
     private HrmAttendanceHolidayService attendanceHolidayService;
+    @Resource
+    private HrmLeaveApplicationMapper leaveApplicationMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -67,7 +83,9 @@
         int earlyCount = 0;
         int absentCount = 0;
         BigDecimal overtimeHours = BigDecimal.ZERO;
-        int actualDays = 0;
+        // 瀹為檯鍑哄嫟澶╂暟鎸夈�屾棩鏈熴�嶅幓閲嶏細鍚屼竴澶╁彲鑳芥湁澶氭潯鎵撳崱璁板綍锛堣ˉ鍗°�侀噸澶嶆墦鍗★級锛�
+        // 涓嶈兘姣忔潯璁板綍閮� +1锛屽惁鍒欏疄鍑哄ぉ鏁颁細缈诲�嶏紝鐩存帴褰卞搷钖叕鐨勭己鍕ゆ墸娆�
+        Set<LocalDate> actualDates = new LinkedHashSet<>();
 
         for (HrmAttendanceRecordDO record : records) {
             // 缁熻杩熷埌
@@ -89,11 +107,19 @@
             if (record.getOvertimeHours() != null) {
                 overtimeHours = overtimeHours.add(record.getOvertimeHours());
             }
-            // 缁熻瀹為檯鍑哄嫟澶╂暟锛堟湁姝e父鎵撳崱璁板綍锛�
+            // 缁熻瀹為檯鍑哄嫟澶╂暟锛堟寜鏃ユ湡鍘婚噸锛�
             if (record.getClockInTime() != null || record.getClockOutTime() != null) {
-                actualDays++;
+                LocalDate attendDate = record.getDate();
+                if (attendDate == null && record.getClockInTime() != null) {
+                    attendDate = record.getClockInTime().toLocalDate();
+                }
+                if (attendDate != null) {
+                    actualDates.add(attendDate);
+                }
             }
         }
+        // 瀹炲嚭澶╂暟涓嶅簲瓒呰繃搴斿嚭澶╂暟
+        int actualDays = Math.min(actualDates.size(), workDays);
 
         // 6. 淇濆瓨鎴栨洿鏂版眹鎬昏褰�
         HrmAttendanceSummaryDO summary = attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth);
@@ -126,12 +152,7 @@
     @Transactional(rollbackFor = Exception.class)
     public void generateDeptMonthlySummary(Long deptId, String yearMonth) {
         // 鑾峰彇閮ㄩ棬鎵�鏈夊湪鑱屽憳宸�
-        List<HrmEmployeeDO> employees = employeeService.getEmployeeList(null);
-        if (CollUtil.isEmpty(employees)) {
-            return;
-        }
-        // 绛涢�夐儴闂ㄥ憳宸�
-        List<HrmEmployeeDO> deptEmployees = employees.stream()
+        List<HrmEmployeeDO> deptEmployees = getActiveEmployeeList().stream()
                 .filter(e -> deptId.equals(e.getDeptId()))
                 .toList();
         for (HrmEmployeeDO employee : deptEmployees) {
@@ -143,13 +164,25 @@
     @Transactional(rollbackFor = Exception.class)
     public void generateAllMonthlySummary(String yearMonth) {
         // 鑾峰彇鎵�鏈夊湪鑱屽憳宸�
-        List<HrmEmployeeDO> employees = employeeService.getEmployeeList(null);
-        if (CollUtil.isEmpty(employees)) {
-            return;
-        }
-        for (HrmEmployeeDO employee : employees) {
+        for (HrmEmployeeDO employee : getActiveEmployeeList()) {
             generateMonthlySummary(employee.getId(), yearMonth);
         }
+    }
+
+    /**
+     * 鑾峰彇鍙備笌鑰冨嫟缁熻鐨勫憳宸ワ紙鍦ㄨ亴 + 璇曠敤锛屾帓闄ゅ凡绂昏亴锛�
+     *
+     * <p>娉ㄦ剰锛氫笉鑳界敤 {@code getEmployeeList(null)}锛孧yBatis-Plus 鐨� selectByIds(null)
+     * 浼氭姏 "The expression 'coll' evaluated to a null value"锛屽鑷淬�岀敓鎴愮粺璁°�嶆姤绯荤粺寮傚父銆�
+     */
+    private List<HrmEmployeeDO> getActiveEmployeeList() {
+        List<HrmEmployeeDO> employees = employeeService.getEmployeeListByStatus(null);
+        if (CollUtil.isEmpty(employees)) {
+            return Collections.emptyList();
+        }
+        return employees.stream()
+                .filter(e -> !Objects.equals(e.getEmployeeStatus(), EMPLOYEE_STATUS_RESIGNED))
+                .toList();
     }
 
     /**
@@ -169,6 +202,10 @@
 
     @Override
     public PageResult<HrmAttendanceSummaryDO> getAttendanceSummaryPage(HrmAttendanceSummaryPageReqVO pageReqVO) {
+        // 浼犱簡寮�濮嬫棩鏈� 鈫� 鎸夋棩鍖洪棿瀹炴椂鑱氬悎鎵撳崱璁板綍锛堝鎴疯姹傘�屾寜鏃ョ瓫閫夛紝涓嶆槸鍥哄畾鏈湀銆嶏級
+        if (pageReqVO.getStartDate() != null) {
+            return getAttendanceSummaryByDateRange(pageReqVO);
+        }
         return attendanceSummaryMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<HrmAttendanceSummaryDO>()
                 .eqIfPresent(HrmAttendanceSummaryDO::getUserId, pageReqVO.getUserId())
                 .eqIfPresent(HrmAttendanceSummaryDO::getDeptId, pageReqVO.getDeptId())
@@ -177,6 +214,126 @@
                 .orderByAsc(HrmAttendanceSummaryDO::getDeptId));
     }
 
+    /**
+     * 鎸夋棩鍖洪棿瀹炴椂缁熻鑰冨嫟锛堜笉钀藉簱锛屼粎鐢ㄤ簬鍒楄〃鏌ヨ锛�
+     *
+     * <p>鍙e緞涓庢湀搴︽眹鎬讳繚鎸佷竴鑷达細搴斿嚭鍕ゅぉ鏁板彇鍖洪棿鍐呭伐浣滄棩鏁帮紝瀹炲嚭鍕ゆ寜鎵撳崱鏃ユ湡鍘婚噸锛�
+     * 杩熷埌/鏃╅��/缂哄崱鎸夋墦鍗¤褰曡鏁帮紝鍔犵彮鏃堕暱绱姞锛岃鍋囨椂闀挎寜姣忓ぉ 8 灏忔椂鎶樼畻銆�
+     */
+    private PageResult<HrmAttendanceSummaryDO> getAttendanceSummaryByDateRange(HrmAttendanceSummaryPageReqVO reqVO) {
+        LocalDate startDate = reqVO.getStartDate();
+        LocalDate endDate = reqVO.getEndDate() != null
+                ? reqVO.getEndDate() : startDate.with(TemporalAdjusters.lastDayOfMonth());
+        // 璧锋鏃ユ湡濉弽鏃惰嚜鍔ㄧ籂姝o紝閬垮厤瀹㈡埛璇搷浣滄煡涓嶅埌鏁版嵁
+        if (endDate.isBefore(startDate)) {
+            LocalDate tmp = startDate;
+            startDate = endDate;
+            endDate = tmp;
+        }
+
+        // 1. 纭畾鍛樺伐鑼冨洿锛堝湪鑱� + 璇曠敤锛夛紝鏀寔閮ㄩ棬 / 鍛樺伐绛涢��
+        List<HrmEmployeeDO> employees = getActiveEmployeeList();
+        if (reqVO.getDeptId() != null) {
+            employees = employees.stream()
+                    .filter(e -> reqVO.getDeptId().equals(e.getDeptId()))
+                    .toList();
+        }
+        if (reqVO.getUserId() != null) {
+            employees = employees.stream()
+                    .filter(e -> reqVO.getUserId().equals(e.getId()))
+                    .toList();
+        }
+        if (CollUtil.isEmpty(employees)) {
+            return PageResult.empty();
+        }
+
+        // 2. 搴斿嚭鍕ゅぉ鏁帮紙鍖洪棿鍐呭伐浣滄棩锛�
+        int workDays = calculateWorkDays(startDate, endDate);
+
+        // 3. 鎵归噺鏌ヨ鍖洪棿鍐呮墦鍗¤褰曪紝鎸夊憳宸ュ垎缁�
+        Set<Long> employeeIds = employees.stream().map(HrmEmployeeDO::getId).collect(Collectors.toSet());
+        List<HrmAttendanceRecordDO> records = attendanceRecordMapper
+                .selectListByUserIdsAndDateBetween(employeeIds, startDate, endDate);
+        Map<Long, List<HrmAttendanceRecordDO>> recordMap = records.stream()
+                .collect(Collectors.groupingBy(HrmAttendanceRecordDO::getUserId));
+
+        // 4. 鎵归噺鏌ヨ鍖洪棿鍐呭凡瀹℃壒閫氳繃鐨勮鍋囧崟锛堣鍋囧崟鐨� user_id 鏄� system_users.id锛�
+        Set<Long> systemUserIds = employees.stream().map(HrmEmployeeDO::getUserId)
+                .filter(Objects::nonNull).collect(Collectors.toSet());
+        List<HrmLeaveApplicationDO> leaves = leaveApplicationMapper.selectApprovedListByUserIdsAndTimeRange(
+                systemUserIds, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
+        Map<Long, BigDecimal> leaveDaysMap = new HashMap<>();
+        for (HrmLeaveApplicationDO leave : leaves) {
+            if (leave.getUserId() == null || leave.getDuration() == null) {
+                continue;
+            }
+            leaveDaysMap.merge(leave.getUserId(), leave.getDuration(), BigDecimal::add);
+        }
+
+        // 5. 閫愬憳宸ョ粍瑁呯粺璁$粨鏋�
+        List<HrmAttendanceSummaryDO> list = new ArrayList<>(employees.size());
+        for (HrmEmployeeDO employee : employees) {
+            List<HrmAttendanceRecordDO> empRecords = recordMap.getOrDefault(employee.getId(), Collections.emptyList());
+            int lateCount = 0;
+            int earlyCount = 0;
+            int absentCount = 0;
+            BigDecimal overtimeHours = BigDecimal.ZERO;
+            Set<LocalDate> actualDates = new LinkedHashSet<>();
+            for (HrmAttendanceRecordDO record : empRecords) {
+                if (HrmAttendanceClockTypeEnum.LATE.getType().equals(record.getClockInType())) {
+                    lateCount++;
+                }
+                if (HrmAttendanceClockTypeEnum.EARLY.getType().equals(record.getClockOutType())) {
+                    earlyCount++;
+                }
+                if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockInType())) {
+                    absentCount++;
+                }
+                if (HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockOutType())) {
+                    absentCount++;
+                }
+                if (record.getOvertimeHours() != null) {
+                    overtimeHours = overtimeHours.add(record.getOvertimeHours());
+                }
+                if (record.getClockInTime() != null || record.getClockOutTime() != null) {
+                    LocalDate attendDate = record.getDate();
+                    if (attendDate == null && record.getClockInTime() != null) {
+                        attendDate = record.getClockInTime().toLocalDate();
+                    }
+                    if (attendDate != null) {
+                        actualDates.add(attendDate);
+                    }
+                }
+            }
+            // 璇峰亣澶╂暟鎶樼畻涓哄皬鏃讹紙鎸夋瘡澶� 8 灏忔椂锛夛紝涓庢湀搴︽眹鎬荤殑 leave_hours 鍙e緞涓�鑷�
+            BigDecimal leaveDays = employee.getUserId() == null ? BigDecimal.ZERO
+                    : leaveDaysMap.getOrDefault(employee.getUserId(), BigDecimal.ZERO);
+            list.add(HrmAttendanceSummaryDO.builder()
+                    // 瀹炴椂缁熻娌℃湁钀藉簱涓婚敭锛岀敤鍛樺伐ID鍏呭綋鍒楄〃琛� key锛岄伩鍏嶅墠绔 key 鍐茬獊
+                    .id(employee.getId())
+                    .userId(employee.getId())
+                    .deptId(employee.getDeptId())
+                    // 鍖洪棿缁熻鏃讹紝骞存湀鍒楀睍绀虹粺璁″尯闂�
+                    .yearMonth(startDate + " ~ " + endDate)
+                    .workDays(workDays)
+                    .actualDays(Math.min(actualDates.size(), workDays))
+                    .lateCount(lateCount)
+                    .earlyCount(earlyCount)
+                    .absentCount(absentCount)
+                    .overtimeHours(overtimeHours)
+                    .leaveHours(leaveDays.multiply(BigDecimal.valueOf(8)))
+                    .build());
+        }
+
+        // 6. 鍐呭瓨鍒嗛〉锛堝尯闂寸粺璁′笉钀藉簱锛屾棤娉曡蛋鏁版嵁搴撳垎椤碉級
+        int total = list.size();
+        int pageNo = reqVO.getPageNo() == null || reqVO.getPageNo() < 1 ? 1 : reqVO.getPageNo();
+        int pageSize = reqVO.getPageSize() == null || reqVO.getPageSize() < 1 ? 10 : reqVO.getPageSize();
+        int fromIndex = Math.min((pageNo - 1) * pageSize, total);
+        int toIndex = Math.min(fromIndex + pageSize, total);
+        return new PageResult<>(list.subList(fromIndex, toIndex), (long) total);
+    }
+
     @Override
     public HrmAttendanceSummaryDO getMonthlySummary(Long userId, String yearMonth) {
         return attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth);

--
Gitblit v1.9.3