From c0332b2548d405ffb115a77ffbabc9adbdf02e60 Mon Sep 17 00:00:00 2001 From: fl <3098731433@qq.com> Date: Mon, 17 Feb 2025 18:21:08 +0800 Subject: [PATCH] =?UTF-8?q?=E8=80=83=E5=8B=A4=E9=80=BB=E8=BE=91=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/att/service/AttCalService.java | 50 ++ .../system/att/service/AttCalServiceImpl.java | 798 ++++++++++++++++++ .../bonus/system/att/tasks/NewAttTask.java | 125 +++ 3 files changed, 973 insertions(+) create mode 100644 bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalService.java create mode 100644 bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalServiceImpl.java create mode 100644 bonus-modules/bonus-system/src/main/java/com/bonus/system/att/tasks/NewAttTask.java diff --git a/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalService.java b/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalService.java new file mode 100644 index 0000000..3b21437 --- /dev/null +++ b/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalService.java @@ -0,0 +1,50 @@ +package com.bonus.system.att.service; + +import com.bonus.system.api.domain.MapVo; +import com.bonus.system.att.entity.OrgChangeBean; + +import java.util.List; + +/** + * 考勤计算 + * @author fly + */ +public interface AttCalService { + void getAttendanceData(String pushDate, int pushType); + + /** + * 插入模板数据 + * @param pushDate + * @param pushType + */ + void insertAttTempData(String pushDate, int pushType); + + /** + * 待考勤人员列表 + * @param pushDate + */ + void insertAttDateHistory(String pushDate); + + /** + * 考勤数据应用(步骤一:考勤数据更新) + * 建议一小时一次 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + void updateAttData(String pushDate, int pushType); + + /** + * 考勤数据应用(步骤二:旷工状态更新) + * 每天12点将当天未打卡考勤人员上班置为旷工 + * 每天晚上22点,将下班未打卡置为旷工 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + void updateAbsenteeismData(String pushDate, int pushType); + /** + * 请假数据应用(步骤一:法假数据更新) + * 法定节假日不会随时间变化,一天执行一次就可以了 + * @param pushDate 时间 + */ + void updateLegalHolidayData(String pushDate); +} diff --git a/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalServiceImpl.java b/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalServiceImpl.java new file mode 100644 index 0000000..92901c8 --- /dev/null +++ b/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/service/AttCalServiceImpl.java @@ -0,0 +1,798 @@ +package com.bonus.system.att.service; + +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson2.JSONObject; +import com.bonus.common.core.utils.DateTimeHelper; +import com.bonus.common.core.utils.DateUtils; +import com.bonus.common.core.utils.StringUtils; +import com.bonus.common.security.utils.SecurityUtils; +import com.bonus.system.api.domain.MapVo; +import com.bonus.system.api.domain.SysUser; +import com.bonus.system.att.dao.AttGroupDao; +import com.bonus.system.att.dao.AttSourceDataDao; +import com.bonus.system.att.dao.OrgChangeDao; +import com.bonus.system.att.entity.*; +import com.bonus.system.att.utils.AddressCoordinateFormatUtil; +import com.bonus.system.att.utils.AttTimeUtil; +import com.bonus.system.att.utils.WorkdayCalculator; +import com.bonus.system.dept.dao.ProDeptRoleDao; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.BeanUtils; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * 组织架构-业务层 + * + * @author zys + */ +@Slf4j +@Service("AttCalService") +public class AttCalServiceImpl implements AttCalService { + + @Resource(name = "attSourceDataDao") + private AttSourceDataDao attSourceDataDao; + + @Resource(name = "attGroupDao") + private AttGroupDao attGroupDao; + + @Resource(name = "sqlSessionTemplate") + private SqlSessionTemplate sqlSessionTemplate; + + /** + * 待考勤人员列表 + * 在模版数据之前生成 + * + * @param pushDate 时间(可为空,为空则推历史60天内没生成的数据,不为空则推指定日期) + */ + @Override + public void insertAttDateHistory(String pushDate) { + List dateList = new ArrayList<>(); + if (StringUtils.isEmpty(pushDate)) { + String startDate = DateUtil.today(); + // 解析 startDate 字符串到 LocalDate 对象 + LocalDate date = LocalDate.parse(startDate, DateTimeFormatter.ISO_LOCAL_DATE); + // 向前推n天 + int n = 60; + LocalDate newDate = date.minusDays(n); + // 如果需要将结果格式化为特定格式的字符串,可以使用以下代码 + String formattedNewDate = newDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + //向前推1个月的时间检索,缺少哪一天推送哪一天 + List dateListOne = attSourceDataDao.getAttSettingDate(startDate, n); + dateList = AttTimeUtil.getStrDateListBetween(formattedNewDate, startDate); + // 从 dateList 中移除所有在 dateListOne 中存在的元素 + dateListOne.removeIf(startDate::contains); + dateList.removeIf(dateListOne::contains); + } else { + dateList.add(pushDate); + } + for (String currentDay : dateList) { + List attList = attSourceDataDao.getAttSettingHistoryDate(currentDay); + if (!attList.isEmpty()) { + attSourceDataDao.insertAttSettingHistoryData(attList); + } + } + } + + /** + * 考勤数据应用(步骤一:考勤数据更新) + * 建议一小时一次 + * + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + @Override + public void updateAttData(String pushDate, int pushType) { + //查出人员考勤记录 + AtomicReference> sourceDataList = new AtomicReference<>(attSourceDataDao.getSourceAttData(pushDate, pushType)); + //查出考勤组 + List groupList = getGroupData(pushDate); + // + groupList.forEach(c -> { + List list = Collections.emptyList(); + //考勤规则里面的attType 1 固定打卡 2 自由打卡 + if ("1".equals(c.getAttType())) { + list = groupFixedData(sourceDataList.get().stream() + .filter(a -> "1".equals(a.getGroupType()) && + a.getGroupId() == c.getGroupId()) + .collect(Collectors.toList()), c); + } else if ("2".equals(c.getAttType())) { + list = groupFreeData(sourceDataList.get().stream() + .filter(a -> "2".equals(a.getGroupType()) && + a.getGroupId() == c.getGroupId()) + .collect(Collectors.toList()), c); + } + //修改考勤记录 att_data + if (!list.isEmpty()) { + if ("1".equals(c.getAttType())) { + //固定打卡 + // 定义时间格式 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + // 移除移除 attType 为 2 且 attCurrentTime 在上午 5 点到 12 点之间的元素 + list.removeIf(record -> { + String attType = record.getAttType(); + String attCurrentTime = record.getAttCurrentTime(); + if (!"".equals(attCurrentTime)) { + if (attCurrentTime.contains(" ")) { + attCurrentTime = attCurrentTime.split(" ")[0]; + } + LocalDateTime time = LocalDateTime.parse(attCurrentTime, formatter); + // 提取时间部分 + LocalTime localTime = time.toLocalTime(); + boolean condition1 = "2".equals(attType) && localTime.isAfter(LocalTime.of(5, 0)) && localTime.isBefore(LocalTime.of(12, 0)); + boolean condition2 = "1".equals(attType) && localTime.isAfter(LocalTime.of(12, 0)) && localTime.isBefore(LocalTime.of(23, 59)); + return condition1 || condition2; + } else { + return false; + } + }); + // 插入剩余的元素 + attSourceDataDao.updateAttStatusData(list); + } else if ("2".equals(c.getAttType())) { + //自由打卡 + attSourceDataDao.updateAttStatusData(list); + } + } + }); + + } + + /** + * 考勤数据应用(步骤二:旷工状态更新) + * 每天12点将当天未打卡考勤人员上班置为旷工 + * 每天晚上22点,将下班未打卡置为旷工 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + @Override + public void updateAbsenteeismData(String pushDate, int pushType) { + List list = new ArrayList<>(); + List listUpdate = new ArrayList<>(); + if(pushType == 1){ + //判断是否大于12点大于则更新所有attType = 1的未打卡状态为旷工 + //判断是否大于22点大于则更新所有未打卡状态为旷工 + boolean pm = AttTimeUtil.timeCheck(21, 59); + if(pm){ + list = attSourceDataDao.getAttDataByStatus(pushDate,""); + listUpdate = attSourceDataDao.getAttDataUpdateByStatus(pushDate,""); + }else{ + boolean am = AttTimeUtil.timeCheck(11, 59); + if(am){ + list = attSourceDataDao.getAttDataByStatus(pushDate,"1"); + listUpdate = attSourceDataDao.getAttDataUpdateByStatus(pushDate,"1"); + } + } + + }else if(pushType == 2){ + list = attSourceDataDao.getAttDataByStatus(pushDate,""); + listUpdate = attSourceDataDao.getAttDataUpdateByStatus(pushDate,""); + } + if(list != null && !list.isEmpty()){ + list.forEach(c -> c.setAttStatus("3")); + attSourceDataDao.updateAttDataList(list); + } + if(listUpdate != null && !listUpdate.isEmpty()){ + listUpdate.forEach(c -> c.setAttStatus("3")); + attSourceDataDao.updateAttDataUpdateList(listUpdate); + } + } + + @Override + public void updateLegalHolidayData(String pushDate) { + //查询当日是否是法假 + List holidays = attSourceDataDao.selectHolidayByDay(pushDate); + if(!holidays.isEmpty()){ + //当日为节假日 + List groupList = Optional.ofNullable(attGroupDao.selectAttGroupList(new AttGroupBean())). + orElseGet(ArrayList::new); + List attList = attSourceDataDao.getAttSettingHistoryDate(pushDate); + groupList.forEach(c -> { + if(c.getIsHaveHoliday() == 1){ + List collect = attList.stream().filter(a -> Objects.equals(a.getGroupId(), c.getGroupId())).collect(Collectors.toList()); + if(!collect.isEmpty()){ + // 创建一个新的 List + List listUpdate = new ArrayList<>(); + collect.forEach(v -> { + AttDataBean dataBean = new AttDataBean(); + dataBean.setUserId(v.getUserId()); + dataBean.setAttCurrentDay(v.getCurrentDay()); + dataBean.setAttStatus("11"); + listUpdate.add(dataBean); + }); + attSourceDataDao.updateAttDataList(listUpdate); + attSourceDataDao.updateAttDataUpdateList(listUpdate); + } + } + }); + + } + + } + + /** + * 插入模板数据 + * + * @param pushDate + * @param pushType 1定时 2手动 + */ + @Override + public void insertAttTempData(String pushDate, int pushType) { + //查询当天应考勤人员列表 + List listPerson = attSourceDataDao.getAttPerson(pushDate); + if (pushType == 1) { + //只会在凌晨执行一次 + attSourceDataDao.insertAttDataList(listPerson); + } else if (pushType == 2) { + //1.查询出历史当天已插入模版 + //2.查询出历史当天考勤列表 + //3.对比增量更新 + List personId = attSourceDataDao.getAttDataPerson(pushDate); + listPerson.removeIf(c -> personId.contains(c.getUserId())); + attSourceDataDao.insertAttDataList(listPerson); + } + } + + /** + * 获取考勤数据(数据拉取) + * + * @param pushDate 日期 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + @Override + public void getAttendanceData(String pushDate, int pushType) { + List qsyAttData = getQsyAttendanceData(pushDate, pushType); + List machineAttSourceList = getMachineAttendanceData(pushDate, pushType); + //新增考勤到考勤来源表(数据过多时分批次插入) + machineAttSourceList.addAll(qsyAttData); + insertAttSourceData(machineAttSourceList); + } + + + /** + * 获取黔送云的考勤数据 + */ + private List getQsyAttendanceData(String pushDate, int pushType) { + List attSourceList = Optional.ofNullable(attSourceDataDao.getQsyAttendances(pushDate, pushType)). + orElseGet(ArrayList::new); + if (!attSourceList.isEmpty()) { + attSourceList.forEach(c -> { + String address; + String province; + try { + JSONObject result = AddressCoordinateFormatUtil. + coordinateToAddress2(c.getAttLon(), c.getAttLat()); + address = result.getString("formatted_address"); + province = result.getJSONObject("addressComponent").getString("province"); + } catch (Exception e) { + address = "地址未转化成功"; + province = "地址未转化成功"; + } + c.setAttAddress(address); + c.setProvince(province); + } + ); + } + return attSourceList; + } + + /** + * 获取考勤机的考勤数据 + */ + private List getMachineAttendanceData(String pushDate, int pushType) { + return Optional.ofNullable(attSourceDataDao.getMachineAttendances(pushDate, pushType)). + orElseGet(ArrayList::new); + } + + /** + * 新增考勤到数据库(考勤来源表) + * 处理过后的数据新增到考勤来源表 + * + * @param attSourceList 考勤数据 + */ + public void insertAttSourceData(List attSourceList) { + if (attSourceList == null || attSourceList.isEmpty()) { + return; + } + attSourceList.forEach(c -> { + if ("0".equals(c.getAttType())) { + //判断打卡类型 + int i = AttTimeUtil.getAttTypeByTime(c.getAttCurrentTime()); + c.setAttType(String.valueOf(i)); + } + //判断打卡时间归属于当天还是昨天 + AttTimeUtil.changeAttCurrentDay(c); + }); + int batchSize = 3000; + int totalBatches = (int) Math.ceil((double) attSourceList.size() / batchSize); + try { + for (int i = 0; i < totalBatches; i++) { + int fromIndex = i * batchSize; + int toIndex = Math.min(fromIndex + batchSize, attSourceList.size()); + List batch = attSourceList.subList(fromIndex, toIndex); + attSourceDataDao.insertAttSourceData(batch); + if ((i + 1) % 100 == 0 || i + 1 == totalBatches) { + sqlSessionTemplate.flushStatements(); // 提交事务 + sqlSessionTemplate.clearCache(); // 清理缓存 + } + } + } catch (Exception e) { + throw new RuntimeException("考勤数据抓取到数据库出错啦", e); + } + } + + /** + * 查出每一个考勤组的应出勤天数以及考勤组的规则 + * + * @return 考勤组集合 + */ + public List getGroupData(String pushDate) { + //查出考勤组的数据 + List groupList = Optional.ofNullable(attGroupDao.selectAttGroupList(new AttGroupBean())). + orElseGet(ArrayList::new); + if (groupList.isEmpty()) { + return new ArrayList<>(); + } + //查询当月否有节假日或补班 + List holidays = attSourceDataDao.selectHolidayByMonth(pushDate); + groupList.forEach(c -> { + //应考勤天 + List attDayList = WorkdayCalculator.getWorkDay(c.getAttDay(), + Integer.parseInt(c.getAttType()), holidays, pushDate); + c.setAttWorkDayBean(new PersonAttWorkDayBean(c.getGroupId(), attDayList.size(), attDayList)); + }); + return groupList; + } + + + /** + * 固定考勤数据计算 + * + * @param list 考勤来源数据 + * @param attGroupBean 考勤组数据 + * @return 考勤数据 + */ + private List groupFixedData(List list, + AttGroupBean attGroupBean) { + List newList = new ArrayList<>(); + //分组排序(将每个人每天在自己考勤组的数据分组) + Map> groupedItems = list.stream() + .collect(Collectors.groupingBy( + c -> c.getUserId() + "|" + c.getAttCurrentDay(), + Collectors.collectingAndThen( + Collectors.toList(), + v -> { + v.sort(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)); + return v; + } + ) + )); + //固定打卡,以最靠近上班打卡时间前的出闸机的时间为基准,去除掉之前的所有打卡数据 + AttTimeUtil.processGroupedItems(groupedItems, attGroupBean); + groupedItems.forEach((c, v) -> { + //第一次上班时间 + AttSourceDataBean frontToWorkBean = v.stream() + .filter(a -> a.getAttType().equals("1")) + .collect(Collectors.toList()) + .stream().min(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null); + //最新一次下班时间 + AttSourceDataBean backOffWorkBean = v.stream() + .filter(a -> a.getAttType().equals("2")) + .collect(Collectors.toList()) + .stream().max(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null); + + if (backOffWorkBean != null) { + //最新一次下班时间插入list + newList.add(backOffWorkBean); + } + //没有下班卡则添加第一次打卡数据 + newList.add(frontToWorkBean); + //获取工作时间外出记录并存储(支线,不影响) + addWorkTimeOutRecord(v, attGroupBean); + }); + Iterator iterator = newList.iterator(); + while (iterator.hasNext()) { + if (iterator.next() == null) { + iterator.remove(); + } + } + processAttendanceStatus(newList, attGroupBean.getToWorkTime(), attGroupBean.getOffWorkTime(), attGroupBean.getLateMinute(), + attGroupBean.getAbsenteeismLateMinute(), attGroupBean.getLeaveMinute(), attGroupBean.getAbsenteeismLeaveMinute()); + return newList; + } + + /** + * 自由考勤数据计算 + * + * @param list 考勤来源数据 + * @param attGroupBean 考勤组数据 + * @return 考勤数据 + */ + private List groupFreeData(List list, AttGroupBean attGroupBean) { + List newList = new ArrayList<>(); + //分组排序(将每个人每天在自己考勤组的数据分组) + Map> groupedItems = list.stream() + .collect(Collectors.groupingBy( + c -> c.getUserId() + "|" + c.getAttCurrentDay(), + Collectors.collectingAndThen( + Collectors.toList(), + v -> { + v.sort(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)); + return v; + } + ) + )); + + groupedItems.forEach((c, v) -> { + //第一次上班时间 + AttSourceDataBean frontToWorkBean = v.stream() + .filter(a -> a.getAttType().equals("1")) + .collect(Collectors.toList()) + .stream().min(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null); + //最新一次下班时间 + AttSourceDataBean backOffWorkBean = v.stream() + .filter(a -> a.getAttType().equals("2")) + .collect(Collectors.toList()) + .stream().max(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null); + //自由打卡不需要去除数据 + getFreeAttData(newList, frontToWorkBean, backOffWorkBean, attGroupBean); + }); + return newList; + } + + /** + * 上下班考勤计算 + * + * @param list 考勤来源数据 + * @param attGroupBean 考勤组数据 + * @param tf 固定|自由 + * @return 考勤数据 + */ + private List getDataList(List list, + AttGroupBean attGroupBean, + Boolean tf) { + List newList = new ArrayList<>(); + //分组排序(将每个人每天在自己考勤组的数据分组) + Map> groupedItems = list.stream() + .collect(Collectors.groupingBy( + c -> c.getUserId() + "|" + c.getAttCurrentDay(), + Collectors.collectingAndThen( + Collectors.toList(), + v -> { + v.sort(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)); + return v; + } + ) + )); + if (!tf) { + //固定打卡,以最靠近上班打卡时间前的出闸机的时间为基准,去除掉之前的所有打卡数据 + AttTimeUtil.processGroupedItems(groupedItems, attGroupBean); + } + groupedItems.forEach((c, v) -> { + //第一次上班时间 + AttSourceDataBean frontToWorkBean = v.stream() + .filter(a -> a.getAttType().equals("1")) + .collect(Collectors.toList()) + .stream().min(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null); + //最新一次下班时间 + AttSourceDataBean backOffWorkBean = v.stream() + .filter(a -> a.getAttType().equals("2")) + .collect(Collectors.toList()) + .stream().max(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null); + if (tf) { +// if(!v.isEmpty() && v.get(0).getName().equals("任荣辉")){ +// System.out.println("11111111111"); +// } + //自由打卡不需要去除数据 + getFreeAttData(newList, frontToWorkBean, backOffWorkBean, attGroupBean); + } else { + if (backOffWorkBean != null) { + //最新一次下班时间插入list + newList.add(backOffWorkBean); + } + //没有下班卡则添加第一次打卡数据 + newList.add(frontToWorkBean); + //获取工作异常 + LocalTime lastOutTime = null; + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + try { + //存储工作异常的list + List longBreakRecords = new ArrayList<>(); + for (int i = 0; i < v.size(); i++) { + AttSourceDataBean record = v.get(i); + // 如果是“出”的记录,保存时间 + if ("2".equals(record.getAttType())) { + lastOutTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime(); + if (lastOutTime.isAfter(LocalTime.parse(attGroupBean.getOffWorkTime(), timeFormatter))) { + //进在下班之后,不考虑 + break; + } + } + // 如果是“进”的记录并且之前有“出”的记录 + else if ("1".equals(record.getAttType()) && lastOutTime != null) { + LocalTime inTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime(); + // 计算实际工作时间(不包括午休时间) +// if(record.getName().equals("何波")){ +// System.out.println("11111111111"); +// } + // 出-》进 的时间在上班时间前,去除 + if (inTime.isBefore(LocalTime.parse(attGroupBean.getToWorkTime(), timeFormatter))) { + //进在上班之后,不考虑 + break; + } + Duration workDuration = AttTimeUtil.calculateWorkDuration(lastOutTime, inTime, attGroupBean); + // 如果工作时间外出超过了规定时间,记录下这对“出”和“进” + if (workDuration != null && !workDuration.isNegative() && workDuration.toMinutes() > attGroupBean.getWorkAbnormalMinute()) { + AttSourceDataBean longBreakRecord = new AttSourceDataBean(); + longBreakRecord.setUserId(record.getUserId()); + longBreakRecord.setName(record.getName()); + longBreakRecord.setOrgId(attGroupBean.getOrgId()); + longBreakRecord.setAttCurrentDay(record.getAttCurrentDay()); + longBreakRecord.setAttCurrentTime(v.get(i - 1).getAttCurrentTime() + " " + record.getAttCurrentTime()); + longBreakRecord.setAttAddress(v.get(i - 1).getAttAddress() + " " + record.getAttAddress()); + longBreakRecords.add(longBreakRecord); + } + // 更新lastOutTime为null,因为已经处理了这一对 + lastOutTime = null; + } + } + //判断有没有临时外出请假等等 + if (!longBreakRecords.isEmpty()) { + int x = attSourceDataDao.getLeaveDataByUserId(longBreakRecords.get(0)); + if (x == 0) { + attSourceDataDao.insertWorkAbnormal(longBreakRecords); + } + } + } catch (Exception e) { + e.printStackTrace(); + log.error("工作异常处理报错"); + } + + } + }); + return newList; + } + + /** + * 处理打卡状态 + * + * @param newList 考勤数据 + * @param toWorkTime 上班时间 + * @param offWorkTime 下班时间 + * @param lateMinute 迟到时间 + * @param absenteeismLateMinute 旷工迟到分钟 + * @param leaveMinute 早退分钟 + * @param absenteeismLeaveMinute 旷工早退分钟 + */ + private void processAttendanceStatus(List newList, String toWorkTime, + String offWorkTime, long lateMinute, + long absenteeismLateMinute, long leaveMinute, + long absenteeismLeaveMinute) { + newList.forEach(c -> { +// if(c.getName().equals("肖阳")){ +// System.out.println("c.getAttCurrentTime() = " + c.getAttCurrentTime()); +// } + System.out.println(c.getName()); + int attStatus = calculateStatus( + c.getAttType(), + c.getAttCurrentTime(), + "1".equals(c.getAttType()) ? toWorkTime : offWorkTime, + "1".equals(c.getAttType()) ? lateMinute : leaveMinute, + "1".equals(c.getAttType()) ? absenteeismLateMinute : absenteeismLeaveMinute + ); + System.out.println(1111); + // 如打卡状态为正常,检查是否有异常出入时间 + if (attStatus == 1 && c.getAttStatus() != null && c.getAttStatus() == 8) { + attStatus = 8; // 异常 + boolean b = DateTimeHelper.compareTime2(c.getAttCurrentTime(), c.getAbnormalAttTime()); + String time = ""; + String address = ""; + if (b) { + time = c.getAttCurrentTime() + " " + c.getAbnormalAttTime(); + address = c.getAttAddress() + " " + c.getAbnormalAttAddress(); + } else { + time = c.getAbnormalAttTime() + " " + c.getAttCurrentTime(); + address = c.getAbnormalAttAddress() + " " + c.getAttAddress(); + } + c.setAttCurrentTime(time); + c.setAttAddress(address); + } + System.out.println(2222); + c.setAttStatus(attStatus); + }); + } + + /** + * 获取自由考勤数据 + * + * @param newList 考勤数据 + * @param frontToWorkBean 第一次上班考勤数据 + * @param backOffWorkBean 最后一次下班考勤数据 + * @param attGroupBean 考勤组数据 + */ + private void getFreeAttData(List newList, + AttSourceDataBean frontToWorkBean, + AttSourceDataBean backOffWorkBean, + AttGroupBean attGroupBean) { + // todayClockNum 自由打卡每天打卡次数 + if (attGroupBean.getTodayClockNum() == 2) { + if (frontToWorkBean != null) { + frontToWorkBean.setAttStatus(1); + newList.add(frontToWorkBean); + } + if (backOffWorkBean != null) { + if (frontToWorkBean != null) { + long minutesDiff = DateUtil.between( + DateUtils.parseDate(frontToWorkBean.getAttCurrentTime()), + DateUtils.parseDate(backOffWorkBean.getAttCurrentTime()), + DateUnit.MINUTE + ); + if (minutesDiff >= attGroupBean.getAttendanceDuration()) { + backOffWorkBean.setAttStatus(1); + } else { + if (minutesDiff > attGroupBean.getAbsenteeismLateMinute()) { + backOffWorkBean.setAttStatus(1); + } else if (minutesDiff > attGroupBean.getAbsenteeismLeaveMinute()) { + backOffWorkBean.setAttStatus(4); + } else { + backOffWorkBean.setAttStatus(3); + } + } + newList.add(backOffWorkBean); + } else { + backOffWorkBean.setAttStatus(1); + newList.add(backOffWorkBean); + } + } + } else { + if (frontToWorkBean != null) { +// if(frontToWorkBean.getName().equals("位兆虎")){ +// System.out.println(1); +// } + frontToWorkBean.setAttStatus(1); + AttSourceDataBean bean; + try { + bean = (AttSourceDataBean) BeanUtils.cloneBean(frontToWorkBean); + } catch (Exception e) { + throw new RuntimeException(e); + } + bean.setAttCurrentTime(""); + bean.setAttType("2"); + newList.add(bean); + newList.add(frontToWorkBean); + } else if (backOffWorkBean != null) { + backOffWorkBean.setAttStatus(1); + AttSourceDataBean bean; + try { + bean = (AttSourceDataBean) BeanUtils.cloneBean(backOffWorkBean); + } catch (Exception e) { + throw new RuntimeException(e); + } + bean.setAttCurrentTime(""); + bean.setAttType("1"); + newList.add(bean); + newList.add(backOffWorkBean); + } + } + } + + /** + * 计算时间差并返回状态 + * + * @param attType 考勤类型 + * @param attTime 考勤时间 + * @param standardTime 上、下班时间 + * @param lateThreshold 迟到、早退时间 + * @param absenteeismThreshold 旷工时间 + * @return 考勤状态 + */ + private int calculateStatus(String attType, String attTime, String standardTime, + long lateThreshold, long absenteeismThreshold) { + int status = 1; + // 创建两个时间点 + Date date1 = DateUtils.parseDate(DateUtils.getYyyyMmDd(attTime) + " " + standardTime); + Date date2 = DateUtils.parseDate(attTime); + if ("2".equals(attType)) { + String startTime = DateUtils.getYyyyMmDd(attTime) + " 00:00:00"; + String endTime = DateUtils.getYyyyMmDd(attTime) + " 04:59:59"; + if (DateUtils.getTimeIsRange(attTime, startTime, endTime)) { + date1 = DateUtils.parseDate(DateUtils.getAfterDate(DateUtils.getYyyyMmDd(attTime)) + " " + standardTime); + } + } + if ("1".equals(attType)) { // 上班打卡 + // 计算时间差 + // 减 1 原因:30上班,30:59不算迟到,类似于默认可以迟到一分钟 + double difference = (date2.getTime() - date1.getTime()) / 60000.0 - 1; + if (difference > 0) { + if (difference > absenteeismThreshold) { + status = 3; // 旷工 + } else if (difference > lateThreshold) { + status = 2; // 迟到 + } + } + } else if ("2".equals(attType)) { // 下班打卡 + // 计算时间差 + double difference = (date2.getTime() - date1.getTime()) / 60000.0; + if (difference < 0) { + if (Math.abs(difference) > lateThreshold) { + status = 4; // 早退 + } + if (Math.abs(difference) > absenteeismThreshold) { + status = 3; // 旷工 + } + } + } + return status; // 正常 + } + + private void addWorkTimeOutRecord(List v, AttGroupBean attGroupBean) { + LocalTime lastOutTime = null; + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + try { + //存储工作异常的list + List longBreakRecords = new ArrayList<>(); + for (int i = 0; i < v.size(); i++) { + AttSourceDataBean record = v.get(i); + // 如果是“出”的记录,保存时间 + if ("2".equals(record.getAttType())) { + lastOutTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime(); + if (lastOutTime.isAfter(LocalTime.parse(attGroupBean.getOffWorkTime(), timeFormatter))) { + //进在下班之后,不考虑 + break; + } + } + // 如果是“进”的记录并且之前有“出”的记录 + else if ("1".equals(record.getAttType()) && lastOutTime != null) { + LocalTime inTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime(); + // 计算实际工作时间(不包括午休时间) +// if(record.getName().equals("何波")){ +// System.out.println("11111111111"); +// } + // 出-》进 的时间在上班时间前,去除 + if (inTime.isBefore(LocalTime.parse(attGroupBean.getToWorkTime(), timeFormatter))) { + //进在上班之后,不考虑 + break; + } + Duration workDuration = AttTimeUtil.calculateWorkDuration(lastOutTime, inTime, attGroupBean); + // 如果工作时间外出超过了规定时间,记录下这对“出”和“进” + if (workDuration != null && !workDuration.isNegative() && workDuration.toMinutes() > attGroupBean.getWorkAbnormalMinute()) { + AttSourceDataBean longBreakRecord = new AttSourceDataBean(); + longBreakRecord.setUserId(record.getUserId()); + longBreakRecord.setName(record.getName()); + longBreakRecord.setOrgId(attGroupBean.getOrgId()); + longBreakRecord.setAttCurrentDay(record.getAttCurrentDay()); + longBreakRecord.setAttCurrentTime(v.get(i - 1).getAttCurrentTime() + " " + record.getAttCurrentTime()); + longBreakRecord.setAttAddress(v.get(i - 1).getAttAddress() + " " + record.getAttAddress()); + longBreakRecords.add(longBreakRecord); + } + // 更新lastOutTime为null,因为已经处理了这一对 + lastOutTime = null; + } + } + //判断有没有临时外出请假等等 + if (!longBreakRecords.isEmpty()) { + int x = attSourceDataDao.getLeaveDataByUserId(longBreakRecords.get(0)); + if (x == 0) { + attSourceDataDao.insertWorkAbnormal(longBreakRecords); + } + } + } catch (Exception e) { + e.printStackTrace(); + log.error("工作异常处理报错"); + } + } + +} diff --git a/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/tasks/NewAttTask.java b/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/tasks/NewAttTask.java new file mode 100644 index 0000000..d1de491 --- /dev/null +++ b/bonus-modules/bonus-system/src/main/java/com/bonus/system/att/tasks/NewAttTask.java @@ -0,0 +1,125 @@ +package com.bonus.system.att.tasks; + +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.date.DateUtil; +import com.bonus.common.core.utils.DateTimeHelper; +import com.bonus.common.core.utils.DateUtils; +import com.bonus.system.att.dao.AttGroupDao; +import com.bonus.system.att.dao.AttSourceDataDao; +import com.bonus.system.att.entity.*; +import com.bonus.system.att.service.AttCalService; +import com.bonus.system.att.utils.AddressCoordinateFormatUtil; +import com.bonus.system.att.utils.AttTimeUtil; +import com.bonus.system.att.utils.WorkdayCalculator; +import com.bonus.system.holiday.dao.HolidayDao; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.BeanUtils; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import javax.annotation.Resource; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * @author zys + * 考勤定时器 + */ +@Configuration +@EnableScheduling +@Slf4j +@EnableAsync +public class NewAttTask { + + + @Resource(name = "attSourceDataDao") + private AttSourceDataDao attSourceDataDao; + + @Resource(name = "sqlSessionTemplate") + private SqlSessionTemplate sqlSessionTemplate; + + @Resource(name = "AttCalService") + private AttCalService attCalService; + + + /** + * 待考勤人员列表 + * 在人员考勤模版数据之前生成 + * @param pushDate 时间(可为空,为空则推历史60天内没生成的数据,不为空则推指定日期) + */ + private void insertAttDateHistory(String pushDate) { + //待考勤人员列表生成 + attCalService.insertAttDateHistory(pushDate); + } + + + /** + * 人员考勤模版数据(默认是未打卡) + * 每天早上0点30执行一次 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + private void insertAttTempData(String pushDate, int pushType) { + //获取考勤数据 + attCalService.insertAttTempData(pushDate, pushType); + } + + /** + * 获取其他系统考勤数据 + * 建议一小时一次 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + private void getAttendanceData(String pushDate, int pushType) { + //获取考勤数据 + attCalService.getAttendanceData(pushDate, pushType); + } + + /** + * 考勤数据应用(步骤一:考勤数据更新) + * 建议一小时一次 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + private void updateAttData(String pushDate, int pushType) { + //获取考勤数据 + attCalService.updateAttData(pushDate, pushType); + } + + /** + * 考勤数据应用(步骤二:旷工状态更新) + * 每天12点将当天未打卡考勤人员上班置为旷工 + * 每天晚上22点,将下班未打卡置为旷工 + * @param pushDate 时间 + * @param pushType 1:自动推送(当天) 2:手动(历史) + */ + private void updateAbsenteeismData(String pushDate, int pushType) { + //旷工状态更新 + attCalService.updateAbsenteeismData(pushDate, pushType); + } + + /** + * 请假数据应用(步骤一:法假数据更新) + * 法定节假日不会随时间变化,一天执行一次就可以了 + * @param pushDate 时间 + */ + private void updateLegalHolidayData(String pushDate) { + //旷工状态更新 + attCalService.updateLegalHolidayData(pushDate); + } + +} \ No newline at end of file