fix(wms/attendance): 优化午休打卡模式识别逻辑

1. 重构打卡记录分割逻辑,将原简单中值法改为基于时间区间和间隔检测的智能分割
2. 新增午休打卡模式检测:支持区间内长间隔、提前下班+午饭回来、午休晚归三种场景识别
3. 当检测到午休打卡模式时,按实际打卡间隔进行合理分割;未检测到时回退使用原中值法
4. 提升考勤统计的准确性,避免因午休打卡时间分布不均导致的上下班时段误判
This commit is contained in:
2026-05-30 17:38:13 +08:00
parent 6a5220ad78
commit 62da44382a

View File

@@ -386,18 +386,91 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
if (hasPeriod2) {
LocalTime p1End = toLocalTime(schedule.getShiftEndTime());
LocalTime p2Start = toLocalTime(schedule.getShiftStartTime2());
LocalTime split = LocalTime.of(
(p1End.getHour() + p2Start.getHour()) / 2,
(p1End.getMinute() + p2Start.getMinute()) / 2);
// 按理论时间范围分割p1End之前、中间区间、p2Start之后
List<AttendanceRecords> beforeGap = new ArrayList<>();
List<AttendanceRecords> inGap = new ArrayList<>();
List<AttendanceRecords> afterGap = new ArrayList<>();
for (AttendanceRecords r : records) {
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
if (!t.isAfter(p1End)) {
beforeGap.add(r);
} else if (!t.isBefore(p2Start)) {
afterGap.add(r);
} else {
inGap.add(r);
}
}
List<AttendanceRecords> p1Records = new ArrayList<>();
List<AttendanceRecords> p2Records = new ArrayList<>();
for (AttendanceRecords r : records) {
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
if (t.isBefore(split)) {
p1Records.add(r);
} else {
p2Records.add(r);
boolean lunchPatternFound = false;
// 检测[p1End, p2Start]区间内是否有午休打卡(取最远两条判断>10分钟按最大连续间隔切开
if (inGap.size() >= 2) {
LocalDateTime firstInGap = toLocalDateTime(inGap.get(0).getChecktime());
LocalDateTime lastInGap = toLocalDateTime(inGap.get(inGap.size() - 1).getChecktime());
if (Duration.between(firstInGap, lastInGap).getSeconds() > 600) {
int splitIdx = 0;
long maxGap = 0;
for (int i = 0; i < inGap.size() - 1; i++) {
LocalDateTime t1 = toLocalDateTime(inGap.get(i).getChecktime());
LocalDateTime t2 = toLocalDateTime(inGap.get(i + 1).getChecktime());
long gap = Duration.between(t1, t2).getSeconds();
if (gap > maxGap) {
maxGap = gap;
splitIdx = i;
}
if (gap > 600) {
break;
}
}
p1Records = new ArrayList<>(beforeGap);
p1Records.addAll(inGap.subList(0, splitIdx + 1));
p2Records = new ArrayList<>(inGap.subList(splitIdx + 1, inGap.size()));
p2Records.addAll(afterGap);
lunchPatternFound = true;
}
}
// 检测边界beforeGap最后一条 与 inGap第一条 间隔>10分钟提前下班+午饭回来)
if (!lunchPatternFound && !beforeGap.isEmpty() && !inGap.isEmpty()) {
LocalDateTime lastBefore = toLocalDateTime(beforeGap.get(beforeGap.size() - 1).getChecktime());
LocalDateTime firstIn = toLocalDateTime(inGap.get(0).getChecktime());
if (Duration.between(lastBefore, firstIn).getSeconds() > 600) {
p1Records = new ArrayList<>(beforeGap);
p2Records = new ArrayList<>(inGap);
p2Records.addAll(afterGap);
lunchPatternFound = true;
}
}
// 检测边界inGap最后一条 与 afterGap第一条 间隔>10分钟午休晚归
if (!lunchPatternFound && !inGap.isEmpty() && !afterGap.isEmpty()) {
LocalDateTime lastIn = toLocalDateTime(inGap.get(inGap.size() - 1).getChecktime());
LocalDateTime firstAfter = toLocalDateTime(afterGap.get(0).getChecktime());
if (Duration.between(lastIn, firstAfter).getSeconds() > 600) {
p1Records = new ArrayList<>(beforeGap);
p1Records.addAll(inGap);
p2Records = new ArrayList<>(afterGap);
lunchPatternFound = true;
}
}
if (!lunchPatternFound) {
// 没有午休打卡模式,回退原中值法
LocalTime split = LocalTime.of(
(p1End.getHour() + p2Start.getHour()) / 2,
(p1End.getMinute() + p2Start.getMinute()) / 2);
p1Records = new ArrayList<>();
p2Records = new ArrayList<>();
for (AttendanceRecords r : records) {
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
if (t.isBefore(split)) {
p1Records.add(r);
} else {
p2Records.add(r);
}
}
}