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/dal/mysql/attendance/HrmAttendanceRecordMapper.java | 18 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java | 61 ++++++++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java | 6 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java | 28 ++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java | 8 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java | 13 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java | 23 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceSummaryServiceImpl.java | 185 ++++++++++++++++++++++++++++--
8 files changed, 327 insertions(+), 15 deletions(-)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java
index 2218ef9..6dc8287 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceSummaryPageReqVO.java
@@ -5,6 +5,9 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟缁熻姹囨�诲垎椤� Request VO")
@Data
@@ -21,4 +24,12 @@
@Schema(description = "骞存湀锛屾牸寮忥細yyyy-MM", example = "2024-01")
private String yearMonth;
-}
\ No newline at end of file
+ @Schema(description = "缁熻寮�濮嬫棩鏈燂紙浼犱簡鎸夋棩鍖洪棿缁熻锛屽疄鏃惰仛鍚堟墦鍗¤褰曪紝涓嶈鏈堝害姹囨�昏〃锛�", example = "2026-10-01")
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
+ private LocalDate startDate;
+
+ @Schema(description = "缁熻缁撴潫鏃ユ湡锛堜笉浼犳椂榛樿鍙� startDate 鎵�鍦ㄦ湀鏈堟湯锛�", example = "2026-10-15")
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
+ private LocalDate endDate;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
index 6d98f1a..08c4a90 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmLeaveApplicationMapper.java
@@ -6,9 +6,12 @@
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.hutool.core.collection.CollUtil;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@@ -54,4 +57,24 @@
.le(HrmLeaveApplicationDO::getEndTime, endTime));
}
+ /**
+ * 鎵归噺鏌ヨ澶氫釜鍛樺伐鍦ㄦ寚瀹氭椂闂磋寖鍥村唴宸插鎵归�氳繃鐨勮鍋囪褰曪紙鐢ㄤ簬鎸夋棩鍖洪棿鐨勮�冨嫟缁熻锛�
+ *
+ * @param userIds 鍛樺伐ID闆嗗悎锛堢郴缁熺敤鎴稩D锛�
+ * @param startTime 寮�濮嬫椂闂�
+ * @param endTime 缁撴潫鏃堕棿
+ * @return 璇峰亣璁板綍鍒楄〃
+ */
+ default List<HrmLeaveApplicationDO> selectApprovedListByUserIdsAndTimeRange(Collection<Long> userIds,
+ LocalDateTime startTime, LocalDateTime endTime) {
+ if (CollUtil.isEmpty(userIds)) {
+ return new ArrayList<>();
+ }
+ return selectList(new LambdaQueryWrapperX<HrmLeaveApplicationDO>()
+ .in(HrmLeaveApplicationDO::getUserId, userIds)
+ .eq(HrmLeaveApplicationDO::getStatus, HrmAuditStatusEnum.APPROVE.getStatus())
+ .le(HrmLeaveApplicationDO::getStartTime, endTime)
+ .ge(HrmLeaveApplicationDO::getEndTime, startTime));
+ }
+
}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
index 1c0d329..9345162 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRecordMapper.java
@@ -5,9 +5,12 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
+import cn.hutool.core.collection.CollUtil;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@@ -43,4 +46,19 @@
.orderByAsc(HrmAttendanceRecordDO::getDate));
}
+ /**
+ * 鎸夊憳宸ラ泦鍚� + 鏃ユ湡鍖洪棿鎵归噺鏌ヨ鎵撳崱璁板綍锛堢敤浜庢寜鏃ュ尯闂寸殑瀹炴椂鑰冨嫟缁熻锛�
+ */
+ default List<HrmAttendanceRecordDO> selectListByUserIdsAndDateBetween(Collection<Long> userIds,
+ LocalDate startDate, LocalDate endDate) {
+ if (CollUtil.isEmpty(userIds)) {
+ return new ArrayList<>();
+ }
+ return selectList(new LambdaQueryWrapperX<HrmAttendanceRecordDO>()
+ .in(HrmAttendanceRecordDO::getUserId, userIds)
+ .ge(HrmAttendanceRecordDO::getDate, startDate)
+ .le(HrmAttendanceRecordDO::getDate, endDate)
+ .orderByAsc(HrmAttendanceRecordDO::getDate));
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
index 776d807..cf58a1b 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
@@ -19,6 +19,7 @@
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
+import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
import cn.iocoder.yudao.module.hrm.service.handover.HrmUserHandoverService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -79,6 +80,8 @@
private BpmProcessDefinitionService bpmProcessDefinitionService;
@Resource
private HrmUserHandoverService handoverService;
+ @Resource
+ private HrmEmployeeService employeeService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -346,6 +349,11 @@
.status(status)
.approveTime(java.time.LocalDateTime.now())
.build());
+ // 瀹℃壒閫氳繃锛氱珛鍗冲悓姝ュ埌浜哄憳淇℃伅锛堝鎴疯姹傘�屽鎵归�氳繃绔嬪嵆鐢熸晥銆嶏級
+ // 绂昏亴鐢宠鍗曠殑 userId 瀛樼殑鏄� system_users.id锛屼笉鏄� hrm_employee.id
+ if (HrmAuditStatusEnum.APPROVE.getStatus().equals(status)) {
+ employeeService.syncEmployeeResignation(application.getUserId(), application.getActualDate());
+ }
}
@Override
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
index b436b05..69391d3 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -390,6 +390,12 @@
.status(status)
.approveTime(java.time.LocalDateTime.now())
.build());
+ // 瀹℃壒閫氳繃锛氱珛鍗冲悓姝ュ埌浜哄憳淇℃伅锛堝鎴疯姹傘�屽鎵归�氳繃绔嬪嵆鐢熸晥銆嶏級
+ // 璋冨矖鐢宠鍗曠殑 userId 瀛樼殑鏄� hrm_employee.id
+ if (HrmAuditStatusEnum.APPROVE.getStatus().equals(status)) {
+ employeeService.syncEmployeeTransfer(application.getUserId(),
+ application.getTargetDeptId(), application.getTargetPostId());
+ }
}
@Override
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);
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
index 9fff42d..d01cc36 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
@@ -84,6 +84,34 @@
List<HrmEmployeeDO> getEmployeeList(Collection<Long> ids);
/**
+ * 鑾峰緱鎸囧畾鐘舵�佺殑鍛樺伐鍒楄〃锛堣繑鍥� DO锛�
+ *
+ * <p>娉ㄦ剰锛氫笉鑳芥妸 null 浼犵粰 {@link #getEmployeeList(Collection)}锛�
+ * MyBatis-Plus 鐨� selectByIds(null) 浼氭姏 "The expression 'coll' evaluated to a null value"銆�
+ *
+ * @param employeeStatus 鍛樺伐鐘舵�侊紝涓� null 鏃舵煡璇㈠叏閮�
+ * @return 鍛樺伐鍒楄〃
+ */
+ List<HrmEmployeeDO> getEmployeeListByStatus(Integer employeeStatus);
+
+ /**
+ * 璋冨矖鐢熸晥锛氭妸鍛樺伐妗f鐨勯儴闂�/宀椾綅鍚屾涓虹洰鏍囬儴闂�/鐩爣宀椾綅
+ *
+ * @param employeeId 鍛樺伐缂栧彿锛坔rm_employee.id锛�
+ * @param targetDeptId 鐩爣閮ㄩ棬缂栧彿锛屼负 null 鏃朵笉鏀�
+ * @param targetPostId 鐩爣宀椾綅缂栧彿锛屼负 null 鏃朵笉鏀�
+ */
+ void syncEmployeeTransfer(Long employeeId, Long targetDeptId, Long targetPostId);
+
+ /**
+ * 绂昏亴鐢熸晥锛氭妸鍛樺伐妗f鐘舵�佺疆涓恒�岀鑱屻��
+ *
+ * @param systemUserId 绯荤粺鐢ㄦ埛缂栧彿锛坔rm_employee.user_id锛屽嵆 system_users.id锛�
+ * @param leaveDate 绂昏亴鏃ユ湡锛屼负 null 鏃跺彇褰撳ぉ
+ */
+ void syncEmployeeResignation(Long systemUserId, java.time.LocalDate leaveDate);
+
+ /**
* 鑾峰緱鎸囧畾鐘舵�佺殑鍛樺伐绠�鍗曞垪琛�
*
* @param employeeStatus 鍛樺伐鐘舵�侊紝涓� null 鏃舵煡璇㈠叏閮�
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index f2a01a6..f32091d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -40,6 +40,7 @@
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -69,7 +70,11 @@
*/
@Service
@Validated
+@Slf4j
public class HrmEmployeeServiceImpl implements HrmEmployeeService {
+
+ /** 鍛樺伐鐘舵�侊細绂昏亴锛�1-鍦ㄨ亴锛�2-璇曠敤锛�3-绂昏亴锛� */
+ private static final Integer EMPLOYEE_STATUS_RESIGNED = 3;
@Resource
private HrmEmployeeMapper employeeMapper;
@@ -369,10 +374,66 @@
@Override
public List<HrmEmployeeDO> getEmployeeList(Collection<Long> ids) {
+ // 闃插尽锛歁yBatis-Plus 鐨� selectByIds(null) 浼氭姏
+ // BuilderException: The expression 'coll' evaluated to a null value
+ if (CollUtil.isEmpty(ids)) {
+ return new ArrayList<>();
+ }
return employeeMapper.selectByIds(ids);
}
@Override
+ public List<HrmEmployeeDO> getEmployeeListByStatus(Integer employeeStatus) {
+ return employeeMapper.selectListByStatus(employeeStatus);
+ }
+
+ @Override
+ public void syncEmployeeTransfer(Long employeeId, Long targetDeptId, Long targetPostId) {
+ if (employeeId == null) {
+ return;
+ }
+ HrmEmployeeDO employee = employeeMapper.selectById(employeeId);
+ if (employee == null) {
+ log.warn("[syncEmployeeTransfer][鍛樺伐({}) 涓嶅瓨鍦紝璺宠繃璋冨矖鍚屾]", employeeId);
+ return;
+ }
+ if (targetDeptId == null && targetPostId == null) {
+ return;
+ }
+ HrmEmployeeDO updateObj = new HrmEmployeeDO();
+ updateObj.setId(employeeId);
+ if (targetDeptId != null) {
+ updateObj.setDeptId(targetDeptId);
+ }
+ if (targetPostId != null) {
+ // hrm_employee.post_ids 瀛� JSON 鏁扮粍瀛楃涓诧紝濡� [38]
+ updateObj.setPostIds(JSONUtil.toJsonStr(java.util.List.of(targetPostId)));
+ }
+ employeeMapper.updateById(updateObj);
+ log.info("[syncEmployeeTransfer][鍛樺伐({}) 璋冨矖鐢熸晥锛氶儴闂� {} -> {}锛屽矖浣� -> {}]",
+ employeeId, employee.getDeptId(), targetDeptId, targetPostId);
+ }
+
+ @Override
+ public void syncEmployeeResignation(Long systemUserId, java.time.LocalDate leaveDate) {
+ if (systemUserId == null) {
+ return;
+ }
+ HrmEmployeeDO employee = employeeMapper.selectByUserId(systemUserId);
+ if (employee == null) {
+ log.warn("[syncEmployeeResignation][绯荤粺鐢ㄦ埛({}) 娌℃湁瀵瑰簲鍛樺伐妗f锛岃烦杩囩鑱屽悓姝", systemUserId);
+ return;
+ }
+ employeeMapper.updateById(HrmEmployeeDO.builder()
+ .id(employee.getId())
+ .employeeStatus(EMPLOYEE_STATUS_RESIGNED)
+ .leaveDate(leaveDate != null ? leaveDate : java.time.LocalDate.now())
+ .build());
+ log.info("[syncEmployeeResignation][鍛樺伐({}) 绂昏亴鐢熸晥锛岀姸鎬� {} -> {}]",
+ employee.getId(), employee.getEmployeeStatus(), EMPLOYEE_STATUS_RESIGNED);
+ }
+
+ @Override
public List<HrmEmployeeSimpleRespVO> getEmployeeSimpleList(Integer employeeStatus) {
List<HrmEmployeeDO> list = employeeMapper.selectListByStatus(employeeStatus);
// 濉厖閮ㄩ棬鍚嶇О
--
Gitblit v1.9.3