feat: 考勤节假日配置改为时间段、物料列表排序优化
1. HRM 考勤:节假日配置由「单日」改为「时间段」,新增 startDate/endDate,date 列保留兼容
(保存时仍写入开始日期);保存校验结束不早于开始、且不与已有配置区间重叠;
列表筛选改为按日期区间交集查询;isWorkDay 改为按时间段覆盖判断。
2. MDM 物料:物料列表排序由按 ID 倒序改为按分类、物料编码升序。
(参考 dev_宁夏_万通新材料 分支实现)
| | |
| | | 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, "打卡失败,当日不是工作日"); |
| | |
| | | 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, "薪酬结构不存在"); |
| | |
| | | @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; |
| | | |
| | | } |
| | |
| | | @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) |
| | |
| | | @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 = "类型不能为空") |
| | |
| | | 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} |
| | |
| | | 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)); |
| | | } |
| | | |
| | | } |
| | |
| | | PageResult<HrmAttendanceHolidayDO> getAttendanceHolidayPage(HrmAttendanceHolidayPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * 获得指定日期范围的节假日列表 |
| | | * 获得时间段与指定日期范围有交集的节假日列表 |
| | | * |
| | | * @param startDate 开始日期 |
| | | * @param endDate 结束日期 |
| | |
| | | |
| | | @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); |
| | | } |
| | |
| | | @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); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | 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()); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | @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(); |
| | |
| | | .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) { |