From 690b713454828f34f901bc1e3a5f7bcc35a31b30 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 26 十月 2026 17:52:05 +0800
Subject: [PATCH] feat(hrm): 支持定位范围打卡
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java | 606 +++++++++++++++++++++++++++++++-----------------------
1 files changed, 345 insertions(+), 261 deletions(-)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
index 95a4a13..5e322f0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -1,12 +1,18 @@
package cn.iocoder.yudao.module.hrm.service.attendance;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
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.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.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleRespVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
@@ -15,10 +21,7 @@
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceDataSourceEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceExceptionTypeEnum;
-import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
-import cn.iocoder.yudao.module.system.api.dept.DeptApi;
-import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -77,12 +80,335 @@
}
@Override
+ public void createImportedRecord(HrmAttendanceRecordDO record) {
+ // 鎸夎�冨嫟瑙勫垯閲嶇畻鎵撳崱绫诲瀷涓庢椂闀匡紝涓嶄俊浠� Excel 濉啓鐨勫��
+ fillClockTypes(record);
+ fillCalculatedHours(record);
+ attendanceRecordMapper.insert(record);
+ }
+
+ @Override
+ public void updateImportedRecord(HrmAttendanceRecordDO record) {
+ fillClockTypes(record);
+ fillCalculatedHours(record);
+ attendanceRecordMapper.updateById(record);
+ }
+
+ /**
+ * 鎸夎�冨嫟瑙勫垯閲嶇畻涓婁笅鐝墦鍗$被鍨嬶紙姝e父/杩熷埌/鏃╅��锛夛紝涓庢墦鍗¢�昏緫淇濇寔涓�鑷�
+ */
+ private void fillClockTypes(HrmAttendanceRecordDO record) {
+ if (record.getDeptId() == null) {
+ return;
+ }
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId(), record.getDate());
+ if (rule == null) {
+ return;
+ }
+ // 涓婄彮鎵撳崱绫诲瀷锛氭櫄浜� 涓婄彮鏃堕棿+杩熷埌鍏佽 璁颁负杩熷埌
+ if (record.getClockInTime() != null && rule.getWorkStartTime() != null) {
+ int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
+ LocalTime lateThreshold = rule.getWorkStartTime().plusMinutes(lateAllowMinutes);
+ LocalTime clockInOnly = record.getClockInTime().toLocalTime();
+ record.setClockInType(clockInOnly.isAfter(lateThreshold)
+ ? HrmAttendanceClockTypeEnum.LATE.getType()
+ : HrmAttendanceClockTypeEnum.NORMAL.getType());
+ }
+ // 涓嬬彮鎵撳崱绫诲瀷锛氭棭浜� 涓嬬彮鏃堕棿-鏃╅��鍏佽 璁颁负鏃╅��
+ if (record.getClockOutTime() != null && rule.getWorkEndTime() != null) {
+ int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0;
+ LocalTime earlyThreshold = rule.getWorkEndTime().minusMinutes(earlyAllowMinutes);
+ LocalTime clockOutOnly = record.getClockOutTime().toLocalTime();
+ record.setClockOutType(clockOutOnly.isBefore(earlyThreshold)
+ ? HrmAttendanceClockTypeEnum.EARLY.getType()
+ : HrmAttendanceClockTypeEnum.NORMAL.getType());
+ }
+ }
+
+ /**
+ * 鏍规嵁涓婁笅鐝墦鍗℃椂闂村拰鑰冨嫟瑙勫垯锛岃绠楀伐浣滄椂闀夸笌鍔犵彮鏃堕暱
+ */
+ private void fillCalculatedHours(HrmAttendanceRecordDO record) {
+ // 宸ヤ綔鏃堕暱 = 涓嬬彮鎵撳崱鏃堕棿 - 涓婄彮鎵撳崱鏃堕棿锛堜笌鎵撳崱閫昏緫涓�鑷达級
+ if (record.getClockInTime() != null && record.getClockOutTime() != null) {
+ Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime());
+ BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
+ .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+ record.setWorkHours(workHours);
+ }
+ // 鍔犵彮鏃堕暱锛氭寜鑰冨嫟瑙勫垯鐨勬爣鍑嗕笅鐝椂闂磋绠楄秴棰濋儴鍒�
+ if (record.getClockOutTime() != null && record.getDeptId() != null) {
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId(), record.getDate());
+ if (rule != null && rule.getWorkEndTime() != null) {
+ LocalTime workEnd = rule.getWorkEndTime();
+ LocalDate outDate = record.getClockOutTime().toLocalDate();
+ LocalDateTime workEndDateTime = LocalDateTime.of(outDate, workEnd);
+ if (record.getClockOutTime().isAfter(workEndDateTime)) {
+ Duration overtimeDuration = Duration.between(workEndDateTime, record.getClockOutTime());
+ BigDecimal overtimeThreshold = rule.getOvertimeThreshold() != null
+ ? rule.getOvertimeThreshold() : BigDecimal.ZERO;
+ BigDecimal overtimeHours = BigDecimal.valueOf(overtimeDuration.toMinutes())
+ .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+ record.setOvertimeHours(overtimeHours.compareTo(overtimeThreshold) > 0 ? overtimeHours : BigDecimal.ZERO);
+ } else {
+ record.setOvertimeHours(BigDecimal.ZERO);
+ }
+ } else {
+ record.setOvertimeHours(BigDecimal.ZERO);
+ }
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ 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. 鑾峰彇鍛樺伐淇℃伅鍜岃�冨嫟瑙勫垯
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS);
+ }
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), clockDate);
+ if (rule == null) {
+ throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
+ }
+ // 3. 鏌ヨ褰撴棩鑰冨嫟璁板綍
+ HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
+ 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;
+ LocalTime lateThreshold = workStartTime.plusMinutes(lateAllowMinutes);
+ LocalTime clockTimeOnly = clockTime.toLocalTime();
+ Integer clockInType = clockTimeOnly.isBefore(lateThreshold) || clockTimeOnly.equals(lateThreshold)
+ ? HrmAttendanceClockTypeEnum.NORMAL.getType()
+ : HrmAttendanceClockTypeEnum.LATE.getType();
+ // 5. 鍒涘缓鎴栨洿鏂拌�冨嫟璁板綍
+ if (record == null) {
+ record = HrmAttendanceRecordDO.builder()
+ .userId(userId)
+ .deptId(employee.getDeptId())
+ .date(clockDate)
+ .clockInTime(clockTime)
+ .clockInType(clockInType)
+ .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
+ .build();
+ applyClockLocation(record, rule, clockLocation, legacyLocation, true);
+ attendanceRecordMapper.insert(record);
+ } else {
+ record.setClockInTime(clockTime);
+ record.setClockInType(clockInType);
+ applyClockLocation(record, rule, clockLocation, legacyLocation, true);
+ attendanceRecordMapper.updateById(record);
+ }
+ return new HrmAttendanceClockResult(record, rule,
+ clockLocation != null ? clockLocation.distanceMeters() : null);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public HrmAttendanceClockResult clockOut(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. 鑾峰彇鍛樺伐淇℃伅鍜岃�冨嫟瑙勫垯
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ if (employee == null) {
+ throw exception(EMPLOYEE_NOT_EXISTS);
+ }
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), clockDate);
+ if (rule == null) {
+ throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
+ }
+ // 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())
+ .date(clockDate)
+ .clockInType(HrmAttendanceClockTypeEnum.MISSING.getType())
+ .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
+ .build();
+ }
+ // 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();
+ // 5. 鏇存柊鑰冨嫟璁板綍
+ record.setClockOutTime(clockTime);
+ record.setClockOutType(clockOutType);
+ applyClockLocation(record, rule, clockLocation, legacyLocation, false);
+ // 6. 璁$畻宸ヤ綔鏃堕暱
+ if (record.getClockInTime() != null) {
+ Duration duration = Duration.between(record.getClockInTime(), clockTime);
+ BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
+ .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+ record.setWorkHours(workHours);
+ }
+ 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
+ @Transactional(rollbackFor = Exception.class)
+ public void supplementClockIn(HrmAttendanceExceptionDO exception) {
+ // 浠呭鐞嗚ˉ鍗$被鍨�
+ if (!HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())) {
+ return;
+ }
+ LocalDate exceptionDate = exception.getExceptionDate();
+ LocalDateTime exceptionTime = exception.getExceptionTime();
+ Long userId = exception.getUserId();
+ // 鏌ヨ鑰冨嫟璁板綍
+ HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, exceptionDate);
+ if (record == null) {
+ // 濡傛灉娌℃湁璁板綍锛屽垱寤轰竴鏉�
+ HrmEmployeeDO employee = employeeService.getEmployee(userId);
+ record = HrmAttendanceRecordDO.builder()
+ .userId(userId)
+ .deptId(employee != null ? employee.getDeptId() : exception.getDeptId())
+ .date(exceptionDate)
+ .dataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType())
+ .build();
+ attendanceRecordMapper.insert(record);
+ }
+ // 鏍规嵁寮傚父鏃堕棿鍒ゆ柇鏄笂鐝繕鏄笅鐝ˉ鍗�
+ LocalTime time = exceptionTime.toLocalTime();
+ if (time.isBefore(LocalTime.of(12, 0))) {
+ // 涓婄彮琛ュ崱
+ record.setClockInTime(exceptionTime);
+ record.setClockInType(HrmAttendanceClockTypeEnum.NORMAL.getType());
+ } else {
+ // 涓嬬彮琛ュ崱
+ record.setClockOutTime(exceptionTime);
+ record.setClockOutType(HrmAttendanceClockTypeEnum.NORMAL.getType());
+ }
+ // 鏇存柊鏁版嵁鏉ユ簮涓鸿ˉ鍗″鎵�
+ record.setDataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType());
+ // 璁$畻宸ヤ綔鏃堕暱
+ if (record.getClockInTime() != null && record.getClockOutTime() != null) {
+ Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime());
+ BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
+ .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+ record.setWorkHours(workHours);
+ }
+ attendanceRecordMapper.updateById(record);
+ }
+
+ // ==================== 鎴戠殑鑰冨嫟鐘舵�侊紙App 鎵撳崱椤碉級 ====================
+
+ @Override
public HrmAttendanceMyStatusRespVO getMyStatus(Long loginUserId, LocalDate date) {
LocalDate queryDate = date != null ? date : LocalDate.now();
// 1. 鐧诲綍鐢ㄦ埛 -> 鍛樺伐妗f
HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId);
if (employee == null) {
- throw exception(EMPLOYEE_NOT_EXISTS,
+ throw new ServiceException(EMPLOYEE_NOT_EXISTS.getCode(),
"褰撳墠鐧诲綍璐﹀彿鏈叧鑱斿憳宸ユ。妗堬紝鏃犳硶鎵撳崱銆傝鑱旂郴绠$悊鍛樺湪銆愬憳宸ョ鐞嗐�戜腑灏嗚鐧诲綍璐﹀彿缁戝畾鍒板憳宸ャ��");
}
HrmAttendanceMyStatusRespVO respVO = new HrmAttendanceMyStatusRespVO();
@@ -106,8 +432,8 @@
respVO.setRestDayReason(getRestDayReason(queryDate));
}
- // 3. 褰撳墠鐢熸晥瑙勫垯锛堥儴闂ㄨ鍒欎紭鍏堬紝鍏舵鍏ㄥ眬瑙勫垯锛�
- HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId());
+ // 3. 褰撳墠鐢熸晥瑙勫垯锛堥儴闂ㄨ鍒欎紭鍏堬紝鍏舵鍏ㄥ眬瑙勫垯锛涙寜鏃ユ湡鍖归厤鐢熸晥鍖洪棿锛�
+ HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), queryDate);
if (rule != null) {
HrmAttendanceRuleRespVO ruleRespVO = BeanUtils.toBean(rule, HrmAttendanceRuleRespVO.class);
if (rule.getDeptId() != null) {
@@ -117,12 +443,21 @@
}
}
respVO.setRule(ruleRespVO);
+ // 鍏煎鏃х増瀹㈡埛绔細淇濈暀骞抽摵鐨勫畾浣嶅瓧娈�
+ 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());
}
// 4. 褰撴棩鑰冨嫟璁板綍
HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(employee.getId(), queryDate);
if (record != null) {
respVO.setRecord(buildRecordRespVO(record, employee));
+ respVO.setRecordId(record.getId());
}
// 5. 褰撳墠鍙墦鍗$姸鎬�
@@ -151,7 +486,7 @@
}
/**
- * 鏋勫缓鎵撳崱璁板綍杩斿洖 VO锛岃ˉ榻愭墦鍗$被鍨嬪悕绉般�佸憳宸ヤ笌閮ㄩ棬淇℃伅
+ * 鏋勫缓鎵撳崱璁板綍杩斿洖 VO锛岃ˉ榻愭墦鍗$被鍨嬪悕绉颁笌鍛樺伐淇℃伅
*/
private HrmAttendanceRecordRespVO buildRecordRespVO(HrmAttendanceRecordDO record, HrmEmployeeDO employee) {
HrmAttendanceRecordRespVO recordRespVO = BeanUtils.toBean(record, HrmAttendanceRecordRespVO.class);
@@ -159,19 +494,12 @@
recordRespVO.setClockOutTypeName(HrmAttendanceClockTypeEnum.getNameByType(record.getClockOutType()));
if (employee != null) {
recordRespVO.setUserName(employee.getName());
- recordRespVO.setPhone(employee.getPhone());
- }
- if (record.getDeptId() != null) {
- DeptRespDTO dept = deptApi.getDept(record.getDeptId());
- if (dept != null) {
- recordRespVO.setDeptName(dept.getName());
- }
}
return recordRespVO;
}
/**
- * 鏍规嵁宸ヤ綔鏃ャ�佽�冨嫟瑙勫垯涓庡綋鏃ヨ褰曡绠楀彲鎵撳崱鐘舵��
+ * 璁$畻褰撳墠鍙墦鍗$姸鎬�
*/
private HrmAttendanceMyStatusRespVO.Clockable buildClockable(boolean workDay, HrmAttendanceRuleDO rule,
HrmAttendanceRecordDO record, LocalDate date) {
@@ -232,248 +560,4 @@
return clockable;
}
- @Override
- public void createImportedRecord(HrmAttendanceRecordDO record) {
- // 鎸夎�冨嫟瑙勫垯閲嶇畻鎵撳崱绫诲瀷涓庢椂闀匡紝涓嶄俊浠� Excel 濉啓鐨勫��
- fillClockTypes(record);
- fillCalculatedHours(record);
- attendanceRecordMapper.insert(record);
- }
-
- @Override
- public void updateImportedRecord(HrmAttendanceRecordDO record) {
- fillClockTypes(record);
- fillCalculatedHours(record);
- attendanceRecordMapper.updateById(record);
- }
-
- /**
- * 鎸夎�冨嫟瑙勫垯閲嶇畻涓婁笅鐝墦鍗$被鍨嬶紙姝e父/杩熷埌/鏃╅��锛夛紝涓庢墦鍗¢�昏緫淇濇寔涓�鑷�
- */
- private void fillClockTypes(HrmAttendanceRecordDO record) {
- if (record.getDeptId() == null) {
- return;
- }
- // 涓婄彮鎵撳崱绫诲瀷锛氭櫄浜� 涓婄彮鏃堕棿+杩熷埌鍏佽 璁颁负杩熷埌锛堟寜涓婄彮鎵撳崱鏃堕棿鍖归厤鐝锛�
- if (record.getClockInTime() != null) {
- HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(
- record.getDeptId(), record.getClockInTime());
- if (rule != null && rule.getWorkStartTime() != null) {
- int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
- LocalTime lateThreshold = rule.getWorkStartTime().plusMinutes(lateAllowMinutes);
- LocalTime clockInOnly = record.getClockInTime().toLocalTime();
- record.setClockInType(clockInOnly.isAfter(lateThreshold)
- ? HrmAttendanceClockTypeEnum.LATE.getType()
- : HrmAttendanceClockTypeEnum.NORMAL.getType());
- }
- }
- // 涓嬬彮鎵撳崱绫诲瀷锛氭棭浜� 涓嬬彮鏃堕棿-鏃╅��鍏佽 璁颁负鏃╅��锛堟寜涓嬬彮鎵撳崱鏃堕棿鍖归厤鐝锛�
- if (record.getClockOutTime() != null) {
- HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(
- record.getDeptId(), record.getClockOutTime());
- if (rule != null && rule.getWorkEndTime() != null) {
- int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0;
- LocalTime earlyThreshold = rule.getWorkEndTime().minusMinutes(earlyAllowMinutes);
- LocalTime clockOutOnly = record.getClockOutTime().toLocalTime();
- record.setClockOutType(clockOutOnly.isBefore(earlyThreshold)
- ? HrmAttendanceClockTypeEnum.EARLY.getType()
- : HrmAttendanceClockTypeEnum.NORMAL.getType());
- }
- }
- }
-
- /**
- * 鏍规嵁涓婁笅鐝墦鍗℃椂闂村拰鑰冨嫟瑙勫垯锛岃绠楀伐浣滄椂闀夸笌鍔犵彮鏃堕暱
- */
- private void fillCalculatedHours(HrmAttendanceRecordDO record) {
- // 宸ヤ綔鏃堕暱 = 涓嬬彮鎵撳崱鏃堕棿 - 涓婄彮鎵撳崱鏃堕棿锛堜笌鎵撳崱閫昏緫涓�鑷达級
- if (record.getClockInTime() != null && record.getClockOutTime() != null) {
- Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime());
- BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
- .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
- record.setWorkHours(workHours);
- }
- // 鍔犵彮鏃堕暱锛氭寜鑰冨嫟瑙勫垯鐨勬爣鍑嗕笅鐝椂闂磋绠楄秴棰濋儴鍒嗭紙鎸変笅鐝墦鍗℃椂闂村尮閰嶇彮娆★級
- if (record.getClockOutTime() != null && record.getDeptId() != null) {
- HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(
- record.getDeptId(), record.getClockOutTime());
- if (rule != null && rule.getWorkEndTime() != null) {
- LocalTime workEnd = rule.getWorkEndTime();
- LocalDate outDate = record.getClockOutTime().toLocalDate();
- LocalDateTime workEndDateTime = LocalDateTime.of(outDate, workEnd);
- if (record.getClockOutTime().isAfter(workEndDateTime)) {
- Duration overtimeDuration = Duration.between(workEndDateTime, record.getClockOutTime());
- BigDecimal overtimeThreshold = rule.getOvertimeThreshold() != null
- ? rule.getOvertimeThreshold() : BigDecimal.ZERO;
- BigDecimal overtimeHours = BigDecimal.valueOf(overtimeDuration.toMinutes())
- .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
- record.setOvertimeHours(overtimeHours.compareTo(overtimeThreshold) > 0 ? overtimeHours : BigDecimal.ZERO);
- } else {
- record.setOvertimeHours(BigDecimal.ZERO);
- }
- } else {
- record.setOvertimeHours(BigDecimal.ZERO);
- }
- }
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location) {
- LocalDate clockDate = clockTime.toLocalDate();
- // 1. 鏍¢獙鏄惁宸ヤ綔鏃�
- if (!attendanceHolidayService.isWorkDay(clockDate)) {
- throw exception(ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY);
- }
- // 2. 鑾峰彇鍛樺伐淇℃伅鍜岃�冨嫟瑙勫垯
- HrmEmployeeDO employee = employeeService.getEmployee(userId);
- if (employee == null) {
- throw exception(EMPLOYEE_NOT_EXISTS);
- }
- // 鎸夋湰娆℃墦鍗℃椂闂村尮閰嶇彮娆★紙鍚屼竴涓儴闂ㄥ彲鑳介厤缃簡澶氭潯瑙勫垯锛�
- HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), clockTime);
- if (rule == null) {
- throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
- }
- // 3. 鏌ヨ褰撴棩鑰冨嫟璁板綍
- HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
- if (record != null && record.getClockInTime() != null) {
- throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED);
- }
- // 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();
- // 5. 鍒涘缓鎴栨洿鏂拌�冨嫟璁板綍
- if (record == null) {
- record = HrmAttendanceRecordDO.builder()
- .userId(userId)
- .deptId(employee.getDeptId())
- .date(clockDate)
- .clockInTime(clockTime)
- .clockInType(clockInType)
- .location(location)
- .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType())
- .build();
- attendanceRecordMapper.insert(record);
- } else {
- record.setClockInTime(clockTime);
- record.setClockInType(clockInType);
- if (location != null) {
- record.setLocation(location);
- }
- attendanceRecordMapper.updateById(record);
- }
- return record;
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location) {
- LocalDate clockDate = clockTime.toLocalDate();
- // 1. 鏍¢獙鏄惁宸ヤ綔鏃�
- if (!attendanceHolidayService.isWorkDay(clockDate)) {
- throw exception(ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY);
- }
- // 2. 鑾峰彇鍛樺伐淇℃伅鍜岃�冨嫟瑙勫垯
- HrmEmployeeDO employee = employeeService.getEmployee(userId);
- if (employee == null) {
- throw exception(EMPLOYEE_NOT_EXISTS);
- }
- // 鎸夋湰娆℃墦鍗℃椂闂村尮閰嶇彮娆★紙鍚屼竴涓儴闂ㄥ彲鑳介厤缃簡澶氭潯瑙勫垯锛�
- HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), clockTime);
- if (rule == null) {
- throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE);
- }
- // 3. 鏌ヨ褰撴棩鑰冨嫟璁板綍
- HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate);
- if (record == null) {
- // 濡傛灉娌℃湁涓婄彮鎵撳崱璁板綍锛屽垱寤轰竴鏉$己鍗$殑璁板綍
- record = HrmAttendanceRecordDO.builder()
- .userId(userId)
- .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);
- }
- // 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();
- // 5. 鏇存柊鑰冨嫟璁板綍
- record.setClockOutTime(clockTime);
- record.setClockOutType(clockOutType);
- if (location != null) {
- record.setLocation(location);
- }
- // 6. 璁$畻宸ヤ綔鏃堕暱
- if (record.getClockInTime() != null) {
- Duration duration = Duration.between(record.getClockInTime(), clockTime);
- BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
- .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
- record.setWorkHours(workHours);
- }
- attendanceRecordMapper.updateById(record);
- return record;
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void supplementClockIn(HrmAttendanceExceptionDO exception) {
- // 浠呭鐞嗚ˉ鍗$被鍨�
- if (!HrmAttendanceExceptionTypeEnum.SUPPLEMENT.getType().equals(exception.getType())) {
- return;
- }
- LocalDate exceptionDate = exception.getExceptionDate();
- LocalDateTime exceptionTime = exception.getExceptionTime();
- Long userId = exception.getUserId();
- // 鏌ヨ鑰冨嫟璁板綍
- HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, exceptionDate);
- if (record == null) {
- // 濡傛灉娌℃湁璁板綍锛屽垱寤轰竴鏉�
- HrmEmployeeDO employee = employeeService.getEmployee(userId);
- record = HrmAttendanceRecordDO.builder()
- .userId(userId)
- .deptId(employee != null ? employee.getDeptId() : exception.getDeptId())
- .date(exceptionDate)
- .dataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType())
- .build();
- attendanceRecordMapper.insert(record);
- }
- // 鏍规嵁寮傚父鏃堕棿鍒ゆ柇鏄笂鐝繕鏄笅鐝ˉ鍗�
- LocalTime time = exceptionTime.toLocalTime();
- if (time.isBefore(LocalTime.of(12, 0))) {
- // 涓婄彮琛ュ崱
- record.setClockInTime(exceptionTime);
- record.setClockInType(HrmAttendanceClockTypeEnum.NORMAL.getType());
- } else {
- // 涓嬬彮琛ュ崱
- record.setClockOutTime(exceptionTime);
- record.setClockOutType(HrmAttendanceClockTypeEnum.NORMAL.getType());
- }
- // 鏇存柊鏁版嵁鏉ユ簮涓鸿ˉ鍗″鎵�
- record.setDataSource(HrmAttendanceDataSourceEnum.SUPPLEMENT.getType());
- // 璁$畻宸ヤ綔鏃堕暱
- if (record.getClockInTime() != null && record.getClockOutTime() != null) {
- Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime());
- BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
- .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
- record.setWorkHours(workHours);
- }
- attendanceRecordMapper.updateById(record);
- }
-
-}
\ No newline at end of file
+}
--
Gitblit v1.9.3