From 405f38870228f32966e267ee37a1a6d23c022a08 Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Wed, 27 May 2026 14:16:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(wms/attendance):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=80=83=E5=8B=A4=E6=A3=80=E6=9F=A5=E9=83=A8=E9=97=A8=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96=E8=B7=A8?= =?UTF-8?q?=E5=A4=A9=E6=8E=92=E7=8F=AD=E9=80=BB=E8=BE=91=E4=B8=8E=E6=89=93?= =?UTF-8?q?=E5=8D=A1=E8=AE=B0=E5=BD=95=E6=89=B9=E9=87=8F=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 前端新增部门筛选下拉框,支持按部门筛选员工并自动勾选,优化穿梭框数据映射逻辑 2. 后端实现跨天排班重叠检测机制,正向跨天夜班被反向跨天班覆盖时跳过下班打卡校验 3. 重构打卡记录查询为批量预取模式,通过单次SQL查询提升性能,支持按员工姓名集合和时间范围精确检索 4. 优化考勤检查记录构建逻辑,调整时段时间计算方式,完善全天缺勤状态判断规则 --- .../wms/hrm/attendance/attendanceCheck.vue | 57 ++++- .../service/IAttendanceRecordsService.java | 6 + .../impl/AttendanceRecordsServiceImpl.java | 10 + .../impl/WmsAttendanceCheckServiceImpl.java | 195 +++++++++++++----- 4 files changed, 203 insertions(+), 65 deletions(-) diff --git a/klp-ui/src/views/wms/hrm/attendance/attendanceCheck.vue b/klp-ui/src/views/wms/hrm/attendance/attendanceCheck.vue index 9e8b4586..6c4e8004 100644 --- a/klp-ui/src/views/wms/hrm/attendance/attendanceCheck.vue +++ b/klp-ui/src/views/wms/hrm/attendance/attendanceCheck.vue @@ -403,10 +403,20 @@ placeholder="请选择结束日期"> + + + + + @@ -499,6 +509,7 @@ export default { departmentList: [], selectedDept: '', currentDeptEmployeeIds: '', + checkDept: '', selectedUserIds: [], detailRecordsLoading: false, @@ -508,9 +519,18 @@ export default { computed: { transferData() { return this.allEmployees.map(emp => ({ - key: emp.infoId, + key: String(emp.infoId), label: emp.name + '(' + emp.dept + ')' })) + }, + checkTransferData() { + if (!this.checkDept) return this.transferData + return this.allEmployees + .filter(emp => (emp.dept || '未分配部门') === this.checkDept) + .map(emp => ({ + key: String(emp.infoId), + label: emp.name + '(' + emp.dept + ')' + })) } }, created() { @@ -548,15 +568,18 @@ export default { this.allEmployees.forEach(emp => { const deptName = emp.dept || '未分配部门' if (!deptMap[deptName]) { - deptMap[deptName] = [] + deptMap[deptName] = new Set() + } + deptMap[deptName].add(String(emp.infoId)) + }) + this.departmentList = Object.keys(deptMap).map(deptName => { + const ids = [...deptMap[deptName]] + return { + deptName, + count: ids.length, + empIds: ids.join(',') } - deptMap[deptName].push(emp.infoId) }) - this.departmentList = Object.keys(deptMap).map(deptName => ({ - deptName, - count: deptMap[deptName].length, - empIds: deptMap[deptName].join(',') - })) if (this.departmentList.length > 0) { this.selectedDept = this.departmentList[0].deptName this.currentDeptEmployeeIds = this.departmentList[0].empIds @@ -841,23 +864,35 @@ export default { startDate: this.dateRangeParams.startDate, endDate: this.dateRangeParams.endDate } - this.selectedUserIds = [] + this.checkDept = this.selectedDept || '' + if (this.checkDept) { + const dept = this.departmentList.find(d => d.deptName === this.checkDept) + this.selectedUserIds = dept ? dept.empIds.split(',') : [] + } else { + this.selectedUserIds = [] + } }, cancelCheck() { this.checkOpen = false this.checkForm = { startDate: undefined, endDate: undefined } this.selectedUserIds = [] + this.checkDept = '' this.$refs['checkForm'] && this.$refs['checkForm'].resetFields() }, + handleCheckDeptChange(deptName) { + const dept = this.departmentList.find(d => d.deptName === deptName) + this.selectedUserIds = dept ? dept.empIds.split(',') : [] + }, + submitCheck() { this.$refs["checkForm"].validate(valid => { if (valid) { this.checkLoading = true const params = { ...this.checkForm, - userIds: this.selectedUserIds.join(',') + userIds: this.selectedUserIds } generateAttendanceCheck(params).then(response => { this.$modal.msgSuccess("比对成功") diff --git a/klp-wms/src/main/java/com/klp/service/IAttendanceRecordsService.java b/klp-wms/src/main/java/com/klp/service/IAttendanceRecordsService.java index c9e9018e..c6028b50 100644 --- a/klp-wms/src/main/java/com/klp/service/IAttendanceRecordsService.java +++ b/klp-wms/src/main/java/com/klp/service/IAttendanceRecordsService.java @@ -7,6 +7,7 @@ import com.klp.common.core.page.TableDataInfo; import com.klp.common.core.domain.PageQuery; import java.util.Collection; +import java.util.Date; import java.util.List; public interface IAttendanceRecordsService { @@ -22,4 +23,9 @@ public interface IAttendanceRecordsService { Boolean updateByBo(AttendanceRecordsBo bo); Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 按员工姓名集合 + 时间范围精确查询打卡记录(eq/in,走索引) + */ + List queryListByEnamesAndDateRange(List enames, Date startTime, Date endTime); } \ No newline at end of file diff --git a/klp-wms/src/main/java/com/klp/service/impl/AttendanceRecordsServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/AttendanceRecordsServiceImpl.java index e6936583..31afd40a 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/AttendanceRecordsServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/AttendanceRecordsServiceImpl.java @@ -15,6 +15,7 @@ import com.klp.domain.AttendanceRecords; import com.klp.mapper.AttendanceRecordsMapper; import com.klp.service.IAttendanceRecordsService; +import java.util.Date; import java.util.List; import java.util.Collection; @@ -81,4 +82,13 @@ public class AttendanceRecordsServiceImpl implements IAttendanceRecordsService { } return baseMapper.deleteBatchIds(ids) > 0; } + + @Override + public List queryListByEnamesAndDateRange(List enames, Date startTime, Date endTime) { + return baseMapper.selectList(Wrappers.lambdaQuery() + .select(AttendanceRecords::getId, AttendanceRecords::getEname, AttendanceRecords::getChecktime) + .in(AttendanceRecords::getEname, enames) + .ge(AttendanceRecords::getChecktime, startTime) + .le(AttendanceRecords::getChecktime, endTime)); + } } \ No newline at end of file diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java index 71f0ed1e..6b26483a 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsAttendanceCheckServiceImpl.java @@ -37,8 +37,11 @@ import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; @RequiredArgsConstructor @@ -97,60 +100,114 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService return lqw; } + /** + * 检查员工考勤情况 + * + * @param bo 考勤检查业务对象,包含开始日期、结束日期和用户ID等信息 + */ @Override - @Transactional(rollbackFor = Exception.class) + @Transactional(rollbackFor = Exception.class) // 使用事务注解,确保方法内所有操作要么全部成功,要么全部回滚 public void checkAttendance(AttendanceCheckBo bo) { + // 创建考勤计划业务对象,并设置查询参数 WmsAttendanceScheduleBo scheduleBo = new WmsAttendanceScheduleBo(); - scheduleBo.setStartDate(bo.getStartDate()); - scheduleBo.setEndDate(bo.getEndDate()); - scheduleBo.setUserIds(bo.getUserIds()); + scheduleBo.setStartDate(bo.getStartDate()); // 设置开始日期 + scheduleBo.setEndDate(bo.getEndDate()); // 设置结束日期 + scheduleBo.setUserIds(bo.getUserIds()); // 设置用户ID列表 + // 根据查询条件获取考勤计划列表 List schedules = scheduleService.queryList(scheduleBo); + // 如果没有考勤计划,直接返回 if (schedules.isEmpty()) { return; } + // 获取当前有效的考勤规则 WmsAttendanceRule rule = getActiveRule(); + // 将日期转换为LocalDate类型 LocalDate startLocal = toLocalDate(bo.getStartDate()); LocalDate endLocal = toLocalDate(bo.getEndDate()); + // 创建待处理的考勤计划列表 List toProcess = new ArrayList<>(); + // 遍历考勤计划,过滤掉无效数据 for (WmsAttendanceScheduleVo schedule : schedules) { - if (schedule.getEmployeeName() == null) { + if (schedule.getEmployeeName() == null) { // 跳过员工名为空的记录 continue; } - if (schedule.getShiftStartTime() == null && schedule.getShiftEndTime() == null) { + if (schedule.getShiftStartTime() == null && schedule.getShiftEndTime() == null) { // 跳过上下班时间为空的记录 continue; } - toProcess.add(schedule); + toProcess.add(schedule); // 添加到待处理列表 } + // 如果过滤后没有有效的考勤计划,直接返回 if (toProcess.isEmpty()) { return; } + // 倒班日重叠检测:若某天有正向跨天夜班(19:00→07:00),且次日存在同员工、同上班时间的反向跨天班(夜转白 19:00→13:00), + // 则正向跨天班的下班时间校验跳过(实际下班按反向跨天班 13:00 算),但上班打卡仍然校验 + // 创建映射表,记录被反向跨天班覆盖的记录 + Map backwardCoveredByEmployee = new HashMap<>(); + for (WmsAttendanceScheduleVo s : toProcess) { + // 如果是跨天班且是反向跨天班,记录该员工在特定日期的上班时间 + if (isCrossDayShift(s) && isBackwardCrossDay(s) && s.getEmployeeName() != null) { + LocalDate workLd = toLocalDate(s.getWorkDate()); + String key = s.getEmployeeName() + "_" + workLd.minusDays(1); + backwardCoveredByEmployee.put(key, toLocalTime(s.getShiftStartTime())); + } + } + // 记录需要覆盖下班检查的考勤计划ID + Set endCheckOverridden = new HashSet<>(); + for (WmsAttendanceScheduleVo s : toProcess) { + // 如果是正向跨天班,检查是否有对应的反向跨天班覆盖 + if (isCrossDayShift(s) && !isBackwardCrossDay(s) && s.getEmployeeName() != null && s.getShiftStartTime() != null) { + LocalDate workLd = toLocalDate(s.getWorkDate()); + String key = s.getEmployeeName() + "_" + workLd; + LocalTime backwardStart = backwardCoveredByEmployee.get(key); + if (backwardStart != null && backwardStart.equals(toLocalTime(s.getShiftStartTime()))) { + endCheckOverridden.add(s.getScheduleId()); + } + } + } + + // 获取受影响的用户ID集合 + Set affectedUserIds = toProcess.stream() + .map(WmsAttendanceScheduleVo::getUserId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 按员工预取考勤记录 Map> recordsByEmployee = prefetchAttendanceRecords(toProcess); + // 获取所有待处理考勤计划的ID列表 List scheduleIds = toProcess.stream() .map(WmsAttendanceScheduleVo::getScheduleId) .collect(Collectors.toList()); + // 批量删除这些考勤计划对应的考勤检查记录 batchDeleteByScheduleIds(scheduleIds); + // 创建要插入的考勤检查记录列表 List checksToInsert = new ArrayList<>(toProcess.size()); + // 遍历待处理的考勤计划,构建考勤检查记录 for (WmsAttendanceScheduleVo schedule : toProcess) { boolean crossDay = isCrossDayShift(schedule); boolean backward = isBackwardCrossDay(schedule); + // 根据考勤计划和考勤记录,按天切片考勤记录 List records = sliceRecordsForDay( recordsByEmployee.get(schedule.getEmployeeName()), schedule.getWorkDate(), crossDay, backward); - checksToInsert.add(buildCheck(schedule, rule, records, crossDay)); + // 构建考勤检查记录并添加到插入列表 + checksToInsert.add(buildCheck(schedule, rule, records, crossDay, endCheckOverridden)); } + // 批量插入考勤检查记录 baseMapper.insertBatch(checksToInsert, BATCH_SIZE); - updateContinuousAbsent(startLocal, endLocal); + // 更新连续缺勤记录 + updateContinuousAbsent(startLocal, endLocal, affectedUserIds); } private boolean isCrossDayShift(WmsAttendanceScheduleVo schedule) { @@ -182,7 +239,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } /** - * 按员工姓名合并时间范围,每人只查一次打卡(与原先按排班逐条查询结果一致)。 + * 一次 SQL 批量查询所有员工在日期范围内的打卡记录(用 IN/eq 精确匹配,走索引)。 */ private Map> prefetchAttendanceRecords(List toProcess) { Map minByName = new HashMap<>(); @@ -193,37 +250,28 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService minByName.merge(name, ld, (a, b) -> a.isBefore(b) ? a : b); maxByName.merge(name, ld, (a, b) -> a.isAfter(b) ? a : b); } - Map> out = new HashMap<>(minByName.size() * 2); - for (Map.Entry e : minByName.entrySet()) { - String name = e.getKey(); - LocalDate minLd = e.getValue(); - LocalDate maxLd = maxByName.get(name); - out.put(name, fetchRecordsForNameRange(name, minLd, maxLd)); + if (minByName.isEmpty()) { + return new HashMap<>(); + } + LocalDate globalMin = minByName.values().stream().min(LocalDate::compareTo).get(); + LocalDate globalMax = maxByName.values().stream().max(LocalDate::compareTo).get(); + Date rangeStart = toDate(globalMin.atStartOfDay()); + Date rangeEnd = toDate(globalMax.plusDays(1).atTime(LocalTime.of(23, 59, 59))); + + List allNames = new ArrayList<>(minByName.keySet()); + List allRecords = attendanceRecordsService.queryListByEnamesAndDateRange( + allNames, rangeStart, rangeEnd); + + Map> out = new HashMap<>(); + for (AttendanceRecords r : allRecords) { + out.computeIfAbsent(r.getEname(), k -> new ArrayList<>()).add(r); + } + for (String name : allNames) { + out.computeIfAbsent(name, k -> new ArrayList<>()); } return out; } - private List fetchRecordsForNameRange(String employeeName, LocalDate minLd, LocalDate maxLd) { - AttendanceRecordsBo recordsBo = new AttendanceRecordsBo(); - recordsBo.setEname(employeeName); - recordsBo.setChecktimeStart(toDate(minLd.atStartOfDay())); - recordsBo.setChecktimeEnd(toDate(maxLd.plusDays(1).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()); - } - - /** - * 与原先 {@code getRecords(ename, workDate, crossDay)} 的时间窗口一致。 - */ private List sliceRecordsForDay(List prefetched, Date workDate, boolean crossDay, boolean backward) { if (prefetched == null || prefetched.isEmpty()) { return new ArrayList<>(); @@ -256,9 +304,22 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } } + /** + * 构建考勤检查对象,根据排班信息和考勤记录生成考勤结果 + * + * @param schedule 排班信息对象 + * @param rule 考勤规则对象 + * @param records 考勤记录列表 + * @param crossDay 是否跨天 + * @param endCheckOverridden 已覆盖的结束检查集合 + * @return 构建好的考勤检查对象 + */ private WmsAttendanceCheck buildCheck(WmsAttendanceScheduleVo schedule, WmsAttendanceRule rule, - List records, boolean crossDay) { + List records, boolean crossDay, + Set endCheckOverridden) { + // 创建考勤检查对象 WmsAttendanceCheck check = new WmsAttendanceCheck(); + // 设置排班基本信息 check.setScheduleId(schedule.getScheduleId()); check.setUserId(schedule.getUserId()); check.setEmployeeName(schedule.getEmployeeName()); @@ -267,13 +328,22 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService check.setShiftName(schedule.getShiftName()); check.setShiftType(schedule.getShiftType()); + // 判断是否有第二时段 boolean hasPeriod2 = schedule.getShiftStartTime2() != null && schedule.getShiftEndTime2() != null; + // 判断是否为向后跨天 boolean backward = isBackwardCrossDay(schedule); + // 处理时段设置 if (hasPeriod2) { - check.setP1StartTime(combineTime(schedule.getWorkDate(), schedule.getShiftStartTime())); - check.setP1EndTime(combineTime(schedule.getWorkDate(), schedule.getShiftEndTime())); + // 设置第一时段开始时间,考虑跨天情况 + check.setP1StartTime(crossDay && backward + ? combinePrevDay(schedule.getWorkDate(), schedule.getShiftStartTime()) + : combineTime(schedule.getWorkDate(), schedule.getShiftStartTime())); + // 设置第一时段结束时间,考虑跨天情况 + check.setP1EndTime(crossDay && !backward + ? combineNextDay(schedule.getWorkDate(), schedule.getShiftEndTime()) + : combineTime(schedule.getWorkDate(), schedule.getShiftEndTime())); check.setP2StartTime(combineTime(schedule.getWorkDate(), schedule.getShiftStartTime2())); check.setP2EndTime(combineTime(schedule.getWorkDate(), schedule.getShiftEndTime2())); } else { @@ -313,12 +383,14 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } } - checkPeriod(check, rule, 1, p1Records, schedule.getShiftStartTime(), schedule.getShiftEndTime()); - checkPeriod(check, rule, 2, p2Records, schedule.getShiftStartTime2(), schedule.getShiftEndTime2()); + checkPeriod(check, rule, 1, p1Records, check.getP1StartTime(), check.getP1EndTime()); + checkPeriod(check, rule, 2, p2Records, check.getP2StartTime(), check.getP2EndTime()); } else { List filtered = filterWindow(records, schedule.getWorkDate(), schedule.getShiftStartTime(), schedule.getShiftEndTime(), crossDay); - checkPeriod(check, rule, 1, filtered, schedule.getShiftStartTime(), schedule.getShiftEndTime()); + Date actualEnd = endCheckOverridden.contains(schedule.getScheduleId()) + ? null : check.getP1EndTime(); + checkPeriod(check, rule, 1, filtered, check.getP1StartTime(), actualEnd); } calculateOverall(check, rule); @@ -326,7 +398,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } private List filterWindow(List records, Date workDate, - Date expectedStart, Date expectedEnd, boolean crossDay) { + Date expectedStart, Date expectedEnd, boolean crossDay) { LocalDate ld = toLocalDate(workDate); LocalTime st = toLocalTime(expectedStart); LocalTime et = toLocalTime(expectedEnd); @@ -366,7 +438,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService } private void checkPeriod(WmsAttendanceCheck check, WmsAttendanceRule rule, int period, - List periodRecords, Date expectedStart, Date expectedEnd) { + List periodRecords, Date expectedStart, Date expectedEnd) { if (periodRecords.isEmpty()) { if (period == 1) { check.setP1Status("missed"); @@ -376,12 +448,12 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService return; } - LocalTime expStart = toLocalTime(expectedStart); - LocalTime expEnd = toLocalTime(expectedEnd); + LocalDateTime expStart = toLocalDateTime(expectedStart); + LocalDateTime expEnd = toLocalDateTime(expectedEnd); AttendanceRecords firstRec = periodRecords.get(0); AttendanceRecords lastRec = periodRecords.get(periodRecords.size() - 1); - LocalTime firstCheck = toLocalDateTime(firstRec.getChecktime()).toLocalTime(); - LocalTime lastCheck = toLocalDateTime(lastRec.getChecktime()).toLocalTime(); + LocalDateTime firstCheck = toLocalDateTime(firstRec.getChecktime()); + LocalDateTime lastCheck = toLocalDateTime(lastRec.getChecktime()); int lateMinutes = 0; int earlyMinutes = 0; @@ -459,10 +531,18 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService 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 ("missed".equals(check.getP1Status())) { + if (check.getP2StartTime() != null) { + if ("missed".equals(check.getP2Status())) { + check.setAbsentType("full_day"); + check.setOverallStatus("absent_full"); + return; + } + } else { + check.setAbsentType("full_day"); + check.setOverallStatus("absent_full"); + return; + } } if (check.getP1StartTime() == null && check.getP2StartTime() == null) { check.setAbsentType("full_day"); @@ -479,8 +559,12 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService check.setOverallStatus(abnormal ? "abnormal" : "normal"); } - private void updateContinuousAbsent(LocalDate startDate, LocalDate endDate) { + private void updateContinuousAbsent(LocalDate startDate, LocalDate endDate, Set affectedUserIds) { + if (affectedUserIds.isEmpty()) { + return; + } List checks = baseMapper.selectList(Wrappers.lambdaQuery() + .in(WmsAttendanceCheck::getUserId, affectedUserIds) .ge(WmsAttendanceCheck::getWorkDate, toDate(startDate.atStartOfDay())) .le(WmsAttendanceCheck::getWorkDate, toDate(endDate.atTime(LocalTime.of(23, 59, 59)))) .eq(WmsAttendanceCheck::getDelFlag, 0)); @@ -515,6 +599,8 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService */ private Map> loadAbsentHistoryByUser(List userIds, LocalDate endDate) { Map> out = new HashMap<>(); + LocalDate startDate = endDate.minusDays(100); + Date start = toDate(startDate.atStartOfDay()); Date end = toDate(endDate.atTime(LocalTime.of(23, 59, 59))); for (int i = 0; i < userIds.size(); i += BATCH_SIZE) { int to = Math.min(i + BATCH_SIZE, userIds.size()); @@ -523,6 +609,7 @@ public class WmsAttendanceCheckServiceImpl implements IWmsAttendanceCheckService .select(WmsAttendanceCheck::getUserId, WmsAttendanceCheck::getWorkDate, WmsAttendanceCheck::getAbsentType, WmsAttendanceCheck::getCheckId, WmsAttendanceCheck::getDelFlag) .in(WmsAttendanceCheck::getUserId, chunk) + .ge(WmsAttendanceCheck::getWorkDate, start) .le(WmsAttendanceCheck::getWorkDate, end) .eq(WmsAttendanceCheck::getDelFlag, 0)); for (WmsAttendanceCheck row : rows) {