gaoluyang
8 小时以前 e71d71bf43c2307c89d3592adf08c1b32fcd0907
fix(hrm): 同步考勤节假日时间段表结构并修复规则部门继承

- 新增 hrm_attendance_holiday 的 start_date/end_date 迁移脚本,补齐 my-status 查询所需列
- 考勤规则改为沿部门树向上继承,解决员工所在部门未直接配置规则时返回「未配置考勤规则」
- 同一部门存在多条启用规则时取最新一条,保证规则解析结果稳定

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加1个文件
已修改2个文件
107 ■■■■■ 文件已修改
sql/mysql/hrm_attendance_holiday_date_range_20261017.sql 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_attendance_holiday_date_range_20261017.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
-- =====================================================
-- HRM è€ƒå‹¤èŠ‚å‡æ—¥é…ç½®ï¼šè¡¥é½ã€Œæ—¶é—´æ®µã€ç»“æž„ï¼ˆstart_date / end_date)
--
-- èƒŒæ™¯ï¼š
--   2026-09-30 çš„æäº¤ a797b580 å°†ã€ŒèŠ‚å‡æ—¥é…ç½®ã€ç”±å•æ—¥(date)改为时间段(startDate/endDate),
--   DO/VO/Mapper å·²æŒ‰ start_date、end_date æŸ¥è¯¢ï¼Œä½†æ•°æ®åº“表结构没有同步。
--   2026-10-17 æ–°å¢žçš„ GET /hrm/attendance/record/my-status ç¬¬ä¸€æ¬¡èµ°åˆ°èŠ‚å‡æ—¥æŸ¥è¯¢ï¼Œ
--   æŠ¥ Unknown column 'start_date' in 'field list'。
--
-- å†…容:
--   â‘  hrm_attendance_holiday æ–°å¢ž start_date、end_date
--   â‘¡ å­˜é‡æ•°æ®æŒ‰ date å›žå¡«ï¼ˆdate åˆ—保留兼容,保存时仍写入 start_date)
--   â‘¢ å›žå¡«åŽæ”¶ç´§ä¸º NOT NULL,避免后续写入半条时间段
--
-- å¹‚等:可重复执行(MySQL ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,先查 information_schema)
-- æ‰§è¡Œæ–¹å¼ï¼š
--   mysql -h<host> -P<port> -u<user> -p --default-character-set=utf8mb4 <db> < docs/sql/hrm_attendance_holiday_date_range_20261017.sql
-- =====================================================
SET NAMES utf8mb4;
-- ------------------------------------------------------------
-- â‘  æ–°å¢ž start_date
-- ------------------------------------------------------------
SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns
                   WHERE table_schema = DATABASE()
                     AND table_name = 'hrm_attendance_holiday'
                     AND column_name = 'start_date');
SET @ddl = IF(@col_exists = 0,
              'ALTER TABLE `hrm_attendance_holiday` ADD COLUMN `start_date` date DEFAULT NULL COMMENT ''开始日期'' AFTER `date`',
              'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ------------------------------------------------------------
-- â‘¡ æ–°å¢ž end_date
-- ------------------------------------------------------------
SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns
                   WHERE table_schema = DATABASE()
                     AND table_name = 'hrm_attendance_holiday'
                     AND column_name = 'end_date');
SET @ddl = IF(@col_exists = 0,
              'ALTER TABLE `hrm_attendance_holiday` ADD COLUMN `end_date` date DEFAULT NULL COMMENT ''结束日期'' AFTER `start_date`',
              'SELECT 1');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ------------------------------------------------------------
-- â‘¢ å­˜é‡æ•°æ®å›žå¡«ï¼šå•日配置的起止日期都等于原 date
-- ------------------------------------------------------------
UPDATE `hrm_attendance_holiday` SET `start_date` = `date` WHERE `start_date` IS NULL;
UPDATE `hrm_attendance_holiday` SET `end_date` = `date` WHERE `end_date` IS NULL;
-- ------------------------------------------------------------
-- â‘£ å›žå¡«å®ŒæˆåŽæ”¶ç´§ä¸º NOT NULL(幂等)
-- ------------------------------------------------------------
ALTER TABLE `hrm_attendance_holiday`
    MODIFY COLUMN `start_date` date NOT NULL COMMENT '开始日期',
    MODIFY COLUMN `end_date` date NOT NULL COMMENT '结束日期';
-- ------------------------------------------------------------
-- æ ¡éªŒ
-- ------------------------------------------------------------
SELECT 'start_date' AS item, COUNT(*) AS cnt FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'hrm_attendance_holiday' AND column_name = 'start_date'
UNION ALL
SELECT 'end_date', COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'hrm_attendance_holiday' AND column_name = 'end_date';
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceRuleMapper.java
@@ -37,4 +37,16 @@
                .orderByAsc(HrmAttendanceRuleDO::getName));
    }
    /**
     * æŸ¥è¯¢éƒ¨é—¨ä¸‹å·²å¯ç”¨çš„规则,最新创建的排在前面
     *
     * åŒä¸€éƒ¨é—¨å­˜åœ¨å¤šæ¡å¯ç”¨è§„则时,以最新创建的一条为当前生效规则
     */
    default List<HrmAttendanceRuleDO> selectEnabledListByDeptId(Long deptId) {
        return selectList(new LambdaQueryWrapperX<HrmAttendanceRuleDO>()
                .eq(HrmAttendanceRuleDO::getDeptId, deptId)
                .eq(HrmAttendanceRuleDO::getStatus, 0)
                .orderByDesc(HrmAttendanceRuleDO::getId));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRuleServiceImpl.java
@@ -6,12 +6,16 @@
import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRuleMapper;
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.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
@@ -27,6 +31,8 @@
    @Resource
    private HrmAttendanceRuleMapper attendanceRuleMapper;
    @Resource
    private DeptApi deptApi;
    @Override
    public Long createAttendanceRule(HrmAttendanceRuleSaveReqVO createReqVO) {
@@ -121,17 +127,26 @@
    @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));
        // 1. ä»Žå‘˜å·¥æ‰€åœ¨éƒ¨é—¨å¼€å§‹é€çº§å‘上查找:配置在上级部门的规则对其子部门生效
        Set<Long> visitedDeptIds = new HashSet<>();
        Long currentDeptId = deptId;
        while (currentDeptId != null && currentDeptId != 0 && visitedDeptIds.add(currentDeptId)) {
            List<HrmAttendanceRuleDO> deptRules = attendanceRuleMapper.selectEnabledListByDeptId(currentDeptId);
        if (!deptRules.isEmpty()) {
                // åŒä¸€éƒ¨é—¨ï¼ˆå¦‚「总公司」)可能配置了多条启用规则,以最新创建的为当前生效规则
            return deptRules.get(0);
            }
            DeptRespDTO dept = deptApi.getDept(currentDeptId);
            if (dept == null) {
                break;
            }
            currentDeptId = dept.getParentId();
        }
        // 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));
                .eq(HrmAttendanceRuleDO::getStatus, 0)
                .orderByDesc(HrmAttendanceRuleDO::getId));
        if (!globalRules.isEmpty()) {
            return globalRules.get(0);
        }