吴羡
7 小时以前 a797b5801af481990e40c029221c55229291eae8
feat: 考勤节假日配置改为时间段、物料列表排序优化

1. HRM 考勤:节假日配置由「单日」改为「时间段」,新增 startDate/endDate,date 列保留兼容
(保存时仍写入开始日期);保存校验结束不早于开始、且不与已有配置区间重叠;
列表筛选改为按日期区间交集查询;isWorkDay 改为按时间段覆盖判断。
2. MDM 物料:物料列表排序由按 ID 倒序改为按分类、物料编码升序。

(参考 dev_宁夏_万通新材料 分支实现)
已修改9个文件
112 ■■■■■ 文件已修改
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayPageReqVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -85,7 +85,7 @@
    ErrorCode ATTENDANCE_RULE_NAME_EXISTS = new ErrorCode(1_030_004_006, "考勤规则名称已存在");
    ErrorCode ATTENDANCE_RULE_DEPT_EXISTS = new ErrorCode(1_030_004_007, "该部门已配置考勤规则");
    ErrorCode ATTENDANCE_HOLIDAY_NOT_EXISTS = new ErrorCode(1_030_004_008, "节假日配置不存在(ID={}),可能已被他人删除。请刷新列表后重试");
    ErrorCode ATTENDANCE_HOLIDAY_DATE_EXISTS = new ErrorCode(1_030_004_009, "日期【{}】已存在节假日配置,不能重复新增。请改为编辑该日期的记录(可直接修改类型与备注),或先删除原记录再新增");
    ErrorCode ATTENDANCE_HOLIDAY_RANGE_OVERLAP = new ErrorCode(1_030_004_009, "日期区间【{} ~ {}】与已有配置「{}」({} ~ {})重叠,无法保存。请调整本次日期区间避开该配置,或直接编辑/删除已有配置后再保存");
    ErrorCode ATTENDANCE_CLOCK_FAIL_NO_RULE = new ErrorCode(1_030_004_010, "打卡失败,未找到考勤规则");
    ErrorCode ATTENDANCE_CLOCK_FAIL_HOLIDAY = new ErrorCode(1_030_004_011, "打卡失败,当日为休息日");
    ErrorCode ATTENDANCE_CLOCK_FAIL_NOT_WORKDAY = new ErrorCode(1_030_004_012, "打卡失败,当日不是工作日");
@@ -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_HOLIDAY_DATE_RANGE_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/HrmAttendanceHolidayPageReqVO.java
@@ -23,7 +23,7 @@
    @Schema(description = "是否法定节假日:0-否,1-是", example = "1")
    private Integer isLegal;
    @Schema(description = "日期区间,传 [开始日期, 结束日期]", example = "[\"2026-01-01\", \"2026-12-31\"]")
    @Schema(description = "日期区间,传 [开始日期, 结束日期],返回与配置时间段有交集的数据", example = "[\"2026-01-01\", \"2026-12-31\"]")
    private LocalDate[] date;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidayRespVO.java
@@ -23,9 +23,13 @@
    @ExcelProperty("节假日名称")
    private String name;
    @Schema(description = "日期")
    @ExcelProperty("日期")
    private LocalDate date;
    @Schema(description = "开始日期")
    @ExcelProperty("开始日期")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @ExcelProperty("结束日期")
    private LocalDate endDate;
    @Schema(description = "类型:1-休息日,2-工作日")
    @ExcelProperty(value = "类型", converter = DictConvert.class)
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceHolidaySaveReqVO.java
@@ -18,9 +18,13 @@
    @NotBlank(message = "节假日名称不能为空")
    private String name;
    @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-10")
    @NotNull(message = "日期不能为空")
    private LocalDate date;
    @Schema(description = "开始日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-10")
    @NotNull(message = "开始日期不能为空")
    private LocalDate startDate;
    @Schema(description = "结束日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-17")
    @NotNull(message = "结束日期不能为空")
    private LocalDate endDate;
    @Schema(description = "类型:1-休息日,2-工作日", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "类型不能为空")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceHolidayDO.java
