sql/mysql/hrm_attendance_rule_add_effective_date.sql
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,41 @@ -- ============================================= -- èå¤è§åï¼æ¯æåä¸é¨é¨é ç½®å¤æ¡è§åï¼æçææ¥æåºé´åºåï¼ -- -- åæ´ï¼hrm_attendance_rule å¢å 2 å -- effective_start_date çæå¼å§æ¥æï¼NULL = ä¸éï¼ -- effective_end_date çæç»ææ¥æï¼NULL = ä¸éï¼ -- -- å¹é è§åï¼å端 HrmAttendanceRuleServiceImpl.getAttendanceRuleByDeptIdï¼ï¼ -- 1) ä¼å åé¨é¨ä¸å±è§åï¼æ²¡æååå ¨å±è§åï¼dept_id IS NULLï¼ -- 2) åªå status=0 ä¸ çæåºé´è¦çè夿¥æ çè§å -- 3) 夿¡å½ä¸æ¶ï¼å effective_start_date ææç飿¡ï¼NULL è§ä¸ºé¿æææãä¼å 级æä½ï¼ -- -- èæ¬å¯é夿§è¡ï¼å¹çï¼ -- ============================================= SET NAMES utf8mb4; SET @sch := DATABASE(); -- 1. å¢å effective_start_date SET @exists := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @sch AND TABLE_NAME = 'hrm_attendance_rule' AND COLUMN_NAME = 'effective_start_date'); SET @sql := IF(@exists = 0, 'ALTER TABLE `hrm_attendance_rule` ADD COLUMN `effective_start_date` date DEFAULT NULL COMMENT ''çæå¼å§æ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼'' AFTER `overtime_threshold`', 'SELECT ''effective_start_date å·²åå¨ï¼è·³è¿'' AS msg'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 2. å¢å effective_end_date SET @exists := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @sch AND TABLE_NAME = 'hrm_attendance_rule' AND COLUMN_NAME = 'effective_end_date'); SET @sql := IF(@exists = 0, 'ALTER TABLE `hrm_attendance_rule` ADD COLUMN `effective_end_date` date DEFAULT NULL COMMENT ''çæç»ææ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼'' AFTER `effective_start_date`', 'SELECT ''effective_end_date å·²åå¨ï¼è·³è¿'' AS msg'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 3. æ ¸å¯¹ SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @sch AND TABLE_NAME = 'hrm_attendance_rule' AND COLUMN_NAME IN ('effective_start_date', 'effective_end_date'); SELECT 'èå¤è§åçææ¥æå段åå§å宿' AS result; yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -93,6 +93,7 @@ ErrorCode ATTENDANCE_EXCEPTION_UPDATE_AUDIT_STATUS_FAIL = new ErrorCode(1_030_004_014, "æ´æ°èå¤å¼å¸¸å®¡æ¹ç¶æå¤±è´¥ï¼åå ï¼ä¸æ¯å®¡æ¹ä¸ç¶æ"); ErrorCode ATTENDANCE_EXCEPTION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_004_015, "èå¤å¼å¸¸å®¡æ¹æµç¨ä¸åå¨"); ErrorCode ATTENDANCE_CLOCK_ALREADY_CLOCKED = new ErrorCode(1_030_004_016, "å·²æè¿å¡ï¼è¯·å¿é夿å¡"); ErrorCode ATTENDANCE_RULE_EFFECTIVE_DATE_INVALID = new ErrorCode(1_030_004_017, "çæå¼å§æ¥æä¸è½æäºçæç»ææ¥æ"); // ========== èªé ¬ç®¡ç 1-030-005-000 ========== ErrorCode SALARY_STRUCTURE_NOT_EXISTS = new ErrorCode(1_030_005_000, "èªé ¬ç»æä¸åå¨"); yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleRespVO.java
@@ -8,6 +8,7 @@ import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -51,6 +52,14 @@ @ExcelProperty("å çèµ·ç®æ¶é¿") private BigDecimal overtimeThreshold; @Schema(description = "çæå¼å§æ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼") @ExcelProperty("çæå¼å§æ¥æ") private LocalDate effectiveStartDate; @Schema(description = "çæç»ææ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼") @ExcelProperty("çæç»ææ¥æ") private LocalDate effectiveEndDate; @Schema(description = "ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨") @ExcelProperty(value = "ç¶æ", converter = DictConvert.class) @DictFormat("hrm_rule_status") yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRuleSaveReqVO.java
@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalTime; @Schema(description = "管çåå° - èå¤è§åæ°å¢/ä¿®æ¹ Request VO") @@ -39,6 +40,12 @@ @Schema(description = "å çèµ·ç®æ¶é¿ï¼å°æ¶ï¼", example = "1") private BigDecimal overtimeThreshold; @Schema(description = "çæå¼å§æ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼", example = "2026-01-01") private LocalDate effectiveStartDate; @Schema(description = "çæç»ææ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼", example = "2026-12-31") private LocalDate effectiveEndDate; @Schema(description = "ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨", example = "0") private Integer status; yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceRuleDO.java
@@ -68,6 +68,16 @@ private BigDecimal overtimeThreshold; /** * çæå¼å§æ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼ */ private LocalDate effectiveStartDate; /** * çæç»ææ¥æï¼ç©ºè¡¨ç¤ºä¸éï¼ */ private LocalDate effectiveEndDate; /** * ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨ */ private Integer status; yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -88,7 +88,7 @@ if (record.getDeptId() == null) { return; } HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId()); HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId(), record.getDate()); if (rule == null) { return; } @@ -125,7 +125,7 @@ } // å çæ¶é¿ï¼æèå¤è§åçæ åä¸çæ¶é´è®¡ç®è¶ é¢é¨å if (record.getClockOutTime() != null && record.getDeptId() != null) { HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId()); HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId(), record.getDate()); if (rule != null && rule.getWorkEndTime() != null) { LocalTime workEnd = rule.getWorkEndTime(); LocalDate outDate = record.getClockOutTime().toLocalDate(); @@ -159,7 +159,7 @@ if (employee == null) { throw exception(EMPLOYEE_NOT_EXISTS); } HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId()); HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), clockDate); if (rule == null) { throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE); } @@ -212,7 +212,7 @@ if (employee == null) { throw exception(EMPLOYEE_NOT_EXISTS); } HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId()); HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(employee.getDeptId(), clockDate); if (rule == null) { throw exception(ATTENDANCE_CLOCK_FAIL_NO_RULE); } yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleService.java
@@ -61,12 +61,14 @@ List<HrmAttendanceRuleDO> getAttendanceRuleList(); /** * æ ¹æ®é¨é¨IDè·åèå¤è§å * ä¼å å¹é é¨é¨è§åï¼å ¶æ¬¡æ¥æ¾å ¨å±è§åï¼deptId=nullï¼ * æ ¹æ®é¨é¨ID + è夿¥æè·åçæçèå¤è§å * ä¼å å¹é é¨é¨è§åï¼å ¶æ¬¡æ¥æ¾å ¨å±è§åï¼deptId=nullï¼ï¼ * åé¨é¨åå¨å¤æ¡è§åæ¶ï¼åãçæå¼å§æ¥æãææä¸è¦çè¯¥æ¥æç飿¡ã * * @param deptId é¨é¨ID * @param date è夿¥æï¼ä¸ºç©ºæ¶æå½å¤©ï¼ * @return èå¤è§å */ HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId); HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId, java.time.LocalDate date); } yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
@@ -2,6 +2,7 @@ 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 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 +12,7 @@ import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -18,6 +20,9 @@ /** * èå¤è§åé ç½® Service å®ç°ç±» * * 说æï¼åä¸é¨é¨å 许é ç½®å¤æ¡è§åï¼æãçæå¼å§æ¥æ / çæç»ææ¥æãåºé´åºåï¼ * å¹é æ¶åè¦çç®æ æ¥æãä¸çæå¼å§æ¥æææç飿¡ï¼æ æ¥æçè§ä¸ºé¿æææï¼ä¼å 级æä½ï¼ã * * @author è¶ çº§ç®¡çå */ @@ -30,12 +35,9 @@ @Override public Long createAttendanceRule(HrmAttendanceRuleSaveReqVO createReqVO) { // æ ¡éªè§ååç§°æ¯å¦éå¤ // æ ¡éªè§ååç§°æ¯å¦éå¤ï¼åä¸é¨é¨å è®¸å¤æ¡è§åï¼ä¸åéå¶é¨é¨å¯ä¸ï¼ validateAttendanceRuleNameUnique(null, createReqVO.getName()); // æ ¡éªé¨é¨æ¯å¦å·²é ç½®è§åï¼å¦ææå®äºé¨é¨ï¼ if (createReqVO.getDeptId() != null) { validateAttendanceRuleDeptUnique(null, createReqVO.getDeptId()); } validateEffectiveDateRange(createReqVO.getEffectiveStartDate(), createReqVO.getEffectiveEndDate()); HrmAttendanceRuleDO attendanceRule = BeanUtils.toBean(createReqVO, HrmAttendanceRuleDO.class); // 设置é»è®¤å¼ if (attendanceRule.getLateAllowMinutes() == null) { @@ -59,10 +61,7 @@ validateAttendanceRuleExists(updateReqVO.getId()); // æ ¡éªè§ååç§°æ¯å¦éå¤ validateAttendanceRuleNameUnique(updateReqVO.getId(), updateReqVO.getName()); // æ ¡éªé¨é¨æ¯å¦å·²é ç½®è§åï¼å¦ææå®äºé¨é¨ï¼ if (updateReqVO.getDeptId() != null) { validateAttendanceRuleDeptUnique(updateReqVO.getId(), updateReqVO.getDeptId()); } validateEffectiveDateRange(updateReqVO.getEffectiveStartDate(), updateReqVO.getEffectiveEndDate()); HrmAttendanceRuleDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceRuleDO.class); attendanceRuleMapper.updateById(updateObj); } @@ -92,15 +91,12 @@ } } 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); } /** * æ ¡éªçææ¥æåºé´ï¼å¼å§æ¥æä¸è½æäºç»ææ¥æ */ private void validateEffectiveDateRange(LocalDate startDate, LocalDate endDate) { if (startDate != null && endDate != null && startDate.isAfter(endDate)) { throw exception(ATTENDANCE_RULE_EFFECTIVE_DATE_INVALID); } } @@ -120,22 +116,47 @@ } @Override public HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId) { // 1. å æ¥æ¾é¨é¨ä¸å±è§å List<HrmAttendanceRuleDO> deptRules = attendanceRuleMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<HrmAttendanceRuleDO>() .eq(HrmAttendanceRuleDO::getDeptId, deptId) .eq(HrmAttendanceRuleDO::getStatus, 0)); if (!deptRules.isEmpty()) { return deptRules.get(0); public HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId, LocalDate date) { LocalDate targetDate = date != null ? date : LocalDate.now(); // 1. å æ¥æ¾é¨é¨ä¸å±è§åï¼è¦çç®æ æ¥æçï¼çæå¼å§æ¥æææçä¼å ï¼ if (deptId != null) { List<HrmAttendanceRuleDO> deptRules = selectEffectiveRules(deptId, targetDate); if (!deptRules.isEmpty()) { return deptRules.get(0); } } // 2. æ¥æ¾å ¨å±è§åï¼deptId 为 nullï¼ List<HrmAttendanceRuleDO> globalRules = attendanceRuleMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<HrmAttendanceRuleDO>() .isNull(HrmAttendanceRuleDO::getDeptId) .eq(HrmAttendanceRuleDO::getStatus, 0)); List<HrmAttendanceRuleDO> globalRules = selectEffectiveRules(null, targetDate); if (!globalRules.isEmpty()) { return globalRules.get(0); } return null; } } /** * æ¥è¯¢å¨æå®æ¥æçæçè§å * * @param deptId é¨é¨IDï¼null è¡¨ç¤ºå ¨å±è§å * @param date ç®æ æ¥æ */ private List<HrmAttendanceRuleDO> selectEffectiveRules(Long deptId, LocalDate date) { LambdaQueryWrapperX<HrmAttendanceRuleDO> wrapper = new LambdaQueryWrapperX<>(); if (deptId == null) { wrapper.isNull(HrmAttendanceRuleDO::getDeptId); } else { wrapper.eq(HrmAttendanceRuleDO::getDeptId, deptId); } wrapper.eq(HrmAttendanceRuleDO::getStatus, 0); // çæå¼å§æ¥æä¸ºç©ºï¼ä¸éï¼æ <= ç®æ æ¥æ wrapper.and(w -> w.isNull(HrmAttendanceRuleDO::getEffectiveStartDate) .or().le(HrmAttendanceRuleDO::getEffectiveStartDate, date)); // çæç»ææ¥æä¸ºç©ºï¼ä¸éï¼æ >= ç®æ æ¥æ wrapper.and(w -> w.isNull(HrmAttendanceRuleDO::getEffectiveEndDate) .or().ge(HrmAttendanceRuleDO::getEffectiveEndDate, date)); // çæå¼å§æ¥æè¶æä¼å 级è¶é«ï¼åå¼å§æ¥ææ¶åå å建ç wrapper.orderByDesc(HrmAttendanceRuleDO::getEffectiveStartDate); wrapper.orderByAsc(HrmAttendanceRuleDO::getId); return attendanceRuleMapper.selectList(wrapper); } }