# RateShopServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.rateshop.cons.PrecisionCons;
    import cloud.wconnect.rateshop.cons.rateshop.HotelTypeEnum;
    import cloud.wconnect.rateshop.cons.rateshop.RateTypeEnum;
    import cloud.wconnect.rateshop.cons.revenue.RevenueCons;
    import cloud.wconnect.rateshop.entity.rateshop.*;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.LatestHotelPriceDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.RateConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.excel.HotelRateExceDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.excel.RateExcelDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.excel.RateShopExcelDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.hotel.PropertyHotelDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.iwc.IwcDTO;
    import cloud.wconnect.rateshop.pojo.query.rateshop.ExportQuery;
    import cloud.wconnect.rateshop.pojo.query.rateshop.IwcQuery;
    import cloud.wconnect.rateshop.pojo.query.rateshop.LatestHotelPriceQuery;
    import cloud.wconnect.rateshop.service.rateshop.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

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

    /**
     * rate shop的业务层
     *
     * @author lichen
     * @date 2024/11/25
     */
    @Service
    @DS("rate_shop")
    public class RateShopServiceImpl implements IRateShopService {

        @Autowired
        private IRoomTypeInfoRelService roomTypeInfoRelService;
        @Autowired
        private IPropertyOperationService projectOperationService;
        @Autowired
        private ITrendService trendService;
        @Autowired
        private ILatestRateService latestRateService;
        @Autowired
        private ILatestBARRateService latestBARRateService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private IPropertyHotelService propertyHotelService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private IIwcService iwcService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ITrendSourceSettingService trendSourceSettingService;

        /**
         * 设置导出文件的显示内容
         *
         * @param propertyDTO 资产DTO
         * @param setting     资产的rate shop配置
         * @param exportQuery 导出的查询条件
         * @param data        数据
         * @param locale      当前用户选择的语言
         * @return void
         * @author lichen
         * @date 2024/11/25
         */
        @Override
        public void packageDataToFile(PropertyDTO propertyDTO, Setting setting, ExportQuery exportQuery, HashMap<String, Object> data, Locale locale) {
            Integer propertyId = propertyDTO.getId();
            //把yyyy-MM-dd的日期转化成时间戳
            long fromTimestamp = DateTimeUtils.dateStrToEpochSecond(exportQuery.getFromDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD, propertyDTO.getTimezone());
            long toTimestamp = DateTimeUtils.dateStrToEpochSecond(exportQuery.getToDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD, propertyDTO.getTimezone());

            //房型下的房间Id集合
            List<Integer> roomInfoIds = new ArrayList<>();
            if (exportQuery.getType() != BaseConstant.ZERO) {
                //0标识any room，所以当type!=0时需要查询到type关联的房间Id集合
                roomInfoIds = roomTypeInfoRelService.listByTypeId(propertyId, exportQuery.getType())
                        .stream().map(RoomTypeInfoRel::getRoomInfoId).toList();
            }

            /*
             * 1. 查询房价
             */
            //查询资产配置的酒店集合
            List<PropertyHotelDTO> propertyHotelDTOS = propertyHotelService.listSortDTOByPropertyId(propertyId);
            List<Integer> hotelIds = propertyHotelDTOS.stream().map(PropertyHotelDTO::getHotelId).toList();
            //房价集合
            List<LatestHotelPriceDTO> latestHotelPriceDTOList;
            if (exportQuery.getType() != BaseConstant.ZERO && CollectionUtils.isEmpty(roomInfoIds)) {
                //房型下未配置对应的房间Id，所以无法查询到房价
                latestHotelPriceDTOList = Collections.emptyList();
            } else {
                //构建查询条件
                LatestHotelPriceQuery latestHotelPriceQuery = new LatestHotelPriceQuery();
                latestHotelPriceQuery.setFromDate(Long.parseLong(exportQuery.getFromDate().replace("-", "")));
                latestHotelPriceQuery.setToDate(Long.parseLong(exportQuery.getToDate().replace("-", "")));
                latestHotelPriceQuery.setPlatformId(exportQuery.getPlatform());
                latestHotelPriceQuery.setDatasourceId(exportQuery.getDatasourceId());
                latestHotelPriceQuery.setRoomIds(roomInfoIds);

                //房价参数配置
                List<RateConfigDTO> rateConfigDTOS = JsonUtils.convertStringToList(objectMapper, setting.getRateConfig(), RateConfigDTO.class);

                if (exportQuery.getRateType() == RateTypeEnum.EARLY_BIRD_RATE.getRateType()) {
                    //查询早鸟价
                    latestHotelPriceDTOList = latestRateService.listLowestEBRHotelPrice(hotelIds, setting.getCurrencyId(),
                            setting.getContainTaxFlag(), latestHotelPriceQuery, rateConfigDTOS, setting.getPos());
                } else {
                    //查询BAR房价
                    latestHotelPriceDTOList = latestBARRateService.listLowestBARHotelPrice(hotelIds, setting.getCurrencyId(),
                            setting.getContainTaxFlag(), latestHotelPriceQuery, rateConfigDTOS, setting.getPos());
                }
            }
            //组装房价数据
            List<HotelRateExceDTO> hotelRateExceDTOList = packageHotelRateExcelDTO(propertyDTO, latestHotelPriceDTOList, propertyHotelDTOS, locale,
                    exportQuery.getFromDate(), exportQuery.getToDate());
            data.put("hotelRateList", hotelRateExceDTOList);

            /*
             * 2.查询PCOcc,SCOcc,ADR
             */
            List<PropertyOperation> propertyOperationList = projectOperationService.list(new LambdaQueryWrapper<PropertyOperation>()
                    .eq(PropertyOperation::getPropertyId, propertyId)
                    .between(PropertyOperation::getTargetDate, fromTimestamp, toTimestamp));
            /*
             * 3.查询市场需求度
             */
            List<Trend> trendList = trendService.listByDataRange(propertyId, exportQuery.getFromDate().replace("-", ""),
                    exportQuery.getToDate().replace("-", ""));

            /*
             * 4.查询iwc数据
             */
            List<IwcDTO> iwcDTOS = new ArrayList<>();
            List<TrendSourceSetting> trendSourceSettings = trendSourceSettingService.listByPropertyId(propertyId);
            if (CollectionUtils.isNotEmpty(trendSourceSettings)) {
                //资产的自身酒店
                Integer selfHotelId = propertyHotelDTOS.stream().filter(item -> item.getType() == HotelTypeEnum.OWN.getType())
                        .findFirst().get().getHotelId();
                //资产配置的需求趋势地址（目前资产只支持配置1个地址）
                Integer locationId = trendSourceSettings.get(0).getLocationId();
                iwcDTOS = iwcService.list(propertyDTO, selfHotelId,
                        locationId, setting, beanMapper.map(exportQuery, IwcQuery.class));
            }

            //组装市场需求趋势、资产运营数据
            List<RateShopExcelDTO> rateShopExcelDTOS = packageRateShopExcelDTO(propertyDTO, propertyOperationList, trendList, iwcDTOS,
                    exportQuery.getFromDate(), exportQuery.getToDate());
            data.put("rateShopList", rateShopExcelDTOS);
        }

        /**
         * 组装房价数据
         *
         * @param propertyDTO                资产DTO
         * @param allLatestHotelPriceDTOList 所有酒店房价数据集合
         * @param propertyHotelDTOS          资产配置的酒店集合
         * @param locale                     当前用户选择的语言
         * @param fromDate                   查询的开始日期
         * @param toDate                     查询的结束日期
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.rateshop.excel.HotelRateExceDTO>
         * @author lichen
         * @date 2024/11/25
         */
        private List<HotelRateExceDTO> packageHotelRateExcelDTO(PropertyDTO propertyDTO, List<LatestHotelPriceDTO> allLatestHotelPriceDTOList,
                                                                List<PropertyHotelDTO> propertyHotelDTOS, Locale locale, String fromDate, String toDate) {
            //国际化的参数
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            String calculationRule = number.getCalculationRule();
            String currency = number.getCurrency();

            //没有数据时的显示
            String noData = i18nMessageUtils.message(RevenueCons.I18N_NO_DATA, locale);
            //售罄时的显示
            String soldOut = i18nMessageUtils.message(RevenueCons.I18N_SOLD_OUT, locale);

            //按照酒店id分组
            Map<Integer, List<LatestHotelPriceDTO>> hotelId2LatestHotelPriceDTOList = allLatestHotelPriceDTOList.stream()
                    .collect(Collectors.groupingBy(LatestHotelPriceDTO::getHotelId));
            //资产自身酒店id
            Optional<PropertyHotelDTO> optional = propertyHotelDTOS.stream().filter(item -> item.getType() == HotelTypeEnum.OWN.getType()).findFirst();
            if (optional.isEmpty()) return Collections.emptyList();
            Integer selfHotelId = optional.get().getHotelId();
            //资产自身酒店指定日期的房价集合
            Map<String, BigDecimal> date2PriceBySelfHotel = hotelId2LatestHotelPriceDTOList.getOrDefault(selfHotelId, Collections.emptyList()).stream()
                    .filter(item -> Objects.nonNull(item.getPrice()))
                    .collect(Collectors.toMap(LatestHotelPriceDTO::getDate, LatestHotelPriceDTO::getPrice));

            //返回的房价集合
            ArrayList<HotelRateExceDTO> hotelRateExceDTOList = Lists.newArrayListWithCapacity(propertyHotelDTOS.size());

            for (PropertyHotelDTO propertyHotelDTO : propertyHotelDTOS) {
                //当酒店是自身时，name=name(Your hotel)
                String name = propertyHotelDTO.getName();
                if (propertyHotelDTO.getType() == HotelTypeEnum.OWN.getType()) {
                    String typeDesc = i18nMessageUtils.message(HotelTypeEnum.OWN.getI18nCode(), locale);
                    name = name + "(" + typeDesc + ")";
                }

                //指定酒店的房价集合
                Map<String, BigDecimal> date2PriceByHotel = hotelId2LatestHotelPriceDTOList.getOrDefault(propertyHotelDTO.getHotelId(), Collections.emptyList())
                        .stream()
                        .filter(item -> Objects.nonNull(item.getPrice()))
                        .collect(Collectors.toMap(LatestHotelPriceDTO::getDate, LatestHotelPriceDTO::getPrice));

                ArrayList<RateExcelDTO> rateExcelDTOList = Lists.newArrayListWithExpectedSize(90);

                //开始日期
                LocalDate fromLocalDate = DateTimeUtils.strToLocalDate(fromDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                //结束日期
                LocalDate toLocalDate = DateTimeUtils.strToLocalDate(toDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                //循环
                for (LocalDate localDate = fromLocalDate; !localDate.isAfter(toLocalDate); localDate = localDate.plusDays(1)) {
                    String dateStr = DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                    String rateStr;
                    String diffRateStr = "";

                    //当前酒店指定日期的房价
                    BigDecimal price = date2PriceByHotel.get(dateStr);
                    if (Objects.nonNull(price)) {
                        //BigDecimal.equals方法0和0.00是不相等的，所以此处与0.00进行比较
                        if (price.equals(new BigDecimal("0.00"))) {
                            //房价等于0.00时显示售罄
                            rateStr = soldOut;
                        } else {
                            //用国际化参数，格式化房价
                            rateStr = InternationalizationUtils.formatAmountViewValue(price, calculationRule, currency);

                            //当前酒店非自身时，才需要比对差价
                            if (propertyHotelDTO.getType() != HotelTypeEnum.OWN.getType()) {
                                //资产自身酒店指定日期的房价
                                BigDecimal selfHotelPrice = date2PriceBySelfHotel.get(dateStr);
                                if (Objects.nonNull(selfHotelPrice)) {
                                    //计算差值 = 当前酒店的房价 - 自身酒店
                                    BigDecimal diffRate = price.subtract(selfHotelPrice);
                                    diffRateStr = InternationalizationUtils.formatAmountViewValue(diffRate, calculationRule, currency);
                                    //用括号包住差值
                                    diffRateStr = "(" + diffRateStr + ")";
                                }
                            }
                        }
                    } else {
                        //房价为空时，显示No Data
                        rateStr = noData;
                    }

                    rateExcelDTOList.add(new RateExcelDTO(rateStr, diffRateStr));
                }
                hotelRateExceDTOList.add(new HotelRateExceDTO(name, rateExcelDTOList));
            }
            return hotelRateExceDTOList;
        }

        /**
         * 组装数据
         *
         * @param propertyDTO           资产DTO
         * @param propertyOperationList 资产运营数据
         * @param trendList             需求趋势数据
         * @param iwcDTOS               iwc集合
         * @param fromDate              开始日期
         * @param toDate                结束日期
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.rateshop.excel.RateShopExcelDTO>
         * @author lichen
         * @date 2024/11/25
         */
        private List<RateShopExcelDTO> packageRateShopExcelDTO(PropertyDTO propertyDTO, List<PropertyOperation> propertyOperationList,
                                                               List<Trend> trendList, List<IwcDTO> iwcDTOS, String fromDate, String toDate) {
            InternationalizationDTO internationalization = propertyDTO.getInternationalization();
            String longDate = internationalization.getDateTime().getLongDate();
            InternationalizationNumberDTO number = internationalization.getNumber();

            //资产运营数据
            Map<Long, PropertyOperation> targetDate2PropertyOperation = propertyOperationList.stream()
                    .collect(Collectors.toMap(PropertyOperation::getTargetDate, Function.identity()));
            //需求趋势数据
            Map<Long, Trend> checkInDate2Trend = trendList.stream().collect(Collectors.toMap(Trend::getCheckInDate,
                    Function.identity()));
            //iwc数据
            Map<String, BigDecimal> date2Iwc = iwcDTOS.stream().collect(Collectors.toMap(IwcDTO::getDate, IwcDTO::getIwc));

            ArrayList<RateShopExcelDTO> rateShopExcelDTOList = Lists.newArrayListWithExpectedSize(90);

            //开始日期
            LocalDate fromLocalDate = DateTimeUtils.strToLocalDate(fromDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            //结束日期
            LocalDate toLocalDate = DateTimeUtils.strToLocalDate(toDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            //循环
            for (LocalDate localDate = fromLocalDate; !localDate.isAfter(toLocalDate); localDate = localDate.plusDays(1)) {
                //需求趋势
                String marketDemandStr = "--";
                Trend trend = checkInDate2Trend.get(Long.valueOf(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD)));
                if (Objects.nonNull(trend)) {
                    BigDecimal multiply = trend.getRate().multiply(new BigDecimal(100));
                    marketDemandStr = InternationalizationUtils.formatViewValue(multiply, PrecisionCons.RATE_SHOP_PRECISION,
                            number.getCalculationRule(), "") + "%";
                }

                //iwc数据
                String iwcStr = "--";
                BigDecimal iwc = date2Iwc.get(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD));
                if (Objects.nonNull(iwc)) {
                    iwcStr = InternationalizationUtils.formatViewValue(iwc, PrecisionCons.RATE_SHOP_PRECISION,
                            number.getCalculationRule(), "");
                }

                //资产运营数据
                String pcOccStr = "--";
                String scOccStr = "--";
                String adrStr = "--";
                PropertyOperation propertyOperation = targetDate2PropertyOperation.get(DateTimeUtils.localDateToLong(localDate, propertyDTO.getTimezone()));
                if (Objects.nonNull(propertyOperation)) {
                    if (Objects.nonNull(propertyOperation.getPcOcc())) {
                        pcOccStr = InternationalizationUtils.formatViewValue(propertyOperation.getPcOcc(), PrecisionCons.RATE_SHOP_PRECISION,
                                number.getCalculationRule(), "") + "%";
                    }
                    if (Objects.nonNull(propertyOperation.getScOcc())) {
                        scOccStr = InternationalizationUtils.formatViewValue(propertyOperation.getScOcc(), PrecisionCons.RATE_SHOP_PRECISION,
                                number.getCalculationRule(), "") + "%";
                    }
                    if (Objects.nonNull(propertyOperation.getAdr())) {
                        adrStr = InternationalizationUtils.formatAmountViewValue(propertyOperation.getAdr(), number.getCalculationRule(), number.getCurrency());
                    }
                }

                RateShopExcelDTO rateShopExcelDTO = new RateShopExcelDTO();
                rateShopExcelDTO.setDateStr(DateTimeUtils.localDateToStr(localDate, longDate));
                rateShopExcelDTO.setMarketDemandStr(marketDemandStr);
                rateShopExcelDTO.setIwcStr(iwcStr);
                rateShopExcelDTO.setPcOccStr(pcOccStr);
                rateShopExcelDTO.setScOccStr(scOccStr);
                rateShopExcelDTO.setAdrStr(adrStr);
                rateShopExcelDTOList.add(rateShopExcelDTO);
            }
            return rateShopExcelDTOList;
        }
    }