@@ -35,11 +35,21 @@
    private String name;
    /**
     * 日期
     * 日期(兼容存量 NOT NULL 列的冗余字段,始终写入 startDate)
     */
    private LocalDate date;
    /**
     * 开始日期
     */
    private LocalDate startDate;
    /**
     * 结束日期
     */
    private LocalDate endDate;
    /**
     * 类型:1-休息日,2-工作日
     *
     * 枚举 {@link cn.iocoder.yudao.module.hrm.enums.HrmAttendanceHolidayTypeEnum}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmAttendanceHolidayMapper.java
@@ -19,23 +19,30 @@
public interface HrmAttendanceHolidayMapper extends BaseMapperX<HrmAttendanceHolidayDO> {
    default PageResult<HrmAttendanceHolidayDO> selectPage(HrmAttendanceHolidayPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
        LambdaQueryWrapperX<HrmAttendanceHolidayDO> query = new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
                .likeIfPresent(HrmAttendanceHolidayDO::getName, reqVO.getName())
                .eqIfPresent(HrmAttendanceHolidayDO::getType, reqVO.getType())
                .eqIfPresent(HrmAttendanceHolidayDO::getIsLegal, reqVO.getIsLegal())
                .betweenIfPresent(HrmAttendanceHolidayDO::getDate, reqVO.getDate())
                .orderByDesc(HrmAttendanceHolidayDO::getDate));
                .eqIfPresent(HrmAttendanceHolidayDO::getIsLegal, reqVO.getIsLegal());
        // 日期筛选:配置时间段与查询区间有交集即命中
        LocalDate[] dateRange = reqVO.getDate();
        if (dateRange != null && dateRange.length >= 2 && dateRange[0] != null && dateRange[1] != null) {
            query.le(HrmAttendanceHolidayDO::getStartDate, dateRange[1])
                    .ge(HrmAttendanceHolidayDO::getEndDate, dateRange[0]);
        }
        return selectPage(reqVO, query.orderByDesc(HrmAttendanceHolidayDO::getStartDate)
                .orderByDesc(HrmAttendanceHolidayDO::getId));
    }
    default HrmAttendanceHolidayDO selectByDate(LocalDate date) {
        return selectOne(HrmAttendanceHolidayDO::getDate, date);
    }
    default List<HrmAttendanceHolidayDO> selectListByDateBetween(LocalDate startDate, LocalDate endDate) {
    /**
     * 查询时间段与 [startDate, endDate] 有交集的配置
     * 传入同一天(date, date)时,即查询覆盖该日的配置
     */
    default List<HrmAttendanceHolidayDO> selectListByDateRange(LocalDate startDate, LocalDate endDate) {
        return selectList(new LambdaQueryWrapperX<HrmAttendanceHolidayDO>()
                .ge(HrmAttendanceHolidayDO::getDate, startDate)
                .le(HrmAttendanceHolidayDO::getDate, endDate)
                .orderByAsc(HrmAttendanceHolidayDO::getDate));
                .le(HrmAttendanceHolidayDO::getStartDate, endDate)
                .ge(HrmAttendanceHolidayDO::getEndDate, startDate)
                .orderByAsc(HrmAttendanceHolidayDO::getStartDate)
                .orderByAsc(HrmAttendanceHolidayDO::getId));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayService.java
