xiaoyi
2026-10-30 437fa3adb398545e111c832cdb7d0fafd37bece6
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.hrm.service.attendance;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordRespVO;
@@ -55,6 +57,8 @@
    private HrmAttendanceHolidayService attendanceHolidayService;
    @Resource
    private DeptApi deptApi;
    @Resource
    private cn.iocoder.yudao.module.hrm.service.approval.HrmLeaveQueryService leaveQueryService;
    @Override
    public PageResult<HrmAttendanceRecordDO> getAttendanceRecordPage(HrmAttendanceRecordPageReqVO pageReqVO) {
@@ -82,13 +86,13 @@
        // 1. 登录用户 -> 员工档案
        HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
        if (employee == null) {
            throw exception(EMPLOYEE_NOT_EXISTS,
                    "当前登录账号未关联员工档案,无法打卡。请联系管理员在【员工管理】中将该登录账号绑定到员工。");
            throw exception(ATTENDANCE_EMPLOYEE_NOT_BOUND);
        }
        HrmAttendanceMyStatusRespVO respVO = new HrmAttendanceMyStatusRespVO();
        respVO.setUserId(loginUserId);
        respVO.setEmployeeId(employee.getId());
        respVO.setEmployeeName(employee.getName());
        respVO.setUserName(employee.getName());
        respVO.setDeptId(employee.getDeptId());
        if (employee.getDeptId() != null) {
            DeptRespDTO dept = deptApi.getDept(employee.getDeptId());
@@ -117,17 +121,140 @@
                }
            }
            respVO.setRule(ruleRespVO);
            fillCompatibleRuleFields(respVO, rule);
        }
        // 4. 当日考勤记录
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(employee.getId(), queryDate);
        // 考勤记录表存的是系统用户 ID,这里用 loginUserId 而不是员工档案 ID
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(loginUserId, queryDate);
        if (record != null) {
            respVO.setRecord(buildRecordRespVO(record, employee));
            fillCompatibleRecordFields(respVO, record);
        }
        // 4.5 当天请假情况(请假仍允许打卡,但不算迟到/早退/缺卡)
        List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> leaves =
                leaveQueryService.getApprovedLeaveList(loginUserId, queryDate);
        boolean onLeave = !leaves.isEmpty();
        java.math.BigDecimal leaveHours = onLeave ? calcLeaveHours(leaves, rule, queryDate) : java.math.BigDecimal.ZERO;
        respVO.setOnLeave(onLeave);
        respVO.setLeaveHours(leaveHours);
        if (onLeave) {
            List<String> typeNames = leaves.stream()
                    .map(l -> cn.iocoder.yudao.module.hrm.enums.HrmLeaveTypeEnum.getNameByStatus(l.getLeaveType()))
                    .distinct().toList();
            respVO.setLeaveTypeNames(String.join("、", typeNames));
        }
        // 5. 当前可打卡状态
        respVO.setClockable(buildClockable(workDay, rule, record, queryDate));
        HrmAttendanceMyStatusRespVO.Clockable clockable = buildClockable(workDay, rule, record, queryDate, onLeave);
        // 5.1 请假当天:把「可打卡」的提示换成请假说明,但依然允许打卡
        if (onLeave) {
            clockable.setMessage("今日已请假(" + respVO.getLeaveTypeNames() + "),如需上班可正常打卡");
        }
        respVO.setClockable(clockable);
        respVO.setClockInDone(record != null && record.getClockInTime() != null);
        respVO.setClockOutDone(record != null && record.getClockOutTime() != null);
        respVO.setCanClockIn(clockable.getClockIn());
        respVO.setCanClockOut(clockable.getClockOut());
        return respVO;
    }
    /** 同时保留旧版 App/Web 使用的平铺规则字段。 */
    private void fillCompatibleRuleFields(HrmAttendanceMyStatusRespVO respVO, HrmAttendanceRuleDO rule) {
        respVO.setRuleName(rule.getName());
        respVO.setWorkStartTime(rule.getWorkStartTime());
        respVO.setWorkEndTime(rule.getWorkEndTime());
        respVO.setLocationRequired(rule.getLocationRequired());
        respVO.setLocationName(rule.getLocationName());
        respVO.setLocationAddress(rule.getLocationAddress());
        respVO.setLongitude(rule.getLongitude());
        respVO.setLatitude(rule.getLatitude());
        respVO.setRadiusMeters(rule.getRadiusMeters());
        respVO.setCoordinateSystem(rule.getCoordinateSystem());
    }
    /** 同时保留旧版 App/Web 使用的平铺考勤记录字段。 */
    private void fillCompatibleRecordFields(HrmAttendanceMyStatusRespVO respVO, HrmAttendanceRecordDO record) {
        respVO.setRecordId(record.getId());
        respVO.setClockInTime(record.getClockInTime());
        respVO.setClockOutTime(record.getClockOutTime());
        respVO.setClockInType(record.getClockInType());
        respVO.setClockOutType(record.getClockOutType());
        respVO.setWorkHours(record.getWorkHours());
        respVO.setOvertimeHours(record.getOvertimeHours());
        respVO.setLocation(record.getLocation());
        respVO.setDataSource(record.getDataSource());
    }
    /**
     * 把请假信息写入考勤记录(leave_flag / leave_hours / leave_types)
     *
     * @param record 考勤记录
     * @param leaves 当天审批通过的请假单
     * @param rule   考勤规则(用于计算工作时段交集)
     * @param date   考勤日期
     */
    private void applyLeaveInfo(HrmAttendanceRecordDO record,
                                List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> leaves,
                                HrmAttendanceRuleDO rule, LocalDate date) {
        if (leaves == null || leaves.isEmpty()) {
            record.setLeaveFlag(false);
            record.setLeaveHours(BigDecimal.ZERO);
            record.setLeaveTypes(null);
            return;
        }
        record.setLeaveFlag(true);
        record.setLeaveHours(calcLeaveHours(leaves, rule, date));
        record.setLeaveTypes(leaves.stream()
                .map(l -> String.valueOf(l.getLeaveType()))
                .distinct()
                .collect(java.util.stream.Collectors.joining(",")));
    }
    /**
     * 计算某天的请假工时
     *
     * <p>把每张请假单的 [start_time, end_time] 与考勤规则的工作时段求交集后累加,
     * 精确到 0.01 小时(36 秒)。无规则时按整天的请假时长折算。</p>
     *
     * <p>例:规则 08:30-17:30,请假单 14:00-18:00 → 交集 14:00-17:30 = 3.5 小时。</p>
     */
    private java.math.BigDecimal calcLeaveHours(
            List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> leaves,
            HrmAttendanceRuleDO rule, LocalDate date) {
        LocalDateTime dayStart = date.atStartOfDay();
        LocalDateTime dayEnd = date.plusDays(1).atStartOfDay();
        // 工作日时段:优先用考勤规则,没配规则就按整天
        LocalDateTime workStart;
        LocalDateTime workEnd;
        if (rule != null && rule.getWorkStartTime() != null && rule.getWorkEndTime() != null) {
            workStart = LocalDateTime.of(date, rule.getWorkStartTime());
            workEnd = LocalDateTime.of(date, rule.getWorkEndTime());
            // 跨零点班次(如 20:00-次日 04:00)
            if (!workEnd.isAfter(workStart)) {
                workEnd = workEnd.plusDays(1);
            }
        } else {
            workStart = dayStart;
            workEnd = dayEnd;
        }
        java.math.BigDecimal total = java.math.BigDecimal.ZERO;
        for (var leave : leaves) {
            if (leave.getStartTime() == null || leave.getEndTime() == null) {
                continue;
            }
            LocalDateTime from = leave.getStartTime().isAfter(workStart) ? leave.getStartTime() : workStart;
            LocalDateTime to = leave.getEndTime().isBefore(workEnd) ? leave.getEndTime() : workEnd;
            if (!to.isAfter(from)) {
                continue;
            }
            long minutes = java.time.Duration.between(from, to).toMinutes();
            total = total.add(java.math.BigDecimal.valueOf(minutes)
                    .divide(java.math.BigDecimal.valueOf(60), 2, java.math.RoundingMode.HALF_UP));
        }
        return total;
    }
    /**
@@ -174,7 +301,8 @@
     * 根据工作日、考勤规则与当日记录计算可打卡状态
     */
    private HrmAttendanceMyStatusRespVO.Clockable buildClockable(boolean workDay, HrmAttendanceRuleDO rule,
                                                                 HrmAttendanceRecordDO record, LocalDate date) {
                                                                 HrmAttendanceRecordDO record, LocalDate date,
                                                                 boolean onLeave) {
        HrmAttendanceMyStatusRespVO.Clockable clockable = new HrmAttendanceMyStatusRespVO.Clockable();
        if (!workDay) {
            clockable.setClockIn(false);
@@ -204,9 +332,9 @@
            clockable.setClockOut(false);
            clockable.setNextAction("in");
            clockable.setMessage("可上班打卡");
            // 仅当天查询时提示已迟到时长
            // 仅当天查询时提示已迟到时长;请假当天不提示迟到
            LocalDateTime now = LocalDateTime.now();
            if (rule.getWorkStartTime() != null && date.equals(now.toLocalDate())) {
            if (!onLeave && rule.getWorkStartTime() != null && date.equals(now.toLocalDate())) {
                LocalDateTime workStart = LocalDateTime.of(date, rule.getWorkStartTime());
                int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
                if (now.isAfter(workStart.plusMinutes(lateAllowMinutes))) {
@@ -314,60 +442,80 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location) {
    public HrmAttendanceClockResult clockIn(Long userId, LocalDateTime clockTime,
                                            HrmAttendanceClockReqVO reqVO, String legacyLocation) {
        LocalDate clockDate = clockTime.toLocalDate();
        // 1. 校验是否工作日
        if (!attendanceHolidayService.isWorkDay(clockDate)) {
            throw exception(ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY);
        }
        // 2. 获取员工信息和考勤规则
        //    注意:入参 userId 实际传的是「员工档案 ID」,
        //    而考勤记录表 / 请假表的 user_id 存的是「系统用户 ID」,必须区分。
        HrmEmployeeDO employee = employeeService.getEmployee(userId);
        if (employee == null) {
            throw exception(EMPLOYEE_NOT_EXISTS);
        }
        Long systemUserId = employee.getUserId();
        HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
        if (rule == null) {
            throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
        }
        // 3. 查询当日考勤记录
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
        // 3. 查询当日考勤记录(用系统用户 ID)
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(systemUserId, clockDate);
        if (record != null && record.getClockInTime() != null) {
            throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
        }
        // 在任何写库操作前完成定位校验,旧版地点字符串不能绕过范围限制。
        ClockLocation clockLocation = validateClockLocation(rule, reqVO);
        // 3.1 当天请假情况:请假当天允许打卡,但不判定为迟到
        //     注意:方法入参 userId 实为「员工档案 ID」,而请假表存的是「系统用户 ID」,
        //     必须用 employee.getUserId() 才能对上。
        List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> leaves =
                leaveQueryService.getApprovedLeaveList(employee.getUserId(), clockDate);
        boolean onLeave = !leaves.isEmpty();
        // 4. 判断打卡类型
        LocalTime workStartTime = rule.getWorkStartTime();
        int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
        LocalTime lateThreshold = workStartTime.plusMinutes(lateAllowMinutes);
        LocalTime clockTimeOnly = clockTime.toLocalTime();
        Integer clockInType = clockTimeOnly.isBefore(lateThreshold) || clockTimeOnly.equals(lateThreshold)
                ? HrmAttendanceClockTypeEnum.NORMAL.getType()
                : HrmAttendanceClockTypeEnum.LATE.getType();
        Integer clockInType;
        if (onLeave) {
            // 请假当天:只要有打卡就算正常,迟到与否不影响考勤
            clockInType = HrmAttendanceClockTypeEnum.NORMAL.getType();
        } else {
            clockInType = clockTimeOnly.isBefore(lateThreshold) || clockTimeOnly.equals(lateThreshold)
                    ? HrmAttendanceClockTypeEnum.NORMAL.getType()
                    : HrmAttendanceClockTypeEnum.LATE.getType();
        }
        // 5. 创建或更新考勤记录
        if (record == null) {
            record = HrmAttendanceRecordDO.builder()
                    .userId(userId)
                    .userId(systemUserId)
                    .deptId(employee.getDeptId())
                    .date(clockDate)
                    .clockInTime(clockTime)
                    .clockInType(clockInType)
                    .location(location)
                    .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
                    .build();
            applyLeaveInfo(record, leaves, rule, clockDate);
            applyClockLocation(record, rule, clockLocation, legacyLocation, true);
            attendanceRecordMapper.insert(record);
        } else {
            record.setClockInTime(clockTime);
            record.setClockInType(clockInType);
            if (location != null) {
                record.setLocation(location);
            }
            applyLeaveInfo(record, leaves, rule, clockDate);
            applyClockLocation(record, rule, clockLocation, legacyLocation, true);
            attendanceRecordMapper.updateById(record);
        }
        return record;
        return new HrmAttendanceClockResult(record, rule,
                clockLocation != null ? clockLocation.distanceMeters() : null);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location) {
    public HrmAttendanceClockResult clockOut(Long userId, LocalDateTime clockTime,
                                             HrmAttendanceClockReqVO reqVO, String legacyLocation) {
        LocalDate clockDate = clockTime.toLocalDate();
        // 1. 校验是否工作日
        if (!attendanceHolidayService.isWorkDay(clockDate)) {
@@ -378,40 +526,55 @@
        if (employee == null) {
            throw exception(EMPLOYEE_NOT_EXISTS);
        }
        Long systemUserId = employee.getUserId();
        HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
        if (rule == null) {
            throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
        }
        // 3. 查询当日考勤记录
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
        // 3. 查询当日考勤记录(系统用户 ID)
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(systemUserId, clockDate);
        if (record != null && record.getClockOutTime() != null) {
            throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
        }
        // 定位不合法时必须在创建“缺上班卡”记录前失败,避免留下脏数据。
        ClockLocation clockLocation = validateClockLocation(rule, reqVO);
        if (record == null) {
            // 如果没有上班打卡记录,创建一条缺卡的记录
            record = HrmAttendanceRecordDO.builder()
                    .userId(userId)
                    .userId(systemUserId)
                    .deptId(employee.getDeptId())
                    .date(clockDate)
                    .clockInType(HrmAttendanceClockTypeEnum.MISSING.getType())
                    .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
                    .build();
            attendanceRecordMapper.insert(record);
        }
        if (record.getClockOutTime() != null) {
            throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
        // 3.1 当天请假情况:请假当天允许打卡,不判定为早退,也不算缺上班卡
        //     同上:用系统用户 ID 查请假表
        List<cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO> leaves =
                leaveQueryService.getApprovedLeaveList(employee.getUserId(), clockDate);
        boolean onLeave = !leaves.isEmpty();
        if (onLeave && HrmAttendanceClockTypeEnum.MISSING.getType().equals(record.getClockInType())) {
            // 请假当天没打上班卡属于正常(人没来),不应记缺卡
            record.setClockInType(HrmAttendanceClockTypeEnum.LEAVE.getType());
        }
        // 4. 判断打卡类型
        LocalTime workEndTime = rule.getWorkEndTime();
        int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0;
        LocalTime earlyThreshold = workEndTime.minusMinutes(earlyAllowMinutes);
        LocalTime clockTimeOnly = clockTime.toLocalTime();
        Integer clockOutType = clockTimeOnly.isAfter(earlyThreshold) || clockTimeOnly.equals(earlyThreshold)
                ? HrmAttendanceClockTypeEnum.NORMAL.getType()
                : HrmAttendanceClockTypeEnum.EARLY.getType();
        Integer clockOutType;
        if (onLeave) {
            // 请假当天:有打卡就算正常
            clockOutType = HrmAttendanceClockTypeEnum.NORMAL.getType();
        } else {
            clockOutType = clockTimeOnly.isAfter(earlyThreshold) || clockTimeOnly.equals(earlyThreshold)
                    ? HrmAttendanceClockTypeEnum.NORMAL.getType()
                    : HrmAttendanceClockTypeEnum.EARLY.getType();
        }
        // 5. 更新考勤记录
        record.setClockOutTime(clockTime);
        record.setClockOutType(clockOutType);
        if (location != null) {
            record.setLocation(location);
        }
        applyLeaveInfo(record, leaves, rule, clockDate);
        applyClockLocation(record, rule, clockLocation, legacyLocation, false);
        // 6. 计算工作时长
        if (record.getClockInTime() != null) {
            Duration duration = Duration.between(record.getClockInTime(), clockTime);
@@ -419,8 +582,102 @@
                    .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
            record.setWorkHours(workHours);
        }
        attendanceRecordMapper.updateById(record);
        return record;
        if (record.getId() == null) {
            attendanceRecordMapper.insert(record);
        } else {
            attendanceRecordMapper.updateById(record);
        }
        return new HrmAttendanceClockResult(record, rule,
                clockLocation != null ? clockLocation.distanceMeters() : null);
    }
    record ClockLocation(BigDecimal longitude, BigDecimal latitude, BigDecimal accuracy,
                         BigDecimal distanceMeters) { }
    ClockLocation validateClockLocation(HrmAttendanceRuleDO rule, HrmAttendanceClockReqVO reqVO) {
        boolean required = Boolean.TRUE.equals(rule.getLocationRequired());
        if (required && (StrUtil.isBlank(rule.getLocationName()) || rule.getLongitude() == null
                || rule.getLatitude() == null || rule.getRadiusMeters() == null
                || rule.getRadiusMeters() < 10 || rule.getRadiusMeters() > 5000
                || !"WGS84".equalsIgnoreCase(rule.getCoordinateSystem()))) {
            throw exception(ATTENDANCE_LOCATION_RULE_INVALID);
        }
        if (reqVO == null || reqVO.getLongitude() == null || reqVO.getLatitude() == null
                || reqVO.getAccuracy() == null || StrUtil.isBlank(reqVO.getCoordinateSystem())) {
            if (required) {
                throw exception(ATTENDANCE_LOCATION_REQUIRED);
            }
            return null;
        }
        boolean coordinatesValid = reqVO.getLongitude().compareTo(BigDecimal.valueOf(-180)) >= 0
                && reqVO.getLongitude().compareTo(BigDecimal.valueOf(180)) <= 0
                && reqVO.getLatitude().compareTo(BigDecimal.valueOf(-90)) >= 0
                && reqVO.getLatitude().compareTo(BigDecimal.valueOf(90)) <= 0;
        if (!coordinatesValid) {
            if (required) {
                throw exception(ATTENDANCE_LOCATION_COORDINATE_INVALID);
            }
            return null;
        }
        if (reqVO.getAccuracy().signum() <= 0
                || reqVO.getAccuracy().compareTo(BigDecimal.valueOf(100)) > 0) {
            if (required) {
                throw exception(ATTENDANCE_LOCATION_ACCURACY_INVALID);
            }
            return null;
        }
        if (!"WGS84".equalsIgnoreCase(reqVO.getCoordinateSystem().trim())) {
            if (required) {
                throw exception(ATTENDANCE_LOCATION_SYSTEM_INVALID);
            }
            return null;
        }
        if (!required) {
            return new ClockLocation(reqVO.getLongitude(), reqVO.getLatitude(), reqVO.getAccuracy(), null);
        }
        double rawDistance = haversineMeters(rule.getLongitude().doubleValue(), rule.getLatitude().doubleValue(),
                reqVO.getLongitude().doubleValue(), reqVO.getLatitude().doubleValue());
        BigDecimal roundedDistance = BigDecimal.valueOf(rawDistance).setScale(2, RoundingMode.HALF_UP);
        if (rawDistance > rule.getRadiusMeters()) {
            throw exception(ATTENDANCE_LOCATION_OUT_OF_RANGE, rule.getLocationName(),
                    roundedDistance.toPlainString(), rule.getRadiusMeters());
        }
        return new ClockLocation(reqVO.getLongitude(), reqVO.getLatitude(), reqVO.getAccuracy(), roundedDistance);
    }
    static double haversineMeters(double lon1, double lat1, double lon2, double lat2) {
        double latDelta = Math.toRadians(lat2 - lat1);
        double lonDelta = Math.toRadians(lon2 - lon1);
        double a = Math.pow(Math.sin(latDelta / 2), 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.pow(Math.sin(lonDelta / 2), 2);
        double bounded = Math.max(0D, Math.min(1D, a));
        return 6_371_000D * 2D * Math.atan2(Math.sqrt(bounded), Math.sqrt(1D - bounded));
    }
    private void applyClockLocation(HrmAttendanceRecordDO record, HrmAttendanceRuleDO rule,
                                    ClockLocation location, String legacyLocation, boolean clockIn) {
        if (StrUtil.isNotBlank(rule.getLocationName())) {
            record.setLocation(rule.getLocationName());
        } else if (!Boolean.TRUE.equals(rule.getLocationRequired()) && StrUtil.isNotBlank(legacyLocation)) {
            record.setLocation(legacyLocation);
        }
        if (location == null) {
            return;
        }
        if (clockIn) {
            record.setClockInLongitude(location.longitude());
            record.setClockInLatitude(location.latitude());
            record.setClockInAccuracy(location.accuracy());
            record.setClockInDistance(location.distanceMeters());
            record.setClockInAddress(rule.getLocationAddress());
        } else {
            record.setClockOutLongitude(location.longitude());
            record.setClockOutLatitude(location.latitude());
            record.setClockOutAccuracy(location.accuracy());
            record.setClockOutDistance(location.distanceMeters());
            record.setClockOutAddress(rule.getLocationAddress());
        }
    }
    @Override
@@ -469,4 +726,4 @@
        attendanceRecordMapper.updateById(record);
    }
}
}