package com.ruoyi.staff.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.base.BaseException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.project.system.domain.SysDept; import com.ruoyi.project.system.mapper.SysDeptMapper; import com.ruoyi.project.system.service.ISysDictDataService; import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto; import com.ruoyi.staff.dto.StaffOnJobDto; import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper; import com.ruoyi.staff.mapper.StaffOnJobMapper; import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig; import com.ruoyi.staff.pojo.PersonalAttendanceRecords; import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper; import com.ruoyi.staff.pojo.StaffOnJob; import com.ruoyi.staff.service.PersonalAttendanceRecordsService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.staff.task.PersonalAttendanceRecordsTask; import com.ruoyi.staff.utils.LocationUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.List; /** *

* 服务实现类 *

* * @author 芯导软件(江苏)有限公司 * @since 2026-02-09 01:20:07 */ @Service @Transactional(rollbackFor = Exception.class) public class PersonalAttendanceRecordsServiceImpl extends ServiceImpl implements PersonalAttendanceRecordsService { @Autowired private PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper; @Autowired private StaffOnJobMapper staffOnJobMapper; @Autowired private PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper; @Autowired private ISysDictDataService dictDataService; @Autowired private SysDeptMapper sysDeptMapper; @Override public int add(PersonalAttendanceRecordsDto personalAttendanceRecordsDto) { // 当前时间 LocalDate currentDate = LocalDate.now(); LocalDateTime currentDateTime = LocalDateTime.now(); /*查询员工信息*/ QueryWrapper staffQueryWrapper = new QueryWrapper<>(); staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername()); staffQueryWrapper.eq("staff_state", 1);//在职 StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper); if (staffOnJob == null) { throw new BaseException("当前用户没有对应的员工信息"); } /*判断打卡位置是否在规则范围内*/ List personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.lambdaQuery() .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId()) .orderByDesc(PersonalAttendanceLocationConfig::getId)); if (personalAttendanceLocationConfigs == null || personalAttendanceLocationConfigs.isEmpty()) { throw new BaseException("当前部门没有设置打卡规则"); } Double punchLongitude = personalAttendanceRecordsDto.getLongitude(); //打卡的经度 Double punchLatitude = personalAttendanceRecordsDto.getLatitude(); // 打卡的纬度 if (punchLongitude == null || punchLatitude == null) { throw new BaseException("打卡失败:未获取到您的位置信息,请开启定位权限"); } //计算打卡位置与考勤点的距离 PersonalAttendanceLocationConfig locationConfig = personalAttendanceLocationConfigs.get(0);//获取最新的一条数据 double allowedRadius = locationConfig.getRadius(); // 允许的范围(米) double actualDistance = LocationUtils.calculateDistance( punchLatitude, punchLongitude, // 员工打卡的经纬度 locationConfig.getLatitude(), locationConfig.getLongitude() // 考勤点的经纬度 ); //判断是否在范围内 if (actualDistance > allowedRadius) { throw new BaseException(String.format("打卡失败:您当前位置距离考勤点%.2f米,超出允许范围(%s米)", actualDistance, allowedRadius)); } /*判断打卡时间*/ LocalTime endAt = locationConfig.getEndAt(); //下班时间 // 获取考勤下班时间点 int standardHour = endAt.getHour(); int standardMinute = endAt.getMinute(); // 当前时间 int actualHour = currentDateTime.getHour(); int actualMinute = currentDateTime.getMinute(); // 判断打卡时间是否晚于当前时间 if (actualHour < standardHour || (actualHour == standardHour && actualMinute < standardMinute)) { throw new BaseException(String.format("打卡失败:打卡时间不能早于下班时间(%02d:%02d)", standardHour, standardMinute)); } // 根据员工ID和当前日期查询打卡记录 QueryWrapper attendanceQueryWrapper = new QueryWrapper<>(); attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId()) .eq("date", currentDate); PersonalAttendanceRecords attendanceRecord = personalAttendanceRecordsMapper.selectOne(attendanceQueryWrapper); // 根据字典设置的考勤时间判断迟到早退 if (attendanceRecord == null) { // 不存在打卡记录,创建新记录 PersonalAttendanceRecords personalAttendanceRecords = new PersonalAttendanceRecords(); personalAttendanceRecords.setStaffOnJobId(staffOnJob.getId()); personalAttendanceRecords.setDate(currentDate); personalAttendanceRecords.setWorkStartAt(currentDateTime); personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords, true,locationConfig)); personalAttendanceRecords.setRemark(personalAttendanceRecords.getRemark()); personalAttendanceRecords.setTenantId(staffOnJob.getTenantId()); return personalAttendanceRecordsMapper.insert(personalAttendanceRecords); } else { if (attendanceRecord.getWorkEndAt() == null) { // 更新工作结束时间和工作时长 attendanceRecord.setWorkEndAt(currentDateTime); // 计算工作时长(精确到分钟,保留2位小数) LocalDateTime startTime = attendanceRecord.getWorkStartAt(); LocalDateTime endTime = attendanceRecord.getWorkEndAt(); // 计算两个时间之间的分钟数 long totalMinutes = java.time.Duration.between(startTime, endTime).toMinutes(); BigDecimal workHours = BigDecimal.valueOf(totalMinutes) .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); attendanceRecord.setWorkHours(workHours); // 更新考勤状态 attendanceRecord.setStatus(determineAttendanceStatus(attendanceRecord, false,locationConfig)); return personalAttendanceRecordsMapper.updateById(attendanceRecord); } else { throw new BaseException("您已经打过卡了,无需重复打卡!!!"); } } } // 根据实际时间和是否上班时间判断考勤状态 // 0 正常 1 迟到 2 早退 3 迟到早退 4 缺勤 private Integer determineAttendanceStatus(PersonalAttendanceRecords attendanceRecord, boolean isStart,PersonalAttendanceLocationConfig locationConfig) { //判断是上班打卡还是下班打卡 LocalDateTime actualTime = isStart ? attendanceRecord.getWorkStartAt() : attendanceRecord.getWorkEndAt(); try { // 获取考勤时间配置 LocalTime startAt = locationConfig.getStartAt();//上班时间 LocalTime endAt = locationConfig.getEndAt();//下班时间 LocalTime timeConfig = isStart ? startAt : endAt; // 解析小时和分钟 int standardHour = timeConfig.getHour(); int standardMinute = timeConfig.getMinute(); // 获取实际时间的时分 int actualHour = actualTime.getHour(); int actualMinute = actualTime.getMinute(); // 判断状态 if (isStart) { // 上班打卡:超过标准时间算迟到 if (actualHour > standardHour || (actualHour == standardHour && actualMinute > standardMinute)) { return 1; // 迟到 } } else { // 下班打卡:早于标准时间算早退 if (actualHour < standardHour || (actualHour == standardHour && actualMinute < standardMinute)) { if (attendanceRecord.getStatus() == 1) { return 3; // 迟到早退 } return 2; // 早退 }else if (attendanceRecord.getStatus() == 1) { return 1; // 下班打卡正常但是上班迟到 } } return 0; // 正常 } catch (Exception e) { // 如果获取配置失败,默认返回正常状态 log.warn("获取考勤时间配置失败,使用默认状态:" + e.getMessage()); return 0; } } @Override public IPage listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) { boolean admin = SecurityUtils.isAdmin(SecurityUtils.getUserId()); if (!admin) { QueryWrapper staffQueryWrapper = new QueryWrapper<>(); staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername()); staffQueryWrapper.eq("staff_state", 1);//在职 StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper); if (staffOnJob == null) { return new Page<>(page.getCurrent(), page.getSize(), 0); } personalAttendanceRecordsDto.setStaffOnJobId(staffOnJob.getId()); } return personalAttendanceRecordsMapper.listPage(page,personalAttendanceRecordsDto); } @Override public PersonalAttendanceRecordsDto todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto) { // 获取当前日期 LocalDate currentDate = LocalDate.now(); // 首先根据用户ID查询员工信息 QueryWrapper staffQueryWrapper = new QueryWrapper<>(); staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername()); staffQueryWrapper.eq("staff_state", 1);//在职 StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper); if (staffOnJob == null) { throw new BaseException("当前用户没有对应的员工信息"); } // 根据员工ID和当前日期查询打卡记录 QueryWrapper attendanceQueryWrapper = new QueryWrapper<>(); attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId()) .eq("date", currentDate); PersonalAttendanceRecords attendanceRecord = personalAttendanceRecordsMapper.selectOne(attendanceQueryWrapper); // 返回参数 PersonalAttendanceRecordsDto resultDto = new PersonalAttendanceRecordsDto(); if (attendanceRecord != null) { // 如果有打卡记录,复制打卡记录信息 BeanUtils.copyProperties(attendanceRecord, resultDto); } // 员工相关信息 resultDto.setStaffName(staffOnJob.getStaffName()); resultDto.setStaffNo(staffOnJob.getStaffNo()); resultDto.setDeptId(staffOnJob.getSysDeptId() != null ? staffOnJob.getSysDeptId() : null); SysDept dept = sysDeptMapper.selectDeptById(staffOnJob.getSysDeptId()); resultDto.setDeptName(dept != null ? dept.getDeptName() : null); //获取该员工对应的打卡规则 List personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.lambdaQuery() .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId()) .orderByDesc(PersonalAttendanceLocationConfig::getId)); if (personalAttendanceLocationConfigs.size()>0){ resultDto.setStartAt(personalAttendanceLocationConfigs.get(0).getStartAt()); resultDto.setEndAt(personalAttendanceLocationConfigs.get(0).getEndAt()); } return resultDto; } @Override public void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) { boolean admin = SecurityUtils.isAdmin(SecurityUtils.getUserId()); if (!admin) { QueryWrapper staffQueryWrapper = new QueryWrapper<>(); staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername()); staffQueryWrapper.eq("staff_state", 1);//在职 StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper); if (staffOnJob == null) { throw new ServiceException("没有员工信息,无法导出考勤记录"); } personalAttendanceRecordsDto.setStaffOnJobId(staffOnJob.getId()); } List personalAttendanceRecords = personalAttendanceRecordsMapper.listPage(new Page<>(1, Integer.MAX_VALUE), personalAttendanceRecordsDto).getRecords(); ExcelUtil util = new ExcelUtil(PersonalAttendanceRecordsDto.class); util.exportExcel(response, personalAttendanceRecords, "考勤记录导出"); } }