# RqCompRoomFormServiceImpl.java

    package cloud.wconnect.frontoffice.service.comproom.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.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.ApproveCons;
    import cloud.wconnect.frontoffice.cons.ApproveStatusEnum;
    import cloud.wconnect.frontoffice.cons.I18nConstant;
    import cloud.wconnect.frontoffice.cons.S3BucketFunctionCons;
    import cloud.wconnect.frontoffice.cons.comproom.CompRoomErrorI18nConstant;
    import cloud.wconnect.frontoffice.cons.comproom.CompRoomNotifyConfigTypeEnum;
    import cloud.wconnect.frontoffice.cons.comproom.CompRoomOrderByFieldEnum;
    import cloud.wconnect.frontoffice.entity.comproom.*;
    import cloud.wconnect.frontoffice.mapper.comproom.RqCompRoomFormMapper;
    import cloud.wconnect.frontoffice.pojo.dto.comproom.CompRoomApproveDTO;
    import cloud.wconnect.frontoffice.pojo.dto.comproom.CompRoomDTO;
    import cloud.wconnect.frontoffice.pojo.dto.comproom.CompRoomUserDTO;
    import cloud.wconnect.frontoffice.pojo.query.comproom.CompRoomExportQuery;
    import cloud.wconnect.frontoffice.pojo.query.comproom.CompRoomQuery;
    import cloud.wconnect.frontoffice.pojo.query.comproom.PropertyCheckInNumTrendQuery;
    import cloud.wconnect.frontoffice.pojo.vo.comproom.*;
    import cloud.wconnect.frontoffice.service.comproom.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    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.Maps;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import jakarta.annotation.Nullable;

    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-03-28
     */
    @Service
    @DS("logbook")
    @Slf4j
    public class RqCompRoomFormServiceImpl extends ServiceImpl<RqCompRoomFormMapper, RqCompRoomForm> implements IRqCompRoomFormService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        @Lazy
        private ICompRoomApproveConfigService approveConfigService;
        @Autowired
        @Lazy
        private ICompRoomApproveLevelService approveLevelService;
        @Autowired
        @Lazy
        private ICompRoomApproveLogService approveLogService;
        @Autowired
        @Lazy
        private ICompRoomNotifyService compRoomNotifyService;
        @Autowired
        private Executor executor;
        @Autowired
        private ICompRoomComplimentaryService complimentaryService;
        @Value("${aws-s3.region}")
        private String region;
        @Value("#{${aws-s3.function-bucket}}")
        private Map<String, Map<String, Object>> bucketMap;
        @Autowired
        private ICompRoomNotifyRuleService notifyRuleService;
        @Autowired
        private ICompRoomCommentService commentService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 新增Comp Room
         *
         * @param compRoomDTO comp room dto
         * @param loginUserId 登录用户id
         * @return java.lang.Long
         * @author lichen
         * @date 2023/8/21
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Long save(CompRoomDTO compRoomDTO, int loginUserId) {
            //查询关联的报告id集合
            List<Long> reportIds = complimentaryService.listAssociatedId(compRoomDTO.getPropertyId(),
                    compRoomDTO.getRoomNumberList(), compRoomDTO.getGuestName(), compRoomDTO.getCheckInDate());

            RqCompRoomForm compRoom = beanMapper.map(compRoomDTO, RqCompRoomForm.class);
            compRoom.setCreatedBy(loginUserId);
            compRoom.setCreatedTime(DateTimeUtils.nowTimestamp());
            compRoom.setComments(JsonUtils.convertObjListToString(objectMapper, compRoomDTO.getCommentList()));
            compRoom.setConfirmation(JsonUtils.convertObjListToString(objectMapper, compRoomDTO.getConfirmationList()));
            compRoom.setIsArchive(false);
            compRoom.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
            if (CollectionUtils.isNotEmpty(compRoomDTO.getRoomNumberList())) {
                compRoom.setRoomNumber(StringUtils.join(compRoomDTO.getRoomNumberList(), ","));
            }
            if (CollectionUtils.isNotEmpty(reportIds)) {
                compRoom.setIsRel(true);
                compRoom.setRelIds(StringUtils.join(reportIds, ","));
            } else {
                compRoom.setIsRel(false);
                compRoom.setRelIds("");
            }
            //新增Comp Room
            save(compRoom);

            if (CollectionUtils.isNotEmpty(reportIds)) {
                //更新为已关联
                List<CompRoomComplimentary> compRoomComplimentaries = reportIds.stream()
                        .map(reportId -> new CompRoomComplimentary().setId(reportId).setRelId(compRoom.getId()))
                        .collect(Collectors.toList());
                complimentaryService.updateBatchById(compRoomComplimentaries);
            }
            return compRoom.getId();
        }

        /**
         * 校验comp room数据是否存在
         *
         * @param id comp room主键id
         * @return cloud.wconnect.frontoffice.entity.comproom.RqCompRoomForm
         * @author lichen
         * @date 2023/8/19
         */
        @Override
        public RqCompRoomForm checkExist(long id) {
            RqCompRoomForm compRoomForm = getById(id);
            if (Objects.isNull(compRoomForm)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND, BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The comp room not found, compRoomId:{0}", id));
            }
            return compRoomForm;
        }

        /**
         * 根据资产id，房间号，入住日期校验唯一
         *
         * @param propertyId     资产id
         * @param checkInDate    入住日期
         * @param roomNumberList 房间号集合
         * @param ignoreId       忽略的数据id
         * @return void
         * @author lichen
         * @date 2023/8/21
         */
        @Override
        public void checkRoomNumberAndCheckInDateSole(Integer propertyId, Long checkInDate, List<String> roomNumberList, Long ignoreId) {
            long count = count(new LambdaQueryWrapper<RqCompRoomForm>().eq(RqCompRoomForm::getPropertyId, propertyId)
                    .eq(RqCompRoomForm::getCheckInDate, checkInDate)
                    .ne(Objects.nonNull(ignoreId), RqCompRoomForm::getId, ignoreId)
                    .apply(String.format("CONCAT( ',', room_number , ',' ) REGEXP ',(%s),'", StringUtils.join(roomNumberList, "|"))));
            if (count > 0) {
                throw new BadRequestException(CompRoomErrorI18nConstant.ROOM_NUMBER_AND_CHECK_IN_DATE_ALREADY_EXISTS,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The Room number and check-in date already exists, " +
                                "propertyId:{0}, checkInDate:{1}, roomNumberList:{2}", propertyId, checkInDate, roomNumberList.toString()));
            }
        }

        /**
         * 修改数据
         *
         * @param oriCompRoom 修改前的comp room数据
         * @param compRoomDTO 修改后的数据
         * @param loginUserId 登录用户id
         * @return void
         * @author lichen
         * @date 2023/8/21
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(RqCompRoomForm oriCompRoom, CompRoomDTO compRoomDTO, int loginUserId) {
            //把w-connect数据 和 报告的数据取消关联
            String oriRelIdStr = oriCompRoom.getRelIds();
            if (StringUtils.isNotBlank(oriRelIdStr)) {
                List<CompRoomComplimentary> disassociateList = Arrays.stream(oriRelIdStr.split(","))
                        .map(str -> new CompRoomComplimentary().setId(Long.valueOf(str)).setRelId(0L)).toList();
                complimentaryService.updateBatchById(disassociateList);
            }

            //查询关联的报告id集合
            List<Long> reportIds = complimentaryService.listAssociatedId(compRoomDTO.getPropertyId(),
                    compRoomDTO.getRoomNumberList(), compRoomDTO.getGuestName(), compRoomDTO.getCheckInDate());

            RqCompRoomForm compRoom = beanMapper.map(compRoomDTO, RqCompRoomForm.class);
            compRoom.setUpdatedBy(loginUserId);
            compRoom.setUpdatedTime(DateTimeUtils.nowTimestamp());
            compRoom.setComments(JsonUtils.convertObjListToString(objectMapper, compRoomDTO.getCommentList()));
            compRoom.setConfirmation(JsonUtils.convertObjListToString(objectMapper, compRoomDTO.getConfirmationList()));
            compRoom.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
            compRoom.setApproveIndex(0);
            compRoom.setBatchId(0L);
            if (CollectionUtils.isNotEmpty(reportIds)) {
                compRoom.setIsRel(true);
                compRoom.setRelIds(StringUtils.join(reportIds, ","));
            } else {
                compRoom.setIsRel(false);
                compRoom.setRelIds("");
            }
            if (CollectionUtils.isNotEmpty(compRoomDTO.getRoomNumberList())) {
                compRoom.setRoomNumber(StringUtils.join(compRoomDTO.getRoomNumberList(), ","));
            } else {
                compRoom.setRoomNumber("");
            }
            //用反射构建出UpdateWrapper
            UpdateWrapper<RqCompRoomForm> updateWrapper = MybatisPlusAbstractWrapperUtil.buildUpdateWrapper(compRoom,
                    "created_by", "created_time", "is_archive");
            updateWrapper.lambda().eq(RqCompRoomForm::getId, compRoom.getId());
            update(updateWrapper);

            if (CollectionUtils.isNotEmpty(reportIds)) {
                //更新为已关联
                List<CompRoomComplimentary> compRoomComplimentaries = reportIds.stream()
                        .map(reportId -> new CompRoomComplimentary().setId(reportId).setRelId(compRoom.getId()))
                        .collect(Collectors.toList());
                complimentaryService.updateBatchById(compRoomComplimentaries);
            }

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //修改前的s3Key集合
                List<String> updateBeforeS3Keys = AttachmentUtil.getKeysByAttachmentArrayJson(oriCompRoom.getAttachement(), objectMapper);
                //修改后的s3Key集合
                List<String> updateAfterS3Keys = AttachmentUtil.getKeysByAttachmentArrayJson(compRoomDTO.getAttachement(), objectMapper);
                //删除的附件 = 修改前的附件集合 - 修改后的附件集合
                List<String> delS3Keys = Lists.newArrayList(updateBeforeS3Keys);
                delS3Keys.removeAll(updateAfterS3Keys);
                S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.COMP_ROOM), delS3Keys);
            });
        }

        /**
         * 送审数据
         *
         * @param compRoom comp room数据
         * @return void
         * @author lichen
         * @date 2023/8/21
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void toApprove(RqCompRoomForm compRoom, int loginUserId) {
            //生成批次id规则：数据id + 当前时间戳
            Long timestamp = DateTimeUtils.nowTimestamp();
            long batchId = Long.parseLong(compRoom.getId().toString() + timestamp);
            compRoom.setBatchId(batchId);
            //查询当前操作人员是否审批人员
            CompRoomApproveConfig approveConfig = approveConfigService.getByUserId(compRoom.getPropertyId(), loginUserId);
            if (Objects.isNull(approveConfig)) {
                /**
                 * 当前操作人员不是审批人员：查询当前资产的第一级审批数据，进行送审处理
                 * 送审处理逻辑：
                 *      1. 当前资产没有设置审批层级，数据更新为审批通过
                 *      2. 当前资产设置了审批层级，审批状态更新为Approving，并发邮件通知审批人
                 */
                CompRoomApproveLevel firstApproveLevel = approveLevelService.getFirstApproveLevel(compRoom.getPropertyId());
                if (Objects.isNull(firstApproveLevel)) {
                    compRoom.setApproveStatus(ApproveStatusEnum.APPROVED.getStatus());
                    updateById(compRoom);
                    //审批通过后发送邮件给数据创建人
                    TransactionUtils.afterCommitAsyncExecute(executor, () -> compRoomNotifyService.notifyCreateUser(compRoom, ApproveStatusEnum.APPROVED.getStatus()));
                } else {
                    //数据更新为审核中
                    compRoom.setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus());
                    compRoom.setApproveIndex(firstApproveLevel.getApproveIndex());
                    updateById(compRoom);
                    //发送给审批人
                    TransactionUtils.afterCommitAsyncExecute(executor, () -> compRoomNotifyService.notifyApprover(compRoom));
                }
            } else {
                /**
                 * 当前操作人员是审批人员：先新增一条审批日志，再根据审批人员归属的审批层级，进行送审处理
                 * 送审处理逻辑：
                 *      1. 审批层级是任一人审批时：数据需要推送到下一个节点
                 *      2. 审批层级是全部人审批时：根据审批配置人员，进行送审处理
                 */
                //查询审批层级信息
                CompRoomApproveLevel approveLevel = approveLevelService.getById(approveConfig.getLevelId());

                //新增一条审批日志
                CompRoomApproveLog approveLog = new CompRoomApproveLog();
                approveLog.setPropertyId(compRoom.getPropertyId());
                approveLog.setBatchId(compRoom.getBatchId());
                approveLog.setCompRoomId(compRoom.getId());
                approveLog.setLevelId(approveLevel.getId());
                approveLog.setApproveIndex(approveLevel.getApproveIndex());
                approveLog.setApproveBy(loginUserId);
                approveLog.setApproveTime(DateTimeUtils.nowTimestamp());
                approveLog.setIsApproved(true);
                approveLogService.save(approveLog);

                //根据审批层级规则进行送审处理
                if (approveLevel.getRule() == ApproveCons.RULE_ANY) {
                    compRoom.setApproveIndex(approveLevel.getApproveIndex());
                    //推送数据至下一个节点
                    pushNextIndex(compRoom);
                } else {
                    //查询当前节点所需的审批人员集合
                    List<CompRoomApproveConfig> needApproveConfigs = approveConfigService.listByLevelId(compRoom.getPropertyId(), approveLevel.getId());
                    List<Integer> needApprovers = needApproveConfigs.stream().map(CompRoomApproveConfig::getUserId).toList();
                    if (needApprovers.size() == 1 && needApprovers.contains(compRoom.getCreatedBy())) {
                        compRoom.setApproveIndex(approveLevel.getApproveIndex());
                        //当前节点所需的审批人员只包括创建人：推送数据至下一个节点
                        pushNextIndex(compRoom);
                    } else {
                        //更新为审核中
                        compRoom.setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus());
                        compRoom.setApproveIndex(approveLevel.getApproveIndex());
                        updateById(compRoom);
                        //发送给审批人
                        TransactionUtils.afterCommitAsyncExecute(executor, () -> compRoomNotifyService.notifyApprover(compRoom));
                    }
                }
            }

        }

        /**
         * 审批数据
         *
         * @param oriCompRoom comp room数据
         * @param approveDTO  审批dto
         * @return void
         * @author lichen
         * @date 2023/8/22
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void approve(RqCompRoomForm oriCompRoom, CompRoomApproveDTO approveDTO) {
            //查询当前审批层级数据
            CompRoomApproveLevel currentApproveLevel = approveLevelService.getByApproveIndex(oriCompRoom.getPropertyId(), oriCompRoom.getApproveIndex());
            Integer rule = currentApproveLevel.getRule();

            //查询当前节点的审批人集合
            List<Integer> currentApproveUsers = approveConfigService.listByLevelId(oriCompRoom.getPropertyId(), currentApproveLevel.getId())
                    .stream().map(CompRoomApproveConfig::getUserId).toList();
            if (!currentApproveUsers.contains(approveDTO.getApproveBy())) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        "Login User Have no authority to approve, currentLoginUserId:" + approveDTO.getApproveBy());
            }

            //审批日志对象
            CompRoomApproveLog approveLog = buildApproveLog(oriCompRoom, approveDTO, currentApproveLevel);

            //审核通过
            if (approveDTO.getApproveStatus() == ApproveStatusEnum.APPROVED.getStatus()) {
                //保存审批日志
                approveLog.setIsApproved(true);
                approveLogService.save(approveLog);
                if (rule == ApproveCons.RULE_ALL) {
                    //查询已经审批的人
                    List<Integer> alreadyApprover = approveLogService.listCurrentApproveIndexLog(oriCompRoom)
                            .stream().map(CompRoomApproveLog::getApproveBy).toList();
                    // 已经审批的人 包含 当前节点所需的审批人集合
                    if (new HashSet<>(alreadyApprover).containsAll(currentApproveUsers)) {
                        //推送数据至下一个节点
                        pushNextIndex(oriCompRoom);
                    }
                } else {
                    //推送数据至下一个节点
                    pushNextIndex(oriCompRoom);
                }
            } else {
                //保存审批日志
                approveLog.setIsApproved(false);
                approveLogService.save(approveLog);
                //更新为审核驳回
                oriCompRoom.setApproveStatus(ApproveStatusEnum.REJECTED.getStatus());
                updateById(oriCompRoom);
                //审批拒绝后发送邮件给数据创建人
                TransactionUtils.afterCommitAsyncExecute(executor, () -> compRoomNotifyService.notifyCreateUser(oriCompRoom, ApproveStatusEnum.REJECTED.getStatus()));
            }
        }

        /**
         * 查询当前审批索引下审核中的数据
         *
         * @param propertyId   资产标识
         * @param approveIndex 审批索引
         * @return java.util.List<cloud.wconnect.frontoffice.entity.comproom.RqCompRoomForm>
         * @author lichen
         * @date 2023/8/22
         */
        @Override
        public List<RqCompRoomForm> listApprovingByApproveIndex(Integer propertyId, Integer approveIndex) {
            return list(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyId)
                    .eq(RqCompRoomForm::getApproveIndex, approveIndex)
                    .eq(RqCompRoomForm::getApproveStatus, ApproveStatusEnum.APPROVEING.getStatus()));
        }

        /**
         * 根据通知规则查询未离店的免费房
         *
         * @param propertyDTO 资产
         * @return java.util.List<cloud.wconnect.frontoffice.entity.comproom.RqCompRoomForm>
         * @author lichen
         * @date 2023/8/22
         */
        @Override
        public List<RqCompRoomForm> listNotCheckOutCompRoomByNotifyRule(PropertyDTO propertyDTO) {
            //获取资产配置的通知规则
            CompRoomNotifyRule notifyRule = notifyRuleService.getByPropertyIdAndTypeOrDefault(propertyDTO.getId(), CompRoomNotifyConfigTypeEnum.COMP_ROOM_CHECK_IN.getType());
            List<Integer> approveStatusList = JsonUtils.convertStringToList(objectMapper, notifyRule.getRule(), Integer.class);

            //获取资产对应的当前日期
            long dateNow = DateTimeUtils.dateNow(propertyDTO.getTimezone()).toEpochSecond();
            //查询出在住的免费房
            return list(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyDTO.getId())
                    .ge(RqCompRoomForm::getCheckOutDate, dateNow)
                    .in(RqCompRoomForm::getApproveStatus, approveStatusList));
        }

        /**
         * 根据审批人，查询待审批的数量
         *
         * @param propertyId 资产ID
         * @param approver   审批人
         * @return java.lang.Long
         * @author lichen
         * @date 2023/12/12
         */
        @Override
        public Long getWaitApprovalTotalByApprover(Integer propertyId, int approver) {
            CompRoomApproveConfig approveConfig = approveConfigService.getByUserId(propertyId, approver);
            if (Objects.isNull(approveConfig)) return 0L;
            //查询当前审批索引下审批中的数据
            return count(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyId)
                    .eq(RqCompRoomForm::getApproveIndex, approveConfig.getApproveIndex())
                    .eq(RqCompRoomForm::getApproveStatus, ApproveStatusEnum.APPROVEING.getStatus()));
        }

        /**
         * 根据创建人 和 审批状态查询免费房数据数量
         *
         * @param propertyId    资产ID
         * @param userId        用户ID
         * @param approveStatus 审批状态
         * @return java.lang.Long
         * @author lichen
         * @date 2023/12/12
         */
        @Override
        public Long getCountByCreatedByAndApproveStatus(Integer propertyId, int userId, int approveStatus) {
            return count(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyId)
                    .eq(RqCompRoomForm::getCreatedBy, userId)
                    .eq(RqCompRoomForm::getApproveStatus, approveStatus));
        }

        /**
         * 查询资产激活的免费房
         *
         * @param startDate    开始时间，例如20231201
         * @param endDate      结束时间，例如20231201
         * @param propertyDTOS 资产集合
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.comproom.PropertyCheckInNumVO>
         * @author lichen
         * @date 2023/12/12
         */
        @Override
        public List<PropertyCheckInNumVO> listPropertyActiveCompRoom(Integer startDate, Integer endDate, List<PropertyDTO> propertyDTOS) {
            if (CollectionUtils.isEmpty(propertyDTOS)) return Collections.emptyList();
            Map<Integer, String> propertyId2Abbr = propertyDTOS.stream().collect(Collectors.toMap(PropertyDTO::getId, PropertyDTO::getAbbr));
            Map<Integer, String> propertyId2Timezone = propertyDTOS.stream().collect(Collectors.toMap(PropertyDTO::getId, PropertyDTO::getTimezone));

            List<PropertyCheckInNumVO> propertyCheckInNumVOS = Lists.newArrayListWithCapacity(propertyDTOS.size());

            //查询资产配置的通知规则
            List<Integer> propertyIds = propertyDTOS.stream().map(PropertyDTO::getId).toList();
            Map<Integer, List<Integer>> propertyId2ApproveStatusList = notifyRuleService.listByPropertyIdAndTypeOrDefault(propertyIds, CompRoomNotifyConfigTypeEnum.COMP_ROOM_CHECK_IN.getType());

            for (PropertyDTO propertyDTO : propertyDTOS) {
                Integer propertyId = propertyDTO.getId();
                String timezone = propertyId2Timezone.get(propertyId);

                //资产时区下的开始时间戳
                long startTimestampByPropertyId = DateTimeUtils.dateStrToEpochSecond(startDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);
                //资产时区下的结束时间戳
                long endTimestampByPropertyId = DateTimeUtils.dateStrToEpochSecond(endDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);

                //查询时间范围内有效的免费房
                List<RqCompRoomForm> compRoomForms = listCompRoomByDateRangeAndApproveStatus(propertyId, startTimestampByPropertyId, endTimestampByPropertyId,
                        propertyId2ApproveStatusList.get(propertyId));

                propertyCheckInNumVOS.add(new PropertyCheckInNumVO(propertyId, propertyId2Abbr.get(propertyId), compRoomForms.size()));

            }
            return propertyCheckInNumVOS;
        }

        /**
         * 查询多资产【免费房入住数量】的趋势
         *
         * @param query        查询对象
         * @param propertyDTOS 资产集合
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.comproom.PropertyCheckInNumTrendVO>
         * @author lichen
         * @date 2023/12/12
         */
        @Override
        public List<PropertyCheckInNumTrendVO> listPropertyCheckInNumTrend(PropertyCheckInNumTrendQuery query, List<PropertyDTO> propertyDTOS) {
            //查询资产配置的通知规则
            List<Integer> propertyIds = propertyDTOS.stream().map(PropertyDTO::getId).toList();
            Map<Integer, List<Integer>> propertyId2ApproveStatusList = notifyRuleService.listByPropertyIdAndTypeOrDefault(propertyIds, CompRoomNotifyConfigTypeEnum.COMP_ROOM_CHECK_IN.getType());

            List<PropertyCheckInNumTrendVO> voList = Lists.newArrayListWithCapacity(propertyDTOS.size());

            for (PropertyDTO propertyDTO : propertyDTOS) {
                //根据统计单位计算统计日期
                Map<String, LocalDate> statisticDate = calculatestatisticDate(query.getUnit(), query.getSpan(), propertyDTO.getTimezone());
                LocalDate startLocalDate = statisticDate.get("startLocalDate");
                LocalDate endLocalDate = statisticDate.get("endLocalDate");
                //资产时区下的开始时间戳
                long startTimestampByPropertyId = DateTimeUtils.localDateToLong(startLocalDate, propertyDTO.getTimezone());
                //资产时区下的结束时间戳
                long endTimestampByPropertyId = DateTimeUtils.localDateToLong(endLocalDate, propertyDTO.getTimezone());

                //查询时间范围内有效的免费房
                List<RqCompRoomForm> compRoomForms = listCompRoomByDateRangeAndApproveStatus(propertyDTO.getId(), startTimestampByPropertyId, endTimestampByPropertyId,
                        propertyId2ApproveStatusList.get(propertyDTO.getId()));

                //根据统计单位和统计时间生成入住数量趋势集合
                List<CheckInNumTrendVO> checkInNumTrendVOS = generateCheckInNumTrendVO(compRoomForms, query.getUnit(), query.getSpan(), propertyDTO);
                //排序
                Collections.sort(checkInNumTrendVOS, Comparator.comparing(CheckInNumTrendVO::getDate));

                voList.add(new PropertyCheckInNumTrendVO(propertyDTO.getId(), propertyDTO.getAbbr(), checkInNumTrendVOS));
            }
            return voList;
        }

        /**
         * 根据统计单位和统计时间生成入住数量趋势集合
         *
         * @param compRoomForms 免费房集合
         * @param unit          统计单位 1：月 2：季度
         * @param span          统计时间跨度
         * @param propertyDTO   资产
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.comproom.CheckInNumTrendVO>
         * @author lichen
         * @date 2023/12/12
         */
        private List<CheckInNumTrendVO> generateCheckInNumTrendVO(List<RqCompRoomForm> compRoomForms, Integer unit, Integer span, PropertyDTO propertyDTO) {
            String timezone = propertyDTO.getTimezone();
            List<CheckInNumTrendVO> checkInNumTrendVOList = Lists.newArrayListWithCapacity(span);
            if (PropertyCheckInNumTrendQuery.MONTH == unit) {
                LocalDate localDate = LocalDate.now(ZoneId.of(timezone));

                for (int i = 1; i <= span; i++) {
                    //获取日期,例如：2023-05
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
                    String date = localDate.format(formatter);

                    //当月的开始日期
                    long startTime = DateTimeUtils.localDateToLong(localDate.withDayOfMonth(1), timezone);
                    //当月的结束日期
                    long endTime = DateTimeUtils.localDateToLong(localDate.withDayOfMonth(localDate.lengthOfMonth()), timezone);

                    //计算免费房数量
                    List<RqCompRoomForm> compRoomsByFilter = compRoomForms.stream()
                            .filter(item -> item.getCheckInDate() <= endTime && item.getCheckOutDate() >= startTime)
                            .toList();
                    checkInNumTrendVOList.add(new CheckInNumTrendVO(date, compRoomsByFilter.size()));

                    //往前推一个月
                    localDate = localDate.minusMonths(1);
                }
            } else {
                LocalDate localDate = LocalDate.now(ZoneId.of(timezone));

                for (int i = 1; i <= span; i++) {
                    int year = localDate.getYear();
                    //当前日期的所属季度
                    int quarter = (localDate.getMonthValue() - 1) / 3 + 1;

                    //季度的开始日期
                    LocalDate startDate = LocalDate.of(year, quarter * 3 - 2, 1);
                    long startTime = DateTimeUtils.localDateToLong(startDate, timezone);
                    //季度的结束日期
                    LocalDate endDate = startDate.plusMonths(3).minusDays(1);
                    //结束日期时间戳
                    long endTime = DateTimeUtils.localDateToLong(endDate, timezone);

                    //计算免费房数量
                    List<RqCompRoomForm> compRoomsByFilter = compRoomForms.stream()
                            .filter(item -> item.getCheckInDate() <= endTime && item.getCheckOutDate() >= startTime)
                            .toList();

                    //季度的日期格式为：2023-1
                    String date = year + "-" + quarter;
                    checkInNumTrendVOList.add(new CheckInNumTrendVO(date, compRoomsByFilter.size()));

                    //往前推一个季度
                    localDate = localDate.minusMonths(3);
                }
            }
            return checkInNumTrendVOList;
        }

        /**
         * 根据审批状态和时间范围查询免费房
         *
         * @param propertyId        资产ID
         * @param startTimestamp    开始时间戳
         * @param endDateTimestamp  结束时间戳
         * @param approveStatusList 审批状态集合
         * @return java.lang.Integer
         * @author lichen
         * @date 2023/12/12
         */
        public List<RqCompRoomForm> listCompRoomByDateRangeAndApproveStatus(Integer propertyId, long startTimestamp, long endDateTimestamp, List<Integer> approveStatusList) {
            return list(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyId)
                    .in(RqCompRoomForm::getApproveStatus, approveStatusList)
                    .le(RqCompRoomForm::getCheckInDate, endDateTimestamp)
                    .ge(RqCompRoomForm::getCheckOutDate, startTimestamp));
        }

        /**
         * 关联报告数据
         *
         * @param compRoom   免费房
         * @param reportList 报告数据集合
         * @return void
         * @author lichen
         * @date 2024/1/5
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void associated(RqCompRoomForm compRoom, @Nullable List<CompRoomComplimentary> reportList) {
            //把w-connect数据 和 报告的数据取消关联
            String oriRelIdStr = compRoom.getRelIds();
            if (StringUtils.isNotBlank(oriRelIdStr)) {
                List<CompRoomComplimentary> disassociateList = Arrays.stream(oriRelIdStr.split(","))
                        .map(str -> new CompRoomComplimentary().setId(Long.valueOf(str)).setRelId(0L))
                        .toList();
                complimentaryService.updateBatchById(disassociateList);
            }

            if (CollectionUtils.isEmpty(reportList)) {
                //更新w-connect数据为未关联
                updateById(new RqCompRoomForm()
                        .setId(compRoom.getId())
                        .setIsRel(false)
                        .setRelIds(""));
            } else {
                //校验报告的数据是否关联其他w-connect数据
                if (reportList.stream().anyMatch(item -> item.getRelId() > 0 && !item.getRelId().equals(compRoom.getId()))) {
                    throw new BadRequestException(CompRoomErrorI18nConstant.COMP_ROOM_ALREADY_ASSOCIATED,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The data has been associated");
                }

                //更新w-connect数据为已关联
                updateById(new RqCompRoomForm()
                        .setId(compRoom.getId())
                        .setIsRel(true)
                        .setRelIds(StringUtils.join(
                                reportList.stream().map(CompRoomComplimentary::getId).toList(), ",")));
                //更新报告的数据为已关联
                complimentaryService.updateBatchById(reportList.stream()
                        .map(report -> new CompRoomComplimentary()
                                .setId(report.getId())
                                .setRelId(compRoom.getId()))
                        .collect(Collectors.toList()));
            }
        }

        /**
         * 删除免费房
         *
         * @param compRoom 免费房
         * @return void
         * @author lichen
         * @date 2024/6/11
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void delete(RqCompRoomForm compRoom) {
            removeById(compRoom.getId());
            //解绑关联的报告数据
            if (StringUtils.isNotBlank(compRoom.getRelIds())) {
                List<CompRoomComplimentary> disassociateList = Arrays.stream(compRoom.getRelIds().split(","))
                        .map(str -> new CompRoomComplimentary().setId(Long.valueOf(str)).setRelId(0L)).toList();
                complimentaryService.updateBatchById(disassociateList);
            }
        }

        /**
         * 分页查询免费房数据
         *
         * @param tenantId      租户id
         * @param propertyId    资产id
         * @param compRoomQuery 查询对象
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.comproom.CompRoomVO>
         * @author lichen
         * @date 2025/8/27
         */
        @Override
        public ListPagedDTO<CompRoomVO> pageList(Integer tenantId, int propertyId, CompRoomQuery compRoomQuery) {
            LambdaQueryWrapper<RqCompRoomForm> lambdaQueryWrapper = buildQueryWrapper(compRoomQuery, propertyId);
            Page<RqCompRoomForm> pageResult = page(new Page<>(compRoomQuery.getPage(), compRoomQuery.getSize()), lambdaQueryWrapper);
            //查询结果集为空时
            if (pageResult.getTotal() == BaseConstant.ZERO) {
                return new ListPagedDTO<>();
            }
            List<RqCompRoomForm> records = pageResult.getRecords();

            //查询备注
            List<CompRoomComment> commentList = commentService.listByCompRoomId(records.stream().map(RqCompRoomForm::getId).toList());
            Map<Long, Long> compRoomId2Count = commentList.stream().collect(Collectors.groupingBy(CompRoomComment::getCompRoomId, Collectors.counting()));

            //收集创建人和修改人
            Set<Integer> userIds = records.stream()
                    .flatMap(compRoom -> Stream.of(compRoom.getCreatedBy(), compRoom.getUpdatedBy()))
                    .collect(Collectors.toSet());
            //查询用户信息
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);
            //List<RqCompRoomForm> -> List<CompRoomFormDTO>
            List<CompRoomVO> compRoomVOS = beanMapper.mapList(records, CompRoomVO.class, (entity, vo) -> {
                vo.setCreatedName(userId2Name.get(entity.getCreatedBy()));
                vo.setUpdatedName(userId2Name.get(entity.getUpdatedBy()));
                vo.setCommentCount(compRoomId2Count.getOrDefault(entity.getId(), 0L));
            });
            return new ListPagedDTO<>(pageResult.getTotal(), compRoomVOS);
        }

        /**
         * 查询创建人
         *
         * @param tenantId   租户id
         * @param propertyId 资产id
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.dto.comproom.CompRoomUserDTO>
         * @author lichen
         * @date 2025/8/27
         */
        @Override
        public List<CompRoomUserDTO> listCreateBy(Integer tenantId, int propertyId) {
            //查询Comp Room 的创建人
            QueryWrapper<RqCompRoomForm> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("property_id", propertyId);
            queryWrapper.select("DISTINCT created_by");
            List<RqCompRoomForm> compRoomList = list(queryWrapper);
            if (CollectionUtils.isEmpty(compRoomList)) return Collections.emptyList();

            //查询用户信息
            List<Integer> userIds = compRoomList.stream().map(RqCompRoomForm::getCreatedBy).collect(Collectors.toList());
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);

            //List<RqCompRoomForm> -> List<CompRoomUserDTO>
            return compRoomList.stream().map(entity ->
                            CompRoomUserDTO.builder()
                                    .userId(entity.getCreatedBy())
                                    .userName(userId2Name.get(entity.getCreatedBy()))
                                    .build())
                    .filter(dto -> StringUtils.isNotBlank(dto.getUserName()))
                    .collect(Collectors.toList());
        }

        /**
         * 查询导出的免费房数据
         *
         * @param propertyDTO         资产DTO
         * @param compRoomExportQuery 导出查询对象
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.comproom.CompRoomExcelVO>
         * @author lichen
         * @date 2025/8/27
         */
        @Override
        public List<CompRoomExcelVO> listExportData(PropertyDTO propertyDTO, CompRoomExportQuery compRoomExportQuery) {
            List<RqCompRoomForm> compRoomList = list(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyDTO.getId())
                    .between(compRoomExportQuery.getType() == CompRoomExportQuery.REQUEST_DATE, RqCompRoomForm::getCreatedTime, compRoomExportQuery.getBeginDate(), compRoomExportQuery.getEndDate())
                    .between(compRoomExportQuery.getType() == CompRoomExportQuery.CHECK_IN_DATE, RqCompRoomForm::getCheckInDate, compRoomExportQuery.getBeginDate(), compRoomExportQuery.getEndDate())
                    //未归档的排在前面
                    .orderByAsc(RqCompRoomForm::getIsArchive)
                    .orderByDesc(RqCompRoomForm::getId));
            if (CollectionUtils.isEmpty(compRoomList)) return Collections.emptyList();
            return convertToExcelVO(propertyDTO, compRoomList);
        }

        /**
         * 统计资产下在住中的免费房数量
         *
         * @param propertyDTO       资产DTO
         * @param approveStatusList 审批状态集合
         * @return java.lang.Long
         * @author lichen
         * @date 2025/12/29
         */
        @Override
        public long countInHouse(PropertyDTO propertyDTO, List<Integer> approveStatusList) {
            //查询在住的免费房数量
            long dateNowTimestamp = DateTimeUtils.dateNowTimestamp(propertyDTO.getTimezone());
            return count(new LambdaQueryWrapper<RqCompRoomForm>()
                    .eq(RqCompRoomForm::getPropertyId, propertyDTO.getId())
                    .le(RqCompRoomForm::getCheckInDate, dateNowTimestamp)
                    .ge(RqCompRoomForm::getCheckOutDate, dateNowTimestamp)
                    .in(RqCompRoomForm::getApproveStatus, approveStatusList));
        }

        /**
         * 转化为ExcelVO
         *
         * @param propertyDTO  资产DTO
         * @param compRoomList 免费房集合
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.comproom.CompRoomExcelVO>
         * @author lichen
         * @date 2024/1/17
         */
        private List<CompRoomExcelVO> convertToExcelVO(PropertyDTO propertyDTO, List<RqCompRoomForm> compRoomList) {
            //查询审批日志
            List<CompRoomApproveLog> approveLogs = approveLogService.listByBatchIdAndCompRoomId(compRoomList);
            Set<Integer> approveBys = approveLogs.stream().map(CompRoomApproveLog::getApproveBy).collect(Collectors.toSet());

            //查询用户集合
            Set<Integer> userIds = compRoomList.stream()
                    .flatMap(compRoom -> Stream.of(compRoom.getCreatedBy(), compRoom.getUpdatedBy()))
                    .collect(Collectors.toSet());
            userIds.addAll(approveBys);
            userIds.remove(BaseConstant.ZERO);
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);

            //语言标识使用用户设置的
            Locale locale = LocaleContextHolder.getLocale();
            //获取资产的属性
            String timezone = propertyDTO.getTimezone();
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            String longDate = dateTime.getLongDate();
            String hourClock = dateTime.getHourClock();

            //审批状态描述
            String approvedMsg = i18nMessageUtils.message(ApproveStatusEnum.APPROVED.getI18nCode(), locale);
            String rejectedMsg = i18nMessageUtils.message(ApproveStatusEnum.REJECTED.getI18nCode(), locale);
            //审批日志集合
            Map<Long, List<CompRoomApproveLogVO>> compRoomId2ApproveLogs = beanMapper.mapList(approveLogs, CompRoomApproveLogVO.class, (entity, vo) -> {
                //设置审批时间
                String approveTimeStr = InternationalizationUtils.formatDateTime(vo.getApproveTime(), longDate, hourClock, timezone, locale);
                vo.setApproveTimeStr(approveTimeStr);
                //设置审批人
                vo.setApproveByName(userId2Name.get(vo.getApproveBy()));
                vo.setApproveStatusDesc(vo.getIsApproved() ? approvedMsg : rejectedMsg);
            }).stream().collect(Collectors.groupingBy(CompRoomApproveLogVO::getCompRoomId));

            //关联状态
            String i18nYes = i18nMessageUtils.message(I18nConstant.YES, locale);
            String i18nNo = i18nMessageUtils.message(I18nConstant.NO, locale);
            //归档状态
            String i18nArchived = i18nMessageUtils.message(I18nConstant.ARCHIVED, locale);

            //List<RqCompRoomForm> -> List<CompRoomExcelVO>
            return beanMapper.mapList(compRoomList, CompRoomExcelVO.class, (compRoom, compRoomVo) -> {
                //审批状态
                String approveStatusDesc = i18nMessageUtils.message(ApproveStatusEnum.getI18nCode(compRoom.getApproveStatus()), locale);

                if (compRoom.getIsArchive()) {
                    //已归档时，显示状态等于归档状态+审批状态
                    compRoomVo.setStatusDesc(i18nArchived + "\r\n" + "(" + approveStatusDesc + ")");
                } else {
                    //未归档时，显示状态等于审批状态
                    compRoomVo.setStatusDesc(approveStatusDesc);
                }
                //创建人
                compRoomVo.setCreatedByName(userId2Name.get(compRoom.getCreatedBy()));
                //创建时间
                Long createdTime = compRoom.getCreatedTime();
                String createdTimeStr = createdTime > 0 ? InternationalizationUtils.formatDateTime(createdTime, longDate, hourClock, timezone, locale) : "--";
                compRoomVo.setCreatedTimeStr(createdTimeStr);
                //入住日期
                compRoomVo.setCheckInDateStr(InternationalizationUtils.formatDateTime(compRoom.getCheckInDate(),
                        longDate, StringUtils.EMPTY, timezone, locale));
                //离店日期
                compRoomVo.setCheckOutDateStr(InternationalizationUtils.formatDateTime(compRoom.getCheckOutDate(),
                        longDate, StringUtils.EMPTY, timezone, locale));
                //审批日志
                List<CompRoomApproveLogVO> compRoomApproveLogVOS = compRoomId2ApproveLogs.get(compRoom.getId());
                if (CollectionUtils.isNotEmpty(compRoomApproveLogVOS)) {
                    String logStr = compRoomApproveLogVOS.stream()
                            .sorted(Comparator.comparing(CompRoomApproveLogVO::getId))
                            .map(log ->
                                    new StringBuilder(log.getApproveByName())
                                            .append("--")
                                            .append(log.getApproveTimeStr())
                                            .append("--")
                                            .append(log.getApproveStatusDesc())
                                            .append("\r\n").toString())
                            .collect(Collectors.joining());
                    compRoomVo.setApproveLogs(logStr);
                } else {
                    compRoomVo.setApproveLogs("--");
                }

                if (StringUtils.isNotBlank(compRoom.getConfirmation())) {
                    List<String> confirmationList = JsonUtils.convertStringToList(objectMapper, compRoom.getConfirmation(), String.class);
                    compRoomVo.setConfirmationStr(StringUtils.join(confirmationList, "\r\n"));
                } else {
                    compRoomVo.setConfirmationStr("--");
                }
                if (StringUtils.isNotBlank(compRoom.getComments())) {
                    List<String> commentList = JsonUtils.convertStringToList(objectMapper, compRoom.getComments(), String.class);
                    compRoomVo.setCommentStr(StringUtils.join(commentList, "\r\n"));
                } else {
                    compRoomVo.setCommentStr("--");
                }

                compRoomVo.setIsRelDesc(compRoom.getIsRel() ? i18nYes : i18nNo);
                if (StringUtils.isNotBlank(compRoom.getRoomNumber())) {
                    List<String> roomNumberList = Lists.newArrayList(compRoom.getRoomNumber().split(","));
                    compRoomVo.setRoomNumberStr(StringUtils.join(roomNumberList, "\r\n"));
                } else {
                    compRoomVo.setRoomNumberStr("--");
                }

                //成年人数为0时，设置为null
                if (compRoom.getAdultsNumber() == 0) {
                    compRoomVo.setAdultsNumberStr("--");
                } else {
                    compRoomVo.setAdultsNumberStr(compRoom.getAdultsNumber().toString());
                }
                //儿童人数为0时，设置为null
                if (compRoom.getChildrenNumber() == 0) {
                    compRoomVo.setChildrenNumberStr("--");
                } else {
                    compRoomVo.setChildrenNumberStr(compRoom.getChildrenNumber().toString());
                }

                //房间数
                compRoomVo.setNumberOfRoomsStr(Objects.isNull(compRoom.getNumberOfRooms()) ? "--" : compRoom.getNumberOfRooms().toString());
            });
        }

        /**
         * 构建查询条件
         *
         * @param compRoomQuery 构建QueryWrapper
         * @param propertyId    资产id
         * @return com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<cloud.wconnect.frontoffice.entity.comproom.RqCompRoomForm>
         * @author lichen
         * @date 2025/8/27
         */
        private LambdaQueryWrapper<RqCompRoomForm> buildQueryWrapper(CompRoomQuery compRoomQuery, int propertyId) {
            //处理排序字段
            if (Objects.isNull(compRoomQuery.getOrderByField()))
                compRoomQuery.setOrderByField(CompRoomOrderByFieldEnum.ID.getFieldId());
            if (Objects.isNull(compRoomQuery.getOrderByWay())) compRoomQuery.setOrderByWay(CompRoomQuery.DESC);

            LambdaQueryWrapper<RqCompRoomForm> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(RqCompRoomForm::getPropertyId, propertyId);
            //createTime
            queryWrapper.between(Objects.nonNull(compRoomQuery.getRequestDateBegin()) && Objects.nonNull(compRoomQuery.getRequestDateEnd()),
                    RqCompRoomForm::getCreatedTime, compRoomQuery.getRequestDateBegin(), compRoomQuery.getRequestDateEnd());
            //checkInDate
            queryWrapper.between(Objects.nonNull(compRoomQuery.getCheckInDateBegin()) && Objects.nonNull(compRoomQuery.getCheckInDateEnd()),
                    RqCompRoomForm::getCheckInDate, compRoomQuery.getCheckInDateBegin(), compRoomQuery.getCheckInDateEnd());
            //创建人
            queryWrapper.eq(Objects.nonNull(compRoomQuery.getCreateBy()), RqCompRoomForm::getCreatedBy, compRoomQuery.getCreateBy());
            //房间号
            queryWrapper.apply(StringUtils.isNotBlank(compRoomQuery.getRoomNumber()),
                    String.format("CONCAT( ',', room_number , ',' ) REGEXP ',(%s),'", compRoomQuery.getRoomNumber()));
            //是否关联
            queryWrapper.eq(Objects.nonNull(compRoomQuery.getIsRel()), RqCompRoomForm::getIsRel, compRoomQuery.getIsRel());
            //客人姓名
            queryWrapper.like(StringUtils.isNotBlank(compRoomQuery.getGuestName()), RqCompRoomForm::getGuestName, compRoomQuery.getGuestName());
            //房间数
            queryWrapper.eq(Objects.nonNull(compRoomQuery.getNumberOfRooms()), RqCompRoomForm::getNumberOfRooms, compRoomQuery.getNumberOfRooms());
            //确认信息
            queryWrapper.like(StringUtils.isNotBlank(compRoomQuery.getConfirmation()), RqCompRoomForm::getConfirmation, compRoomQuery.getConfirmation());

            //审批状态 与 归档状态
            boolean isCondition = CollectionUtils.isNotEmpty(compRoomQuery.getApproveStatus()) || Objects.nonNull(compRoomQuery.getIsArchive());
            queryWrapper.and(isCondition,
                    child -> child.in(CollectionUtils.isNotEmpty(compRoomQuery.getApproveStatus()), RqCompRoomForm::getApproveStatus, compRoomQuery.getApproveStatus())
                            .or()
                            .eq(Objects.nonNull(compRoomQuery.getIsArchive()), RqCompRoomForm::getIsArchive, compRoomQuery.getIsArchive()));

            //未归档的排在前面
            queryWrapper.orderByAsc(RqCompRoomForm::getIsArchive);

            if (compRoomQuery.getOrderByWay() == CompRoomQuery.DESC) {
                queryWrapper.orderByDesc(CompRoomOrderByFieldEnum.getSFunction(compRoomQuery.getOrderByField()));
            } else {
                queryWrapper.orderByAsc(CompRoomOrderByFieldEnum.getSFunction(compRoomQuery.getOrderByField()));
            }
            return queryWrapper;
        }

        /**
         * 计算统计日期
         *
         * @param unit     统计单位 1：月 2：季度
         * @param span     时间跨度 n个月/季度
         * @param timezone 时区
         * @return java.util.Map<java.lang.String, java.lang.Integer>
         * @author lichen
         * @date 2023/12/12
         */
        private Map<String, LocalDate> calculatestatisticDate(Integer unit, Integer span, String timezone) {
            HashMap<String, LocalDate> statisticDate = Maps.newHashMapWithExpectedSize(2);
            LocalDate currentLocalDate = LocalDate.now(ZoneId.of(timezone));

            if (PropertyCheckInNumTrendQuery.MONTH == unit) {
                //当前月份的第一天往前推（n：时间跨度-1）个月
                statisticDate.put("startLocalDate", currentLocalDate.withDayOfMonth(1).minusMonths(span - 1));
                //当月的最后一天
                statisticDate.put("endLocalDate", currentLocalDate.withDayOfMonth(currentLocalDate.lengthOfMonth()));
            } else {
                //获取n个季度前的开始日期
                statisticDate.put("startLocalDate", QuarterUtil.getBeforQuarterStartDate(currentLocalDate, span));
                //获取当前季度的结束日期
                statisticDate.put("endLocalDate", QuarterUtil.getQuarterEndDate(currentLocalDate));
            }
            return statisticDate;
        }

        /**
         * 构建审批日志
         *
         * @param oriCompRoom  comp room数据
         * @param approveDTO   审批dto
         * @param approveLevel 审批层级
         * @return cloud.wconnect.frontoffice.entity.comproom.CompRoomApproveLog
         * @author lichen
         * @date 2023/8/22
         */
        private CompRoomApproveLog buildApproveLog(RqCompRoomForm oriCompRoom, CompRoomApproveDTO approveDTO, CompRoomApproveLevel approveLevel) {
            CompRoomApproveLog approveLog = new CompRoomApproveLog();
            approveLog.setPropertyId(oriCompRoom.getPropertyId());
            approveLog.setBatchId(oriCompRoom.getBatchId());
            approveLog.setCompRoomId(oriCompRoom.getId());
            approveLog.setLevelId(approveLevel.getId());
            approveLog.setApproveIndex(approveLevel.getApproveIndex());
            approveLog.setApproveBy(approveDTO.getApproveBy());
            approveLog.setApproveOpinion(approveDTO.getApproveOpinion());
            approveLog.setApproveTime(DateTimeUtils.nowTimestamp());
            return approveLog;
        }

        /**
         * 推送数据至下一个节点
         *
         * @param compRoom comp room数据
         * @return void
         * @author lichen
         * @date 2023/8/21
         */
        private void pushNextIndex(RqCompRoomForm compRoom) {
            //查询下一个节点
            CompRoomApproveLevel nextApproveIndex = approveLevelService.getNextByApproveIndex(compRoom.getPropertyId(), compRoom.getApproveIndex());
            if (Objects.isNull(nextApproveIndex)) {
                //更新数据为审核结束
                compRoom.setApproveStatus(ApproveStatusEnum.APPROVED.getStatus());
                updateById(compRoom);
                //审批通过后发邮件给创建人
                TransactionUtils.afterCommitAsyncExecute(executor, () -> compRoomNotifyService.notifyCreateUser(compRoom, ApproveStatusEnum.APPROVED.getStatus()));
            } else {
                //更新数据，推送至下一个审核节点
                compRoom.setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus());
                compRoom.setApproveIndex(nextApproveIndex.getApproveIndex());
                updateById(compRoom);
                //发送给审批人
                TransactionUtils.afterCommitAsyncExecute(executor, () -> compRoomNotifyService.notifyApprover(compRoom));
            }
        }
    }