yusm 4 周之前
父節點
當前提交
ba2bf89117

+ 13 - 1
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/AttendanceStaff.java

@@ -22,7 +22,7 @@ import org.springframework.format.annotation.DateTimeFormat;
 
 /**
  * <p>
- * 
+ *
  * </p>
  *
  * @author Seyason
@@ -101,6 +101,18 @@ public class AttendanceStaff extends Model<AttendanceStaff> {
     @TableField("attendance_type_name")
     private String attendanceTypeName; //考勤状态名称
 
+
+
+    @TableField("is_over")
+    private Integer isOver; //考勤状态名称
+
+
+    /**
+     * 加班时长
+     */
+    @TableField("overtime_hour")
+    private BigDecimal overtimeHour;
+
     @TableField(exist = false)
     private String color;//针对考勤状态对应的颜色
 

+ 2 - 1
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/DayInfo.java

@@ -8,6 +8,7 @@ public class DayInfo {
     private String date;       // 日期,格式:yyyy-MM-dd
     private int dayOfWeekValue;     // 星期几,1-7 (Monday-Sunday)
     private String dayOfWeek;     // 星期几,1-7 (Monday-Sunday)
-    private int type;          // 0-工作日,1-休息
+    private int type;          // 0-休息日,1-调休,2-工作日,3-法定节假
 
+    private String typeName;   // 日期类型名称
 }

+ 51 - 25
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/AttendanceServiceImpl.java

@@ -207,21 +207,29 @@ public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attenda
                 String dayWeekName = getDayWeekName(value);
                 dayInfo.setDayOfWeek(dayWeekName); // 1-7 (Monday-Sunday)
 //                dayInfo.setType(2);//工作日
-                if (WorkDayCalculateUtils.isWorkDay(date)) {
+                if (WorkDayCalculateUtils.isLegalHoliday(date)) {
+                    dayInfo.setType(3);//法定节假日
+                    dayInfo.setTypeName("法定节假日");
+                } else if (WorkDayCalculateUtils.isWorkDay(date)) {
                     dayInfo.setType(2);//工作日
+                    dayInfo.setTypeName("班");
                 } else {
                     dayInfo.setType(0);//休息日
+                    dayInfo.setTypeName("休");
                 }
                 // 判断是否为休息日(周末或自定义假日)
                 if (customHolidays.contains(date)) {
                     dayInfo.setType(0); // 休息日
+                    dayInfo.setTypeName("休");
+                }
+                if (freeDays.contains(date)) {
+                    dayInfo.setType(1);//调休
+                    dayInfo.setTypeName("调");
+                }
+                if (workdays.contains(date)){
+                    dayInfo.setType(2);//工作日
+                    dayInfo.setTypeName("班");
                 }
-//                if (freeDays.contains(date)) {
-//                    dayInfo.setType(1);//调休
-//                }
-//                if (workdays.contains(date)){
-//                    dayInfo.setType(2);//工作日
-//                }
                 calendar.add(dayInfo);
             }
 
@@ -249,6 +257,10 @@ public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attenda
         YearMonth yearMonth = YearMonth.parse(month, formatter);
         LocalDate startDate = yearMonth.atDay(1);
         LocalDate endDate = startDate.plusMonths(1);
+        List<LocalDate> workdays = getWorkDays(yearMonth);
+        List<LocalDate> freeDays = getFreeDays(yearMonth);
+        Set<LocalDate> specialWorkDays = new HashSet<>(workdays);
+        Set<LocalDate> specialRestDays = new HashSet<>(freeDays);
         int year = yearMonth.getYear();
         int monthValue = yearMonth.getMonthValue();
         String title=year+"年"+monthValue+"月考勤表";
@@ -281,7 +293,7 @@ public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attenda
 //                }
                 //只计算工作日
                 if (applyForm.getSumTime().compareTo(new BigDecimal(8.0)) > 0) {
-                    int workDays = WorkDayCalculateUtils.getWorkDaysCountInRange(formatter2.format(applyForm.getStartTime()), formatter2.format(applyForm.getEndTime()), 0);
+                    int workDays = getWorkDaysCountInRange(applyForm.getStartTime().toLocalDate(), applyForm.getEndTime().toLocalDate(), specialWorkDays, specialRestDays);
                     applyForm.setSumTime((BigDecimal.valueOf(workDays).multiply(BigDecimal.valueOf(8))));
                 }
 //                if (applyForm.getApplyId().equals("LEW1157")) {
@@ -356,23 +368,17 @@ public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attenda
                     continue;
                 }
                 LocalTime endTime = clockEndTime.toLocalTime();