@@ -55,7 +55,7 @@
    PageResult<HrmAttendanceHolidayDO> getAttendanceHolidayPage(HrmAttendanceHolidayPageReqVO pageReqVO);
    /**
     * 获得指定日期范围的节假日列表
     * 获得时间段与指定日期范围有交集的节假日列表
     *
     * @param startDate 开始日期
     * @param endDate 结束日期
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceHolidayServiceImpl.java
@@ -32,9 +32,10 @@
    @Override
    public Long createAttendanceHoliday(HrmAttendanceHolidaySaveReqVO createReqVO) {
        // 校验日期是否已存在配置
        validateAttendanceHolidayDateExists(createReqVO.getDate());
        validateDateRange(createReqVO);
        validateDateRangeNoOverlap(createReqVO.getStartDate(), createReqVO.getEndDate(), null);
        HrmAttendanceHolidayDO attendanceHoliday = BeanUtils.toBean(createReqVO, HrmAttendanceHolidayDO.class);
        attendanceHoliday.setDate(createReqVO.getStartDate());
        if (attendanceHoliday.getIsLegal() == null) {
            attendanceHoliday.setIsLegal(0);
        }
@@ -45,12 +46,10 @@
    @Override
    public void updateAttendanceHoliday(HrmAttendanceHolidaySaveReqVO updateReqVO) {
        validateAttendanceHolidayExists(updateReqVO.getId());
        // 如果日期变更,校验新日期是否已存在配置
        HrmAttendanceHolidayDO oldHoliday = attendanceHolidayMapper.selectById(updateReqVO.getId());
        if (!oldHoliday.getDate().equals(updateReqVO.getDate())) {
            validateAttendanceHolidayDateExists(updateReqVO.getDate());
        }
        validateDateRange(updateReqVO);
        validateDateRangeNoOverlap(updateReqVO.getStartDate(), updateReqVO.getEndDate(), updateReqVO.getId());
        HrmAttendanceHolidayDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceHolidayDO.class);
        updateObj.setDate(updateReqVO.getStartDate());
        attendanceHolidayMapper.updateById(updateObj);
    }
@@ -66,9 +65,19 @@
        }
    }
    private void validateAttendanceHolidayDateExists(LocalDate date) {
        if (attendanceHolidayMapper.selectByDate(date) != null) {
            throw exception(ATTENDANCE_HOLIDAY_DATE_EXISTS, date);
    private void validateDateRange(HrmAttendanceHolidaySaveReqVO reqVO) {
        if (reqVO.getEndDate().isBefore(reqVO.getStartDate())) {
            throw exception(ATTENDANCE_HOLIDAY_DATE_RANGE_INVALID, reqVO.getEndDate(), reqVO.getStartDate());
        }
    }
    private void validateDateRangeNoOverlap(LocalDate startDate, LocalDate endDate, Long excludeId) {
        for (HrmAttendanceHolidayDO exist : attendanceHolidayMapper.selectListByDateRange(startDate, endDate)) {
            if (excludeId != null && excludeId.equals(exist.getId())) {
                continue;
            }
            throw exception(ATTENDANCE_HOLIDAY_RANGE_OVERLAP, startDate, endDate,
                    exist.getName(), exist.getStartDate(), exist.getEndDate());
        }
    }
@@ -84,16 +93,16 @@
    @Override
    public List<HrmAttendanceHolidayDO> getAttendanceHolidayListByDateRange(LocalDate startDate, LocalDate endDate) {
        return attendanceHolidayMapper.selectListByDateBetween(startDate, endDate);
        return attendanceHolidayMapper.selectListByDateRange(startDate, endDate);
    }
    @Override
    public boolean isWorkDay(LocalDate date) {
        // 1. 查询节假日配置
        HrmAttendanceHolidayDO holiday = attendanceHolidayMapper.selectByDate(date);
        if (holiday != null) {
        // 1. 查询覆盖该日期的节假日配置(配置时间段不允许重叠,至多命中一条)
        List<HrmAttendanceHolidayDO> holidays = attendanceHolidayMapper.selectListByDateRange(date, date);
        if (!holidays.isEmpty()) {
            // 有配置时,按配置判断
            return HrmAttendanceHolidayTypeEnum.WORK_DAY.getType().equals(holiday.getType());
            return HrmAttendanceHolidayTypeEnum.WORK_DAY.getType().equals(holidays.get(0).getType());
        }
        // 2. 无配置时,按周末判断
        DayOfWeek dayOfWeek = date.getDayOfWeek();
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
@@ -30,7 +30,8 @@
                .eqIfPresent(MdmItemDO::getBrandId, reqVO.getBrandId())
                .eqIfPresent(MdmItemDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(MdmItemDO::getCreateTime, reqVO.getCreateTime())
                .orderByDesc(MdmItemDO::getId));
                .orderByAsc(MdmItemDO::getCategoryId)
                .orderByAsc(MdmItemDO::getCode));
    }
    default List<MdmItemDO> selectList(MdmItemPageReqVO reqVO, Collection<Long> categoryIds) {