# LatestBARRateServiceImpl.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.LatestBARRateMapper;
    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.ILatestBARRateService;
    import cloud.wconnect.rateshop.service.rateshop.IRoomInfoService;
    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>
     * BAR 房价表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2024-05-14
     */
    @DS("rate_shop")
    @Service
    public class LatestBARRateServiceImpl extends ServiceImpl<LatestBARRateMapper, LatestBARRate> implements ILatestBARRateService, ILatestRateByRateTypeService {

        @Autowired
        private BeanMapper beanMapper;
        @Resource
        private PlatformTransactionManager transactionManager;
        @Resource
        private TransactionDefinition transactionDefinition;
        @Resource
        private LatestBARRateMapper latestBARRateMapper;
        @Resource
        private IRoomInfoService roomInfoService;
        @Resource
        private CommonService commonService;

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

        /**
         * 是否支持
         *
         * @param rateType
         * @return java.lang.Boolean
         * @author lichen
         * @date 2024/5/14
         */
        @Override
        public Boolean supports(int rateType) {
            return rateType == BEST_AVAILABLE_RATE_ENUM.getRateType();
        }

        /**
         * 保存BAR房价
         *
         * @param latestRateDTOS 房价集合
         * @return void
         * @author lichen
         * @date 2024/5/14
         */
        @Override
        public void saveBatchDynamicTableName(List<LatestRateDTO> latestRateDTOS) {
            List<LatestBARRate> latestBARRates = beanMapper.mapList(latestRateDTOS, LatestBARRate.class);

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

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

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

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

        /**
         * 查询条件下 多个酒店、指定币种的BAR房价
         *
         * @param hotelIds              酒店ID集合
         * @param currencyId            币种
         * @param latestHotelPriceQuery 查询条件
         * @param rateConfigDTOS        房价参数配置DTO
         * @param pos                   销售地区 us：美国 tw：台湾
         * @return java.util.List<cloud.wconnect.rateshop.entity.rateshop.LatestBARRate>
         * @author lichen
         * @date 2024/5/14
         */
        @Override
        public List<LatestBARRate> listBARHotelPrice(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 -> BEST_AVAILABLE_RATE_ENUM.getTableNamePrefix() + item).toList();
            //查询存在的表名
            List<String> existTableNames = commonService.listTableName(tableNames);

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

        /**
         * 查询条件下 多个酒店、指定币种、指定是否含税、最低的BAR房价
         * 最低价逻辑：采集日期最新 且 价格最低
         *
         * @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/16
         */
        @Override
        public List<LatestHotelPriceDTO> listLowestBARHotelPrice(List<Integer> hotelIds, Integer currencyId, Integer containTaxFlag,
                                                                 LatestHotelPriceQuery latestHotelPriceQuery, List<RateConfigDTO> rateConfigDTOS,
                                                                 String pos) {
            List<LatestBARRate> allLatestBARRates = listBARHotelPrice(hotelIds, currencyId, latestHotelPriceQuery, rateConfigDTOS, pos);
            if (CollectionUtils.isEmpty(allLatestBARRates)) return Collections.emptyList();

            List<LatestBARRate> minPriceList = Lists.newArrayListWithCapacity(allLatestBARRates.size());
            //按照酒店Id分组
            Map<Integer, List<LatestBARRate>> hotelId2LatestBARRateList = allLatestBARRates.stream().collect(Collectors.groupingBy(LatestBARRate::getHotelId));
            //循环
            hotelId2LatestBARRateList.forEach((hotelId, latestBARRateListByHotelId) -> {
                //按照入住日期分组
                Map<Long, List<LatestBARRate>> checkInDate2LatestBARRate = latestBARRateListByHotelId.stream().collect(Collectors.groupingBy(LatestBARRate::getCheckInDate));

                for (Map.Entry<Long, List<LatestBARRate>> entry : checkInDate2LatestBARRate.entrySet()) {
                    List<LatestBARRate> latestBARRateListByCheckInDate = entry.getValue();
                    if (latestBARRateListByCheckInDate.size() == 1) {
                        minPriceList.add(latestBARRateListByCheckInDate.get(0));
                    } else {
                        LatestBARRate barPriceOrderByQueryDateAndRate = getBARPriceOrderByQueryDateAndRate(latestBARRateListByCheckInDate, containTaxFlag);
                        if (Objects.nonNull(barPriceOrderByQueryDateAndRate)) {
                            minPriceList.add(barPriceOrderByQueryDateAndRate);
                        }
                    }
                }
            });

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

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

            //List<LatestRate> -> List<HotelPriceVO>
            return minPriceList.stream().map(latestBARRate -> {
                //把yyyyMMdd的checkInDate转成LocalDate
                LocalDate checkInDateLocalDate = DateTimeUtils.strToLocalDate(latestBARRate.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) ? latestBARRate.getRate() : latestBARRate.getAfterTaxRate();
                //构建HotelPriceVO对象
                return LatestHotelPriceDTO.builder()
                        .hotelId(latestBARRate.getHotelId())
                        .date(date)
                        .price(price)
                        .updateTime(latestBARRate.getQueryDate())
                        .room(roomInfoId2Room.getOrDefault(latestBARRate.getRoomId(), ""))
                        .platformId(latestBARRate.getPlatformId())
                        .rateType(BEST_AVAILABLE_RATE_ENUM.getRateType())
                        .datasourceId(latestBARRate.getDatasourceId())
                        .build();
            }).toList();
        }

        /**
         * 根据queryDate和rate排序获取房价
         *
         * @param latestBARRateListByCheckInDate 指定日期的房价集合
         * @param containTaxFlag                 含税标识
         * @return cloud.wconnect.rateshop.entity.rateshop.LatestBARRate
         * @author lichen
         * @date 2025/7/28
         */
        private LatestBARRate getBARPriceOrderByQueryDateAndRate(List<LatestBARRate> latestBARRateListByCheckInDate, Integer containTaxFlag) {
            //指定日期非空的价格集合
            List<LatestBARRate> rateListByCheckInDateByNotNull = latestBARRateListByCheckInDate.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<LatestBARRate>> queryHour2LatestBARRate = 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<>(queryHour2LatestBARRate.keySet());
                queryHourList.sort(Comparator.reverseOrder());
                //获取最大时刻的房价集合
                List<LatestBARRate> latestBARRatesByMaxQueryHour = queryHour2LatestBARRate.get(queryHourList.get(0));

                //所有的房价都是0才表示当天是Sold out
                boolean isSoldOut = latestBARRatesByMaxQueryHour.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;
                    }
                });
                LatestBARRate result;
                if (isSoldOut) {
                    //取任意一条数据即可
                    result = latestBARRatesByMaxQueryHour.get(0);
                } else {
                    //房价最低的数据
                    Optional<LatestBARRate> optional = latestBARRatesByMaxQueryHour.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;
        }
    }