-                if (type == JIA_BAN) {
-                    //加班处理
-                    BigDecimal workHour = staff.getWorkHour();
-                    if (workHour != null) {
-                        LocalDate clockDate = staff.getClockDate();
-                        if (WorkDayCalculateUtils.isStatutoryRestDay(clockDate)) {
-                            fadingStaffOvertime += workHour.doubleValue();
-                        } else if (!WorkDayCalculateUtils.isWorkDay(clockDate)) {
-                            zhoumoStaffOvertime += workHour.doubleValue();
-                        } else {
-                            otherStaffOvertime += workHour.doubleValue() - 8.0f;
-                        }
+                if (staff.getIsOver() != null && staff.getIsOver() == 1 && staff.getOvertimeHour() != null) {
+                    LocalDate clockDate = staff.getClockDate();
+                    if (WorkDayCalculateUtils.isLegalHoliday(clockDate)) {
+                        fadingStaffOvertime += staff.getOvertimeHour().doubleValue();
+                    } else if (!isWorkDay(clockDate, specialWorkDays, specialRestDays)) {
+                        zhoumoStaffOvertime += staff.getOvertimeHour().doubleValue();
+                    } else {
+                        otherStaffOvertime += staff.getOvertimeHour().doubleValue();
                     }
-//                    if (staff.getName().equals("陈寿明")) {
-//                        System.out.println("陈寿明加班:"+zhoumoStaffOvertime+"+"+fadingStaffOvertime+"+"+otherStaffOvertime);
-//                    }
-                } else if (type == BAI_BAN) {
+                }
+                if (type == BAI_BAN) {
                     if (startTime.isAfter(LocalTime.of(8,0,0))) {
                         Duration duration = Duration.between(LocalTime.of(8,0,0),startTime );
                         long v = duration.toMinutes();
@@ -711,6 +717,26 @@ public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attenda
         return dayWeekName;
     }
 
+    private int getWorkDaysCountInRange(LocalDate startDate, LocalDate endDate, Set<LocalDate> specialWorkDays, Set<LocalDate> specialRestDays) {
+        int count = 0;
+        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
+            if (isWorkDay(date, specialWorkDays, specialRestDays)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private boolean isWorkDay(LocalDate date, Set<LocalDate> specialWorkDays, Set<LocalDate> specialRestDays) {
+        if (specialWorkDays != null && specialWorkDays.contains(date)) {
+            return true;
+        }
+        if (specialRestDays != null && specialRestDays.contains(date)) {
+            return false;
+        }
+        return WorkDayCalculateUtils.isWorkDay(date);
+    }
+
     private List<LocalDate> getWorkDays(YearMonth yearMonth) {
         List<LocalDate> workDays = new ArrayList<>();
         LocalDate endOfMonth = yearMonth.atEndOfMonth();
@@ -769,7 +795,7 @@ public class AttendanceServiceImpl extends ServiceImpl<AttendanceMapper, Attenda
         List<LocalDate> holidays = new ArrayList<>();
 
         //todo 手动去维护或者数据库手动维护
-        // 2025年中国法定节假日列表
+        // 2026年中国法定节假日列表
         String[] holidayDates = {
                 "2025-01-01", // 元旦
                 "2025-01-28", "2025-01-29", "2025-01-30", "2025-01-31", "2025-02-03", "2025-02-04", // 春节

+ 173 - 170
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/AttendanceStaffServiceImpl.java

@@ -37,6 +37,8 @@ import static com.management.platform.constant.Constant.*;
 @Service
 public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMapper, AttendanceStaff> implements AttendanceStaffService {
 
+    private static final BigDecimal STANDARD_WORK_HOUR = BigDecimal.valueOf(8);
+
     @Resource
     private AttendanceService attendanceService;
 
@@ -78,7 +80,7 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         LocalDate nextMonthDate = startDate.plusMonths(1);//下月开始第一天
         ArrayList<LocalDate> localDates = new ArrayList<>();//本月所有日期
         for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
-           localDates.add(date);
+            localDates.add(date);
         }
         List<AttendanceRule> ruleList = attendanceRuleService.list();
 
@@ -90,6 +92,8 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         System.out.println("开始获取applyFormList==="+new Date().toString());
         //获取申请单数据
         List<ApplyForm> applyFormList=applyFormService.getAllListByFirstDateAndLastDate(startDate,nextMonthDate);
+        Set<LocalDate> specialWorkDays = getSpecialDates(startDate, endDate, 1);
+        Set<LocalDate> specialRestDays = getSpecialDates(startDate, endDate, 0);
 
         List<AttendanceStaff> addList = new ArrayList<>();
         Map<String, List<Attendance>> listMap = attendanceList.stream().collect(Collectors.groupingBy(Attendance::getJobNumber));
@@ -125,6 +129,7 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                                 staff.setWorkHour(BigDecimal.valueOf(8));
                                 staff.setAttendanceType(QING_JIA);
                                 staff.setAttendanceTypeName(applyForm.getApplyBillName());
+                                markNoOvertime(staff);
 
                                 addList.add(staff);
                                 continue;
@@ -147,7 +152,10 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                         else if (applyForm.getType()==4){
                             //加班以实际打卡的时间和时长为准
                             List<Attendance> dayAttendance = attendances.stream().filter(a -> a.getClockTime().toLocalDate().equals(date)).collect(Collectors.toList());
-                            dayAttendance = resolveDayClockAttendances(date, dayAttendance, attendances);
+                            //7点之前的算是前一天的打卡时间
+                            dayAttendance = dayAttendance.stream().filter(a->a.getClockTime().toLocalTime().isAfter(LocalTime.of(6,0)))
+                                    .sorted(Comparator.comparing(Attendance::getClockTime))
+                                    .collect(Collectors.toList());
                             if (dayAttendance.size() > 0) {
                                 if (dayAttendance.size() == 1) {
                                     staff.setClockStartTime(dayAttendance.get(0).getClockTime());
@@ -155,6 +163,7 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                                     staff.setWorkHour(new BigDecimal(0));
                                     staff.setAttendanceType(YI_CHANG);
                                     staff.setAttendanceTypeName("班次异常");
+                                    markNoOvertime(staff);
                                 } else {
                                     //取上下班时间;按时间排序后取第一个和最后一个时间
                                     if (attendances.size() > 0) {
@@ -175,10 +184,11 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                                             staff.setAttendanceTypeName("班次异常");
                                             continue;
                                         } else {
-                                            BigDecimal bigDecimal = calculateWorkHours(startClockTime, endClockTime, workType.getAttendanceType());
+                                            BigDecimal bigDecimal = calculateWorkHours(startClockTime, endClockTime, workType.getAttendanceType(), specialWorkDays, specialRestDays);
                                             staff.setWorkHour(bigDecimal.compareTo(new BigDecimal(0)) > 0 ? bigDecimal : new BigDecimal(0));
                                             staff.setAttendanceType(workType.getAttendanceType());
-                                            staff.setAttendanceTypeName("加班");
+                                            staff.setAttendanceTypeName(workType.getAttendanceTypeName());
+                                            applyOvertime(staff, specialWorkDays, specialRestDays);
                                         }
                                     }
                                 }
@@ -262,6 +272,7 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                                 staff.setAttendanceType(XIAO_YE_BAN_YI_CHANG_2);
                                 staff.setAttendanceTypeName("异常小夜班2");
                             }
+                            markNoOvertime(staff);
                             addList.add(staff);
                             continue;
                         }
@@ -285,21 +296,22 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
 
                     AttendanceStaff workTimeType = getWorkTimeType(ruleList, curDateAttendances);
                     if (workTimeType != null) {
+                        //上面处理了请假的数据 存在请假的考勤就不需要判断了
                         staff.setClockStartTime(workTimeType.getClockStartTime());
                         staff.setAttendanceType(workTimeType.getAttendanceType());
                         staff.setAttendanceTypeName(workTimeType.getAttendanceTypeName());
 //                        ruleList.stream().filter(r -> r.getId().intValue() == workTimeType.getAttendanceType().intValue()).findFirst().ifPresent(r -> staff.setAttendanceTypeName(r.getName()));
                         //白班或者异常小夜班,下班时间是当天
                         LocalDateTime endClockTime = null;
-                        if (workTimeType.getAttendanceType() == JIA_BAN) {
-                            List<Attendance> effectiveDayAttendances = resolveDayClockAttendances(date, curDateAttendances, attendances);
-                            if (effectiveDayAttendances.isEmpty()) {
-                                continue;
-                            }
-                            staff.setClockStartTime(effectiveDayAttendances.get(0).getClockTime());
-                            endClockTime = effectiveDayAttendances.get(effectiveDayAttendances.size() - 1).getClockTime();
-                        }
-                        else if (workTimeType.getAttendanceType().intValue() == BAI_BAN || workTimeType.getAttendanceType().intValue() == BAI_BAN_YI_CHANG_1 || workTimeType.getAttendanceType().intValue() == BAI_BAN_YI_CHANG_2 ||
+//                        if (workTimeType.getIsOver() == 1) {
+//                            curDateAttendances.stream().filter(a->a.getClockTime().toLocalTime().isAfter(LocalTime.of(6, 0))).findFirst()
+//                                    .ifPresent(a->staff.setClockStartTime(a.getClockTime()));
+//                            if (staff.getClockStartTime() != null) {
+//                                endClockTime = curDateAttendances.stream().max(Comparator.comparing(Attendance::getClockTime)).get().getClockTime();
+//                            }
+//                        }
+//                        else
+                        if (workTimeType.getAttendanceType().intValue() == BAI_BAN || workTimeType.getAttendanceType().intValue() == BAI_BAN_YI_CHANG_1 || workTimeType.getAttendanceType().intValue() == BAI_BAN_YI_CHANG_2 ||
                                 workTimeType.getAttendanceType().intValue() == ZHONG_BAN || workTimeType.getAttendanceType().intValue() == XIAO_YE_BAN_YI_CHANG_1 || workTimeType.getAttendanceType().intValue() == XIAO_YE_BAN_YI_CHANG_2) {
                             endClockTime = curDateAttendances.stream().max(Comparator.comparing(Attendance::getClockTime)).get().getClockTime();
                         } else {
@@ -312,24 +324,29 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                             }
                         }
                         staff.setClockEndTime(endClockTime);
-                        if (staff.getClockEndTime() == null) {
-                            staff.setClockEndTime(staff.getClockStartTime());
-                        }
+//                        if (staff.getClockEndTime() == null) {
+//                            staff.setClockEndTime(staff.getClockStartTime());
+//                        }
                         //仅仅一次打卡,为异常班次
-                        if (staff.getClockEndTime().equals(staff.getClockStartTime())) {
+                        if (staff.getClockEndTime()==null||staff.getClockStartTime()==null) {
                             staff.setClockStartTime(staff.getClockStartTime());
                             staff.setAttendanceType(YI_CHANG);
                             staff.setAttendanceTypeName("班次异常");
                             staff.setWorkHour(new BigDecimal(0));
+                            markNoOvertime(staff);
                         } else {
                             //有上下班打卡时间,计算时长
-                            BigDecimal bigDecimal = calculateWorkHours(staff.getClockStartTime(), endClockTime, workTimeType.getAttendanceType());
+                            BigDecimal bigDecimal = calculateWorkHours(staff.getClockStartTime(), endClockTime, workTimeType.getAttendanceType(), specialWorkDays, specialRestDays);
                             staff.setWorkHour(bigDecimal);
+                            applyOvertime(staff, specialWorkDays, specialRestDays);
                         }
                     }
 
                     //过滤掉空数据
                     if (!StringUtils.isEmpty(staff.getJobNumber()) && (staff.getClockStartTime()!=null || staff.getClockEndTime()!=null)) {
+                        if (staff.getIsOver() == null) {
+                            markNoOvertime(staff);
+                        }
                         addList.add(staff);
                     }
                 }
@@ -339,56 +356,6 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         return msg;
     }
 
-    private static final LocalTime EARLY_MORNING_CUTOFF = LocalTime.of(7, 0);
-    private static final LocalTime NIGHT_SHIFT_START_THRESHOLD = LocalTime.of(16, 0);
-
-    /**
-     * 解析当天有效打卡:同日≥2次保留全部;仅1次凌晨卡且前日未闭合夜班则归前日下班。
-     */
-    private List<Attendance> resolveDayClockAttendances(LocalDate date, List<Attendance> dayAttendances, List<Attendance> allAttendances) {
-        if (dayAttendances == null || dayAttendances.isEmpty()) {
-            return Collections.emptyList();
-        }
-        List<Attendance> sorted = dayAttendances.stream()
-                .sorted(Comparator.comparing(Attendance::getClockTime))
-                .collect(Collectors.toList());
-        if (sorted.size() >= 2) {
-            return sorted;
-        }
-        Attendance only = sorted.get(0);
-        if (!only.getClockTime().toLocalTime().isAfter(EARLY_MORNING_CUTOFF)
-                && isPreviousDayUnclosedNightShift(date, allAttendances, only.getClockTime())) {
-            return Collections.emptyList();
-        }
-        return sorted;
-    }
-
-    /**
-     * 前一日末卡为晚班/夜班且未形成完整班次时,当日凌晨单卡视为前一日下班。
-     */
-    private boolean isPreviousDayUnclosedNightShift(LocalDate date, List<Attendance> allAttendances, LocalDateTime earlyPunch) {
-        LocalDate prevDate = date.minusDays(1);
-        List<Attendance> prevDay = allAttendances.stream()
-                .filter(a -> a.getClockTime().toLocalDate().equals(prevDate))
-                .sorted(Comparator.comparing(Attendance::getClockTime))
-                .collect(Collectors.toList());
-        if (prevDay.isEmpty()) {
-            return false;
-        }
-        if (prevDay.size() >= 2) {
-            long hours = Duration.between(prevDay.get(0).getClockTime(), prevDay.get(prevDay.size() - 1).getClockTime()).toHours();
-            if (hours >= 8) {
-                return false;
-            }
-        }
-        Attendance lastPrev = prevDay.get(prevDay.size() - 1);
-        if (lastPrev.getClockTime().toLocalTime().isBefore(NIGHT_SHIFT_START_THRESHOLD)) {
-            return false;
-        }
-        long hoursSinceLastPrev = Duration.between(lastPrev.getClockTime(), earlyPunch).toHours();
-        return hoursSinceLastPrev < 8;
-    }
-
     private AttendanceStaff getWorkTimeType(List<AttendanceRule> ruleList, List<Attendance> attendanceList) {
         AttendanceRule baiBanRule = ruleList.get(0);
         AttendanceRule yichangBaiBan1Rule = ruleList.get(1);
@@ -402,12 +369,6 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         AttendanceStaff staff = new AttendanceStaff();
         for (Attendance attendance : attendanceList) {
             staff.setClockStartTime(attendance.getClockTime());
-            //如果是非工作日,就是加班
-            if (!WorkDayCalculateUtils.isWorkDay(attendance.getClockTime().toLocalDate())) {
-                staff.setAttendanceType(JIA_BAN);
-                staff.setAttendanceTypeName("加班");
-                return staff;
-            }
             LocalTime workTime = attendance.getClockTime().toLocalTime();
             if (!workTime.isBefore(baiBanRule.getBeginWorkStartTime()) && !workTime.isAfter(baiBanRule.getBeginWorkEndTime())) {
                 staff.setAttendanceType(BAI_BAN);
@@ -450,6 +411,80 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         return null;
     }
 
+    private void applyOvertime(AttendanceStaff staff, Set<LocalDate> specialWorkDays, Set<LocalDate> specialRestDays) {
+        BigDecimal workHour = staff.getWorkHour();
+        if (workHour == null || staff.getClockDate() == null) {
+            staff.setIsOver(0);
+            staff.setOvertimeHour(BigDecimal.ZERO);
+            return;
+        }
+
+        BigDecimal overtimeHour;
+        if (WorkDayCalculateUtils.isLegalHoliday(staff.getClockDate()) || !isWorkDay(staff.getClockDate(), specialWorkDays, specialRestDays)) {
+            overtimeHour = workHour;
+        } else {
+            overtimeHour = workHour.subtract(STANDARD_WORK_HOUR);
+        }
+
+        if (overtimeHour.compareTo(BigDecimal.ZERO) <= 0) {
+            staff.setIsOver(0);
+            staff.setOvertimeHour(BigDecimal.ZERO);
+        } else {
+            staff.setIsOver(1);
+            staff.setOvertimeHour(overtimeHour.setScale(1, RoundingMode.HALF_UP));
+        }
+    }
+
+    private static boolean isWorkDay(LocalDate date, Set<LocalDate> specialWorkDays, Set<LocalDate> specialRestDays) {
+        if (specialWorkDays != null && specialWorkDays.contains(date)) {
+            return true;
+        }
+        if (specialRestDays != null && specialRestDays.contains(date)) {
+            return false;
+        }
+        return WorkDayCalculateUtils.isWorkDay(date);
+    }
+
+    private Set<LocalDate> getSpecialDates(LocalDate startDate, LocalDate endDate, int type) {
+        return specialDateSetService.list(new QueryWrapper<SpecialDateSet>()
+                        .between("special_date", startDate, endDate)
+                        .eq("type", type))
+                .stream()
+                .map(SpecialDateSet::getSpecialDate)
+                .collect(Collectors.toSet());
+    }
+
+    private void markNoOvertime(AttendanceStaff staff) {
+        staff.setIsOver(0);
+        staff.setOvertimeHour(BigDecimal.ZERO);
+    }
+
+    private String getAttendanceColor(AttendanceStaff staff) {
+        Integer attendanceType = staff.getAttendanceType();
+        if (attendanceType == null) {
+            return "#2e2e2e";
+        }
+        if (attendanceType == YI_CHANG) {
+            return "#F56C6C";
+        }
+        if (staff.getIsOver() != null && staff.getIsOver() == 1) {
+            return "#E6A23C";
+        }
+        if (attendanceType == DA_YE_BAN
+                || attendanceType == XIAO_YE_BAN
+                || attendanceType == XIAO_YE_BAN_2
+                || attendanceType == XIAO_YE_BAN_YI_CHANG_1
+                || attendanceType == XIAO_YE_BAN_YI_CHANG_2) {
+            return "#67C23A";
+        }
+        if (attendanceType == ZHONG_BAN
+                || attendanceType == QING_JIA
+                || attendanceType == TI_QIAN_XIA_BAN) {
+            return "#909399";
+        }
+        return "#2e2e2e";
+    }
+
     @Override
     public HttpRespMsg getListData(String month,String userId,Integer pageIndex ,Integer pageSize,HttpServletRequest request) {
         HttpRespMsg msg = new HttpRespMsg();
@@ -464,51 +499,17 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         }else {
             wrapper.eq("job_number", u.getJobNumber());
         }
-        List<HighTemperatureSet> dateSetList = highTemperatureSetService.list();
-
-        // 定义午休时间段的开始和结束
-        LocalTime noonBreakStart = LocalTime.of(12, 0);
-        LocalTime noonBreakEndHotDay = LocalTime.of(13, 30); // 高温日结束时间
-        LocalTime noonBreakEndNormalDay = LocalTime.of(13, 0); // 普通日结束时间
-
         IPage<AttendanceStaff> iPage = page(new Page(pageIndex, pageSize), wrapper);
         HashMap<String, Object> map = new HashMap<>();
         map.put("total", iPage.getTotal());
         List<AttendanceStaff> records = iPage.getRecords();
         for (AttendanceStaff record : records) {
-            boolean isHotDay = dateSetList.stream().anyMatch(d -> d != null && !d.getStartDate().isAfter(record.getClockDate())&&!d.getEndDate().isBefore(record.getClockDate()));
-            // 获取当天的打卡时间(转换为当天的时间)
-            LocalDateTime clockStart = record.getClockStartTime();
-            LocalDateTime clockEnd = record.getClockEndTime();
-            // 检查是否跨过午休时间
-            if (clockStart != null && clockEnd != null) {
-                if (clockStart.toLocalTime().isBefore(noonBreakStart) &&
-                        clockEnd.toLocalTime().isAfter(isHotDay ? noonBreakEndHotDay : noonBreakEndNormalDay)) {
-
-                    // 完整午休时间扣除
-                    if (isHotDay) {
-                        record.setWorkHour(record.getWorkHour().subtract(BigDecimal.valueOf(1.5))); // 高温日扣1.5小时
-                    } else {
-                        record.setWorkHour(record.getWorkHour().subtract(BigDecimal.valueOf(1))); // 普通日扣1小时
-                    }
-                }
-                // 部分重叠的情况
-                else {
-                    // 计算重叠的午休时间(分钟)
-                    long overlapMinutes = calculateOverlapMinutes(
-                            clockStart.toLocalTime(),
-                            clockEnd.toLocalTime(),
-                            noonBreakStart,
-                            isHotDay ? noonBreakEndHotDay : noonBreakEndNormalDay
-                    );
-
-                    if (overlapMinutes > 0) {
-                        double overlapHours = overlapMinutes / 60.0;
-                        record.setWorkHour(record.getWorkHour().subtract(BigDecimal.valueOf(overlapHours)));
-                    }
-                }
+            if (record.getWorkHour() != null) {
                 record.setWorkHour(record.getWorkHour().setScale(1, RoundingMode.HALF_UP));
             }
+            if (record.getOvertimeHour() != null) {
+                record.setOvertimeHour(record.getOvertimeHour().setScale(1, RoundingMode.HALF_UP));
+            }
         }
         map.put("records",records );
         msg.setData(map);
@@ -548,21 +549,24 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         List<LocalDate> workdays = getWorkDays(yearMonth);
         // 2.2 获取自定义调休日列表
         List<LocalDate> freeDays = getFreeDays(yearMonth);
+        Set<LocalDate> specialWorkDays = new HashSet<>(workdays);
+        Set<LocalDate> specialRestDays = new HashSet<>(freeDays);
 
         for (LocalDate d = startDate; !d.isAfter(endDate); d = d.plusDays(1)) {
             AttendanceStaff staff = new AttendanceStaff();
             staff.setClockDate(d);
-            // 判断是否为休息日(周末或自定义假日)
-            if (d.getDayOfWeek() == DayOfWeek.SATURDAY
-                    || d.getDayOfWeek() == DayOfWeek.SUNDAY
-                    || customHolidays.contains(d)) {
+            if (WorkDayCalculateUtils.isLegalHoliday(d)) {
+                staff.setAttendanceType(XIU_XI);//法定节假日
+                staff.setAttendanceTypeName("法定节假日");
+                staff.setColor("#8e44ad");
+            } else if (!isWorkDay(d, specialWorkDays, specialRestDays) || customHolidays.contains(d)) {
                 staff.setAttendanceType(XIU_XI);//休息
-                staff.setAttendanceTypeName("休息");
+                staff.setAttendanceTypeName("休息");
                 staff.setColor("#2e2e2e");
             }
             if (freeDays.contains(d)) {
                 staff.setAttendanceType(TIAO_XIU);//调休
-                staff.setAttendanceTypeName("休");
+                staff.setAttendanceTypeName("休息日");
                 staff.setColor("#2e2e2e");
             }
             if (workdays.contains(d)){
@@ -593,42 +597,7 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
             if (first.isPresent()) {
                 AttendanceStaff s = first.get();
                 BeanUtils.copyProperties(s,staff);
-                if (staff.getAttendanceType()==BAI_BAN){
-                    staff.setColor("#2e2e2e");
-                }
-                else if (staff.getAttendanceType()==BAI_BAN_YI_CHANG_1){
-                    staff.setColor("#2e2e2e");//绿色
-                }
-                else if (staff.getAttendanceType()==BAI_BAN_YI_CHANG_2){
-                    staff.setColor("#2e2e2e");//绿色
-                }
-                else if (staff.getAttendanceType()==DA_YE_BAN){
-                    staff.setColor("#67C23A");//绿色
-                }
-                else if (staff.getAttendanceType()==XIAO_YE_BAN){
-                    staff.setColor("#67C23A");//绿色
-                }
-                else if (staff.getAttendanceType()==XIAO_YE_BAN_2){
-                    staff.setColor("#67C23A");//绿色
-                }
-                else if (staff.getAttendanceType()==XIAO_YE_BAN_YI_CHANG_1){
-                    staff.setColor("#67C23A");//绿色
-                }
-                else if (staff.getAttendanceType()==XIAO_YE_BAN_YI_CHANG_2){
-                    staff.setColor("#67C23A");//绿色
-                }
-                else if (staff.getAttendanceType()==ZHONG_BAN){
-                    staff.setColor("#909399");
-                }
-                else if (staff.getAttendanceType()==QING_JIA){
-                    staff.setColor("#909399");
-                }
-                else if (staff.getAttendanceType()==JIA_BAN){
-                    staff.setColor("#909399");
-                }
-                else if (staff.getAttendanceType()==TI_QIAN_XIA_BAN){
-                    staff.setColor("#909399");
-                }
+                staff.setColor(getAttendanceColor(staff));
             }else {
                 if (staff.getId()==null&&staff.getAttendanceType()==null) {
                     staff.setAttendanceType(YI_CHANG);//班次异常
@@ -641,6 +610,7 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
         }
         for (AttendanceStaff staff : allList) {
             Integer type = staff.getAttendanceType();
+            Integer isOver = staff.getIsOver();
             LocalDate clockDate = staff.getClockDate();
             String format = clockDate.format(formatter1);
             List<ApplyForm> applyFormList= applyFormService.getListByDate(format,userId==null|| StringUtils.isEmpty(userId)?request.getHeader("Token"):userId);
@@ -655,7 +625,34 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                 String startStr = startTime.format(timeFormatter);
                 String endStr = endTime.format(timeFormatter);
                 List<HashMap<String, Object>> maplist = new ArrayList<>();
-                if (type == BAI_BAN) {
+                // 周末加班(非法定节假日)不判迟到早退;法定节假日加班按班次规则正常判断
+                boolean isLegalOvertime = isOver != null && isOver == 1 && WorkDayCalculateUtils.isLegalHoliday(clockDate);
+                boolean isRestOvertime = isOver != null && isOver == 1 && !WorkDayCalculateUtils.isLegalHoliday(clockDate) && !isWorkDay(clockDate, specialWorkDays, specialRestDays);
+                if (isLegalOvertime) {
+                    HashMap<String, Object> startMap = new HashMap<>();
+                    startMap.put("res", "正常");
+                    startMap.put("msg", startStr + "上班考勤打卡");
+                    maplist.add(startMap);
+                    HashMap<String, Object> endMap = new HashMap<>();
+                    endMap.put("res", "正常");
+                    endMap.put("msg", endStr + "下班考勤打卡");
+                    maplist.add(endMap);
+                    staff.setAttendanceTypeName("法定节假日加班");
+                    staff.setColor(getAttendanceColor(staff));
+                }
+                else if (isRestOvertime) {
+                    HashMap<String, Object> startMap = new HashMap<>();
+                    startMap.put("res", "正常");
+                    startMap.put("msg", startStr + "上班考勤打卡");
+                    maplist.add(startMap);
+                    HashMap<String, Object> endMap = new HashMap<>();
+                    endMap.put("res", "正常");
+                    endMap.put("msg", endStr + "下班考勤打卡");
+                    maplist.add(endMap);
+                    staff.setAttendanceTypeName("休息日加班");
+                    staff.setColor(getAttendanceColor(staff));
+                }
+                else if (type == BAI_BAN) {
                     if (startTime.isAfter(LocalTime.of(8, 0, 0))) {
                         HashMap<String, Object> map = new HashMap<>();
                         map.put("res", "晚点");
@@ -1147,14 +1144,16 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                         maplist.add(map);
                     }
                     staff.setAttendanceTypeName("异常小夜班2");
-                } else if (type == JIA_BAN) {
-                    //提取加班数据
+                }
+                if (isOver != null && isOver == 1) {
                     HashMap<String, Object> map = new HashMap<>();
+                    BigDecimal overtimeHour = staff.getOvertimeHour() == null ? BigDecimal.ZERO : staff.getOvertimeHour();
                     String str = staff.getClockDate().format(formatter1) + " " + staff.getClockStartTime().format(hhMMFormatter)
-                            + "-" + staff.getClockEndTime().format(hhMMFormatter) + ", 共" + staff.getWorkHour().toString() + "小时";
+                            + "-" + staff.getClockEndTime().format(hhMMFormatter) + ", 加班" + overtimeHour.toString() + "小时";
                     map.put("res", "加班");
                     map.put("msg", str);
                     maplist.add(map);
+                    staff.setHasOverTime(overtimeHour.compareTo(BigDecimal.ZERO) > 0);
                 }
                 if (!maplist.isEmpty()) {
                     HashMap<String, Object> map = new HashMap<>();
@@ -1376,12 +1375,13 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
 
         // 2025年中国法定节假日列表
         String[] holidayDates = {
-                "2025-01-01", // 元旦
-                "2025-01-28", "2025-01-29", "2025-01-30", "2025-01-31", "2025-02-03", "2025-02-04", // 春节
-                "2025-04-04", // 清明节
-                "2025-05-01", "2025-05-02", "2025-05-05", // 劳动节
-                "2025-06-02", // 端午节
-                "2025-10-01", "2025-10-02", "2025-10-03", "2025-10-06", "2025-10-07", "2025-10-08" // 国庆节
+                "2026-01-01","2026-01-02",//元旦
+                "2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19", "2026-02-20","2026-02-23",//春节
+                "2026-04-06",//清明节
+                "2026-05-01","2026-05-02",//劳动节
+                "2026-06-19",//端午节
+                "2026-09-25",//中秋节
+                "2026-10-01","2026-10-02","2026-10-03",//国庆节
         };
         // 将字符串日期转换为LocalDate并添加到列表中
         DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
@@ -1402,11 +1402,15 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
     }
 
     public static BigDecimal calculateWorkHours(LocalDateTime start, LocalDateTime end, int workType) {
+        return calculateWorkHours(start, end, workType, Collections.emptySet(), Collections.emptySet());
+    }
+
+    public static BigDecimal calculateWorkHours(LocalDateTime start, LocalDateTime end, int workType, Set<LocalDate> specialWorkDays, Set<LocalDate> specialRestDays) {
         String startTime = start.format(DateTimeFormatter.ofPattern("HH:mm"));
         String endTime = end.format(DateTimeFormatter.ofPattern("HH:mm"));
 
         LocalDate startDate = start.toLocalDate();
-        boolean isWorkDay = WorkDayCalculateUtils.isWorkDay(startDate);
+        boolean isWorkDay = isWorkDay(startDate, specialWorkDays, specialRestDays);
 
         // 12:00-13:00为午休,中间来的得从下午上班时间开始算
         if (startTime.compareTo("12:00") > 0 && startTime.compareTo("13:00") < 0) {
@@ -1431,8 +1435,8 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
                     hours -= 1;
                 }
                 if (workType == BAI_BAN) {
-                    //正常白班减去晚休时间,17:30-18:00
-                    if (startHour <= 17 && endHour >= 18) {
+                    //正常白班下班后半小时不计入加班时间。
+                    if (startHour < 18 && (endHour > 17 || (endHour == 17 && endMinute > 0))) {
                         hours -= 0.5;
                     }
                 }
@@ -1485,9 +1489,8 @@ public class AttendanceStaffServiceImpl extends ServiceImpl<AttendanceStaffMappe
             }
         }
         long totalMinutes = workDuration.toMinutes();
-        if (totalMinutes < 60) return 0;
-        long overtimeMinutes = totalMinutes;
-        return (int) Math.floor(overtimeMinutes / 30.0);
+        if (totalMinutes < 30) return 0;
+        return (int) Math.floor(totalMinutes / 30.0);
     }
 
 

+ 27 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/util/WorkDayCalculateUtils.java

@@ -15,6 +15,7 @@ public class WorkDayCalculateUtils {
 
     private static final String KEY_SPECIAL_WORK_DAYS = "work";
     private static final String KEY_SPECIAL_REST_DAYS = "rest";
+    private static final String KEY_LEGAL_HOLIDAYS = "legal";
 
     private static final HashMap<String, HashMap> YEAR_DEFINE = new HashMap<>();
     static {
@@ -158,6 +159,15 @@ public class WorkDayCalculateUtils {
                 "2026-09-25",//中秋节
                 "2026-10-01","2026-10-02","2026-10-05","2026-10-06","2026-10-07",//国庆节
         });
+        map2026.put(KEY_LEGAL_HOLIDAYS, new String[]{
+                "2026-01-01",//元旦
+                "2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19",//春节
+                "2026-04-05",//清明节
+                "2026-05-01", "2026-05-02",//劳动节
+                "2026-06-19",//端午节
+                "2026-09-25",//中秋节
+                "2026-10-01", "2026-10-02", "2026-10-03",//国庆节
+        });
         YEAR_DEFINE.put("2026", map2026);
     }
 
@@ -359,6 +369,23 @@ public class WorkDayCalculateUtils {
         return restDays != null && isInArray(dateStr, restDays);
     }
 
+    /**
+     * 是否为法定三倍工资节假日,不包含调休补休日。
+     */
+    public static boolean isLegalHoliday(LocalDate date) {
+        HashMap<String, String[]> map = YEAR_DEFINE.get(date.getYear() + "");
+        if (map == null) {
+            return false;
+        }
+        String dateStr = dateTimeFormatter.format(date);
+        String[] legalHolidays = map.get(KEY_LEGAL_HOLIDAYS);
+        if (legalHolidays != null) {
+            return isInArray(dateStr, legalHolidays);
+        }
+        String[] restDays = map.get(KEY_SPECIAL_REST_DAYS);
+        return restDays != null && isInArray(dateStr, restDays);
+    }
+
 
     public static void main(String[] args) {
         System.out.println(getWorkDaysCountInRange("2022-08-06","2022-08-08", 1));

+ 4 - 1
fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/AttendanceStaffMapper.xml

@@ -14,11 +14,14 @@
         <result column="clock_end_time" property="clockEndTime" />
         <result column="work_hour" property="workHour" />
         <result column="attendance_type" property="attendanceType" />
+        <result column="attendance_type_name" property="attendanceTypeName" />
+        <result column="is_over" property="isOver" />
+        <result column="overtime_hour" property="overtimeHour" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, month, clock_date, job_number, name, dept_name, clock_start_time, clock_end_time, work_hour, attendance_type
+        id, month, clock_date, job_number, name, dept_name, clock_start_time, clock_end_time, work_hour, attendance_type, attendance_type_name, is_over, overtime_hour
     </sql>
 
 </mapper>