|
@@ -0,0 +1,398 @@
|
|
|
+package com.management.platform.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.management.platform.entity.Attendance;
|
|
|
+import com.management.platform.entity.AttendanceStaff;
|
|
|
+import com.management.platform.entity.DayInfo;
|
|
|
+import com.management.platform.entity.User;
|
|
|
+import com.management.platform.mapper.AttendanceStaffMapper;
|
|
|
+import com.management.platform.service.AttendanceService;
|
|
|
+import com.management.platform.service.AttendanceStaffService;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.management.platform.service.UserService;
|
|
|
+import com.management.platform.util.HttpRespMsg;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.*;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import static com.management.platform.constant.Constant.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * <p>
|
|
|
+ * 服务实现类
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @author Seyason
|
|
|
+ * @since 2025-06-17
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMapper, AttendanceStaff> implements AttendanceStaffService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private AttendanceService attendanceService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserService userService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public HttpRespMsg refreshData(String month) {
|
|
|
+ HttpRespMsg msg = new HttpRespMsg();
|
|
|
+ this.remove(new QueryWrapper<AttendanceStaff>().eq("month", month));
|
|
|
+
|
|
|
+
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
|
|
|
+ YearMonth yearMonth = YearMonth.parse(month, formatter);
|
|
|
+ LocalDate localDate = yearMonth.plusMonths(1).atDay(2);//查询条件
|
|
|
+
|
|
|
+ LocalDate startDate = yearMonth.atDay(1);//本月开始日期
|
|
|
+ LocalDate endDate = yearMonth.atEndOfMonth();//本月结束日期
|
|
|
+ ArrayList<LocalDate> localDates = new ArrayList<>();//本月所有日期
|
|
|
+ for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
|
|
|
+ localDates.add(date);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Attendance> attendanceList = attendanceService.list(new QueryWrapper<Attendance>()
|
|
|
+ .eq("month", month).or().le("clock_time", localDate).orderByAsc("job_number","clock_time"));
|
|
|
+
|
|
|
+ List<AttendanceStaff> addList = new ArrayList<>();
|
|
|
+ Map<String, List<Attendance>> listMap = attendanceList.stream().collect(Collectors.groupingBy(Attendance::getJobNumber));
|
|
|
+ Set<Map.Entry<String, List<Attendance>>> entries = listMap.entrySet();
|
|
|
+ for (Map.Entry<String, List<Attendance>> entry : entries) {
|
|
|
+ String jobNumber = entry.getKey();
|
|
|
+ List<Attendance> attendances = entry.getValue();
|
|
|
+ if (!attendances.isEmpty()) {
|
|
|
+ Attendance attendance = attendances.get(0);
|
|
|
+ //遍历本月的日期,根据考勤记录,确定考勤的上班打卡时间,下班打卡时间,上班时间, 班次类型:白班,中班,小夜班,大夜班
|
|
|
+ for (LocalDate date : localDates) {
|
|
|
+
|
|
|
+
|
|
|
+ LocalDateTime dayStartTime = date.atStartOfDay();//当日的最早时间
|
|
|
+ LocalDateTime dayEndTime = date.plusDays(1).atStartOfDay().plusHours(10);//假设的当日大夜班到第二天的下班最晚时间
|
|
|
+ int year = date.getYear();
|
|
|
+ int monthValue = date.getMonthValue();
|
|
|
+ int dayValue = date.getDayOfMonth();
|
|
|
+ //早班上下班时间判断
|
|
|
+ Optional<Attendance> zaoShangCount = attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 7, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue, 9, 0))
|
|
|
+ ).findFirst();
|
|
|
+ Optional<Attendance> zaoXiaCount = attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 16, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue, 19, 0))
|
|
|
+ ).max(Comparator.comparing(Attendance::getClockTime));
|
|
|
+ //中班上下班时间判断
|
|
|
+ Optional<Attendance> zhongShangCount = attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 12, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue, 14, 0))
|
|
|
+ ).findFirst();
|
|
|
+
|
|
|
+ Optional<Attendance> zhongXiaCount = attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 20, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue, 23, 0))
|
|
|
+ ).max(Comparator.comparing(Attendance::getClockTime));
|
|
|
+ //小夜班上下班时间判断
|
|
|
+ Optional<Attendance> xiaoYeShangCount = attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 15, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue, 19, 0))
|
|
|
+ ).findFirst();
|
|
|
+
|
|
|
+ boolean isLastDay = endDate.isEqual(LocalDate.of(year, monthValue, dayValue));//是否是该月份最后一天
|
|
|
+ Optional<Attendance> xiaoYeXiaCount = isLastDay ?
|
|
|
+ attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 22, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue+1, 1, 3, 0))
|
|
|
+ ).max(Comparator.comparing(Attendance::getClockTime)) :
|
|
|
+ attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 22, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue+1, 3, 0))
|
|
|
+ ).max(Comparator.comparing(Attendance::getClockTime));
|
|
|
+ //大夜班上下班时间判断
|
|
|
+ Optional<Attendance> daYeShangCount =isLastDay?
|
|
|
+ attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 23, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue+1, 1, 1, 0))
|
|
|
+ ).findFirst():
|
|
|
+ attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue, 23, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue + 1, 1, 0))
|
|
|
+ ).findFirst();
|
|
|
+
|
|
|
+ Optional<Attendance> daYeXiaCount =isLastDay?
|
|
|
+ attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue+1, 1, 6, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue+1, 1, 10, 0))
|
|
|
+ ).max(Comparator.comparing(Attendance::getClockTime))
|
|
|
+ :
|
|
|
+ attendances.stream().filter(a -> a.getClockTime().isAfter(LocalDateTime.of(year, monthValue, dayValue + 1, 6, 0))
|
|
|
+ && a.getClockTime().isBefore(LocalDateTime.of(year, monthValue, dayValue + 1, 10, 0))
|
|
|
+ ).max(Comparator.comparing(Attendance::getClockTime));
|
|
|
+
|
|
|
+ AttendanceStaff staff = new AttendanceStaff();
|
|
|
+ staff.setJobNumber(jobNumber);
|
|
|
+ staff.setMonth(month);
|
|
|
+ staff.setName(attendance.getName());
|
|
|
+ staff.setDeptName(attendance.getDeptName());
|
|
|
+ staff.setClockDate(date);
|
|
|
+ /**
|
|
|
+ * 考勤状态 0白班,1大夜班,2小夜班,3中班,4班次异常,5休息,6调休,7请假
|
|
|
+ */
|
|
|
+
|
|
|
+ //1先判断白班
|
|
|
+ if (zaoShangCount.isPresent()&&zaoXiaCount.isPresent()) {
|
|
|
+ LocalDateTime startClockTime = zaoShangCount.get().getClockTime();
|
|
|
+ LocalDateTime endClockTime = zaoXiaCount.get().getClockTime();
|
|
|
+ staff.setClockStartTime(startClockTime);
|
|
|
+ staff.setClockEndTime(endClockTime);
|
|
|
+ BigDecimal bigDecimal = calculateWorkHours(startClockTime, endClockTime);
|
|
|
+ staff.setWorkHour(bigDecimal);
|
|
|
+ staff.setAttendanceType(BAI_BAN);
|
|
|
+ }
|
|
|
+
|
|
|
+ //2再判断中班
|
|
|
+ else if (zhongShangCount.isPresent()&&zhongXiaCount.isPresent()) {
|
|
|
+ LocalDateTime startClockTime = zhongShangCount.get().getClockTime();
|
|
|
+ LocalDateTime endClockTime = zhongXiaCount.get().getClockTime();
|
|
|
+ staff.setClockStartTime(startClockTime);
|
|
|
+ staff.setClockEndTime(endClockTime);
|
|
|
+ BigDecimal bigDecimal = calculateWorkHours(startClockTime, endClockTime);
|
|
|
+ staff.setWorkHour(bigDecimal);
|
|
|
+ staff.setAttendanceType(ZHONG_BAN);
|
|
|
+ }
|
|
|
+
|
|
|
+ //3判断小夜班
|
|
|
+ else if (xiaoYeShangCount.isPresent()&&xiaoYeXiaCount.isPresent()) {
|
|
|
+ LocalDateTime startClockTime = xiaoYeShangCount.get().getClockTime();
|
|
|
+ LocalDateTime endClockTime = xiaoYeXiaCount.get().getClockTime();
|
|
|
+ staff.setClockStartTime(startClockTime);
|
|
|
+ staff.setClockEndTime(endClockTime);
|
|
|
+ BigDecimal bigDecimal = calculateWorkHours(startClockTime, endClockTime);
|
|
|
+ staff.setWorkHour(bigDecimal);
|
|
|
+ staff.setAttendanceType(XIAO_YE_BAN);
|
|
|
+ }
|
|
|
+
|
|
|
+ //4判断大夜班
|
|
|
+ else if (daYeShangCount.isPresent()&&daYeXiaCount.isPresent()) {
|
|
|
+ LocalDateTime startClockTime = daYeShangCount.get().getClockTime();
|
|
|
+ LocalDateTime endClockTime = daYeXiaCount.get().getClockTime();
|
|
|
+ staff.setClockStartTime(startClockTime);
|
|
|
+ staff.setClockEndTime(endClockTime);
|
|
|
+ BigDecimal bigDecimal = calculateWorkHours(startClockTime, endClockTime);
|
|
|
+ staff.setWorkHour(bigDecimal);
|
|
|
+ staff.setAttendanceType(DA_YE_BAN);
|
|
|
+ }
|
|
|
+ if (staff.getClockStartTime()!=null){
|
|
|
+ addList.add(staff);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ saveBatch(addList);
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public HttpRespMsg getListData(String month) {
|
|
|
+ HttpRespMsg msg = new HttpRespMsg();
|
|
|
+ List<AttendanceStaff> list = list(new QueryWrapper<AttendanceStaff>().eq("month", month));
|
|
|
+ msg.setData(list);
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public HttpRespMsg getAttendanceUserData(String month, String date, String userId, HttpServletRequest request) {
|
|
|
+ HttpRespMsg msg = new HttpRespMsg();
|
|
|
+ QueryWrapper<AttendanceStaff> wrapper = new QueryWrapper<AttendanceStaff>()
|
|
|
+ .eq("month", month).eq("date", date);
|
|
|
+ if (userId==null|| StringUtils.isEmpty(userId)){
|
|
|
+ User user = userService.getById(request.getHeader("Token"));
|
|
|
+ wrapper.eq("job_number", user.getJobNumber());
|
|
|
+ }else {
|
|
|
+ User user = userService.getById(userId);
|
|
|
+ wrapper.eq("job_number", user.getJobNumber());
|
|
|
+ }
|
|
|
+ List<AttendanceStaff> list = list(wrapper);
|
|
|
+ for (AttendanceStaff staff : list) {
|
|
|
+ Integer type = staff.getAttendanceType();
|
|
|
+ LocalDate clockDate = staff.getClockDate();
|
|
|
+ LocalDateTime clockStartTime = staff.getClockStartTime();
|
|
|
+ LocalTime startTime = clockStartTime.toLocalTime();
|
|
|
+ LocalDateTime clockEndTime = staff.getClockEndTime();
|
|
|
+ LocalTime endTime = clockEndTime.toLocalTime();
|
|
|
+
|
|
|
+ List<HashMap<String,Object>> maplist = new ArrayList<>();
|
|
|
+ switch (type) {
|
|
|
+ case BAI_BAN:
|
|
|
+ if (startTime.isAfter(LocalTime.of(8,0,0))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","晚点");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endTime.isBefore(LocalTime.of(15,0,0))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","早退");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ double v = calculateOvertimeHours(startTime, endTime);
|
|
|
+ if (v>0) {
|
|
|
+ map.put("extra", "(17:00-"+endTime+"加班"+v+"小时)");
|
|
|
+ }
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ZHONG_BAN:
|
|
|
+ if (startTime.isAfter(LocalTime.of(13,0,0))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","晚点");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endTime.isBefore(LocalTime.of(21,0,0))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","早退");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ double v = calculateOvertimeHours(startTime, endTime);
|
|
|
+ if (v>0) {
|
|
|
+ map.put("extra", "(13:00-"+endTime+"加班"+v+"小时)");
|
|
|
+ }
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case XIAO_YE_BAN:
|
|
|
+ if (startTime.isAfter(LocalTime.of(16,0,0))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","晚点");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endTime.isBefore(LocalTime.of(23,59,59))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","早退");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ double v = calculateOvertimeHours(startTime, endTime);
|
|
|
+ if (v>0) {
|
|
|
+ map.put("extra", "(00:00-"+endTime+"加班"+v+"小时)");
|
|
|
+ }
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case DA_YE_BAN:
|
|
|
+ if (startTime.isAfter(LocalTime.of(23,59,59))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","晚点");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",startTime+"上班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endTime.isBefore(LocalTime.of(8,0,0))){
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","早退");
|
|
|
+ maplist.add(map);
|
|
|
+ }else {
|
|
|
+ HashMap<String, Object> map = new HashMap<>();
|
|
|
+ map.put("msg",endTime+"下班考勤打卡");
|
|
|
+ map.put("res","正常");
|
|
|
+ double v = calculateOvertimeHours(startTime, endTime);
|
|
|
+ if (v>0) {
|
|
|
+ map.put("extra", "(08:00-"+endTime+"加班"+v+"小时)");
|
|
|
+ }
|
|
|
+ maplist.add(map);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ msg.setData(list);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static BigDecimal calculateWorkHours(LocalDateTime start, LocalDateTime end) {
|
|
|
+ // 1. 计算时间差
|
|
|
+ Duration duration = Duration.between(start, end);
|
|
|
+
|
|
|
+ // 2. 转换为小时数(double)
|
|
|
+ double hours = duration.toMinutes() / 60.0;
|
|
|
+
|
|
|
+ // 3. 转换为BigDecimal并四舍五入保留1位小数
|
|
|
+ return BigDecimal.valueOf(hours)
|
|
|
+ .setScale(1, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算加班时长(以30分钟为单位)
|
|
|
+ * @param startTime 开始时间
|
|
|
+ * @param endTime 结束时间
|
|
|
+ * @return 加班单位数(每30分钟为一个单位)
|
|
|
+ */
|
|
|
+ public static int calculateOvertimeUnits(LocalTime startTime, LocalTime endTime) {
|
|
|
+ Duration workDuration;
|
|
|
+
|
|
|
+ // 处理跨午夜情况
|
|
|
+ if (endTime.isBefore(startTime)) {
|
|
|
+ workDuration = Duration.between(startTime, LocalTime.MAX)
|
|
|
+ .plus(Duration.between(LocalTime.MIN, endTime))
|
|
|
+ .plusMinutes(1); // 加上午夜这一分钟
|
|
|
+ } else {
|
|
|
+ workDuration = Duration.between(startTime, endTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ long totalMinutes = workDuration.toMinutes();
|
|
|
+
|
|
|
+ if (totalMinutes <= 60) return 0;
|
|
|
+
|
|
|
+ long overtimeMinutes = totalMinutes - 60;
|
|
|
+ return (int) Math.round(overtimeMinutes / 30.0);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算加班时长(返回小时数,保留1位小数)
|
|
|
+ */
|
|
|
+ public static double calculateOvertimeHours(LocalTime startTime, LocalTime endTime) {
|
|
|
+ int units = calculateOvertimeUnits(startTime, endTime);
|
|
|
+ return units * 0.5; // 每个单位是0.5小时
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|