# PastReservationOpGuestinHouseServiceImpl.java

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

    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.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.CommonUtils;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.frontoffice.cons.pastreservation.PastReservationStatusEnum;
    import cloud.wconnect.frontoffice.entity.pastreservation.PastReservation;
    import cloud.wconnect.frontoffice.entity.pastreservation.PastReservationOpGuestinHouse;
    import cloud.wconnect.frontoffice.mapper.pastreservation.PastReservationOpGuestinHouseMapper;
    import cloud.wconnect.frontoffice.pojo.bo.pastreservation.GuestInHouseBO;
    import cloud.wconnect.frontoffice.pojo.bo.pastreservation.GuestNameBO;
    import cloud.wconnect.frontoffice.pojo.bo.pastreservation.UpdateGroupKeyBO;
    import cloud.wconnect.frontoffice.pojo.dto.pastreservation.SaveCheckInRecordDTO;
    import cloud.wconnect.frontoffice.service.pastreservation.IPastReservationIntervalDayService;
    import cloud.wconnect.frontoffice.service.pastreservation.IPastReservationOpGuestinHouseService;
    import cloud.wconnect.frontoffice.service.pastreservation.IPastReservationService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.ibatis.session.ExecutorType;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import jakarta.annotation.Nullable;
    import jakarta.annotation.Resource;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 酒店在住客人信息 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2024-10-01
     */
    @Service
    @DS("logbook")
    public class PastReservationOpGuestinHouseServiceImpl extends ServiceImpl<PastReservationOpGuestinHouseMapper, PastReservationOpGuestinHouse> implements IPastReservationOpGuestinHouseService {

        @Resource
        private IPastReservationIntervalDayService pastReservationIntervalDayService;
        @Resource
        private IPastReservationService pastReservationService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private SqlSessionFactory sqlSessionFactory;

        /**
         * 查询指定用户的入住记录
         *
         * @param propertyId 资产 id
         * @param names      指定用户
         * @param excludeIds 排除掉已经查询到的数据
         * @return 住记录
         * @author yyt
         * @date 2024/10/3
         **/
        @Override
        public List<PastReservationOpGuestinHouse> listByNames(Integer propertyId, List<GuestNameBO> names, @Nullable List<Long> excludeIds) {
            return baseMapper.listByNames(propertyId, names, excludeIds);
        }

        /**
         * 批量更新
         *
         * @param inHouseRecords 入住信息
         * @author yyt
         * @date 2024/10/3
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateBatchById(List<PastReservationOpGuestinHouse> inHouseRecords) {
            if (inHouseRecords.isEmpty()) {
                return;
            }

            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
            PastReservationOpGuestinHouseMapper mapper = sqlSession.getMapper(PastReservationOpGuestinHouseMapper.class);
            for (int i = 0; i < inHouseRecords.size(); i++) {
                PastReservationOpGuestinHouse item = inHouseRecords.get(i);
                mapper.updateById(item);

                //每 50 条提交一次
                if ((i + 1) % 50 == 0) {
                    sqlSession.flushStatements();
                }
            }

            sqlSession.flushStatements();
        }

        /**
         * 查询当前在店人员（不包含已删除的数据）
         *
         * @param propertyDTO 资产
         * @param existNames  排除掉已经在连续入住列表中的用户
         * @return 在店人员
         * @author yyt
         * @date 2024/10/3
         **/
        @Override
        public List<PastReservationOpGuestinHouse> listInHouseUser(PropertyDTO propertyDTO, @Nullable List<GuestNameBO> existNames) {
            long currentDate = DateTimeUtils.dateNowTimestamp(propertyDTO.getTimezone());
            return baseMapper.listInHouseUser(propertyDTO.getId(), currentDate, existNames);
        }

        /**
         * 查询当前在店人员（不包含已删除的数据）
         *
         * @param propertyDTO 资产
         * @param guestName   指定用户
         * @return 在店人员
         * @author yyt
         * @date 2024/10/3
         **/
        @Override
        public List<PastReservationOpGuestinHouse> listInHouseUserForTargetUser(PropertyDTO propertyDTO, @Nullable GuestNameBO guestName) {
            long currentDate = DateTimeUtils.dateNowTimestamp(propertyDTO.getTimezone());
            String firstName;
            String lastName;
            if (Objects.nonNull(guestName)) {
                firstName = guestName.getFirstName();
                lastName = guestName.getLastName();
            } else {
                firstName = null;
                lastName = null;
            }

            return list(new QueryWrapper<PastReservationOpGuestinHouse>().lambda()
                    .eq(PastReservationOpGuestinHouse::getPropertyId, propertyDTO.getId())
                    .eq(PastReservationOpGuestinHouse::getDeleted, false)
                    .le(PastReservationOpGuestinHouse::getCheckInDate, currentDate)
                    .ge(PastReservationOpGuestinHouse::getCheckOutDate, currentDate)
                    .nested(Objects.nonNull(firstName) && Objects.nonNull(lastName),
                            sql -> sql.eq(PastReservationOpGuestinHouse::getFirstName, firstName).eq(PastReservationOpGuestinHouse::getLastName, lastName)
                                    .or()
                                    .eq(PastReservationOpGuestinHouse::getFirstName, lastName).eq(PastReservationOpGuestinHouse::getLastName, firstName)));
        }

        /**
         * 查询连续入住标识对应的入住记录（排除掉已经查询过的入住记录 id、已经删除的数据）
         *
         * @param propertyId 资产 id
         * @param groupKees 连续入住标识
         * @param existIds   排除记录 id
         * @return 入住记录
         * @author yyt
         * @date 2024/10/3
         **/
        @Override
        public List<PastReservationOpGuestinHouse> listByGroupKey(Integer propertyId, List<String> groupKees, @Nullable List<Long> existIds) {
            return list(new QueryWrapper<PastReservationOpGuestinHouse>().lambda()
                    .eq(PastReservationOpGuestinHouse::getPropertyId, propertyId)
                    .in(PastReservationOpGuestinHouse::getGroupKey, groupKees)
                    .notIn(CollectionUtils.isNotEmpty(existIds), PastReservationOpGuestinHouse::getId, existIds)
                    .eq(PastReservationOpGuestinHouse::getDeleted, false));
        }

        /**
         * 处理插入 guest in house 数据
         *
         * @param propertyDTO 资产
         * @param data        guest in house 数据
         * @param userId      操作人
         * @author yyt
         * @date 2023/7/31
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleGuestInHouseData(PropertyDTO propertyDTO, List<GuestInHouseBO> data, int userId) {
            Integer propertyId = propertyDTO.getId();

            // 查询退房日期超过当天的人
            List<PastReservationOpGuestinHouse> checkOutExceedCurrentDayInHouseUser = list(new QueryWrapper<PastReservationOpGuestinHouse>()
                    .lambda().eq(PastReservationOpGuestinHouse::getPropertyId, propertyId)
                    .eq(PastReservationOpGuestinHouse::getDeleted, false)
                    .gt(PastReservationOpGuestinHouse::getCheckOutDate, DateTimeUtils.dateNowTimestamp(propertyDTO.getTimezone())));

            // 获取设置的间隔天数
            Integer intervalDay = pastReservationIntervalDayService.getByPropertyIdForUpdate(propertyId).getDay();

            // 保存文件数据
            saveFileData(propertyDTO, intervalDay, data, checkOutExceedCurrentDayInHouseUser, userId);

            // 处理提前离店的情况
            if (!checkOutExceedCurrentDayInHouseUser.isEmpty()) {
                handleEarlyCheckOut(propertyDTO, intervalDay, checkOutExceedCurrentDayInHouseUser, userId);
            }

            // 计算入住天数
            pastReservationService.computeInHouseDay(propertyId, userId, null);
        }

        /**
         * 保存文件数据
         *
         * @param propertyDTO                         资产
         * @param intervalDay                         间隔天数
         * @param data                                报告数据
         * @param checkOutExceedCurrentDayInHouseUser 退房日期超过当天的人
         * @param userId                              操作人
         * @author yyt
         * @date 2024/10/18
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveFileData(PropertyDTO propertyDTO, Integer intervalDay, List<GuestInHouseBO> data,
                                  List<PastReservationOpGuestinHouse> checkOutExceedCurrentDayInHouseUser, int userId) {
            long now = DateTimeUtils.nowTimestamp();
            Integer propertyId = propertyDTO.getId();
            String timezone = propertyDTO.getTimezone();

            if (!data.isEmpty()) {
                // 文件数据 -> entity
                List<PastReservationOpGuestinHouse> opGuestInHouses = new ArrayList<>(data.stream().map(item -> {
                    long checkInDate = DateTimeUtils.dateStrToEpochSecond(item.getCheckInDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD, timezone);
                    long checkOutDate = DateTimeUtils.dateStrToEpochSecond(item.getCheckOutDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD, timezone);
                    PastReservationOpGuestinHouse opGuestInHouse = new PastReservationOpGuestinHouse()
                            .setPropertyId(propertyId).setFirstName(item.getFirstName()).setLastName(item.getLastName())
                            .setFullName(item.getFullName())
                            .setRoomNumber(Objects.isNull(item.getRoomNumber()) ? StringUtils.EMPTY : item.getRoomNumber())
                            .setCheckInDate(checkInDate)
                            .setCheckOutDate(checkOutDate)
                            .setCreatedBy(userId).setCreatedTime(now)
                            .setStatus(PastReservationStatusEnum.NORMAL_DISPLAY.getCode())
                            .setStatusSetBy(userId).setStatusSetTime(now);

                    // 根据姓名、房间号，清除当日报告中出现过的数据，目的是循环结束后，保留下来除当前房间号外提前离店的人，当前房间号下离店的人在下面合并数据时会做处理
                    String nameAndRoomNumber = opGuestInHouse.getNameAndRoomNumber();
                    List<PastReservationOpGuestinHouse> needHandleData = checkOutExceedCurrentDayInHouseUser.stream()
                            .filter(existData -> existData.getNameAndRoomNumber().equals(nameAndRoomNumber) || existData.getNameReversalAndRoomNumber().equals(nameAndRoomNumber))
                            .toList();
                    checkOutExceedCurrentDayInHouseUser.removeAll(needHandleData);

                    return opGuestInHouse;
                }).toList());

                // 查询已经导入过的客人信息
                Map<String, PastReservationOpGuestinHouse> existOpInHouseByUniKey = Collections.emptyMap();
                Map<String, PastReservationOpGuestinHouse> existOpInHouseByUniKeyForNameReversal = Collections.emptyMap();
                List<PastReservationOpGuestinHouse> existOpGuestInHouses = baseMapper.listInHouseRecordByFileData(propertyId, opGuestInHouses);
                if (!existOpGuestInHouses.isEmpty()) {
                    // 分组根据文件中的入住记录查询表中的数据
                    existOpInHouseByUniKey = existOpGuestInHouses.stream().collect(Collectors.toMap(PastReservationOpGuestinHouse::getUniKey, v -> v, (v1, v2) -> v2));
                    existOpInHouseByUniKeyForNameReversal = existOpGuestInHouses.stream().collect(Collectors.toMap(PastReservationOpGuestinHouse::getUniKeyForNameReversal, v -> v, (v1, v2) -> v2));
                }

                // 新数据组成的集合
                int size = opGuestInHouses.size();
                List<PastReservationOpGuestinHouse> addList = Lists.newArrayListWithExpectedSize(size);
                List<PastReservationOpGuestinHouse> updateList = Lists.newArrayListWithExpectedSize(size);
                // 重置多个入住记录的连续入住标识的用户
                List<GuestNameBO> guestNameBOS = Lists.newArrayListWithExpectedSize(size);

                for (PastReservationOpGuestinHouse item : opGuestInHouses) {
                    guestNameBOS.add(new GuestNameBO(item.getFirstName(), item.getLastName()));
                    guestNameBOS.add(new GuestNameBO(item.getLastName(), item.getFirstName()));
                    item.setGroupKey(CommonUtils.randomUuidWithoutDashes());
                    handelNewData(item, existOpInHouseByUniKey, existOpInHouseByUniKeyForNameReversal, addList, updateList);
                }

                // 插入数据
                if (!addList.isEmpty()) {
                    saveBatch(addList);
                }
                // 修改数据
                if (!updateList.isEmpty()) {
                    updateBatchById(updateList);
                }

                // 重置入住记录的连续入住标识
                handleRestGroupKey(propertyId, guestNameBOS, opGuestInHouses);
            }
        }

        /**
         * 处理新数据
         *
         * @param guestInHouse                          报告数据
         * @param existOpInHouseByUniKey                已经存在的数据
         * @param existOpInHouseByUniKeyForNameReversal 已经存在的数据
         * @param addList                               需要新增的集合
         * @param updateList                            需要修改的集合
         * @author yyt
         * @date 2024/11/5
         **/
        private void handelNewData(PastReservationOpGuestinHouse guestInHouse, Map<String, PastReservationOpGuestinHouse> existOpInHouseByUniKey,
                                   Map<String, PastReservationOpGuestinHouse> existOpInHouseByUniKeyForNameReversal,
                                   List<PastReservationOpGuestinHouse> addList, List<PastReservationOpGuestinHouse> updateList) {
            // 判断是否为新数据
            String uniKey = guestInHouse.getUniKey();
            PastReservationOpGuestinHouse existOpGuestInHouse = existOpInHouseByUniKey.get(uniKey);
            if (Objects.isNull(existOpGuestInHouse)) {
                // 考虑姓名颠倒的情况
                existOpGuestInHouse = existOpInHouseByUniKeyForNameReversal.get(uniKey);
            }
            if (Objects.isNull(existOpGuestInHouse)) {
                // 新记录
                addList.add(guestInHouse);
            } else {
                // 若是已删除，则置为未删除
                if (existOpGuestInHouse.getDeleted()) {
                    updateList.add(new PastReservationOpGuestinHouse().setId(existOpGuestInHouse.getId())
                            .setGroupKey(guestInHouse.getGroupKey())
                            .setDeleted(false).setDeletedBy(BaseConstant.ZERO).setDeletedTime(BaseConstant.LONG_ZERO));
                }
            }
        }

        /**
         * 处理设置连续入住标识
         *
         * @param propertyId      资产 id
         * @param guestNameBOS    用户
         * @param opGuestInHouses 受影响的入住记录
         * @author yyt
         * @date 2024/11/15
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleRestGroupKey(Integer propertyId, List<GuestNameBO> guestNameBOS, List<PastReservationOpGuestinHouse> opGuestInHouses) {
            if (CollectionUtils.isNotEmpty(guestNameBOS) && CollectionUtils.isNotEmpty(opGuestInHouses)) {
                opGuestInHouses.sort(Comparator.comparing(PastReservationOpGuestinHouse::getCheckInDate));
                Long minCheckInDate = opGuestInHouses.get(0).getCheckInDate();
                opGuestInHouses.sort(Comparator.comparing(PastReservationOpGuestinHouse::getCheckOutDate));
                Long maxCheckOutDate = opGuestInHouses.get(opGuestInHouses.size() - 1).getCheckOutDate();
                resetGroupKey(propertyId, minCheckInDate, maxCheckOutDate, guestNameBOS);
            }
        }

        /**
         * 处理提前离店的情况
         *
         * @param propertyDTO           资产
         * @param intervalDay           间隔天数
         * @param earlyCheckOutInHouses 提前离店的人
         * @param userId                操作人
         * @author yyt
         * @date 2024/10/14
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleEarlyCheckOut(PropertyDTO propertyDTO, Integer intervalDay, List<PastReservationOpGuestinHouse> earlyCheckOutInHouses, int userId) {
            // 提前离店的设置退房日期为今天，并重置连续入住标识
            Integer propertyId = propertyDTO.getId();
            String timezone = propertyDTO.getTimezone();
            long dateNow = DateTimeUtils.dateNowTimestamp(timezone);
            long now = DateTimeUtils.nowTimestamp();
            int size = earlyCheckOutInHouses.size();
            List<PastReservationOpGuestinHouse> updateCheckOutDateList = Lists.newArrayListWithExpectedSize(size);
            List<GuestNameBO> guestNameBOS = Lists.newArrayListWithExpectedSize(size);

            earlyCheckOutInHouses.forEach(item -> {
                // 默认设置退房日期为今天
                long checkOutDate = dateNow;
                // 若是未来数据，则置退房日期为入住日期
                if (item.getCheckInDate() > dateNow) {
                    checkOutDate = item.getCheckInDate();
                }
                item.setCheckOutDate(checkOutDate);

                updateCheckOutDateList.add(new PastReservationOpGuestinHouse().setId(item.getId())
                        .setCheckOutDate(checkOutDate).setUpdatedBy(userId).setUpdatedTime(now));

                guestNameBOS.add(new GuestNameBO(item.getFirstName(), item.getLastName()));
                guestNameBOS.add(new GuestNameBO(item.getLastName(), item.getFirstName()));
            });

            if (CollectionUtils.isNotEmpty(updateCheckOutDateList)) {
                updateBatchById(updateCheckOutDateList);
            }

            // 重置入住记录的连续入住标识
            handleRestGroupKey(propertyId, guestNameBOS, earlyCheckOutInHouses);
        }

        /**
         * 批量新增
         *
         * @param opGuestinHouses 入住信息
         * @author yyt
         * @date 2024/10/3
         **/
        private void saveBatch(List<PastReservationOpGuestinHouse> opGuestinHouses) {
            if (opGuestinHouses.isEmpty()) {
                return;
            }

            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
            PastReservationOpGuestinHouseMapper mapper = sqlSession.getMapper(PastReservationOpGuestinHouseMapper.class);
            for (int i = 0; i < opGuestinHouses.size(); i++) {
                PastReservationOpGuestinHouse item = opGuestinHouses.get(i);
                mapper.insert(item);

                //每 50 条提交一次
                if ((i + 1) % 50 == 0) {
                    sqlSession.flushStatements();
                }
            }

            sqlSession.flushStatements();
        }

        /**
         * 获取指定入住记录
         *
         * @param propertyId   资产 id
         * @param firstName    first name
         * @param lastName     last name
         * @param roomNumber   房间号
         * @param checkInDate  入住日期
         * @param checkOutDate 退房日期
         * @param excludeId    排除自身
         * @return 已存在的入住记录
         * @author yyt
         * @date 2024/10/15
         **/
        @Override
        public PastReservationOpGuestinHouse getTargetRecord(Integer propertyId, String firstName, String lastName, String roomNumber,
                                                         Long checkInDate, Long checkOutDate, @Nullable Long excludeId) {
            return baseMapper.getTargetRecord(propertyId, firstName, lastName, roomNumber, checkInDate, checkOutDate, excludeId);
        }

        /**
         * 保存入住记录
         *
         * @param propertyId 资产 id
         * @param dto        入住信息
         * @param userId     操作人
         * @return 入住记录 id
         * @author yyt
         * @date 2024/10/15
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Long saveInHouseRecord(Integer propertyId, SaveCheckInRecordDTO dto, int userId) {
            String firstName = dto.getFirstName();
            String lastName = dto.getLastName();
            Long checkInDate = dto.getCheckInDate();
            Long checkOutDate = dto.getCheckOutDate();

            long now = DateTimeUtils.nowTimestamp();

            // 先检查下相同姓名、入住区间、房间号的数据是否存在，若已存在，则不用再次新增，若是已删除，则置为未删除
            PastReservationOpGuestinHouse existRecord = getTargetRecord(propertyId, firstName, lastName, dto.getRoomNumber(), checkInDate, checkOutDate, null);
            if (Objects.isNull(existRecord)) {
                existRecord = new PastReservationOpGuestinHouse().setPropertyId(propertyId).setFirstName(firstName).setLastName(lastName)
                        .setFullName(new GuestNameBO(firstName, lastName).getFullName()).setRoomNumber(StringUtils.isEmpty(dto.getRoomNumber()) ? StringUtils.EMPTY : dto.getRoomNumber())
                        .setCheckInDate(checkInDate).setCheckOutDate(checkOutDate).setGroupKey(CommonUtils.randomUuidWithoutDashes())
                        .setCreatedBy(userId).setCreatedTime(now)
                        .setStatus(PastReservationStatusEnum.NORMAL_DISPLAY.getCode()).setStatusSetBy(userId).setStatusSetTime(now);
                save(existRecord);
            } else {
                if (existRecord.getDeleted()) {
                    updateById(new PastReservationOpGuestinHouse().setId(existRecord.getId()).setDeleted(false)
                            .setDeletedBy(BaseConstant.ZERO).setDeletedTime(BaseConstant.LONG_ZERO));
                }
            }

            // 根据入住区间，重置受影响的数据的连续入住标识
            resetGroupKey(propertyId, checkInDate, checkOutDate, Lists.newArrayList(new GuestNameBO(firstName, lastName),
                    new GuestNameBO(lastName, firstName)));

            return existRecord.getId();
        }

        /**
         * 校验存在
         *
         * @param id 入住记录 id
         * @return 入住记录
         * @author yyt
         * @date 2024/10/16
         **/
        @Override
        public PastReservationOpGuestinHouse checkExist(long id) {
            PastReservationOpGuestinHouse opGuestinHouse = getById(id);
            if (Objects.isNull(opGuestinHouse)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the pastReservationOpGuestinHouse is not found;id:" + id);
            }

            return opGuestinHouse;
        }

        /**
         * 修改连续入住记录
         *
         * @param pastReservation 连续入住信息
         * @param cid             入住记录 id
         * @param dto             修改的入住记录
         * @param userId          操作人
         * @author yyt
         * @date 2024/10/16
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateInHouseRecord(PastReservation pastReservation, long cid, SaveCheckInRecordDTO dto, int userId) {
            Long checkInDate = dto.getCheckInDate();
            Long checkOutDate = dto.getCheckOutDate();

            updateById(new PastReservationOpGuestinHouse().setId(cid).setCheckInDate(checkInDate).setCheckOutDate(checkOutDate)
                    .setRoomNumber(StringUtils.isEmpty(dto.getRoomNumber()) ? StringUtils.EMPTY : dto.getRoomNumber())
                    .setUpdatedBy(userId).setUpdatedTime(DateTimeUtils.nowTimestamp()));

            // 根据入住区间，重置受影响的数据的连续入住标识
            resetGroupKey(pastReservation.getPropertyId(), checkInDate, checkOutDate,
                    Lists.newArrayList(new GuestNameBO(dto.getFirstName(), dto.getLastName()),
                            new GuestNameBO(dto.getLastName(), dto.getFirstName())));
        }

        /**
         * 删除入住记录
         *
         * @param opGuestinHouse 入住记录
         * @param userId         操作人
         * @author yyt
         * @date 2024/10/16
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void deleteCheckInRecord(PastReservationOpGuestinHouse opGuestinHouse, int userId) {
            updateById(new PastReservationOpGuestinHouse().setId(opGuestinHouse.getId()).setDeleted(true)
                    .setDeletedBy(userId).setDeletedTime(DateTimeUtils.nowTimestamp()));

            resetGroupKey(opGuestinHouse.getPropertyId(), opGuestinHouse.getCheckInDate(), opGuestinHouse.getCheckOutDate(),
                    Lists.newArrayList(new GuestNameBO(opGuestinHouse.getFirstName(), opGuestinHouse.getLastName()),
                            new GuestNameBO(opGuestinHouse.getLastName(), opGuestinHouse.getFirstName())));
        }

        /**
         * 根据入住区间，重置受影响的数据的连续入住标识
         *
         * @param propertyId   资产 id
         * @param checkInDate  入住日期
         * @param checkOutDate 退房日期
         * @param guestNameBOS guestNameBOS
         * @author yyt
         * @date 2024/10/16
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void resetGroupKey(Integer propertyId, Long checkInDate, Long checkOutDate, List<GuestNameBO> guestNameBOS) {
            // 查询选择用户下，未删除的入住记录跟 当前入住区间+间隔天数 有交集的记录
            Integer intervalDay = pastReservationIntervalDayService.getByPropertyIdForUpdate(propertyId).getDay();
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            long startDate = DateTimeUtils.toZonedDateTime(checkInDate, timezone).minusDays(intervalDay).toEpochSecond();
            long endDate = DateTimeUtils.toZonedDateTime(checkOutDate, timezone).plusDays(intervalDay).toEpochSecond();
            List<PastReservationOpGuestinHouse> haveIntersectionInHouseRecords =
                    baseMapper.listByDateRange(propertyId, startDate, endDate, guestNameBOS);

            // 有交集的入住记录处理连续入住标识
            if (CollectionUtils.isNotEmpty(haveIntersectionInHouseRecords)) {
                // 取 key 关联的其他的入住记录
                List<String> groupKeys = haveIntersectionInHouseRecords.stream().map(PastReservationOpGuestinHouse::getGroupKey)
                        .distinct().toList();
                haveIntersectionInHouseRecords.addAll(listByGroupKey(propertyId, groupKeys,
                        haveIntersectionInHouseRecords.stream().map(PastReservationOpGuestinHouse::getId).toList()));

                mergeCheckInRecords(haveIntersectionInHouseRecords, timezone);

                updateGroupKey(haveIntersectionInHouseRecords, intervalDay, timezone);
            }
        }

        /**
         * 合并入住记录
         *
         * @param inHouseRecords 入住记录
         * @param timezone 时区
         * @author yyt
         * @date 2024/11/26
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void mergeCheckInRecords(List<PastReservationOpGuestinHouse> inHouseRecords, String timezone) {
            // 姓名颠倒的入住记录
            Map<String, List<PastReservationOpGuestinHouse>> inHouseRecordByNameReversal = inHouseRecords
                    .stream().collect(Collectors.groupingBy(PastReservationOpGuestinHouse::getGuestNameReversal));

            List<PastReservationOpGuestinHouse> updateDateList = Lists.newArrayListWithExpectedSize(inHouseRecords.size());
            List<Long> deleteIdList = Lists.newArrayListWithExpectedSize(inHouseRecords.size());

            List<String> alreadyHandleGuestNames = Lists.newArrayListWithExpectedSize(inHouseRecordByNameReversal.size());
            inHouseRecords.stream().collect(Collectors.groupingBy(PastReservationOpGuestinHouse::getGuestName))
                    .forEach((name, groupRecords) -> {
                        if (alreadyHandleGuestNames.contains(name)) return;

                        groupRecords.addAll(inHouseRecordByNameReversal.getOrDefault(name, Collections.emptyList()));
                        List<PastReservationOpGuestinHouse> finalGroupRecords = new ArrayList<>(groupRecords.stream().collect(Collectors.toMap(PastReservationOpGuestinHouse::getId, v -> v, (v1, v2) -> v2)).values());

                        // 相同房间号下，多条入住记录有交集的合并成一条
                        Map<String, List<PastReservationOpGuestinHouse>> inHouseRecordsByRoomNumber = groupRecords.stream()
                                .collect(Collectors.groupingBy(PastReservationOpGuestinHouse::getRoomNumber));
                        inHouseRecordsByRoomNumber.forEach((roomNumber, records) -> {
                            if (records.size() == 1) return;

                            List<UpdateGroupKeyBO> updateGroupKeyBOS = groupCheckInRecords(records, BaseConstant.ZERO, timezone);
                            for (UpdateGroupKeyBO updateGroupKeyBO : updateGroupKeyBOS) {
                                List<Long> ids = updateGroupKeyBO.getIds();
                                if (ids.size() == 1) {
                                    continue;
                                }

                                List<PastReservationOpGuestinHouse> needHandleRecords = new ArrayList<>(finalGroupRecords.stream().filter(item -> ids.contains(item.getId())).toList());

                                Long minCheckInDate = needHandleRecords.stream().min(Comparator.comparing(PastReservationOpGuestinHouse::getCheckInDate)).get().getCheckInDate();
                                Long maxCheckOutDate = needHandleRecords.stream().max(Comparator.comparing(PastReservationOpGuestinHouse::getCheckOutDate)).get().getCheckOutDate();

                                PastReservationOpGuestinHouse opGuestInHouse = needHandleRecords.get(0);
                                opGuestInHouse.setCheckInDate(minCheckInDate);
                                opGuestInHouse.setCheckOutDate(maxCheckOutDate);
                                updateDateList.add(new PastReservationOpGuestinHouse().setId(opGuestInHouse.getId()).setCheckInDate(minCheckInDate)
                                        .setCheckOutDate(maxCheckOutDate));

                                needHandleRecords.remove(opGuestInHouse);
                                deleteIdList.addAll(needHandleRecords.stream().map(PastReservationOpGuestinHouse::getId).toList());

                                inHouseRecords.removeAll(needHandleRecords);
                            }
                        });
                        alreadyHandleGuestNames.add(name);
                        alreadyHandleGuestNames.add(getGuestNameReversal(name));
                    });

            if (CollectionUtils.isNotEmpty(updateDateList)) {
                updateBatchById(updateDateList);
            }
            if (CollectionUtils.isNotEmpty(deleteIdList)) {
                removeBatchByIds(deleteIdList);
            }
        }

        /**
         * 根据入住区间，重置所有用户的入住记录的连续入住标识
         *
         * @param propertyId   资产 id
         * @param checkInDate  入住日期
         * @param checkOutDate 退房日期
         * @author yyt
         * @date 2024/10/16
         **/
        @Override
        public void resetGroupKeyForAllRecord(Integer propertyId, Long checkInDate, Long checkOutDate) {
            // 查询未删除的入住记录跟 当前入住区间+间隔天数 有交集的记录
            Integer intervalDay = pastReservationIntervalDayService.getByPropertyIdForUpdate(propertyId).getDay();
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            long startDate = DateTimeUtils.toZonedDateTime(checkInDate, timezone).minusDays(intervalDay).toEpochSecond();
            long endDate = DateTimeUtils.toZonedDateTime(checkOutDate, timezone).plusDays(intervalDay).toEpochSecond();
            List<PastReservationOpGuestinHouse> haveIntersectionInHouseRecords = baseMapper.listByDateRange(propertyId, startDate, endDate, null);

            // 有交集的入住记录处理连续入住标识
            if (CollectionUtils.isNotEmpty(haveIntersectionInHouseRecords)) {
                // 根据用户姓名，查询用户的全部入住记录
                List<GuestNameBO> names = haveIntersectionInHouseRecords.stream()
                        .flatMap(item -> Lists.newArrayList(new GuestNameBO(item.getFirstName(), item.getLastName())
                                , new GuestNameBO(item.getLastName(), item.getFirstName())).stream()).collect(Collectors.toList());
                haveIntersectionInHouseRecords.addAll(listByNames(propertyId, names,
                        haveIntersectionInHouseRecords.stream().map(PastReservationOpGuestinHouse::getId).collect(Collectors.toList())));

                updateGroupKey(haveIntersectionInHouseRecords, intervalDay, timezone);
            }
        }

        /**
         * 修改连续入住标识
         *
         * @param inHouseRecords 入住记录
         * @param intervalDay    间隔天数
         * @param timezone       时区
         * @author yyt
         * @date 2024/11/12
         **/
        private void updateGroupKey(List<PastReservationOpGuestinHouse> inHouseRecords, Integer intervalDay, String timezone) {
            // 姓名颠倒的入住记录
            Map<String, List<PastReservationOpGuestinHouse>> inHouseRecordByNameReversal = inHouseRecords
                    .stream().collect(Collectors.groupingBy(PastReservationOpGuestinHouse::getGuestNameReversal));

            // 分组处理每个人的入住记录，根据两两入住记录间隔天数是否大于设置的间隔天数来分组汇总入住记录
            List<String> alreadyHandleGuestNames = Lists.newArrayListWithExpectedSize(inHouseRecordByNameReversal.size());
            List<UpdateGroupKeyBO> updateGroupKeyBOS = Lists.newArrayListWithExpectedSize(inHouseRecords.size());
            inHouseRecords.stream().collect(Collectors.groupingBy(PastReservationOpGuestinHouse::getGuestName))
                    .forEach((name, groupRecords) -> {
                        if (alreadyHandleGuestNames.contains(name)) return;

                        groupRecords.addAll(inHouseRecordByNameReversal.getOrDefault(name, Collections.emptyList()));
                        groupRecords = new ArrayList<>(groupRecords.stream().collect(Collectors.toMap(PastReservationOpGuestinHouse::getId, v -> v, (v1, v2) -> v2)).values());

                        updateGroupKeyBOS.addAll(groupCheckInRecords(groupRecords, intervalDay, timezone));

                        alreadyHandleGuestNames.add(name);
                        alreadyHandleGuestNames.add(getGuestNameReversal(name));
                    });

            if (CollectionUtils.isNotEmpty(updateGroupKeyBOS)) {
                baseMapper.updateGroupKeyByIds(updateGroupKeyBOS);
            }
        }

        /**
         * 获取颠倒的姓名
         *
         * @param name 用户姓名
         * @return 颠倒的姓名
         * @author yyt
         * @date 2024/10/29
         **/
        private String getGuestNameReversal(String name) {
            String[] names = name.split(",");
            if (names.length > 1) {
                return names[1] + "," + names[0];
            } else {
                return name;
            }
        }

        /**
         * 根据入住记录之间的间隔天数是否大于设置的间隔天数，来分组汇总入住记录
         *
         * @param haveIntersectionInHouseRecords 入住记录
         * @param intervalDay                    间隔天数
         * @param timezone                       时区
         * @return 分组的入住记录
         * @author yyt
         * @date 2024/10/18
         **/
        @Override
        public List<UpdateGroupKeyBO> groupCheckInRecords(List<PastReservationOpGuestinHouse> haveIntersectionInHouseRecords,
                                                           Integer intervalDay, String timezone) {
            // 根据两两入住记录间隔天数是否大于设置的间隔天数来进行分组，然后每组设置新的连续入住标识
            // 入住日期排序
            haveIntersectionInHouseRecords.sort(Comparator.comparing(PastReservationOpGuestinHouse::getCheckInDate));

            int size = haveIntersectionInHouseRecords.size();
            List<UpdateGroupKeyBO> updateGroupKeyBOS = Lists.newArrayListWithExpectedSize(size);
            List<Long> ids = Lists.newArrayListWithExpectedSize(size);
            // 处理第一条数据
            PastReservationOpGuestinHouse firstRecord = haveIntersectionInHouseRecords.get(0);
            ids.add(firstRecord.getId());
            Long end = firstRecord.getCheckOutDate();

            for (int i = 1; i < size; i++) {
                PastReservationOpGuestinHouse current = haveIntersectionInHouseRecords.get(i);
                Long checkInDate = current.getCheckInDate();
                Long checkOutDate = current.getCheckOutDate();

                // 当前入住记录在结束范围内
                if (checkInDate < end) {
                    // 更新结束日期
                    if (checkOutDate > end) {
                        end = checkOutDate;
                    }
                } else {
                    // 计算间隔天数，这里算出来的天数用大于判断
                    // （例如：当前设置间隔天数是1，现有上次退房日期9-1、这次入住日期9-2，这种情况下间隔天数就是1天，不应该算连续入住；只有退房日期跟入住日期是同一天，才不算有间隔天数）
                    long diffOfDays = DateTimeUtils.dateDiffOfDays(end, checkInDate, timezone);
                    if (diffOfDays > intervalDay) {
                        // 添加分组记录
                        updateGroupKeyBOS.add(packageUpdateGroupKeyBO(haveIntersectionInHouseRecords, ids));
                        ids = Lists.newArrayListWithExpectedSize(size);
                    }

                    // 更新结束日期
                    end = checkOutDate;
                }

                ids.add(current.getId());
            }

            // 处理最后一组
            if (CollectionUtils.isNotEmpty(ids)) {
                updateGroupKeyBOS.add(packageUpdateGroupKeyBO(haveIntersectionInHouseRecords, ids));
            }

            return updateGroupKeyBOS;
        }

        /**
         * 封装 updateGroupKeyBO
         *
         * @param haveIntersectionInHouseRecords 所有的入住记录
         * @param ids                            统一重置连续入住标识的入住记录 id
         * @return updateGroupKeyBO
         * @author yyt
         * @date 2024/11/15
         **/
        private UpdateGroupKeyBO packageUpdateGroupKeyBO(List<PastReservationOpGuestinHouse> haveIntersectionInHouseRecords,
                                                          List<Long> ids) {
            Integer status = null;
            Integer statusSetBy = null;
            Long statusSetTime = null;

            // 过滤是否有忽略状态的入住记录，若有，则统一置忽略状态
            Optional<PastReservationOpGuestinHouse> optional = haveIntersectionInHouseRecords.stream()
                    .filter(item -> ids.contains(item.getId()) && item.getStatus() == PastReservationStatusEnum.IGNORE.getCode()).findFirst();
            if (optional.isPresent()) {
                status = PastReservationStatusEnum.IGNORE.getCode();
                PastReservationOpGuestinHouse opGuestinHouse = optional.get();
                statusSetBy = opGuestinHouse.getStatusSetBy();
                statusSetTime = opGuestinHouse.getStatusSetTime();
            }

            return new UpdateGroupKeyBO(ids, CommonUtils.randomUuidWithoutDashes(), status, statusSetBy, statusSetTime);
        }

        /**
         * 检查当前连续入住记录列表中是否显示指定入住记录
         *
         * @param pastReservation 连续入住人员
         * @param roomNumber      房间号
         * @param checkInDate     入住日期
         * @param checkOutDate    退房日期
         * @return true/false
         * @author yyt
         * @date 2024/11/26
         **/
        @Override
        public boolean checkCheckInRecordShow(PastReservation pastReservation, String roomNumber, Long checkInDate, Long checkOutDate) {
            // 房间号、入住区间跟当前连续入住记录列表中的数据的入住区间有交集
            List<PastReservationOpGuestinHouse> opGuestInHouses = listByIds(Arrays.stream(pastReservation.getMatchedIds().split(",")).map(Long::parseLong).toList());
            return opGuestInHouses.stream().anyMatch(item ->
                    item.getRoomNumber().equals(roomNumber) && (item.getCheckInDate() <= checkOutDate && item.getCheckOutDate() >= checkInDate)
            );
        }

    }