package cn.iocoder.yudao.module.hrm.service.attendance; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO; import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO; 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.dal.mysql.attendance.HrmAttendanceRecordMapper; import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; class HrmAttendanceClockLocationTest { private final HrmAttendanceRecordServiceImpl service = new HrmAttendanceRecordServiceImpl(); private HrmAttendanceRuleDO requiredRule() { return HrmAttendanceRuleDO.builder().locationRequired(true).locationName("公司前台") .longitude(BigDecimal.ZERO).latitude(BigDecimal.ZERO) .radiusMeters(100).coordinateSystem("WGS84").build(); } private HrmAttendanceClockReqVO position(String latitude, String accuracy) { HrmAttendanceClockReqVO reqVO = new HrmAttendanceClockReqVO(); reqVO.setLongitude(BigDecimal.ZERO); reqVO.setLatitude(new BigDecimal(latitude)); reqVO.setAccuracy(new BigDecimal(accuracy)); reqVO.setCoordinateSystem("WGS84"); return reqVO; } @Test void oldRuleWithoutLocationStillAcceptsClock() { HrmAttendanceRuleDO rule = HrmAttendanceRuleDO.builder().locationRequired(false).build(); assertNull(service.validateClockLocation(rule, null)); assertNull(service.validateClockLocation(rule, position("0.0001", "20")).distanceMeters()); } @Test void requiredLocationValidatesCompletenessCoordinatesAndAccuracy() { HrmAttendanceRuleDO rule = requiredRule(); assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, null)); assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, position("91", "20"))); assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, position("0", "100.01"))); assertNotNull(service.validateClockLocation(rule, position("0", "100"))); } @Test void comparesUnroundedDistanceWithRadius() { HrmAttendanceRuleDO rule = requiredRule(); assertTrue(HrmAttendanceRecordServiceImpl.haversineMeters(0, 0, 0, 0.00089) < 100); assertNotNull(service.validateClockLocation(rule, position("0.00089", "20"))); assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, position("0.00091", "20"))); } @Test void invalidClockOutLocationDoesNotCreateMissingClockInRecord() { HrmAttendanceHolidayService holidayService = mock(HrmAttendanceHolidayService.class); HrmEmployeeService employeeService = mock(HrmEmployeeService.class); HrmAttendanceRuleService ruleService = mock(HrmAttendanceRuleService.class); HrmAttendanceRecordMapper mapper = mock(HrmAttendanceRecordMapper.class); ReflectionTestUtils.setField(service, "attendanceHolidayService", holidayService); ReflectionTestUtils.setField(service, "employeeService", employeeService); ReflectionTestUtils.setField(service, "attendanceRuleService", ruleService); ReflectionTestUtils.setField(service, "attendanceRecordMapper", mapper); LocalDateTime clockTime = LocalDateTime.of(2026, 10, 22, 18, 56); when(holidayService.isWorkDay(any(LocalDate.class))).thenReturn(true); HrmEmployeeDO employee = HrmEmployeeDO.builder().id(1L).deptId(2L).build(); when(employeeService.getEmployee(1L)).thenReturn(employee); when(ruleService.getAttendanceRuleByDeptId(2L)).thenReturn(requiredRule()); assertThrows(ServiceException.class, () -> service.clockOut(1L, clockTime, position("0.00091", "20"), null)); verify(mapper, never()).insert(any(HrmAttendanceRecordDO.class)); verify(mapper, never()).updateById(any(HrmAttendanceRecordDO.class)); } }