liu
12 小时以前 5611b19e904ec0daa96ea5f648500cf6205c9385
fix(hrm): 合并定位打卡与考勤状态功能
已修改4个文件
296 ■■■■■ 文件已修改
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceMyStatusRespVO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
@@ -8,6 +8,8 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportExcelVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportReqVO;
@@ -16,6 +18,7 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceClockResult;
import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceRecordService;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -64,33 +67,44 @@
    @PostMapping("/clock-in")
    @Operation(summary = "上班打卡")
    public CommonResult<HrmAttendanceRecordRespVO> clockIn(@RequestParam("location") String location) {
    @PreAuthorize("@ss.hasPermission('hrm:attendance-record:clock')")
    @ApiAccessLog(requestEnable = false)
    public CommonResult<HrmAttendanceClockRespVO> clockIn(
            @Valid @RequestBody(required = false) HrmAttendanceClockReqVO reqVO,
            @RequestParam(value = "location", required = false) String legacyLocation) {
        Long loginUserId = getLoginUserId();
        HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
        if (employee == null) {
            throw exception(EMPLOYEE_NOT_EXISTS,
                    "当前登录账号未关联员工档案,无法打卡。请联系管理员在【员工管理】中将该登录账号绑定到员工。");
        }
        HrmAttendanceRecordDO record = attendanceRecordService.clockIn(employee.getId(), LocalDateTime.now(), location);
        return success(buildClockRespVO(record, employee));
        HrmAttendanceClockResult result = attendanceRecordService.clockIn(
                employee.getId(), LocalDateTime.now(), reqVO, legacyLocation);
        return success(buildClockResultVO(result, employee));
    }
    @PostMapping("/clock-out")
    @Operation(summary = "下班打卡")
    public CommonResult<HrmAttendanceRecordRespVO> clockOut(@RequestParam("location") String location) {
    @PreAuthorize("@ss.hasPermission('hrm:attendance-record:clock')")
    @ApiAccessLog(requestEnable = false)
    public CommonResult<HrmAttendanceClockRespVO> clockOut(
            @Valid @RequestBody(required = false) HrmAttendanceClockReqVO reqVO,
            @RequestParam(value = "location", required = false) String legacyLocation) {
        Long loginUserId = getLoginUserId();
        HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
        if (employee == null) {
            throw exception(EMPLOYEE_NOT_EXISTS,
                    "当前登录账号未关联员工档案,无法打卡。请联系管理员在【员工管理】中将该登录账号绑定到员工。");
        }
        HrmAttendanceRecordDO record = attendanceRecordService.clockOut(employee.getId(), LocalDateTime.now(), location);
        return success(buildClockRespVO(record, employee));
        HrmAttendanceClockResult result = attendanceRecordService.clockOut(
                employee.getId(), LocalDateTime.now(), reqVO, legacyLocation);
        return success(buildClockResultVO(result, employee));
    }
    @GetMapping("/my-status")
    @Operation(summary = "获得当前登录人的考勤状态")
    @Parameter(name = "date", description = "考勤日期,格式 yyyy-MM-dd,默认服务器当天", example = "2026-10-17")
    @PreAuthorize("@ss.hasPermission('hrm:attendance-record:clock')")
    public CommonResult<HrmAttendanceMyStatusRespVO> getMyStatus(
            @RequestParam(value = "date", required = false)
            @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) LocalDate date) {
@@ -114,6 +128,19 @@
        return respVO;
    }
    private HrmAttendanceClockRespVO buildClockResultVO(HrmAttendanceClockResult result,
                                                         HrmEmployeeDO employee) {
        HrmAttendanceClockRespVO respVO = new HrmAttendanceClockRespVO();
        respVO.setRecord(buildClockRespVO(result.record(), employee));
        respVO.setLocationName(result.rule().getLocationName());
        if (Boolean.TRUE.equals(result.rule().getLocationRequired())) {
            respVO.setDistanceMeters(result.distanceMeters());
            respVO.setRadiusMeters(result.rule().getRadiusMeters());
            respVO.setWithinRange(true);
        }
        return respVO;
    }
    private Long getLoginUserId() {
        return cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId();
    }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceMyStatusRespVO.java
@@ -3,8 +3,10 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Schema(description = "管理后台 - 我的考勤状态 Response VO")
@Data
@@ -18,6 +20,9 @@
    @Schema(description = "员工姓名", example = "阿卜杜克热木·吾拉依木")
    private String employeeName;
    @Schema(description = "员工姓名(兼容旧版客户端)")
    private String userName;
    @Schema(description = "部门ID", example = "131")
    private Long deptId;
@@ -40,9 +45,78 @@
    @Schema(description = "当前生效的考勤规则,未配置时为 null")
    private HrmAttendanceRuleRespVO rule;
    @Schema(description = "考勤规则名称(兼容旧版客户端)")
    private String ruleName;
    @Schema(description = "上班时间(兼容旧版客户端)")
    private LocalTime workStartTime;
    @Schema(description = "下班时间(兼容旧版客户端)")
    private LocalTime workEndTime;
    @Schema(description = "是否启用定位范围校验")
    private Boolean locationRequired;
    @Schema(description = "打卡地点名称")
    private String locationName;
    @Schema(description = "打卡地点地址")
    private String locationAddress;
    @Schema(description = "打卡中心经度(WGS84)")
    private BigDecimal longitude;
    @Schema(description = "打卡中心纬度(WGS84)")
    private BigDecimal latitude;
    @Schema(description = "允许打卡半径,单位米")
    private Integer radiusMeters;
    @Schema(description = "坐标系")
    private String coordinateSystem;
    @Schema(description = "当日考勤记录,未打卡时为 null")
    private HrmAttendanceRecordRespVO record;
    @Schema(description = "今日记录ID(兼容旧版客户端)")
    private Long recordId;
    @Schema(description = "上班打卡时间(兼容旧版客户端)")
    private LocalDateTime clockInTime;
    @Schema(description = "下班打卡时间(兼容旧版客户端)")
    private LocalDateTime clockOutTime;
    @Schema(description = "上班打卡类型(兼容旧版客户端)")
    private Integer clockInType;
    @Schema(description = "下班打卡类型(兼容旧版客户端)")
    private Integer clockOutType;
    @Schema(description = "是否已上班打卡(兼容旧版客户端)")
    private Boolean clockInDone;
    @Schema(description = "是否已下班打卡(兼容旧版客户端)")
    private Boolean clockOutDone;
    @Schema(description = "是否可上班打卡(兼容旧版客户端)")
    private Boolean canClockIn;
    @Schema(description = "是否可下班打卡(兼容旧版客户端)")
    private Boolean canClockOut;
    @Schema(description = "工作时长,小时(兼容旧版客户端)")
    private BigDecimal workHours;
    @Schema(description = "加班时长,小时(兼容旧版客户端)")
    private BigDecimal overtimeHours;
    @Schema(description = "打卡地点(兼容旧版客户端)")
    private String location;
    @Schema(description = "数据来源(兼容旧版客户端)")
    private Integer dataSource;
    @Schema(description = "当前可打卡状态")
    private Clockable clockable;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
@@ -2,6 +2,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
@@ -58,20 +59,24 @@
     *
     * @param userId 员工ID
     * @param clockTime 打卡时间
     * @param location 打卡地点
     * @return 考勤记录
     * @param reqVO 定位原始数据
     * @param legacyLocation 旧客户端传入的地点文本,仅在未启用定位范围时兼容使用
     * @return 打卡结果
     */
    HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location);
    HrmAttendanceClockResult clockIn(Long userId, LocalDateTime clockTime,
                                     HrmAttendanceClockReqVO reqVO, String legacyLocation);
    /**
     * 打卡(下班打卡)
     *
     * @param userId 员工ID
     * @param clockTime 打卡时间
     * @param location 打卡地点
     * @return 考勤记录
     * @param reqVO 定位原始数据
     * @param legacyLocation 旧客户端传入的地点文本,仅在未启用定位范围时兼容使用
     * @return 打卡结果
     */
    HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location);
    HrmAttendanceClockResult clockOut(Long userId, LocalDateTime clockTime,
                                      HrmAttendanceClockReqVO reqVO, String legacyLocation);
    /**
     * 补卡审批通过后更新考勤记录
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;
@@ -89,6 +91,7 @@
        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 +120,51 @@
                }
            }
            respVO.setRule(ruleRespVO);
            fillCompatibleRuleFields(respVO, rule);
        }
        // 4. 当日考勤记录
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(employee.getId(), queryDate);
        if (record != null) {
            respVO.setRecord(buildRecordRespVO(record, employee));
            fillCompatibleRecordFields(respVO, record);
        }
        // 5. 当前可打卡状态
        respVO.setClockable(buildClockable(workDay, rule, record, queryDate));
        HrmAttendanceMyStatusRespVO.Clockable clockable = buildClockable(workDay, rule, record, queryDate);
        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());
    }
    /**
@@ -314,7 +351,8 @@
    @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)) {
@@ -334,6 +372,8 @@
        if (record != null && record.getClockInTime() != null) {
            throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
        }
        // 在任何写库操作前完成定位校验,旧版地点字符串不能绕过范围限制。
        ClockLocation clockLocation = validateClockLocation(rule, reqVO);
        // 4. 判断打卡类型
        LocalTime workStartTime = rule.getWorkStartTime();
        int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
@@ -350,24 +390,24 @@
                    .date(clockDate)
                    .clockInTime(clockTime)
                    .clockInType(clockInType)
                    .location(location)
                    .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
                    .build();
            applyClockLocation(record, rule, clockLocation, legacyLocation, true);
            attendanceRecordMapper.insert(record);
        } else {
            record.setClockInTime(clockTime);
            record.setClockInType(clockInType);
            if (location != null) {
                record.setLocation(location);
            }
            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)) {
@@ -384,8 +424,12 @@
        }
        // 3. 查询当日考勤记录
        HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, 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)
                    .deptId(employee.getDeptId())
@@ -393,10 +437,6 @@
                    .clockInType(HrmAttendanceClockTypeEnum.MISSING.getType())
                    .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
                    .build();
            attendanceRecordMapper.insert(record);
        }
        if (record.getClockOutTime() != null) {
            throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
        }
        // 4. 判断打卡类型
        LocalTime workEndTime = rule.getWorkEndTime();
@@ -409,9 +449,7 @@
        // 5. 更新考勤记录
        record.setClockOutTime(clockTime);
        record.setClockOutType(clockOutType);
        if (location != null) {
            record.setLocation(location);
        }
        applyClockLocation(record, rule, clockLocation, legacyLocation, false);
        // 6. 计算工作时长
        if (record.getClockInTime() != null) {
            Duration duration = Duration.between(record.getClockInTime(), clockTime);
@@ -419,8 +457,102 @@
                    .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
            record.setWorkHours(workHours);
        }
        if (record.getId() == null) {
            attendanceRecordMapper.insert(record);
        } else {
        attendanceRecordMapper.updateById(record);
        return 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