package com.klp.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.klp.common.core.domain.PageQuery; import com.klp.common.core.page.TableDataInfo; import com.klp.common.utils.StringUtils; import com.klp.domain.AttendanceRecords; import com.klp.domain.WmsAttendanceCheck; import com.klp.domain.WmsAttendanceRule; import com.klp.domain.bo.AttendanceCheckBo; import com.klp.domain.bo.AttendanceRecordsBo; import com.klp.domain.bo.WmsAttendanceCheckBo; import com.klp.domain.bo.WmsAttendanceScheduleBo; import com.klp.domain.vo.AttendanceRecordsVo; import com.klp.domain.vo.WmsAttendanceCheckVo; import com.klp.domain.vo.WmsAttendanceScheduleVo; import com.klp.mapper.WmsAttendanceCheckMapper; import com.klp.mapper.WmsAttendanceRuleMapper; import com.klp.service.IAttendanceRecordsService; import com.klp.service.IWmsAttendanceCheckService; import com.klp.service.IWmsAttendanceScheduleService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.stream.Collectors; @RequiredArgsConstructor @Service public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService { private final WmsAttendanceCheckMapper baseMapper; private final IWmsAttendanceScheduleService scheduleService; private final IAttendanceRecordsService attendanceRecordsService; private final WmsAttendanceRuleMapper ruleMapper; @Override public WmsAttendanceCheckVo queryById(Long checkId) { return baseMapper.selectVoById(checkId); } @Override public TableDataInfo queryPageList(WmsAttendanceCheckBo bo, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); return TableDataInfo.build(result); } @Override public List queryList(WmsAttendanceCheckBo bo) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); return baseMapper.selectVoList(lqw); } @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { return baseMapper.deleteBatchIds(ids) > 0; } private LambdaQueryWrapper buildQueryWrapper(WmsAttendanceCheckBo bo) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(bo.getUserId() != null, WmsAttendanceCheck::getUserId, bo.getUserId()); lqw.like(StringUtils.isNotBlank(bo.getEmployeeName()), WmsAttendanceCheck::getEmployeeName, bo.getEmployeeName()); lqw.eq(bo.getShiftId() != null, WmsAttendanceCheck::getShiftId, bo.getShiftId()); lqw.ge(bo.getStartDate() != null, WmsAttendanceCheck::getWorkDate, bo.getStartDate()); lqw.le(bo.getEndDate() != null, WmsAttendanceCheck::getWorkDate, bo.getEndDate()); lqw.orderByDesc(WmsAttendanceCheck::getWorkDate); return lqw; } @Override @Transactional(rollbackFor = Exception.class) public void checkAttendance(AttendanceCheckBo bo) { WmsAttendanceScheduleBo scheduleBo = new WmsAttendanceScheduleBo(); scheduleBo.setStartDate(bo.getStartDate()); scheduleBo.setEndDate(bo.getEndDate()); List schedules = scheduleService.queryList(scheduleBo); if (schedules.isEmpty()) { return; } WmsAttendanceRule rule = getActiveRule(); LocalDate startLocal = toLocalDate(bo.getStartDate()); LocalDate endLocal = toLocalDate(bo.getEndDate()); for (WmsAttendanceScheduleVo schedule : schedules) { if (schedule.getEmployeeName() == null || schedule.getShiftStartTime() == null) { continue; } // 获取该员工该日期的所有打卡记录 List records = getRecords(schedule.getEmployeeName(), schedule.getWorkDate()); WmsAttendanceCheck check = buildCheck(schedule, rule, records); // 删除已有的比对结果 baseMapper.delete(Wrappers.lambdaQuery() .eq(WmsAttendanceCheck::getScheduleId, schedule.getScheduleId())); baseMapper.insert(check); } // 更新连续旷工天数 updateContinuousAbsent(startLocal, endLocal); } private WmsAttendanceRule getActiveRule() { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(WmsAttendanceRule::getDelFlag, 0); wrapper.last("LIMIT 1"); WmsAttendanceRule rule = ruleMapper.selectOne(wrapper); if (rule == null) { rule = new WmsAttendanceRule(); rule.setLateWarn(3L); rule.setLateOne(5L); rule.setLateTwo(15L); rule.setDeductOne(new BigDecimal("10")); rule.setDeductTwo(new BigDecimal("30")); rule.setAbsentHalfDay(15L); rule.setContinuousAbsentDays(3L); } return rule; } private List getRecords(String employeeName, Date workDate) { AttendanceRecordsBo recordsBo = new AttendanceRecordsBo(); recordsBo.setEname(employeeName); LocalDate ld = toLocalDate(workDate); recordsBo.setChecktimeStart(toDate(ld.atStartOfDay())); recordsBo.setChecktimeEnd(toDate(ld.atTime(LocalTime.of(23, 59, 59)))); List voList = attendanceRecordsService.queryList(recordsBo); return voList.stream() .map(v -> { AttendanceRecords r = new AttendanceRecords(); r.setId(v.getId()); r.setEname(v.getEname()); r.setChecktime(v.getChecktime()); return r; }) .sorted(Comparator.comparing(AttendanceRecords::getChecktime)) .collect(Collectors.toList()); } private WmsAttendanceCheck buildCheck(WmsAttendanceScheduleVo schedule, WmsAttendanceRule rule, List records) { WmsAttendanceCheck check = new WmsAttendanceCheck(); check.setScheduleId(schedule.getScheduleId()); check.setUserId(schedule.getUserId()); check.setEmployeeName(schedule.getEmployeeName()); check.setWorkDate(schedule.getWorkDate()); check.setShiftId(schedule.getShiftId()); check.setShiftName(schedule.getShiftName()); check.setShiftType(schedule.getShiftType()); boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null; if (records.isEmpty()) { check.setOverallStatus("absent_full"); check.setAbsentType("full_day"); return check; } List checkTimes = records.stream() .map(r -> toLocalDateTime(r.getChecktime()).toLocalTime()) .collect(Collectors.toList()); 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); List period1Times = new ArrayList<>(); List period2Times = new ArrayList<>(); for (LocalTime t : checkTimes) { if (t.isBefore(split)) { period1Times.add(t); } else { period2Times.add(t); } } check.setP1StartTime(schedule.getShiftStartTime()); check.setP1EndTime(schedule.getShiftEndTime()); checkPeriod(check, rule, 1, period1Times, schedule.getShiftStartTime(), schedule.getShiftEndTime(), records); check.setP2StartTime(schedule.getShiftStartTime2()); check.setP2EndTime(schedule.getShiftEndTime2()); checkPeriod(check, rule, 2, period2Times, schedule.getShiftStartTime2(), schedule.getShiftEndTime2(), records); } else { // 倒班:全天一个时段 check.setP1StartTime(schedule.getShiftStartTime()); check.setP1EndTime(schedule.getShiftEndTime()); checkPeriod(check, rule, 1, checkTimes, schedule.getShiftStartTime(), schedule.getShiftEndTime(), records); } // 总体判定 calculateOverall(check, rule); return check; } private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period, List periodTimes, Date expectedStart, Date expectedEnd, List allRecords) { if (periodTimes.isEmpty()) { if (period == 1) { check.setP1Status("missed"); } else { check.setP2Status("missed"); } return; } LocalTime expStart = toLocalTime(expectedStart); LocalTime expEnd = toLocalTime(expectedEnd); LocalTime firstCheck = periodTimes.get(0); LocalTime lastCheck = periodTimes.get(periodTimes.size() - 1); int lateMinutes = 0; int earlyMinutes = 0; BigDecimal deduct = BigDecimal.ZERO; String status = "normal"; // 迟到判定:最早打卡 vs 理论上班时间 if (firstCheck.isAfter(expStart)) { lateMinutes = (int) Duration.between(expStart, firstCheck).toMinutes(); if (lateMinutes > rule.getAbsentHalfDay()) { status = "absent_half"; } else if (lateMinutes > rule.getLateTwo()) { status = "absent_half"; } else if (lateMinutes > rule.getLateOne()) { status = "late_two"; deduct = deduct.add(rule.getDeductTwo()); } else if (lateMinutes > rule.getLateWarn()) { status = "late_one"; deduct = deduct.add(rule.getDeductOne()); } else { status = "late_warn"; } } // 早退判定:最晚打卡 vs 理论下班时间 if (lastCheck.isBefore(expEnd)) { int min = (int) Duration.between(lastCheck, expEnd).toMinutes(); if (min > rule.getAbsentHalfDay()) { status = maxSeverity(status, "absent_half"); earlyMinutes = min; } else if (min > rule.getLateTwo()) { status = maxSeverity(status, "absent_half"); earlyMinutes = min; } else if (min > rule.getLateOne()) { status = maxSeverity(status, "early_2"); deduct = deduct.add(rule.getDeductTwo()); earlyMinutes = min; } else if (min > rule.getLateWarn()) { status = maxSeverity(status, "early_1"); deduct = deduct.add(rule.getDeductOne()); earlyMinutes = min; } else { if ("normal".equals(status)) { status = "early_warn"; } earlyMinutes = min; } } // 找到对应的打卡时间 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) { check.setP1LateMinutes(lateMinutes); check.setP1EarlyMinutes(earlyMinutes); check.setP1Status(status); check.setP1Deduct(deduct); } else { check.setP2LateMinutes(lateMinutes); check.setP2EarlyMinutes(earlyMinutes); check.setP2Status(status); check.setP2Deduct(deduct); } } private String maxSeverity(String a, String b) { List severity = java.util.Arrays.asList("normal", "late_warn", "early_warn", "late_one", "early_1", "late_two", "early_2", "absent_half"); int ai = severity.indexOf(a); int bi = severity.indexOf(b); return severity.get(Math.max(ai, bi)); } private void calculateOverall(WmsAttendanceCheck check, WmsAttendanceRule rule) { BigDecimal total = BigDecimal.ZERO; boolean hasAbsentHalf = false; if (check.getP1Deduct() != null) total = total.add(check.getP1Deduct()); if (check.getP2Deduct() != null) total = total.add(check.getP2Deduct()); check.setTotalDeduct(total); if ("absent_half".equals(check.getP1Status()) || "absent_half".equals(check.getP2Status())) { hasAbsentHalf = true; } if ("missed".equals(check.getP1Status()) && check.getP2StartTime() != null && "missed".equals(check.getP2Status())) { check.setAbsentType("full_day"); check.setOverallStatus("absent_full"); return; } if (check.getP1StartTime() == null && check.getP2StartTime() == null) { check.setAbsentType("full_day"); check.setOverallStatus("absent_full"); return; } if (hasAbsentHalf) { check.setAbsentType("half_day"); check.setOverallStatus("absent_half"); return; } boolean abnormal = !"normal".equals(check.getP1Status()) || (check.getP2Status() != null && !"normal".equals(check.getP2Status())); check.setOverallStatus(abnormal ? "abnormal" : "normal"); } private void updateContinuousAbsent(LocalDate startDate, LocalDate endDate) { List checks = baseMapper.selectList(Wrappers.lambdaQuery() .ge(WmsAttendanceCheck::getWorkDate, toDate(startDate.atStartOfDay())) .le(WmsAttendanceCheck::getWorkDate, toDate(endDate.atTime(LocalTime.of(23, 59, 59)))) .eq(WmsAttendanceCheck::getDelFlag, 0)); for (WmsAttendanceCheck check : checks) { if (check.getAbsentType() != null) { int continuous = countContinuousAbsent(check.getUserId(), toLocalDate(check.getWorkDate())); check.setContinuousAbsentDays(continuous); baseMapper.updateById(check); } } } private int countContinuousAbsent(Long userId, LocalDate workDate) { int count = 0; LocalDate date = workDate.minusDays(1); while (true) { WmsAttendanceCheck prev = baseMapper.selectOne(Wrappers.lambdaQuery() .eq(WmsAttendanceCheck::getUserId, userId) .eq(WmsAttendanceCheck::getWorkDate, toDate(date.atStartOfDay())) .eq(WmsAttendanceCheck::getDelFlag, 0)); if (prev != null && prev.getAbsentType() != null) { count++; date = date.minusDays(1); } else { break; } } return count; } private static LocalDate toLocalDate(Date date) { return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } private static LocalTime toLocalTime(Date date) { if (date == null) return null; return date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); } private static LocalDateTime toLocalDateTime(Date date) { if (date == null) return null; return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); } private static Date toDate(LocalDateTime ldt) { return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); } }