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