# AdjOutOfOfficeConfigServiceImpl.java

    package cloud.wconnect.frontoffice.service.adjustment.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.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.result.ResultDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjApproveConfig;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjOutOfOfficeConfig;
    import cloud.wconnect.frontoffice.mapper.adjustment.AdjOutOfOfficeConfigMapper;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.OutOfOfficeDTO;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.SaveOutOfOfficeDTO;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.OutOfOfficeQuery;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjApproveConfigService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjCheckUserEffectiveService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjOutOfOfficeConfigService;
    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 lombok.RequiredArgsConstructor;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.stereotype.Service;

    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * <p>
     * out of office config 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2026-03-18
     */
    @RequiredArgsConstructor
    @Service
    @DS("logbook")
    public class AdjOutOfOfficeConfigServiceImpl extends ServiceImpl<AdjOutOfOfficeConfigMapper, AdjOutOfOfficeConfig> implements IAdjOutOfOfficeConfigService {

        private final UserInternalService userInternalService;
        private final PropertyI18nRedisService propertyI18nRedisService;
        private final IAdjApproveConfigService adjApproveConfigService;
        private final IAdjCheckUserEffectiveService adjCheckUserEffectiveService;
        private final I18nMessageUtils i18nMessageUtils;
        private final BeanMapper beanMapper;

        /**
         * 获取 out of office 配置列表
         *
         * @param propertyId  资产 id
         * @param loginUserId 登陆用户 id
         * @param query       查询条件
         * @return out of office 配置列表
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public ResultDTO<ListPagedDTO<OutOfOfficeDTO>> listConfigDTO(int propertyId, int loginUserId, OutOfOfficeQuery query) {
            // 分页查询
            Page<AdjOutOfOfficeConfig> page = page(new Page<>(query.getPage(), query.getSize()), new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(Objects.nonNull(query.getType()), AdjOutOfOfficeConfig::getType, query.getType())
                    .eq(AdjOutOfOfficeConfig::getUpdatedBy, loginUserId)
                    .orderByDesc(AdjOutOfOfficeConfig::getId));
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = page.getRecords();
            if (CollectionUtils.isEmpty(outOfOfficeConfigs)) return ResultDTO.success(new ListPagedDTO<>());

            // 查询补位人员姓名
            Map<Integer, String> nameById = userInternalService.getNameMapByIds(propertyI18nRedisService.get(propertyId).getTenantId(),
                    outOfOfficeConfigs.stream().map(AdjOutOfOfficeConfig::getSubstitutePersonnelId).toList());

            // 返回配置
            return ResultDTO.success(new ListPagedDTO<>(page.getTotal(), beanMapper.mapList(outOfOfficeConfigs, OutOfOfficeDTO.class,
                    (entity, dto) -> {
                        dto.setSubstitutePersonnelName(nameById.get(entity.getSubstitutePersonnelId()));
                    })));
        }

        /**
         * 检查是否有有效的 out of office 配置
         *
         * @param propertyId  资产 id
         * @param type        类型
         * @param loginUserId 登陆用户 id
         * @return true:有，false：无
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public Boolean checkHasEffectiveConfig(int propertyId, Integer type, int loginUserId) {
            // 校验是否有未开始、进行中的配置，条件中加上未关闭的条件
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            AdjOutOfOfficeConfig hasConfig = getOne(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(AdjOutOfOfficeConfig::getType, type)
                    .eq(AdjOutOfOfficeConfig::getUpdatedBy, loginUserId)
                    .eq(AdjOutOfOfficeConfig::getClose, false)
                    .ge(AdjOutOfOfficeConfig::getEndDate, Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD)))
                    .last("limit 1"));
            return hasConfig != null;
        }

        /**
         * 保存配置
         *
         * @param propertyId     资产 id
         * @param outOfOfficeDTO 配置信息
         * @param loginUserId    登陆用户 id
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public void save(int propertyId, SaveOutOfOfficeDTO outOfOfficeDTO, int loginUserId) {
            save(new AdjOutOfOfficeConfig()
                    .setPropertyId(propertyId)
                    .setType(outOfOfficeDTO.getType())
                    .setStartDate(Integer.parseInt(outOfOfficeDTO.getStartDate()))
                    .setEndDate(Integer.parseInt(outOfOfficeDTO.getEndDate()))
                    .setSubstitutePersonnelId(outOfOfficeDTO.getSubstitutePersonnelId())
                    .setUpdatedBy(loginUserId)
                    .setUpdatedTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 检查 out of office 配置存在
         *
         * @param id 配置 id
         * @return out of office 配置
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public AdjOutOfOfficeConfig checkExist(int id) {
            AdjOutOfOfficeConfig config = getById(id);
            if (Objects.isNull(config)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("adjOutOfOfficeConfig"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the adjOutOfOfficeConfig is not found;id:" + id);
            }
            return config;
        }

        /**
         * 修改配置
         *
         * @param id             配置 id
         * @param outOfOfficeDTO 配置信息
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public void update(int id, SaveOutOfOfficeDTO outOfOfficeDTO) {
            updateById(new AdjOutOfOfficeConfig()
                    .setId(id)
                    .setType(outOfOfficeDTO.getType())
                    .setStartDate(Integer.parseInt(outOfOfficeDTO.getStartDate()))
                    .setEndDate(Integer.parseInt(outOfOfficeDTO.getEndDate()))
                    .setSubstitutePersonnelId(outOfOfficeDTO.getSubstitutePersonnelId())
                    .setUpdatedTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 关闭配置
         *
         * @param id 配置 id
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public void close(int id) {
            updateById(new AdjOutOfOfficeConfig()
                    .setId(id)
                    .setClose(true)
                    .setCloseTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 删除配置
         *
         * @param id 配置 id
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public void delete(int id) {
            removeById(id);
        }

        /**
         * 获取有效的配置
         *
         * @param propertyId 资产 id
         * @param type       类型
         * @param userIds    审批配置的用户 id
         * @return 有效的配置
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listEffectiveConfig(int propertyId, Integer type, List<Integer> userIds) {
            // 查询审批人配置的有效的补位配置
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            return list(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(Objects.nonNull(type), AdjOutOfOfficeConfig::getType, type)
                    .in(AdjOutOfOfficeConfig::getUpdatedBy, userIds)
                    .le(AdjOutOfOfficeConfig::getStartDate, currentDate)
                    .ge(AdjOutOfOfficeConfig::getEndDate, currentDate)
                    .eq(AdjOutOfOfficeConfig::getClose, false));
        }

        /**
         * 查询补位人员对应的有效的补位配置
         *
         * @param propertyId               资产 id
         * @param type                     类型
         * @param supplementaryPersonnelId 补位人员 id
         * @return 有效的配置
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listEffectiveConfigForSupplementaryPersonnel(int propertyId, Integer type, Integer supplementaryPersonnelId) {
            // 查询补位人员对应的有效的补位配置
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = list(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(AdjOutOfOfficeConfig::getType, type)
                    .eq(AdjOutOfOfficeConfig::getSubstitutePersonnelId, supplementaryPersonnelId)
                    .le(AdjOutOfOfficeConfig::getStartDate, currentDate)
                    .ge(AdjOutOfOfficeConfig::getEndDate, currentDate)
                    .eq(AdjOutOfOfficeConfig::getClose, false));

           return filterEffectiveApprover(propertyId, outOfOfficeConfigs);
        }

        /**
         * 查询补位人员对应的有效的补位配置
         *
         * @param propertyId                资产 id
         * @param type                      类型
         * @param supplementaryPersonnelIds 补位人员 id
         * @return 有效的配置
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listEffectiveConfigForSupplementaryPersonnel(int propertyId, Integer type, List<Integer> supplementaryPersonnelIds) {
            // 查询补位人员对应的有效的补位配置
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = list(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(AdjOutOfOfficeConfig::getType, type)
                    .in(AdjOutOfOfficeConfig::getSubstitutePersonnelId, supplementaryPersonnelIds)
                    .le(AdjOutOfOfficeConfig::getStartDate, currentDate)
                    .ge(AdjOutOfOfficeConfig::getEndDate, currentDate)
                    .eq(AdjOutOfOfficeConfig::getClose, false));

            return filterEffectiveApprover(propertyId, outOfOfficeConfigs);
        }

        /**
         * 查询补位人员对应的有效的补位配置
         *
         * @param propertyId               资产 id
         * @param supplementaryPersonnelId 补位人员 id
         * @return 有效的配置
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listEffectiveConfigForSupplementaryPersonnel(int propertyId, Integer supplementaryPersonnelId) {
            // 查询补位人员对应的有效的补位配置
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = list(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(AdjOutOfOfficeConfig::getSubstitutePersonnelId, supplementaryPersonnelId)
                    .le(AdjOutOfOfficeConfig::getStartDate, currentDate)
                    .ge(AdjOutOfOfficeConfig::getEndDate, currentDate)
                    .eq(AdjOutOfOfficeConfig::getClose, false));

            return filterEffectiveApprover(propertyId, outOfOfficeConfigs);
        }

        /**
         * 获取有效的配置（补位人员是有效的【未关闭且有审批权限】）
         *
         * @param propertyId 资产 id
         * @param type       类型
         * @param userIds    审批配置的用户 id
         * @return 有效的配置
         * @author yyt
         * @date 2026/3/18
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listEffectiveConfigForApprover(int propertyId, Integer type, List<Integer> userIds) {
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = listEffectiveConfig(propertyId, type, userIds);
            if (CollectionUtils.isEmpty(outOfOfficeConfigs)) return Collections.emptyList();

            // 过滤有效的补位人员
            List<Integer> effectiveSupplementaryPersonnelIds = adjCheckUserEffectiveService.checkUserEffective(propertyId,
                    outOfOfficeConfigs.stream().map(AdjOutOfOfficeConfig::getSubstitutePersonnelId).collect(Collectors.toList()));
            if (CollectionUtils.isEmpty(effectiveSupplementaryPersonnelIds)) return Collections.emptyList();

            return outOfOfficeConfigs.stream()
                    .filter(outOfOfficeConfig -> effectiveSupplementaryPersonnelIds.contains(outOfOfficeConfig.getSubstitutePersonnelId()))
                    .toList();
        }

        /**
         * 查询刚进入请假的补位配置
         *
         * @param propertyDTO 资产
         * @return 补位配置
         * @author yyt
         * @date 2026/3/19
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listJustStartedTakingLeaveConfig(PropertyDTO propertyDTO) {
            Integer propertyId = propertyDTO.getId();
            String timezone = propertyDTO.getTimezone();
            int currentDate = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = list(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(AdjOutOfOfficeConfig::getStartDate, currentDate)
                    .eq(AdjOutOfOfficeConfig::getClose, false));
            if (CollectionUtils.isEmpty(outOfOfficeConfigs)) return Collections.emptyList();

            // 过滤出有效的补位人员
            List<Integer> effectiveSupplementaryPersonnelIds = adjCheckUserEffectiveService.checkUserEffective(propertyId,
                    outOfOfficeConfigs.stream().map(AdjOutOfOfficeConfig::getSubstitutePersonnelId).collect(Collectors.toList()));
            outOfOfficeConfigs = outOfOfficeConfigs.stream()
                    .filter(outOfOfficeConfig -> effectiveSupplementaryPersonnelIds.contains(outOfOfficeConfig.getSubstitutePersonnelId()))
                    .toList();
            if (CollectionUtils.isEmpty(outOfOfficeConfigs)) return Collections.emptyList();

            // 过滤出有效的且在审批层级上的请假人
            return filterEffectiveApprover(propertyId, outOfOfficeConfigs);
        }

        /**
         * 查询刚休假结束的补位配置
         *
         * @param propertyDTO 资产
         * @return 补位配置
         * @author yyt
         * @date 2026/3/20
         **/
        @Override
        public List<AdjOutOfOfficeConfig> listJustFinishedVacationConfig(PropertyDTO propertyDTO) {
            Integer propertyId = propertyDTO.getId();
            String timezone = propertyDTO.getTimezone();
            int yesterday = Integer.parseInt(DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).minusDays(1).toLocalDate(), BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<AdjOutOfOfficeConfig> outOfOfficeConfigs = list(new LambdaQueryWrapper<AdjOutOfOfficeConfig>()
                    .eq(AdjOutOfOfficeConfig::getPropertyId, propertyId)
                    .eq(AdjOutOfOfficeConfig::getEndDate, yesterday)
                    .eq(AdjOutOfOfficeConfig::getClose, false));
            if (CollectionUtils.isEmpty(outOfOfficeConfigs)) return Collections.emptyList();

            // 过滤出有效的且在审批层级上的请假人
            return filterEffectiveApprover(propertyId, outOfOfficeConfigs);
        }

        /**
         * 过滤出有效且在审批层级上的请假人
         *
         * @param propertyId         资产 id
         * @param outOfOfficeConfigs 补位配置
         * @return 补位配置
         * @author yyt
         * @date 2026/3/25
         **/
        private List<AdjOutOfOfficeConfig> filterEffectiveApprover(Integer propertyId, List<AdjOutOfOfficeConfig> outOfOfficeConfigs) {
            if (CollectionUtils.isEmpty(outOfOfficeConfigs)) return Collections.emptyList();

            // 过滤出有效且在审批层级上的请假人
            List<Integer> approvers = outOfOfficeConfigs.stream().map(AdjOutOfOfficeConfig::getUpdatedBy).collect(Collectors.toList());
            approvers = adjCheckUserEffectiveService.checkUserEffective(propertyId, approvers);
            if (CollectionUtils.isEmpty(approvers)) return Collections.emptyList();

            List<Integer> approveUserIds = adjApproveConfigService.listByUserIds(propertyId, approvers).stream().map(AdjApproveConfig::getUserId).toList();
            return outOfOfficeConfigs.stream().filter(outOfOfficeConfig -> approveUserIds.contains(outOfOfficeConfig.getUpdatedBy())).collect(Collectors.toList());
        }

    }