# LatestRateServiceImpl.java

    package cloud.wconnect.rateshop.service.rateshop.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.service.CommonService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.rateshop.cons.rateshop.RateTypeEnum;
    import cloud.wconnect.rateshop.entity.rateshop.*;
    import cloud.wconnect.rateshop.mapper.rateshop.LatestRateMapper;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.LatestHotelPriceDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.RateConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.consumer.hotelprice.LatestRateDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.filterrate.RateQuery;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.filterrate.RateValue;
    import cloud.wconnect.rateshop.pojo.query.rateshop.LatestHotelPriceQuery;
    import cloud.wconnect.rateshop.service.ILatestRateByRateTypeService;
    import cloud.wconnect.rateshop.service.rateshop.*;
    import cloud.wconnect.rateshop.util.rateshop.FilterRateUtil;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;

    import jakarta.annotation.Resource;

    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 房价 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-06-06
     */
    @DS("rate_shop")
    @Service
    public class LatestRateServiceImpl extends ServiceImpl<LatestRateMapper, LatestRate> implements ILatestRateService, ILatestRateByRateTypeService {

        @Resource
        private LatestRateMapper latestRateMapper;
        @Resource
        private IRoomInfoService roomInfoService;
        @Resource
        private CommonService commonService;

        @Resource
        private PlatformTransactionManager transactionManager;
        @Resource
        private TransactionDefinition transactionDefinition;
        @Autowired
        private BeanMapper beanMapper;

        //当前类对应的房价类型枚举类
        public static final RateTypeEnum EARLY_BIRD_RATE_ENUM = RateTypeEnum.EARLY_BIRD_RATE;

        /**
         * 是否支持
         *
         * @param rateType 房价类型 1:早鸟价 2:BAR房价
         * @return java.lang.Boolean
         * @author lichen
         * @date 2024/5/14
         */
        @Override
        public Boolean supports(int rateType) {
            return rateType == EARLY_BIRD_RATE_ENUM.getRateType();
        }

        /**
         * 批量新增房价表
         *
         * @param latestRateDTOS 房价集合
         * @author lichen
         * @date 2023/6/6
         */
        @Override
        public void saveBatchDynamicTableName(List<LatestRateDTO> latestRateDTOS) {
            List<LatestRate> latestRates = beanMapper.mapList(latestRateDTOS, LatestRate.class);

            Map<String, List<LatestRate>> year2latestRates = latestRates.stream()
                    .collect(Collectors.groupingBy(latestRate -> {
                        //入住日期（yyyyMMdd）
                        Long checkInDate = latestRate.getCheckInDate();
                        //获取年份
                        return checkInDate.toString().substring(0, 4);
                    }));

            for (Map.Entry<String, List<LatestRate>> entry : year2latestRates.entrySet()) {
                TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
                try {
                    String tableName = EARLY_BIRD_RATE_ENUM.getTableNamePrefix() + entry.getKey();
                    List<LatestRate> batchList = entry.getValue();
                    //根据唯一索引删除数据
                    latestRateMapper.deleteByUnique(tableName, batchList);

                    //根据唯一索引分组
                    ArrayList<LatestRate> latestRateList = Lists.newArrayListWithCapacity(batchList.size());
                    Map<String, List<LatestRate>> uniqueKey2LatestRate = batchList.stream().collect(Collectors.groupingBy(LatestRate::generateKey));
                    for (Map.Entry<String, List<LatestRate>> latestRateEntry : uniqueKey2LatestRate.entrySet()) {
                        List<LatestRate> latestRateListByUniqueKey = latestRateEntry.getValue();
                        if (latestRateListByUniqueKey.size() == 1) {
                            latestRateList.add(latestRateListByUniqueKey.get(0));
                        } else {
                            //税前最低价格
                            BigDecimal beforeTaxMinRate = latestRateListByUniqueKey.stream().map(LatestRate::getRate).filter(Objects::nonNull)
                                    .min(BigDecimal::compareTo).orElse(null);
                            //税后最低价格
                            BigDecimal afterTaxMinRate = latestRateListByUniqueKey.stream().map(LatestRate::getAfterTaxRate).filter(Objects::nonNull)
                                    .min(BigDecimal::compareTo).orElse(null);
                            //只保留最低价格
                            latestRateList.add(latestRateListByUniqueKey.get(0)
                                    .setRate(beforeTaxMinRate).setAfterTaxRate(afterTaxMinRate));
                        }
                    }
                    latestRateList.sort(Comparator.comparing(LatestRate::getCheckInDate)
                            .thenComparing(LatestRate::getPlatformId)
                            .thenComparing(LatestRate::getRoomId));
                    //批量新增
                    latestRateMapper.saveBatch(tableName, latestRateList);
                    transactionManager.commit(transaction);

                } catch (Exception e) {
                    transactionManager.rollback(transaction);
                    throw e;
                }
            }
        }

        /**
         * 查询条件下 指定酒店、指定币种的早鸟价
         *
         * @param hotelIds              酒店ID集合
         * @param currencyId            币种
         * @param latestHotelPriceQuery 查询条件
         * @param rateConfigDTOS        房价参数配置DTO
         * @param pos                   销售地区 us：美国 tw：台湾
         * @return java.util.List<cloud.wconnect.rateshop.entity.rateshop.LatestRate>
         * @author lichen
         * @date 2024/5/14
         */
        @Override
        public List<LatestRate> listEBRHotelPrice(List<Integer> hotelIds, Integer currencyId, LatestHotelPriceQuery latestHotelPriceQuery,
                                                  List<RateConfigDTO> rateConfigDTOS, String pos) {
            //开始时间
            String fromDate = latestHotelPriceQuery.getFromDate().toString();
            //结束时间
            String toDate = latestHotelPriceQuery.getToDate().toString();
            //查询房价的对象
            RateQuery rateQuery = new RateQuery(latestHotelPriceQuery.getDatasourceId(),
                    latestHotelPriceQuery.getPlatformId(), latestHotelPriceQuery.getRoomIds());

            //开始时间和结束时间涉及的年份集合（years的长度最大等于2）
            Set<String> years = Sets.newHashSet(fromDate.substring(0, 4), toDate.substring(0, 4));
            List<String> tableNames = years.stream().map(item -> EARLY_BIRD_RATE_ENUM.getTableNamePrefix() + item).toList();
            //查询存在的表名
            List<String> existTableNames = commonService.listTableName(tableNames);

            //查询房价数据
            List<LatestRate> allLatestRates = new ArrayList<>();
            for (String existTableName : existTableNames) {
                List<LatestRate> latestRateList = latestRateMapper.list(existTableName, hotelIds, currencyId, fromDate, toDate, pos);
                //过滤出符合查询条件的房价集合
                List<LatestRate> filterList = latestRateList.stream().filter(rate -> {
                    RateValue rateValue = beanMapper.map(rate, RateValue.class);
                    return FilterRateUtil.isMatchByQueryCondition(rateValue, rateQuery, rateConfigDTOS, EARLY_BIRD_RATE_ENUM.getRateType());
                }).toList();
                allLatestRates.addAll(filterList);
            }

            return allLatestRates;
        }

        /**
         * 查询条件下 多个酒店、指定币种、指定是否含税的最低早鸟价
         * 最低判断标准：采集时间最新 且 价格最低
         *
         * @param hotelIds              酒店ID集合
         * @param currencyId            币种
         * @param containTaxFlag        含税标识1:税前 2:税后
         * @param latestHotelPriceQuery 查询条件
         * @param rateConfigDTOS        房价参数配置DTO
         * @param pos                   销售地区 us：美国 tw：台湾
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.rateshop.LatestHotelPriceDTO>
         * @author lichen
         * @date 2024/5/14
         */
        @Override
        public List<LatestHotelPriceDTO> listLowestEBRHotelPrice(List<Integer> hotelIds, Integer currencyId, Integer containTaxFlag,
                                                                 LatestHotelPriceQuery latestHotelPriceQuery, List<RateConfigDTO> rateConfigDTOS,
                                                                 String pos) {
            List<LatestRate> allLatestRates = listEBRHotelPrice(hotelIds, currencyId, latestHotelPriceQuery, rateConfigDTOS, pos);
            if (CollectionUtils.isEmpty(allLatestRates)) return Collections.emptyList();

            //最低、最新价格集合
            List<LatestRate> minPriceLatestRateList = Lists.newArrayListWithCapacity(allLatestRates.size());

            //根据酒店Id分组
            Map<Integer, List<LatestRate>> hotelId2LatestRateList = allLatestRates.stream().collect(Collectors.groupingBy(LatestRate::getHotelId));
            for (Map.Entry<Integer, List<LatestRate>> hotelId2LatestRateListEntry : hotelId2LatestRateList.entrySet()) {
                List<LatestRate> latestRateListByHotelId = hotelId2LatestRateListEntry.getValue();

                //同一天内，按照最近查询日期的最低价格筛选数据
                Map<Long, List<LatestRate>> checkInDate2LatestRate = latestRateListByHotelId.stream().collect(Collectors.groupingBy(LatestRate::getCheckInDate));
                for (Map.Entry<Long, List<LatestRate>> entry : checkInDate2LatestRate.entrySet()) {
                    List<LatestRate> latestRateListByCheckInDate = entry.getValue();
                    if (latestRateListByCheckInDate.size() == 1) {
                        minPriceLatestRateList.add(latestRateListByCheckInDate.get(0));
                    } else {
                        LatestRate priceOrderByQueryDateAndRate = getPriceOrderByQueryDateAndRate(latestRateListByCheckInDate, containTaxFlag);
                        if (Objects.nonNull(priceOrderByQueryDateAndRate)) {
                            minPriceLatestRateList.add(priceOrderByQueryDateAndRate);
                        }
                    }
                }
            }

            if(CollectionUtils.isEmpty(minPriceLatestRateList)) return Collections.emptyList();

            //获取room.info的id集合
            Set<Integer> roomInfoIdSet = minPriceLatestRateList.stream().map(LatestRate::getRoomId).collect(Collectors.toSet());
            //获取roomInfo
            Map<Integer, String> roomInfoId2Room = roomInfoService.listByIds(roomInfoIdSet).stream().collect(Collectors.toMap(RoomInfo::getId, RoomInfo::getRoom));

            //List<LatestRate> -> List<HotelPriceVO>
            return minPriceLatestRateList.stream().map(latestRate -> {
                //把yyyyMMdd的checkInDate转成LocalDate
                LocalDate checkInDateLocalDate = DateTimeUtils.strToLocalDate(latestRate.getCheckInDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                //把localDate转成yyyy-MM-dd格式的字符串
                String date = DateTimeUtils.localDateToStr(checkInDateLocalDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                //房价
                BigDecimal price = containTaxFlag.equals(Setting.BEFORE_TAX_RATE) ? latestRate.getRate() : latestRate.getAfterTaxRate();
                //构建HotelPriceVO对象
                return LatestHotelPriceDTO.builder()
                        .hotelId(latestRate.getHotelId())
                        .date(date)
                        .price(price)
                        .updateTime(latestRate.getQueryDate())
                        .room(roomInfoId2Room.getOrDefault(latestRate.getRoomId(), ""))
                        .platformId(latestRate.getPlatformId())
                        .rateType(EARLY_BIRD_RATE_ENUM.getRateType())
                        .datasourceId(latestRate.getDatasourceId())
                        .build();
            }).toList();
        }

        /**
         * 根据queryDate和rate排序获取房价
         *
         * @param latestRateListByCheckInDate 指定日期的房价集合
         * @param containTaxFlag              含税标识
         * @return cloud.wconnect.rateshop.entity.rateshop.LatestRate
         * @author lichen
         * @date 2025/7/28
         */
        private LatestRate getPriceOrderByQueryDateAndRate(List<LatestRate> latestRateListByCheckInDate, Integer containTaxFlag) {
            //指定日期非空的价格集合
            List<LatestRate> rateListByCheckInDateByNotNull = latestRateListByCheckInDate.stream().filter(item -> {
                if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {//税前价格
                    return Objects.nonNull(item.getRate());
                } else {//税后价格
                    return Objects.nonNull(item.getAfterTaxRate());
                }
            }).toList();
            if (CollectionUtils.isNotEmpty(rateListByCheckInDateByNotNull)) {
                //按照查询时刻（yyyyMMddHH）分组
                Map<Integer, List<LatestRate>> queryHour2LatestRate = rateListByCheckInDateByNotNull.stream().collect(Collectors.groupingBy(item -> {
                    Long queryDate = item.getQueryDate();
                    return Integer.valueOf(DateTimeUtils.timestampToStr(queryDate, BaseConstant.DATE_FORMAT_YYYYMMDDHH, ""));
                }));
                //查询时刻集合
                ArrayList<Integer> queryHourList = new ArrayList<>(queryHour2LatestRate.keySet());
                queryHourList.sort(Comparator.reverseOrder());
                //获取最大时刻的房价集合
                List<LatestRate> latestRatesByMaxQueryHour = queryHour2LatestRate.get(queryHourList.get(0));

                //所有的房价都是0才表示当天是Sold out
                boolean isSoldOut = latestRatesByMaxQueryHour.stream().allMatch(item -> {
                    if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                        return item.getRate().compareTo(BigDecimal.ZERO) == 0;
                    } else {
                        return item.getAfterTaxRate().compareTo(BigDecimal.ZERO) == 0;
                    }
                });
                LatestRate result;
                if (isSoldOut) {
                    //取任意一条数据即可
                    result = latestRatesByMaxQueryHour.get(0);
                } else {
                    //取房价最低的数据
                    Optional<LatestRate> optional = latestRatesByMaxQueryHour.stream()
                            .filter(item -> {
                                if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                                    return item.getRate().compareTo(BigDecimal.ZERO) > 0;
                                } else {
                                    return item.getAfterTaxRate().compareTo(BigDecimal.ZERO) > 0;
                                }
                            })
                            .min(Comparator.comparing(item -> {
                                if (containTaxFlag.equals(Setting.BEFORE_TAX_RATE)) {
                                    return item.getRate();
                                } else {
                                    return item.getAfterTaxRate();
                                }
                            }));
                    result = optional.orElse(null);
                }
                return result;
            }
            return null;
        }
    }