From 5db13c2ad6678cf4614b81c95df035c6f47d313d Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 16 十月 2026 21:57:36 +0800
Subject: [PATCH] feat(hrm): 考勤规则放开部门唯一限制,支持同一部门多班次
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java | 104 ++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 77 insertions(+), 27 deletions(-)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
index ecc82a7..15c6391 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
@@ -2,6 +2,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.google.common.annotations.VisibleForTesting;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRulePageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
@@ -11,6 +13,10 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Comparator;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -25,6 +31,9 @@
@Validated
public class HrmAttendanceRuleServiceImpl implements HrmAttendanceRuleService {
+ /** 瑙勫垯鐘舵�侊細0-鍚敤 */
+ private static final Integer ENABLED_STATUS = 0;
+
@Resource
private HrmAttendanceRuleMapper attendanceRuleMapper;
@@ -32,10 +41,7 @@
public Long createAttendanceRule(HrmAttendanceRuleSaveReqVO createReqVO) {
// 鏍¢獙瑙勫垯鍚嶇О鏄惁閲嶅
validateAttendanceRuleNameUnique(null, createReqVO.getName());
- // 鏍¢獙閮ㄩ棬鏄惁宸查厤缃鍒欙紙濡傛灉鎸囧畾浜嗛儴闂級
- if (createReqVO.getDeptId() != null) {
- validateAttendanceRuleDeptUnique(null, createReqVO.getDeptId());
- }
+ // 娉細鍚屼竴涓儴闂ㄥ厑璁搁厤缃鏉¤鍒欙紙澶氱彮娆★級锛岃繖閲屼笉鍐嶆牎楠岄儴闂ㄥ敮涓�
HrmAttendanceRuleDO attendanceRule = BeanUtils.toBean(createReqVO, HrmAttendanceRuleDO.class);
// 璁剧疆榛樿鍊�
if (attendanceRule.getLateAllowMinutes() == null) {
@@ -59,10 +65,7 @@
validateAttendanceRuleExists(updateReqVO.getId());
// 鏍¢獙瑙勫垯鍚嶇О鏄惁閲嶅
validateAttendanceRuleNameUnique(updateReqVO.getId(), updateReqVO.getName());
- // 鏍¢獙閮ㄩ棬鏄惁宸查厤缃鍒欙紙濡傛灉鎸囧畾浜嗛儴闂級
- if (updateReqVO.getDeptId() != null) {
- validateAttendanceRuleDeptUnique(updateReqVO.getId(), updateReqVO.getDeptId());
- }
+ // 娉細鍚屼竴涓儴闂ㄥ厑璁搁厤缃鏉¤鍒欙紙澶氱彮娆★級锛岃繖閲屼笉鍐嶆牎楠岄儴闂ㄥ敮涓�
HrmAttendanceRuleDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceRuleDO.class);
attendanceRuleMapper.updateById(updateObj);
}
@@ -92,18 +95,6 @@
}
}
- private void validateAttendanceRuleDeptUnique(Long id, Long deptId) {
- List<HrmAttendanceRuleDO> rules = attendanceRuleMapper.selectListByDeptId(deptId);
- if (rules.isEmpty()) {
- return;
- }
- for (HrmAttendanceRuleDO rule : rules) {
- if (id == null || !rule.getId().equals(id)) {
- throw exception(ATTENDANCE_RULE_DEPT_EXISTS);
- }
- }
- }
-
@Override
public HrmAttendanceRuleDO getAttendanceRule(Long id) {
return attendanceRuleMapper.selectById(id);
@@ -121,21 +112,80 @@
@Override
public HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId) {
- // 1. 鍏堟煡鎵鹃儴闂ㄤ笓灞炶鍒�
- List<HrmAttendanceRuleDO> deptRules = attendanceRuleMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ return getAttendanceRuleByDeptId(deptId, null);
+ }
+
+ @Override
+ public HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId, LocalDateTime clockTime) {
+ // 1. 鍏堟煡鎵鹃儴闂ㄤ笓灞炶鍒欙紙鍚屼竴涓儴闂ㄥ彲浠ラ厤缃鏉★紝鐢ㄤ簬澶氱彮娆★級
+ List<HrmAttendanceRuleDO> deptRules = attendanceRuleMapper.selectList(new LambdaQueryWrapperX<HrmAttendanceRuleDO>()
.eq(HrmAttendanceRuleDO::getDeptId, deptId)
- .eq(HrmAttendanceRuleDO::getStatus, 0));
+ .eq(HrmAttendanceRuleDO::getStatus, ENABLED_STATUS));
if (!deptRules.isEmpty()) {
- return deptRules.get(0);
+ return matchRuleByClockTime(deptRules, clockTime);
}
// 2. 鏌ユ壘鍏ㄥ眬瑙勫垯锛坉eptId 涓� null锛�
- List<HrmAttendanceRuleDO> globalRules = attendanceRuleMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<HrmAttendanceRuleDO>()
+ List<HrmAttendanceRuleDO> globalRules = attendanceRuleMapper.selectList(new LambdaQueryWrapperX<HrmAttendanceRuleDO>()
.isNull(HrmAttendanceRuleDO::getDeptId)
- .eq(HrmAttendanceRuleDO::getStatus, 0));
+ .eq(HrmAttendanceRuleDO::getStatus, ENABLED_STATUS));
if (!globalRules.isEmpty()) {
- return globalRules.get(0);
+ return matchRuleByClockTime(globalRules, clockTime);
}
return null;
}
+ /**
+ * 澶氭潯瑙勫垯锛堝悓涓�涓儴闂ㄥ鐝锛夋椂锛屾寜鎵撳崱鏃堕棿閫夌彮娆★細
+ * 1. 鎵撳崱鏃堕棿钀藉湪鏌愪釜鐝鐨� [涓婄彮鏃堕棿, 涓嬬彮鏃堕棿] 鍐咃紝灏卞彇杩欎竴鏉�
+ * 2. 閮戒笉鍦ㄧ獥鍙e唴鏃讹紝鍙栫彮娆℃椂闂寸獥鍙g鎵撳崱鏃堕棿鏈�杩戠殑涓�鏉�
+ * 3. 浠嶇劧骞跺垪鏃讹紝鎸変笂鐝椂闂淬�佺紪鍙锋帓搴忥紝淇濊瘉缁撴灉绋冲畾锛堜笉浼氶殢鏁版嵁搴撹繑鍥為『搴忔紓绉伙級
+ * 鎵撳崱鏃堕棿涓虹┖锛堜笉鍏冲績鐝锛夋椂锛屽彇涓婄彮鏃堕棿鏈�鏃╃殑涓�鏉°��
+ */
+ @VisibleForTesting
+ static HrmAttendanceRuleDO matchRuleByClockTime(List<HrmAttendanceRuleDO> rules, LocalDateTime clockTime) {
+ if (rules.size() == 1) {
+ return rules.get(0);
+ }
+ if (clockTime == null) {
+ return rules.stream().min(Comparator
+ .comparing(HrmAttendanceRuleDO::getWorkStartTime,
+ Comparator.nullsLast(Comparator.<LocalTime>naturalOrder()))
+ .thenComparing(HrmAttendanceRuleDO::getId))
+ .orElse(rules.get(0));
+ }
+ LocalTime time = clockTime.toLocalTime();
+ return rules.stream().min(Comparator
+ .comparingLong((HrmAttendanceRuleDO rule) -> minutesToRuleWindow(rule, time))
+ .thenComparing(HrmAttendanceRuleDO::getWorkStartTime,
+ Comparator.nullsLast(Comparator.<LocalTime>naturalOrder()))
+ .thenComparing(HrmAttendanceRuleDO::getId))
+ .orElse(rules.get(0));
+ }
+
+ /**
+ * 鎵撳崱鏃堕棿鍒拌鍒欑彮娆$獥鍙g殑璺濈锛堝垎閽燂級锛氳惤鍦ㄧ獥鍙e唴涓� 0锛岀彮娆℃椂闂存湭閰嶇疆鏃惰涓烘渶澶с��
+ * 鏀寔璺ㄥぉ鐝锛堝 22:00-06:00锛屽彧瑕� end <= start 灏辨寜璺ㄥぉ澶勭悊锛夈��
+ */
+ private static long minutesToRuleWindow(HrmAttendanceRuleDO rule, LocalTime time) {
+ LocalTime start = rule.getWorkStartTime();
+ LocalTime end = rule.getWorkEndTime();
+ if (start == null || end == null) {
+ return Long.MAX_VALUE;
+ }
+ boolean crossDay = !end.isAfter(start);
+ boolean inWindow = crossDay
+ ? !time.isBefore(start) || !time.isAfter(end)
+ : !time.isBefore(start) && !time.isAfter(end);
+ if (inWindow) {
+ return 0;
+ }
+ return Math.min(minutesBetween(time, start), minutesBetween(time, end));
+ }
+
+ /** 涓や釜鏃跺埢鐨勫垎閽熷樊锛屾寜涓�澶� 24 灏忔椂鍙栨渶杩戠殑鐜洖璺濈锛�23:50 涓� 00:10 鍙樊 20 鍒嗛挓锛� */
+ private static long minutesBetween(LocalTime one, LocalTime other) {
+ long minutes = Math.abs(Duration.between(one, other).toMinutes());
+ return Math.min(minutes, 24 * 60 - minutes);
+ }
+
}
\ No newline at end of file
--
Gitblit v1.9.3