package cn.iocoder.yudao.module.hrm.service.attendance; import cn.hutool.core.collection.CollUtil; 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.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.attendance.HrmAttendanceRecordMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper; import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum; import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.time.LocalDate; import java.time.YearMonth; import java.util.List; /** * 考勤统计汇总 Service 实现类 * * @author 芋道源码 */ @Service @Validated @Slf4j public class HrmAttendanceSummaryServiceImpl implements HrmAttendanceSummaryService { @Resource private HrmAttendanceSummaryMapper attendanceSummaryMapper; @Resource private HrmAttendanceRecordMapper attendanceRecordMapper; @Resource private HrmEmployeeService employeeService; @Resource private HrmAttendanceHolidayService attendanceHolidayService; @Override @Transactional(rollbackFor = Exception.class) public void generateMonthlySummary(Long userId, String yearMonth) { // 1. 解析年月,获取该月起始日期 YearMonth ym = YearMonth.parse(yearMonth); LocalDate startDate = ym.atDay(1); LocalDate endDate = ym.atEndOfMonth(); // 2. 获取员工信息 HrmEmployeeDO employee = employeeService.getEmployee(userId); if (employee == null) { log.warn("[generateMonthlySummary] 员工不存在: {}", userId); return; } // 3. 查询该月所有考勤记录 List records = attendanceRecordMapper.selectListByUserIdAndDateBetween(userId, startDate, endDate); // 4. 计算应出勤天数(工作日数量) int workDays = calculateWorkDays(startDate, endDate); // 5. 统计各项数据 int lateCount = 0; int earlyCount = 0; int absentCount = 0; BigDecimal overtimeHours = BigDecimal.ZERO; int actualDays = 0; for (HrmAttendanceRecordDO record : records) { // 统计迟到 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) { actualDays++; } } // 6. 保存或更新汇总记录 HrmAttendanceSummaryDO summary = attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth); if (summary == null) { summary = HrmAttendanceSummaryDO.builder() .userId(userId) .deptId(employee.getDeptId()) .yearMonth(yearMonth) .workDays(workDays) .actualDays(actualDays) .lateCount(lateCount) .earlyCount(earlyCount) .absentCount(absentCount) .overtimeHours(overtimeHours) .leaveHours(BigDecimal.ZERO) .build(); attendanceSummaryMapper.insert(summary); } else { summary.setWorkDays(workDays); summary.setActualDays(actualDays); summary.setLateCount(lateCount); summary.setEarlyCount(earlyCount); summary.setAbsentCount(absentCount); summary.setOvertimeHours(overtimeHours); attendanceSummaryMapper.updateById(summary); } } @Override @Transactional(rollbackFor = Exception.class) public void generateDeptMonthlySummary(Long deptId, String yearMonth) { // 获取部门所有在职员工 List employees = employeeService.getEmployeeList(null); if (CollUtil.isEmpty(employees)) { return; } // 筛选部门员工 List deptEmployees = employees.stream() .filter(e -> deptId.equals(e.getDeptId())) .toList(); for (HrmEmployeeDO employee : deptEmployees) { generateMonthlySummary(employee.getId(), yearMonth); } } @Override @Transactional(rollbackFor = Exception.class) public void generateAllMonthlySummary(String yearMonth) { // 获取所有在职员工 List employees = employeeService.getEmployeeList(null); if (CollUtil.isEmpty(employees)) { return; } for (HrmEmployeeDO employee : employees) { generateMonthlySummary(employee.getId(), yearMonth); } } /** * 计算应出勤天数(工作日数量) */ private int calculateWorkDays(LocalDate startDate, LocalDate endDate) { int workDays = 0; LocalDate current = startDate; while (!current.isAfter(endDate)) { if (attendanceHolidayService.isWorkDay(current)) { workDays++; } current = current.plusDays(1); } return workDays; } @Override public PageResult getAttendanceSummaryPage(HrmAttendanceSummaryPageReqVO pageReqVO) { return attendanceSummaryMapper.selectPage(pageReqVO, new LambdaQueryWrapperX() .eqIfPresent(HrmAttendanceSummaryDO::getUserId, pageReqVO.getUserId()) .eqIfPresent(HrmAttendanceSummaryDO::getDeptId, pageReqVO.getDeptId()) .eqIfPresent(HrmAttendanceSummaryDO::getYearMonth, pageReqVO.getYearMonth()) .orderByDesc(HrmAttendanceSummaryDO::getYearMonth) .orderByAsc(HrmAttendanceSummaryDO::getDeptId)); } @Override public HrmAttendanceSummaryDO getMonthlySummary(Long userId, String yearMonth) { return attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth); } @Override public List getDeptMonthlySummaryList(Long deptId, String yearMonth) { return attendanceSummaryMapper.selectListByDeptIdAndYearMonth(deptId, yearMonth); } }