# EosShiftConfigServiceImpl.java

    package cloud.wconnect.frontoffice.service.endofshift.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.bo.PropertyBO;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.UpdateLogUtil;
    import cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig;
    import cloud.wconnect.frontoffice.entity.endofshift.ShiftReportInfo;
    import cloud.wconnect.frontoffice.entity.endofshift.ShiftWakeupCall;
    import cloud.wconnect.frontoffice.mapper.endofshift.EosShiftConfigMapper;
    import cloud.wconnect.frontoffice.pojo.dto.EosShiftDetailDTO;
    import cloud.wconnect.frontoffice.pojo.dto.endofshift.EosShiftConfigDTO;
    import cloud.wconnect.frontoffice.pojo.vo.endofshift.EosShiftConfigDetailVO;
    import cloud.wconnect.frontoffice.pojo.vo.endofshift.EosShiftConfigVO;
    import cloud.wconnect.frontoffice.service.endofshift.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.text.MessageFormat;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * End Of Shift排班配置表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-06-18
     */
    @Service
    @DS("ratelog_manage")
    public class EosShiftConfigServiceImpl extends ServiceImpl<EosShiftConfigMapper, EosShiftConfig> implements IEosShiftConfigService {

        @Autowired
        private UpdateLogUtil updateLogUtil;
        @Autowired
        private IShiftReportInfoService shiftReportInfoService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private IShiftWakeupCallService shiftWakeupCallService;
        @Autowired
        private IEndOfShiftService endOfShiftService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 检查排班配置是否存在
         *
         * @param id 排班配置id
         * @return cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig
         * @author lichen
         * @date 2025/6/18
         */
        @Override
        public EosShiftConfig checkExist(Integer id) {
            EosShiftConfig eosShiftConfig = getById(id);
            if (Objects.isNull(eosShiftConfig)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "end of shift shift config not found, id = " + id);
            }
            return eosShiftConfig;
        }

        /**
         * 校验是否归属于资产
         *
         * @param shiftConfig 排班配置
         * @param propertyId  资产id
         * @return void
         * @author lichen
         * @date 2025/6/18
         */
        @Override
        public void checkBelong(EosShiftConfig shiftConfig, Integer propertyId) {
            if (!shiftConfig.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("End of Shift shift config not belong property, shiftConfigId:{0}, belongPropertyId:{1}, propertyId:{2}",
                                shiftConfig.getId(), shiftConfig.getPropertyId(), propertyId));
            }
        }

        /**
         * 校验排班时间(1.所有班次的整个时间跨度不能大于24个小时 2.单个班次的时间跨度必须大于1个小时 3.班次之间不能有重叠)
         *
         * @param propertyId   资产id
         * @param shiftConfigs 排班配置集合
         * @return void
         * @author lichen
         * @date 2025/6/18
         */
        @Override
        public void checkShiftTime(Integer propertyId, List<EosShiftDetailDTO> shiftConfigs) {
            /*
             * 所有班次的整个时间跨度不能大于24个小时
             */
            //第一个区间的开始时间
            String firstIntervalStartTime = shiftConfigs.get(0).getStartTime();
            //最后一个区间的结束时间
            String lastIntervalEndTime = shiftConfigs.get(shiftConfigs.size() - 1).getEndTime();
            //所有班次的整个时间跨度（单位分钟数）
            int[] allShiftTotalTimeSpan = ShiftTimeUtil.timeToInterval(firstIntervalStartTime, lastIntervalEndTime);
            if (ShiftTimeUtil.isCrossDay(firstIntervalStartTime, lastIntervalEndTime)) {
                //时间跨度值 = 24小时的分钟数 - 跨天班次的开始时刻分钟数 + 跨天班次的结束时刻分钟数
                if ((1440 - allShiftTotalTimeSpan[0] + allShiftTotalTimeSpan[1]) > 24 * 60) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("shiftTime", "24 hour"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The shiftTime cannot be longer than 24 hours");
                }
            } else {
                if ((allShiftTotalTimeSpan[1] - allShiftTotalTimeSpan[0]) > 24 * 60) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("shiftTime", "24 hour"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The shiftTime cannot be longer than 24 hours");
                }
            }

            /*
             * 已经存在的排班时间（单位小时）集合拆分成不跨天的时间区间（单位分钟）
             * [07:00,15:00)、[15:00,23:00)、[23:00,07:00)转化成
             * [420,900)、[900,1380)、[1380,420)]
             */
            ArrayList<int[]> allIntervals = new ArrayList<>();
            for (EosShiftDetailDTO shiftDetailDTO : shiftConfigs) {
                //转化成分钟单位的时间区间
                int[] minuteInterval = ShiftTimeUtil.timeToInterval(shiftDetailDTO.getStartTime(), shiftDetailDTO.getEndTime());

                //判断时间区间是否跨天
                if (ShiftTimeUtil.isCrossDay(shiftDetailDTO.getStartTime(), shiftDetailDTO.getEndTime())) {
                    //24小时的分钟数 - 跨天班次的开始时刻分钟数 + 跨天班次的结束时刻分钟数
                    int range = 1440 - minuteInterval[0] + minuteInterval[1];
                    if (range < 60) {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_LESS_THAN,
                                i18nMessageUtils.getErrorParamMapNotNeedTranslate("shiftTime", "1 hour"),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The shift time must be more than one hour");
                    }
                } else {
                    if (minuteInterval[1] - minuteInterval[0] < 60) {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_LESS_THAN,
                                i18nMessageUtils.getErrorParamMapNotNeedTranslate("shiftTime", "1 hour"),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The shift time must be more than one hour");
                    }
                }

                //将时间区间拆分为不跨天的子区间
                List<int[]> subIntervals = ShiftTimeUtil.splitInterval(minuteInterval[0], minuteInterval[1]);
                allIntervals.addAll(subIntervals);
            }

            //校验时间是否有重叠
            if (hasOverlapIntervals(allIntervals)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("shiftTime"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The shiftTime cannot repeat");
            }
        }

        /**
         * 新增排班配置
         *
         * @param propertyId  资产id
         * @param dto         排班配置DTO
         * @param loginUserId 登录用户id
         * @return java.lang.Integer
         * @author lichen
         * @date 2025/6/19
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Integer insert(Integer propertyId, EosShiftConfigDTO dto, int loginUserId) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            List<EosShiftDetailDTO> shiftDetailDTOS = dto.getShiftConfigs();
            //班次详情设置shift，从1开始递增
            for (int i = 0; i < shiftDetailDTOS.size(); i++) {
                EosShiftDetailDTO shiftDetailDTO = shiftDetailDTOS.get(i);
                shiftDetailDTO.setShift(i + 1);

            }

            //新增排班配置
            EosShiftConfig shiftConfig = beanMapper.map(dto, EosShiftConfig.class);
            shiftConfig.setPropertyId(propertyId);
            shiftConfig.setTenantId(propertyDTO.getTenantId());
            shiftConfig.setShiftConfig(JsonUtils.convertObjListToString(objectMapper, shiftDetailDTOS));
            shiftConfig.setUpdateLog(updateLogUtil.get(loginUserId));
            save(shiftConfig);

            //同步更新排班信息
            syncUpdateShiftReportInfo(propertyDTO, dto.getStartDate(), shiftConfig.getId(), shiftDetailDTOS);

            return shiftConfig.getId();
        }

        /**
         * 大于等于开始时间的排班信息重新初始化，并根据叫醒时间重新分配walk up call数据
         *
         * @param propertyDTO     资产DTO
         * @param startDate       开始时间(yyyyMMdd)
         * @param shiftConfigId   重新初始化数据时，使用的排班配置id
         * @param shiftDetailDTOS 重新初始化数据时，使用的班次信息集合
         * @return void
         * @author lichen
         * @date 2025/6/23
         */
        private void syncUpdateShiftReportInfo(PropertyDTO propertyDTO, Integer startDate, Integer shiftConfigId, List<EosShiftDetailDTO> shiftDetailDTOS) {
            //查询大于等于指定日期的排班信息
            List<ShiftReportInfo> shiftReportInfos = shiftReportInfoService.listByGeTargetDate(propertyDTO.getId(), startDate);
            if (CollectionUtils.isEmpty(shiftReportInfos)) return;

            //获取排班和日期的对应关系
            Map<Integer, Integer> oriShiftId2TargetDate = shiftReportInfos.stream().collect(Collectors.toMap(ShiftReportInfo::getId, ShiftReportInfo::getTargetDate));
            Set<Integer> oriShiftIds = oriShiftId2TargetDate.keySet();
            Set<Integer> targetDates = Sets.newHashSet(oriShiftId2TargetDate.values());

            //删除所有的排班数据（wakeup call数据不需要删除）
            endOfShiftService.removeAllEndOfShiftExcludeWakeupCall(propertyDTO.getId(), oriShiftIds);

            //初始化多个日期的排班信息
            List<ShiftReportInfo> newShiftReportInfos = shiftReportInfoService.initMultiTargetDateShiftReportInfo(propertyDTO.getId(), targetDates, shiftConfigId, shiftDetailDTOS);
            Map<Integer, List<ShiftReportInfo>> targetDate2NewShiftReportInfos = newShiftReportInfos.stream().collect(Collectors.groupingBy(ShiftReportInfo::getTargetDate));

            //查询walk up call数据
            List<ShiftWakeupCall> shiftWakeupCalls = shiftWakeupCallService.listByShiftIds(oriShiftIds);
            //根据叫醒时间重新分配walk up call数据
            if (CollectionUtils.isNotEmpty(shiftWakeupCalls)) {
                for (ShiftWakeupCall shiftWakeupCall : shiftWakeupCalls) {
                    Integer shiftId = getNewShiftId(propertyDTO, shiftWakeupCall, targetDate2NewShiftReportInfos, oriShiftId2TargetDate, shiftDetailDTOS);
                    shiftWakeupCall.setShiftId(shiftId);
                }
                shiftWakeupCallService.updateBatchById(shiftWakeupCalls);
            }
        }

        /**
         * 根据叫醒时间获取wakeup call数据对应的新排班ID
         *
         * @param propertyDTO                    资产DTO
         * @param shiftWakeupCall                叫醒数据
         * @param targetDate2NewShiftReportInfos 目标日期对应的新排班数据
         * @param oriShiftId2TargetDates         原排班ID和目标日期的映射关系
         * @param shiftConfigs                   排班配置详情
         * @return java.lang.Integer
         * @author lichen
         * @date 2025/6/24
         */
        private Integer getNewShiftId(PropertyDTO propertyDTO, ShiftWakeupCall shiftWakeupCall,
                                      Map<Integer, List<ShiftReportInfo>> targetDate2NewShiftReportInfos,
                                      Map<Integer, Integer> oriShiftId2TargetDates, List<EosShiftDetailDTO> shiftConfigs) {
            Long alarmTime = shiftWakeupCall.getAlarmTime();
            LocalDateTime localDateTime = DateTimeUtils.timestampToLocalDateTime(alarmTime, propertyDTO.getTimezone());
            //计算叫醒时间命中哪个班次
            Integer shift = getSpecifiedTimeHitShiftConfigDetailWithDefault(localDateTime.getHour(), localDateTime.getMinute(), shiftConfigs)
                    .getShift();

            //叫醒数据对应的排班目标日期
            Integer targetDate = oriShiftId2TargetDates.get(shiftWakeupCall.getShiftId());
            //目标日期对应的新排班数据
            List<ShiftReportInfo> shiftReportInfos = targetDate2NewShiftReportInfos.get(targetDate);
            for (ShiftReportInfo shiftReportInfo : shiftReportInfos) {
                if (shiftReportInfo.getShift().equals(shift)) {
                    return shiftReportInfo.getId();
                }
            }
            //找不到就返回第一个排班详情
            return shiftReportInfos.get(0).getId();
        }

        /**
         * 删除排班配置
         *
         * @param shiftConfig 排班配置
         * @return void
         * @author lichen
         * @date 2025/6/19
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void delete(EosShiftConfig shiftConfig) {
            //删除配置
            removeById(shiftConfig.getId());

            PropertyDTO propertyDTO = propertyI18nRedisService.get(shiftConfig.getPropertyId());

            //查询资产最后一条配置（按生效的开始日期倒序取第一条）
            EosShiftConfig lastShiftConfig = getLastShiftConfig(shiftConfig.getPropertyId());
            if (Objects.nonNull(lastShiftConfig)) {
                //班次详情集合
                List<EosShiftDetailDTO> lastShiftDetailDTOS = JsonUtils.convertStringToList(objectMapper, lastShiftConfig.getShiftConfig(), EosShiftDetailDTO.class);
                //同步更新排班信息
                syncUpdateShiftReportInfo(propertyDTO, shiftConfig.getStartDate(), lastShiftConfig.getId(), lastShiftDetailDTOS);
            }
        }

        /**
         * 查询资产最后一条配置（按生效日期倒序取第一条）
         *
         * @param propertyId 资产id
         * @return cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public EosShiftConfig getLastShiftConfig(Integer propertyId) {
            return getOne(new LambdaQueryWrapper<EosShiftConfig>().eq(EosShiftConfig::getPropertyId, propertyId)
                    .orderByDesc(EosShiftConfig::getStartDate).last("limit 1"));
        }

        /**
         * 分页查询资产排班配置
         *
         * @param propertyId 资产id
         * @param page       页码
         * @param size       每页大小
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.endofshift.EosShiftConfigVO>
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public ListPagedDTO<EosShiftConfigVO> pageByPropertyId(Integer propertyId, int page, int size) {
            Page<EosShiftConfig> pageResult = page(new Page<>(page, size),
                    new LambdaQueryWrapper<EosShiftConfig>().eq(EosShiftConfig::getPropertyId, propertyId)
                            .orderByDesc(EosShiftConfig::getStartDate));
            if (pageResult.getTotal() == 0L) return new ListPagedDTO<>();

            List<EosShiftConfig> records = pageResult.getRecords();

            ArrayList<EosShiftConfigVO> voList = Lists.newArrayListWithCapacity(records.size());

            for (int i = 0; i < records.size(); i++) {
                EosShiftConfig shiftConfig = records.get(i);
                EosShiftConfigVO vo = beanMapper.map(shiftConfig, EosShiftConfigVO.class);
                vo.setShiftConfigs(JsonUtils.convertStringToList(objectMapper, shiftConfig.getShiftConfig(), EosShiftConfigDetailVO.class));
                if (i == 0) {
                    /*
                     * 第一页时，第一条数据的结束日期为空
                     * 非第一页时，第一条数据的结束日期 = 上一页的最后一条数据的开始日期
                     */
                    if (page != 1) {
                        Page<EosShiftConfig> prevPageResult = page(new Page<>(page - 1, size),
                                new LambdaQueryWrapper<EosShiftConfig>().eq(EosShiftConfig::getPropertyId, propertyId)
                                        .orderByDesc(EosShiftConfig::getStartDate));
                        vo.setEndDate(prevPageResult.getRecords().get(size - 1).getStartDate());
                    }
                } else {
                    //当前数据的结束日期=上一条数据的开始日期
                    int prevIndex = i - 1;
                    vo.setEndDate(records.get(prevIndex).getStartDate());
                }
                voList.add(vo);
            }
            return new ListPagedDTO<>(pageResult.getTotal(), voList);
        }

        /**
         * 校验目标日期的排班配置是否存在
         *
         * @param propertyId 资产id
         * @param targetDate 目标日期(yyyyMMdd)
         * @return cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig
         * @author lichen
         * @date 2025/6/25
         */
        @Override
        public EosShiftConfig checkTargetDateShiftConfigExist(Integer propertyId, Integer targetDate) {
            EosShiftConfig eosShiftConfig = getTargetDateHitShiftConfig(propertyId, targetDate);
            if (Objects.isNull(eosShiftConfig)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The shift config for the target date does not exist");
            }
            return eosShiftConfig;
        }

        /**
         * 查询目标日期命中的排班配置
         *
         * @param propertyId 资产id
         * @param targetDate 目标日期(yyyyMMdd)
         * @return cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig
         * @author lichen
         * @date 2025/6/25
         */
        @Override
        public EosShiftConfig getTargetDateHitShiftConfig(Integer propertyId, Integer targetDate) {
            //按照开始日期倒序查询所有的排班配置
            List<EosShiftConfig> shiftConfigs = list(new LambdaQueryWrapper<EosShiftConfig>()
                    .eq(EosShiftConfig::getPropertyId, propertyId)
                    .orderByDesc(EosShiftConfig::getStartDate));
            if (CollectionUtils.isEmpty(shiftConfigs)) return null;

            return getTargetDateHitShiftConfig(targetDate, shiftConfigs);
        }

        /**
         * 查询目标日期命中的排班配置
         *
         * @param targetDate   目标日期(yyyyMMdd)
         * @param shiftConfigs 排班配置集合
         * @return cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig
         * @author lichen
         * @date 2025/6/25
         */
        @Override
        public EosShiftConfig getTargetDateHitShiftConfig(Integer targetDate, List<EosShiftConfig> shiftConfigs) {
            if (CollectionUtils.isEmpty(shiftConfigs)) return null;
            /*
             * 判断是否命中配置的逻辑
             * 1.按照开始日期倒序查询排班配置
             * 2.目标日期>=生效开始日期，则命中此配置
             * 例如一组配置的开始日期如下：
             * 20250801、20241201、20250401、20240401
             * 目标日期是20250405，依次与倒序的开始日期比较，则命中20250401的配置
             *
             */
            for (EosShiftConfig shiftConfig : shiftConfigs) {
                //目标日期大于等于生效开始日期，则命中此配置
                if (targetDate >= shiftConfig.getStartDate()) {
                    return shiftConfig;
                }
            }

            return null;
        }

        /**
         * 获取指定时刻（小时:分钟）命中的班次
         * 注意：当命中不到班次时，返回null
         *
         * @param hour               小时数
         * @param minute             分钟数
         * @param shiftDetailDTOList 班次集合
         * @return cloud.wconnect.frontoffice.pojo.dto.EosShiftConfigDetailDTO
         * @author lichen
         * @date 2025/6/25
         */
        @Override
        public EosShiftDetailDTO getSpecifiedTimeHitShiftConfigDetail(int hour, int minute, List<EosShiftDetailDTO> shiftDetailDTOList) {
            //指定时刻的分钟数
            int specifiedTimeMinute = hour * 60 + minute;

            //计算命中的区间
            EosShiftDetailDTO hitShiftDetailDTO = null;
            for (EosShiftDetailDTO shiftConfigDetailDTO : shiftDetailDTOList) {
                //排班时间区间（单位分钟）
                int[] shiftTimeRange = ShiftTimeUtil.timeToInterval(shiftConfigDetailDTO.getStartTime(), shiftConfigDetailDTO.getEndTime());
                if (shiftTimeRange[0] > shiftTimeRange[1]) {
                    //跨天，例如：23:00 - 07:00，则大于23:00或者小于07:00为命中区间
                    if (specifiedTimeMinute >= shiftTimeRange[0] || specifiedTimeMinute < shiftTimeRange[1]) {
                        hitShiftDetailDTO = shiftConfigDetailDTO;
                        break;
                    }
                } else {
                    //非跨天，例如：07:00 - 15:00，则大于等于7:00且小于15:00为命中区间
                    if (specifiedTimeMinute >= shiftTimeRange[0] && specifiedTimeMinute < shiftTimeRange[1]) {
                        hitShiftDetailDTO = shiftConfigDetailDTO;
                        break;
                    }
                }
            }
            return hitShiftDetailDTO;
        }

        /**
         * 获取指定时刻（小时:分钟）命中的班次
         * 注意：当命中不到班次时，返回第一个班次
         *
         * @param hour               小时数
         * @param minute             分钟数
         * @param shiftDetailDTOList 班次集合
         * @return cloud.wconnect.frontoffice.pojo.dto.EosShiftConfigDetailDTO
         * @author lichen
         * @date 2025/6/25
         */
        @Override
        public EosShiftDetailDTO getSpecifiedTimeHitShiftConfigDetailWithDefault(int hour, int minute, List<EosShiftDetailDTO> shiftDetailDTOList) {
            EosShiftDetailDTO hitDTO = getSpecifiedTimeHitShiftConfigDetail(hour, minute, shiftDetailDTOList);
            if (Objects.nonNull(hitDTO)) return hitDTO;

            //命中不到班次时，返回第一个班次
            return shiftDetailDTOList.get(0);
        }

        /**
         * 校验未来日期的配置是否已经存在，未来日期的配置有且只有一条
         *
         * @param propertyDTO 资产DTO
         * @return void
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public void checkFutureDateConfigExist(PropertyDTO propertyDTO) {
            Integer currentDate = Integer.valueOf(DateTimeUtils.dateNowStr(propertyDTO.getTimezone(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            long count = count(new LambdaQueryWrapper<EosShiftConfig>().eq(EosShiftConfig::getPropertyId, propertyDTO.getId())
                    .gt(EosShiftConfig::getStartDate, currentDate));
            if (count > 0) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_EXISTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "the future dates shift config already exists, propertyId:" + propertyDTO.getId());
            }
        }

        /**
         * 判断是否有重叠的区间
         *
         * @param intervals 区间集合
         * @return boolean
         * @author lichen
         * @date 2025/6/26
         */
        public boolean hasOverlapIntervals(List<int[]> intervals) {
            for (int i = 0; i < intervals.size(); i++) {
                for (int j = i + 1; j < intervals.size(); j++) {
                    int[] intervalA = intervals.get(i);
                    int[] intervalB = intervals.get(j);

                    // 检查是否满足左闭右开区间的重叠条件
                    if (intervalA[0] < intervalB[1] && intervalB[0] < intervalA[1]) {
                        return true;
                    }
                }
            }
            return false;
        }
    }