refactor(attendance): 优化考勤检查逻辑支持跨天班次
- 添加跨天班次判断方法 isCrossDayShift - 修改 getRecords 方法支持跨天查询参数 - 重构考勤记录过滤逻辑,添加 filterWindow 方法 - 调整时间段检查逻辑,区分单时段和多时段班次 - 修复早退状态码命名错误(early_1/2 统一为 early_one/two) - 优化打卡时间匹配逻辑,直接使用记录对象而非时间比较 - 更新严重程度比较列表,修正状态码对应关系
This commit is contained in:
@@ -103,22 +103,33 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取该员工该日期的所有打卡记录
|
boolean crossDay = isCrossDayShift(schedule);
|
||||||
List<AttendanceRecords> records = getRecords(schedule.getEmployeeName(), schedule.getWorkDate());
|
List<AttendanceRecords> records = getRecords(schedule.getEmployeeName(), schedule.getWorkDate(), crossDay);
|
||||||
|
|
||||||
WmsAttendanceCheck check = buildCheck(schedule, rule, records);
|
WmsAttendanceCheck check = buildCheck(schedule, rule, records);
|
||||||
|
|
||||||
// 删除已有的比对结果
|
|
||||||
baseMapper.delete(Wrappers.<WmsAttendanceCheck>lambdaQuery()
|
baseMapper.delete(Wrappers.<WmsAttendanceCheck>lambdaQuery()
|
||||||
.eq(WmsAttendanceCheck::getScheduleId, schedule.getScheduleId()));
|
.eq(WmsAttendanceCheck::getScheduleId, schedule.getScheduleId()));
|
||||||
|
|
||||||
baseMapper.insert(check);
|
baseMapper.insert(check);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新连续旷工天数
|
|
||||||
updateContinuousAbsent(startLocal, endLocal);
|
updateContinuousAbsent(startLocal, endLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isCrossDayShift(WmsAttendanceScheduleVo schedule) {
|
||||||
|
if (schedule.getShiftStartTime() == null || schedule.getShiftEndTime() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null;
|
||||||
|
if (hasPeriod2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LocalTime start = toLocalTime(schedule.getShiftStartTime());
|
||||||
|
LocalTime end = toLocalTime(schedule.getShiftEndTime());
|
||||||
|
return end.isBefore(start);
|
||||||
|
}
|
||||||
|
|
||||||
private WmsAttendanceRule getActiveRule() {
|
private WmsAttendanceRule getActiveRule() {
|
||||||
LambdaQueryWrapper<WmsAttendanceRule> wrapper = Wrappers.lambdaQuery();
|
LambdaQueryWrapper<WmsAttendanceRule> wrapper = Wrappers.lambdaQuery();
|
||||||
wrapper.eq(WmsAttendanceRule::getDelFlag, 0);
|
wrapper.eq(WmsAttendanceRule::getDelFlag, 0);
|
||||||
@@ -137,12 +148,16 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AttendanceRecords> getRecords(String employeeName, Date workDate) {
|
private List<AttendanceRecords> getRecords(String employeeName, Date workDate, boolean crossDay) {
|
||||||
AttendanceRecordsBo recordsBo = new AttendanceRecordsBo();
|
AttendanceRecordsBo recordsBo = new AttendanceRecordsBo();
|
||||||
recordsBo.setEname(employeeName);
|
recordsBo.setEname(employeeName);
|
||||||
LocalDate ld = toLocalDate(workDate);
|
LocalDate ld = toLocalDate(workDate);
|
||||||
recordsBo.setChecktimeStart(toDate(ld.atStartOfDay()));
|
recordsBo.setChecktimeStart(toDate(ld.atStartOfDay()));
|
||||||
recordsBo.setChecktimeEnd(toDate(ld.atTime(LocalTime.of(23, 59, 59))));
|
if (crossDay) {
|
||||||
|
recordsBo.setChecktimeEnd(toDate(ld.plusDays(1).atTime(LocalTime.of(23, 59, 59))));
|
||||||
|
} else {
|
||||||
|
recordsBo.setChecktimeEnd(toDate(ld.atTime(LocalTime.of(23, 59, 59))));
|
||||||
|
}
|
||||||
List<AttendanceRecordsVo> voList = attendanceRecordsService.queryList(recordsBo);
|
List<AttendanceRecordsVo> voList = attendanceRecordsService.queryList(recordsBo);
|
||||||
return voList.stream()
|
return voList.stream()
|
||||||
.map(v -> {
|
.map(v -> {
|
||||||
@@ -156,7 +171,8 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private WmsAttendanceCheck buildCheck(WmsAttendanceScheduleVo schedule, WmsAttendanceRule rule, List<AttendanceRecords> records) {
|
private WmsAttendanceCheck buildCheck(WmsAttendanceScheduleVo schedule, WmsAttendanceRule rule,
|
||||||
|
List<AttendanceRecords> records) {
|
||||||
WmsAttendanceCheck check = new WmsAttendanceCheck();
|
WmsAttendanceCheck check = new WmsAttendanceCheck();
|
||||||
check.setScheduleId(schedule.getScheduleId());
|
check.setScheduleId(schedule.getScheduleId());
|
||||||
check.setUserId(schedule.getUserId());
|
check.setUserId(schedule.getUserId());
|
||||||
@@ -167,6 +183,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
check.setShiftType(schedule.getShiftType());
|
check.setShiftType(schedule.getShiftType());
|
||||||
|
|
||||||
boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null;
|
boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null;
|
||||||
|
boolean crossDay = isCrossDayShift(schedule);
|
||||||
|
|
||||||
if (records.isEmpty()) {
|
if (records.isEmpty()) {
|
||||||
check.setOverallStatus("absent_full");
|
check.setOverallStatus("absent_full");
|
||||||
@@ -174,51 +191,71 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
return check;
|
return check;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<LocalTime> checkTimes = records.stream()
|
if (!hasPeriod2) {
|
||||||
.map(r -> toLocalDateTime(r.getChecktime()).toLocalTime())
|
List<AttendanceRecords> filtered = filterWindow(records, schedule.getWorkDate(),
|
||||||
.collect(Collectors.toList());
|
schedule.getShiftStartTime(), schedule.getShiftEndTime(), crossDay);
|
||||||
|
check.setP1StartTime(schedule.getShiftStartTime());
|
||||||
if (hasPeriod2) {
|
check.setP1EndTime(schedule.getShiftEndTime());
|
||||||
// 正常班次:按时间中点划分上下午
|
checkPeriod(check, rule, 1, filtered, schedule.getShiftStartTime(), schedule.getShiftEndTime());
|
||||||
|
} else {
|
||||||
LocalTime p1End = toLocalTime(schedule.getShiftEndTime());
|
LocalTime p1End = toLocalTime(schedule.getShiftEndTime());
|
||||||
LocalTime p2Start = toLocalTime(schedule.getShiftStartTime2());
|
LocalTime p2Start = toLocalTime(schedule.getShiftStartTime2());
|
||||||
LocalTime split = LocalTime.of(
|
LocalTime split = LocalTime.of(
|
||||||
(p1End.getHour() + p2Start.getHour()) / 2,
|
(p1End.getHour() + p2Start.getHour()) / 2,
|
||||||
(p1End.getMinute() + p2Start.getMinute()) / 2);
|
(p1End.getMinute() + p2Start.getMinute()) / 2);
|
||||||
|
|
||||||
List<LocalTime> period1Times = new ArrayList<>();
|
List<AttendanceRecords> p1Records = new ArrayList<>();
|
||||||
List<LocalTime> period2Times = new ArrayList<>();
|
List<AttendanceRecords> p2Records = new ArrayList<>();
|
||||||
for (LocalTime t : checkTimes) {
|
for (AttendanceRecords r : records) {
|
||||||
|
LocalTime t = toLocalDateTime(r.getChecktime()).toLocalTime();
|
||||||
if (t.isBefore(split)) {
|
if (t.isBefore(split)) {
|
||||||
period1Times.add(t);
|
p1Records.add(r);
|
||||||
} else {
|
} else {
|
||||||
period2Times.add(t);
|
p2Records.add(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
check.setP1StartTime(schedule.getShiftStartTime());
|
check.setP1StartTime(schedule.getShiftStartTime());
|
||||||
check.setP1EndTime(schedule.getShiftEndTime());
|
check.setP1EndTime(schedule.getShiftEndTime());
|
||||||
checkPeriod(check, rule, 1, period1Times, schedule.getShiftStartTime(), schedule.getShiftEndTime(), records);
|
checkPeriod(check, rule, 1, p1Records, schedule.getShiftStartTime(), schedule.getShiftEndTime());
|
||||||
|
|
||||||
check.setP2StartTime(schedule.getShiftStartTime2());
|
check.setP2StartTime(schedule.getShiftStartTime2());
|
||||||
check.setP2EndTime(schedule.getShiftEndTime2());
|
check.setP2EndTime(schedule.getShiftEndTime2());
|
||||||
checkPeriod(check, rule, 2, period2Times, schedule.getShiftStartTime2(), schedule.getShiftEndTime2(), records);
|
checkPeriod(check, rule, 2, p2Records, schedule.getShiftStartTime2(), schedule.getShiftEndTime2());
|
||||||
} else {
|
|
||||||
// 倒班:全天一个时段
|
|
||||||
check.setP1StartTime(schedule.getShiftStartTime());
|
|
||||||
check.setP1EndTime(schedule.getShiftEndTime());
|
|
||||||
checkPeriod(check, rule, 1, checkTimes, schedule.getShiftStartTime(), schedule.getShiftEndTime(), records);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 总体判定
|
|
||||||
calculateOverall(check, rule);
|
calculateOverall(check, rule);
|
||||||
return check;
|
return check;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<AttendanceRecords> filterWindow(List<AttendanceRecords> records, Date workDate,
|
||||||
|
Date expectedStart, Date expectedEnd, boolean crossDay) {
|
||||||
|
LocalDate ld = toLocalDate(workDate);
|
||||||
|
LocalTime st = toLocalTime(expectedStart);
|
||||||
|
LocalTime et = toLocalTime(expectedEnd);
|
||||||
|
|
||||||
|
LocalDateTime windowStart;
|
||||||
|
LocalDateTime windowEnd;
|
||||||
|
|
||||||
|
if (crossDay) {
|
||||||
|
windowStart = LocalDateTime.of(ld, st).minusHours(2);
|
||||||
|
windowEnd = LocalDateTime.of(ld.plusDays(1), et).plusHours(2);
|
||||||
|
} else {
|
||||||
|
windowStart = LocalDateTime.of(ld, st).minusHours(2);
|
||||||
|
windowEnd = LocalDateTime.of(ld, et).plusHours(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return records.stream()
|
||||||
|
.filter(r -> {
|
||||||
|
LocalDateTime ct = toLocalDateTime(r.getChecktime());
|
||||||
|
return !ct.isBefore(windowStart) && !ct.isAfter(windowEnd);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period,
|
private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period,
|
||||||
List<LocalTime> periodTimes, Date expectedStart, Date expectedEnd,
|
List<AttendanceRecords> periodRecords, Date expectedStart, Date expectedEnd) {
|
||||||
List<AttendanceRecords> allRecords) {
|
if (periodRecords.isEmpty()) {
|
||||||
if (periodTimes.isEmpty()) {
|
|
||||||
if (period == 1) {
|
if (period == 1) {
|
||||||
check.setP1Status("missed");
|
check.setP1Status("missed");
|
||||||
} else {
|
} else {
|
||||||
@@ -229,21 +266,20 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
|
|
||||||
LocalTime expStart = toLocalTime(expectedStart);
|
LocalTime expStart = toLocalTime(expectedStart);
|
||||||
LocalTime expEnd = toLocalTime(expectedEnd);
|
LocalTime expEnd = toLocalTime(expectedEnd);
|
||||||
LocalTime firstCheck = periodTimes.get(0);
|
AttendanceRecords firstRec = periodRecords.get(0);
|
||||||
LocalTime lastCheck = periodTimes.get(periodTimes.size() - 1);
|
AttendanceRecords lastRec = periodRecords.get(periodRecords.size() - 1);
|
||||||
|
LocalTime firstCheck = toLocalDateTime(firstRec.getChecktime()).toLocalTime();
|
||||||
|
LocalTime lastCheck = toLocalDateTime(lastRec.getChecktime()).toLocalTime();
|
||||||
|
|
||||||
int lateMinutes = 0;
|
int lateMinutes = 0;
|
||||||
int earlyMinutes = 0;
|
int earlyMinutes = 0;
|
||||||
BigDecimal deduct = BigDecimal.ZERO;
|
BigDecimal deduct = BigDecimal.ZERO;
|
||||||
String status = "normal";
|
String status = "normal";
|
||||||
|
|
||||||
// 迟到判定:最早打卡 vs 理论上班时间
|
|
||||||
if (firstCheck.isAfter(expStart)) {
|
if (firstCheck.isAfter(expStart)) {
|
||||||
lateMinutes = (int) Duration.between(expStart, firstCheck).toMinutes();
|
lateMinutes = (int) Duration.between(expStart, firstCheck).toMinutes();
|
||||||
if (lateMinutes > rule.getAbsentHalfDay()) {
|
if (lateMinutes > rule.getAbsentHalfDay()) {
|
||||||
status = "absent_half";
|
status = "absent_half";
|
||||||
} else if (lateMinutes > rule.getLateTwo()) {
|
|
||||||
status = "absent_half";
|
|
||||||
} else if (lateMinutes > rule.getLateOne()) {
|
} else if (lateMinutes > rule.getLateOne()) {
|
||||||
status = "late_two";
|
status = "late_two";
|
||||||
deduct = deduct.add(rule.getDeductTwo());
|
deduct = deduct.add(rule.getDeductTwo());
|
||||||
@@ -255,21 +291,17 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 早退判定:最晚打卡 vs 理论下班时间
|
|
||||||
if (lastCheck.isBefore(expEnd)) {
|
if (lastCheck.isBefore(expEnd)) {
|
||||||
int min = (int) Duration.between(lastCheck, expEnd).toMinutes();
|
int min = (int) Duration.between(lastCheck, expEnd).toMinutes();
|
||||||
if (min > rule.getAbsentHalfDay()) {
|
if (min > rule.getAbsentHalfDay()) {
|
||||||
status = maxSeverity(status, "absent_half");
|
status = maxSeverity(status, "absent_half");
|
||||||
earlyMinutes = min;
|
earlyMinutes = min;
|
||||||
} else if (min > rule.getLateTwo()) {
|
|
||||||
status = maxSeverity(status, "absent_half");
|
|
||||||
earlyMinutes = min;
|
|
||||||
} else if (min > rule.getLateOne()) {
|
} else if (min > rule.getLateOne()) {
|
||||||
status = maxSeverity(status, "early_2");
|
status = maxSeverity(status, "early_two");
|
||||||
deduct = deduct.add(rule.getDeductTwo());
|
deduct = deduct.add(rule.getDeductTwo());
|
||||||
earlyMinutes = min;
|
earlyMinutes = min;
|
||||||
} else if (min > rule.getLateWarn()) {
|
} else if (min > rule.getLateWarn()) {
|
||||||
status = maxSeverity(status, "early_1");
|
status = maxSeverity(status, "early_one");
|
||||||
deduct = deduct.add(rule.getDeductOne());
|
deduct = deduct.add(rule.getDeductOne());
|
||||||
earlyMinutes = min;
|
earlyMinutes = min;
|
||||||
} else {
|
} else {
|
||||||
@@ -280,25 +312,16 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 找到对应的打卡时间
|
|
||||||
for (AttendanceRecords r : allRecords) {
|
|
||||||
LocalTime ct = toLocalDateTime(r.getChecktime()).toLocalTime();
|
|
||||||
if (ct.equals(firstCheck)) {
|
|
||||||
if (period == 1) check.setP1FirstCheck(r.getChecktime());
|
|
||||||
else check.setP2FirstCheck(r.getChecktime());
|
|
||||||
}
|
|
||||||
if (ct.equals(lastCheck)) {
|
|
||||||
if (period == 1) check.setP1LastCheck(r.getChecktime());
|
|
||||||
else check.setP2LastCheck(r.getChecktime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (period == 1) {
|
if (period == 1) {
|
||||||
|
check.setP1FirstCheck(firstRec.getChecktime());
|
||||||
|
check.setP1LastCheck(lastRec.getChecktime());
|
||||||
check.setP1LateMinutes(lateMinutes);
|
check.setP1LateMinutes(lateMinutes);
|
||||||
check.setP1EarlyMinutes(earlyMinutes);
|
check.setP1EarlyMinutes(earlyMinutes);
|
||||||
check.setP1Status(status);
|
check.setP1Status(status);
|
||||||
check.setP1Deduct(deduct);
|
check.setP1Deduct(deduct);
|
||||||
} else {
|
} else {
|
||||||
|
check.setP2FirstCheck(firstRec.getChecktime());
|
||||||
|
check.setP2LastCheck(lastRec.getChecktime());
|
||||||
check.setP2LateMinutes(lateMinutes);
|
check.setP2LateMinutes(lateMinutes);
|
||||||
check.setP2EarlyMinutes(earlyMinutes);
|
check.setP2EarlyMinutes(earlyMinutes);
|
||||||
check.setP2Status(status);
|
check.setP2Status(status);
|
||||||
@@ -307,7 +330,8 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String maxSeverity(String a, String b) {
|
private String maxSeverity(String a, String b) {
|
||||||
List<String> severity = java.util.Arrays.asList("normal", "late_warn", "early_warn", "late_one", "early_1", "late_two", "early_2", "absent_half");
|
List<String> severity = java.util.Arrays.asList("normal", "late_warn", "early_warn",
|
||||||
|
"late_one", "early_one", "late_two", "early_two", "absent_half");
|
||||||
int ai = severity.indexOf(a);
|
int ai = severity.indexOf(a);
|
||||||
int bi = severity.indexOf(b);
|
int bi = severity.indexOf(b);
|
||||||
return severity.get(Math.max(ai, bi));
|
return severity.get(Math.max(ai, bi));
|
||||||
|
|||||||
Reference in New Issue
Block a user