|
@@ -1,31 +1,43 @@
|
|
|
package cn.iocoder.yudao.module.attendance.service.info;
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
|
|
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
|
|
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
|
|
|
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
|
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
|
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
|
|
-import cn.iocoder.yudao.module.attendance.controller.admin.info.vo.AttendanceInfoImportExcelVO;
|
|
|
-import cn.iocoder.yudao.module.attendance.controller.admin.info.vo.AttendanceInfoImportRespVO;
|
|
|
-import cn.iocoder.yudao.module.attendance.controller.admin.info.vo.AttendanceInfoPageReqVO;
|
|
|
-import cn.iocoder.yudao.module.attendance.controller.admin.info.vo.AttendanceInfoSaveReqVO;
|
|
|
+import cn.iocoder.yudao.module.attendance.controller.admin.employeesetting.vo.AttendanceEmployeeSettingRespVO;
|
|
|
+import cn.iocoder.yudao.module.attendance.controller.admin.info.vo.*;
|
|
|
+import cn.iocoder.yudao.module.attendance.dal.dataobject.business.AttendanceBusinessDO;
|
|
|
import cn.iocoder.yudao.module.attendance.dal.dataobject.info.AttendanceInfoDO;
|
|
|
+import cn.iocoder.yudao.module.attendance.dal.dataobject.leave.AttendanceLeaveDO;
|
|
|
+import cn.iocoder.yudao.module.attendance.dal.dataobject.workdaysetting.AttendanceWorkdaySettingDO;
|
|
|
import cn.iocoder.yudao.module.attendance.dal.mysql.info.AttendanceInfoMapper;
|
|
|
+import cn.iocoder.yudao.module.attendance.service.business.AttendanceBusinessService;
|
|
|
+import cn.iocoder.yudao.module.attendance.service.employeesetting.AttendanceEmployeeSettingService;
|
|
|
+import cn.iocoder.yudao.module.attendance.service.leave.AttendanceLeaveService;
|
|
|
+import cn.iocoder.yudao.module.attendance.service.out.AttendanceOutService;
|
|
|
+import cn.iocoder.yudao.module.attendance.service.workdaysetting.AttendanceWorkdaySettingService;
|
|
|
import cn.iocoder.yudao.module.employee.controller.admin.info.vo.EmployeeInfoPageReqVO;
|
|
|
import cn.iocoder.yudao.module.employee.dal.dataobject.info.EmployeeInfoDO;
|
|
|
import cn.iocoder.yudao.module.employee.service.info.EmployeeInfoService;
|
|
|
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.text.ParseException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalTime;
|
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
|
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
|
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
@@ -33,7 +45,6 @@ import javax.annotation.Resource;
|
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
import static cn.iocoder.yudao.module.attendance.enums.ErrorCodeConstants.*;
|
|
|
import static cn.iocoder.yudao.module.employee.enums.ErrorCodeConstants.EMPLOYEE_INFO_NOT_EXISTS;
|
|
|
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|
|
|
|
|
/**
|
|
|
* 考勤信息 Service 实现类
|
|
@@ -44,10 +55,21 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|
|
@Validated
|
|
|
public class AttendanceInfoServiceImpl implements AttendanceInfoService {
|
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(AttendanceInfoServiceImpl.class);
|
|
|
@Resource
|
|
|
private AttendanceInfoMapper infoMapper;
|
|
|
@Resource
|
|
|
private EmployeeInfoService employeeInfoService;
|
|
|
+ @Resource
|
|
|
+ private AttendanceEmployeeSettingService attendanceEmployeeSettingService;
|
|
|
+ @Resource
|
|
|
+ private AttendanceLeaveService attendanceLeaveService;
|
|
|
+ @Resource
|
|
|
+ private AttendanceBusinessService attendanceBusinessService;
|
|
|
+ @Resource
|
|
|
+ private AttendanceOutService attendanceOutService;
|
|
|
+ @Resource
|
|
|
+ private AttendanceWorkdaySettingService workdaySettingService;
|
|
|
|
|
|
@Override
|
|
|
@TenantIgnore
|
|
@@ -156,6 +178,95 @@ public class AttendanceInfoServiceImpl implements AttendanceInfoService {
|
|
|
return respVO;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ @TenantIgnore
|
|
|
+ public List<AttendanceDailyInfoRespVO> getDailyInfoPage(AttendanceDailyInfoPageReqVO pageReqVO) {
|
|
|
+ List<AttendanceDailyInfoRespVO> list = infoMapper.getDailyInfoPage(pageReqVO);
|
|
|
+ if (list != null && list.size() > 0) {
|
|
|
+ for (AttendanceDailyInfoRespVO info : list) {
|
|
|
+ // 查询该员工考勤规则
|
|
|
+ AttendanceEmployeeSettingRespVO respVO = attendanceEmployeeSettingService.getByEmployeeId(info.getEmployeeId());
|
|
|
+ if (respVO != null && respVO.getStartTime() != null && respVO.getEndTime() != null
|
|
|
+ && info.getWorkStartTime() != null && info.getWorkEndTime() != null) {
|
|
|
+
|
|
|
+ LocalTime startTime = respVO.getStartTime();
|
|
|
+ LocalTime endTime = respVO.getEndTime();
|
|
|
+ LocalTime workStartTime = info.getWorkStartTime();
|
|
|
+ LocalTime workEndTime = info.getWorkEndTime();
|
|
|
+
|
|
|
+ // 计算迟到分钟数
|
|
|
+ long lateMinute = Duration.between(startTime, workStartTime).toMinutes();
|
|
|
+ if (lateMinute < 0) { // 如果上班时间早于开始时间,则视为不迟到
|
|
|
+ lateMinute = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算早退分钟数
|
|
|
+ long leaveEarlyMinute = Duration.between(workEndTime, endTime).toMinutes();
|
|
|
+ if (leaveEarlyMinute < 0) { // 如果下班时间晚于结束时间,则视为不早退
|
|
|
+ leaveEarlyMinute = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算总工作分钟数
|
|
|
+ long totalWorkingMinute = Duration.between(workStartTime, workEndTime).toMinutes();
|
|
|
+
|
|
|
+ // 设置结果
|
|
|
+ info.setLateMinute(String.format("%d", lateMinute));
|
|
|
+ info.setEarlyMinute(String.format("%d", leaveEarlyMinute));
|
|
|
+ info.setTotalWorkingMinute(String.format("%d", totalWorkingMinute));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @TenantIgnore
|
|
|
+ public List<AttendanceMonthlyInfoRespVO> getMonthlyInfoPage(AttendanceMonthlyInfoPageReqVO pageReqVO) {
|
|
|
+ // 获取每日考勤信息
|
|
|
+ List<AttendanceInfoDO> dos = infoMapper.selectPage(pageReqVO).getList();
|
|
|
+ List<AttendanceDailyInfoRespVO> dailyInfoList = BeanUtils.toBean(dos, AttendanceDailyInfoRespVO.class);
|
|
|
+ // 创建一个空的月度考勤信息列表
|
|
|
+ List<AttendanceMonthlyInfoRespVO> monthlyInfoList = new ArrayList<>();
|
|
|
+
|
|
|
+ // 查询指定月份的工作日
|
|
|
+ List<AttendanceWorkdaySettingDO> workDays = workdaySettingService.getWorkdays(pageReqVO.getAttendanceMonth());
|
|
|
+
|
|
|
+ // 按员工ID和月份分组每日考勤信息
|
|
|
+ Map<String, List<AttendanceDailyInfoRespVO>> groupedDailyInfo = new HashMap<>();
|
|
|
+ for (AttendanceDailyInfoRespVO dailyInfo : dailyInfoList) {
|
|
|
+ String key = dailyInfo.getEmployeeId() + "-" + pageReqVO.getAttendanceMonth();
|
|
|
+ groupedDailyInfo.computeIfAbsent(key, k -> new ArrayList<>()).add(dailyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历每个员工的每日考勤信息
|
|
|
+ for (Map.Entry<String, List<AttendanceDailyInfoRespVO>> entry : groupedDailyInfo.entrySet()) {
|
|
|
+ List<AttendanceDailyInfoRespVO> employeeDailyInfoList = entry.getValue();
|
|
|
+ if (employeeDailyInfoList.isEmpty()) continue;
|
|
|
+
|
|
|
+ // 创建一个新的月度考勤信息对象
|
|
|
+ AttendanceMonthlyInfoRespVO monthlyInfo = new AttendanceMonthlyInfoRespVO();
|
|
|
+ monthlyInfo.setEmployeeId(employeeDailyInfoList.get(0).getEmployeeId());
|
|
|
+ monthlyInfo.setAttendanceMonth(pageReqVO.getAttendanceMonth());
|
|
|
+
|
|
|
+ // 计算月度数据
|
|
|
+ Map<String, Object> monthlyDataMap = getMonthlyData(employeeDailyInfoList, workDays);
|
|
|
+
|
|
|
+ // 设置月度考勤统计数据
|
|
|
+ monthlyInfo.setAttendanceDays(((BigDecimal) monthlyDataMap.get("cq")).intValue());
|
|
|
+ monthlyInfo.setNormalClockCount(((BigDecimal) monthlyDataMap.get("zc")).intValue());
|
|
|
+ monthlyInfo.setLateCount(((BigDecimal) monthlyDataMap.get("cd")).intValue());
|
|
|
+ monthlyInfo.setAbsenteeismCount(((BigDecimal) monthlyDataMap.get("kg")).intValue());
|
|
|
+ monthlyInfo.setEarlyCount(((BigDecimal) monthlyDataMap.get("zt")).intValue());
|
|
|
+ monthlyInfo.setMissingClockCount(((BigDecimal) monthlyDataMap.get("qk")).intValue());
|
|
|
+ monthlyInfo.setLeaveCount(((BigDecimal) monthlyDataMap.get("qj")).intValue());
|
|
|
+
|
|
|
+ // 添加到月度考勤信息列表
|
|
|
+ monthlyInfoList.add(monthlyInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return monthlyInfoList;
|
|
|
+ }
|
|
|
+
|
|
|
private boolean validateEmployeeExists(String employeeName, String employeeMobile, Long tenantId) {
|
|
|
// 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
|
|
|
return DataPermissionUtils.executeIgnore(() -> {
|
|
@@ -168,4 +279,181 @@ public class AttendanceInfoServiceImpl implements AttendanceInfoService {
|
|
|
}
|
|
|
|
|
|
|
|
|
+ // 计算月度数据的方法
|
|
|
+ private Map<String, Object> getMonthlyData(List<AttendanceDailyInfoRespVO> dailyInfoList,
|
|
|
+ List<AttendanceWorkdaySettingDO> workDays) {
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ map.put("cq", BigDecimal.ZERO); // 出勤天数
|
|
|
+ map.put("zc", BigDecimal.ZERO); // 正常打卡
|
|
|
+ map.put("cd", BigDecimal.ZERO); // 迟到次数
|
|
|
+ map.put("kg", BigDecimal.ZERO); // 矿工次数
|
|
|
+ map.put("zt", BigDecimal.ZERO); // 早退次数
|
|
|
+ map.put("qk", BigDecimal.ZERO); // 缺卡次数
|
|
|
+ map.put("qj", BigDecimal.ZERO); // 请假次数
|
|
|
+
|
|
|
+ // 查询该员工考勤规则
|
|
|
+ AttendanceEmployeeSettingRespVO respVO = attendanceEmployeeSettingService.getByEmployeeId(dailyInfoList.get(0).getEmployeeId());
|
|
|
+ if (respVO == null) {
|
|
|
+ throw exception(ATTENDANCE_EMPLOYEE_SETTING_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ LocalTime ruleStartTime = respVO.getStartTime();
|
|
|
+ LocalTime ruleEndTime = respVO.getEndTime();
|
|
|
+ if (ruleStartTime == null || ruleEndTime == null) {
|
|
|
+ throw exception(ATTENDANCE_EMPLOYEE_SETTING_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ for (AttendanceDailyInfoRespVO dailyInfo : dailyInfoList) {
|
|
|
+ LocalDate date = dailyInfo.getAttendanceDate();
|
|
|
+ LocalTime start = dailyInfo.getWorkStartTime();
|
|
|
+ LocalTime end = dailyInfo.getWorkEndTime();
|
|
|
+
|
|
|
+ boolean isWorkDay = workDays.stream().anyMatch(wd -> wd.getWorkDate().equals(date));
|
|
|
+
|
|
|
+ if (isWorkDay) {
|
|
|
+ AttendanceBusinessDO businessDO = attendanceBusinessService
|
|
|
+ .selectByDateAndEmployeeId(date, dailyInfo.getEmployeeId());
|
|
|
+ List<AttendanceLeaveDO> leaveDOs = attendanceLeaveService
|
|
|
+ .selectByDateAndEmployeeId(date, dailyInfo.getEmployeeId());
|
|
|
+
|
|
|
+ if (start == null && end == null) {
|
|
|
+ handleNoClock(map, businessDO, leaveDOs);
|
|
|
+ } else if (start == null || end == null) {
|
|
|
+ handleSingleClock(map, start, end, ruleStartTime, ruleEndTime, businessDO, leaveDOs, date);
|
|
|
+ } else {
|
|
|
+ handleBothClocks(map, start, end, ruleStartTime, ruleEndTime, businessDO, leaveDOs, date);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理未打卡情况的方法 工作日
|
|
|
+ private void handleNoClock(Map<String, Object> map, AttendanceBusinessDO businessDO, List<AttendanceLeaveDO> leaveDOs) {
|
|
|
+ if (businessDO != null) {
|
|
|
+ map.put("qj", ((BigDecimal) map.get("qj")).add(BigDecimal.ONE));
|
|
|
+ } else if (!leaveDOs.isEmpty()) {
|
|
|
+ double day = calculateLeaveDays(leaveDOs);
|
|
|
+ map.put("qj", ((BigDecimal) map.get("qj")).add(BigDecimal.valueOf(day)));
|
|
|
+ if (day == 0.5) {
|
|
|
+ map.put("qk", ((BigDecimal) map.get("qk")).add(BigDecimal.ONE));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ map.put("qk", ((BigDecimal) map.get("qk")).add(new BigDecimal(2)));
|
|
|
+ map.put("kg", ((BigDecimal) map.get("kg")).add(BigDecimal.ONE));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理一次打卡情况的方法
|
|
|
+ private void handleSingleClock(Map<String, Object> map, LocalTime start, LocalTime end,
|
|
|
+ LocalTime ruleStartTime, LocalTime ruleEndTime,
|
|
|
+ AttendanceBusinessDO businessDO, List<AttendanceLeaveDO> leaveDOs, LocalDate date) {
|
|
|
+ double day = 0.0;
|
|
|
+ if (businessDO != null) {
|
|
|
+ map.put("qj", ((BigDecimal) map.get("qj")).add(new BigDecimal(0.5)));
|
|
|
+ } else if (!leaveDOs.isEmpty()) {
|
|
|
+ day = calculateLeaveDays(leaveDOs);
|
|
|
+ map.put("qj", ((BigDecimal) map.get("qj")).add(BigDecimal.valueOf(day)));
|
|
|
+ } else {
|
|
|
+ map.put("qk", ((BigDecimal) map.get("qk")).add(BigDecimal.ONE));
|
|
|
+ }
|
|
|
+ if (day != 1.0) {
|
|
|
+ if (start != null) {
|
|
|
+ boolean isLate = start.isAfter(ruleStartTime);
|
|
|
+ updateAttendanceCounts(map, isLate, true);
|
|
|
+ } else if (end != null) {
|
|
|
+ boolean isEarly = end.isBefore(ruleEndTime);
|
|
|
+ updateAttendanceCounts(map, isEarly, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理两次打卡情况的方法
|
|
|
+ private void handleBothClocks(Map<String, Object> map, LocalTime start, LocalTime end,
|
|
|
+ LocalTime ruleStartTime, LocalTime ruleEndTime,
|
|
|
+ AttendanceBusinessDO businessDO, List<AttendanceLeaveDO> leaveDOs, LocalDate date) {
|
|
|
+ boolean isLate = start.isAfter(ruleStartTime);
|
|
|
+ boolean isEarly = end.isBefore(ruleEndTime);
|
|
|
+ updateAttendanceCounts(map, isLate, isEarly);
|
|
|
+ if (businessDO != null) {
|
|
|
+ map.put("qj", ((BigDecimal) map.get("qj")).add(BigDecimal.ONE));
|
|
|
+ } else if (!leaveDOs.isEmpty()) {
|
|
|
+ double day = calculateLeaveDays(leaveDOs);
|
|
|
+ map.put("qj", ((BigDecimal) map.get("qj")).add(BigDecimal.valueOf(day)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算请假天数的方法
|
|
|
+ private double calculateLeaveDays(List<AttendanceLeaveDO> leaveDOs) {
|
|
|
+ double totalDays = 0.0;
|
|
|
+ for (AttendanceLeaveDO leaveDO : leaveDOs) {
|
|
|
+ totalDays += getLeaveDays(leaveDO.getStartDate(), leaveDO.getEndDate(), leaveDO.getStartTime(), leaveDO.getEndTime());
|
|
|
+ }
|
|
|
+ return totalDays;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新考勤计数的方法
|
|
|
+ private void updateAttendanceCounts(Map<String, Object> map, boolean isLate, boolean isEarly) {
|
|
|
+ if (isLate) {
|
|
|
+ map.put("cd", ((BigDecimal) map.get("cd")).add(BigDecimal.ONE));
|
|
|
+ map.put("cq", ((BigDecimal) map.get("cq")).add(BigDecimal.valueOf(0.5)));
|
|
|
+ } else {
|
|
|
+ map.put("zc", ((BigDecimal) map.get("zc")).add(BigDecimal.ONE));
|
|
|
+ map.put("cq", ((BigDecimal) map.get("cq")).add(BigDecimal.valueOf(0.5)));
|
|
|
+ }
|
|
|
+ if (isEarly) {
|
|
|
+ map.put("zt", ((BigDecimal) map.get("zt")).add(BigDecimal.ONE));
|
|
|
+ map.put("cq", ((BigDecimal) map.get("cq")).add(BigDecimal.valueOf(0.5)));
|
|
|
+ } else {
|
|
|
+ map.put("zc", ((BigDecimal) map.get("zc")).add(BigDecimal.ONE));
|
|
|
+ map.put("cq", ((BigDecimal) map.get("cq")).add(BigDecimal.valueOf(0.5)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取请假天数的方法
|
|
|
+ private Double getLeaveDays(String startDateStr, String endDateStr, String startTime, String endTime) {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ Date startDate = null;
|
|
|
+ Date endDate = null;
|
|
|
+ try {
|
|
|
+ startDate = sdf.parse(startDateStr);
|
|
|
+ endDate = sdf.parse(endDateStr);
|
|
|
+ } catch (ParseException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.setTime(startDate);
|
|
|
+ Double day = 0.0;
|
|
|
+// String[] remarks = remark.split(":");
|
|
|
+ Boolean isSameDay = DateUtil.isSameDay(endDate, startDate);
|
|
|
+ long size = ((endDate.getTime() - startDate.getTime()) / 1000 / 24 / 60 / 60) + 1;
|
|
|
+ for (int j = 0; j < size; j++) {
|
|
|
+// String time =sdf.format(cal.getTime());
|
|
|
+ if (isSameDay) {//请假开始时间和结束时间是同一天
|
|
|
+ if (startTime.equals(endTime)) {
|
|
|
+ day += 0.5;
|
|
|
+ } else {
|
|
|
+ day += 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (j == 0) {
|
|
|
+ if (startTime.equals(endTime)) {
|
|
|
+ day += 1;
|
|
|
+ } else {
|
|
|
+ day += 0.5;
|
|
|
+ }
|
|
|
+ } else if (j == size - 1) {
|
|
|
+ if (startTime.equals(endTime)) {
|
|
|
+ day += 0.5;
|
|
|
+ } else {
|
|
|
+ day += 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ day += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cal.add(Calendar.DATE, 1);
|
|
|
+ }
|
|
|
+ return day;
|
|
|
+ }
|
|
|
+
|
|
|
}
|