# BookingAnalysisServiceImpl.java

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

    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.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.WeekUtils;
    import cloud.wconnect.rateshop.cons.booking.AdvanceAbscissaEnum;
    import cloud.wconnect.rateshop.cons.booking.StayLengthAbscissaEnum;
    import cloud.wconnect.rateshop.entity.booking.*;
    import cloud.wconnect.rateshop.mapper.booking.BookingMapper;
    import cloud.wconnect.rateshop.pojo.dto.booking.BookingWidgetDTO;
    import cloud.wconnect.rateshop.pojo.dto.booking.BookingWidgetDetailDTO;
    import cloud.wconnect.rateshop.pojo.query.booking.BookingAnalysisQuery;
    import cloud.wconnect.rateshop.service.booking.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.DayOfWeek;
    import java.time.Month;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2024-07-22
     */
    @Service
    @DS("rate_shop")
    public class BookingAnalysisServiceImpl extends ServiceImpl<BookingMapper, Booking> implements IBookingAnalysisService {

        @Resource
        private IBookingDayService bookingDayService;
        @Resource
        private IBookingMonthService bookingMonthService;
        @Resource
        private IBookingAdvanceService bookingAdvanceService;
        @Resource
        private IBookingWeekService bookingWeekService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 查询 Stay Length 数据
         *
         * @param query       查询条件
         * @return stay length 数据
         * @author yyt
         * @date 2024/7/23
         **/
        @Override
        public List<BookingWidgetDTO> listStayLength(BookingAnalysisQuery query) {
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(query.getPropertyIds());

            List<BookingDay> bookingDays = bookingDayService.list(query.getPropertyIds(), query.getStartMonth(), query.getEndMonth());
            Map<Integer, Map<Integer, List<BookingDay>>> bookingDaysByPropertyId = bookingDays.stream()
                    .collect(Collectors.groupingBy(BookingDay::getPropertyId, Collectors.groupingBy(BookingDay::getAbscissa)));

            StayLengthAbscissaEnum[] stayLengthAbscissaEnums = StayLengthAbscissaEnum.values();
            List<BookingWidgetDTO> bookingWidgetDTOS = Lists.newArrayListWithExpectedSize(propertyDTOS.size());

            int dataPrecision = BaseConstant.DATA_PRECISION;

            propertyDTOS.forEach(propertyDTO -> {
                BookingWidgetDTO bookingWidgetDTO = new BookingWidgetDTO();
                Integer propertyId = propertyDTO.getId();
                bookingWidgetDTO.setPropertyId(propertyId);
                bookingWidgetDTO.setPropertyName(propertyDTO.getAbbr());

                InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

                Map<Integer, List<BookingDay>> bookingDaysByAbscissa = bookingDaysByPropertyId.getOrDefault(propertyId, Collections.emptyMap());

                bookingWidgetDTO.setData(Arrays.stream(stayLengthAbscissaEnums).map(item -> {
                    BookingWidgetDetailDTO bookingWidgetDetailDTO = new BookingWidgetDetailDTO();
                    bookingWidgetDetailDTO.setAbscissa(item.getAbscissa());

                    List<BookingDay> currentBookingDays = bookingDaysByAbscissa.getOrDefault(item.getAbscissa(), Collections.emptyList());

                    // 订单数量
                    bookingWidgetDetailDTO.setCount(currentBookingDays.stream().map(BookingDay::getCount).reduce(BaseConstant.ZERO, Integer::sum));

                    // 收入
                    bookingWidgetDetailDTO.setRevenue(currentBookingDays.stream().map(BookingDay::getRevenue)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));

                    // 入住天数
                    bookingWidgetDetailDTO.setDays(currentBookingDays.stream().map(BookingDay::getDays)
                            .reduce(BaseConstant.ZERO, Integer::sum));

                    // 平均单价
                    if (bookingWidgetDetailDTO.getDays() != BaseConstant.ZERO) {
                        bookingWidgetDetailDTO.setAveragePrice(bookingWidgetDetailDTO.getRevenue()
                                .divide(new BigDecimal(bookingWidgetDetailDTO.getDays()), dataPrecision, roundingMode));
                    } else {
                        bookingWidgetDetailDTO.setAveragePrice(BigDecimal.ZERO);
                    }

                    return bookingWidgetDetailDTO;
                }).toList());

                bookingWidgetDTOS.add(bookingWidgetDTO);
            });

            return bookingWidgetDTOS;
        }

        /**
         * 查询 Quarterly Trends 数据
         *
         * @param query 查询条件
         * @return Quarterly Trends 数据
         * @author yyt
         * @date 2024/7/23
         **/
        @Override
        public List<BookingWidgetDTO> listQuarterlyTrends(BookingAnalysisQuery query) {
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(query.getPropertyIds());

            List<BookingMonth> bookingMonths = bookingMonthService.list(query.getPropertyIds(), query.getStartMonth(), query.getEndMonth());
            Map<Integer, List<BookingMonth>> bookingMonthsByPropertyId = bookingMonths.stream()
                    .collect(Collectors.groupingBy(BookingMonth::getPropertyId));

            List<Integer> quarters = Lists.newArrayList(1, 2, 3, 4);
            int firstQuarter = 1;
            List<Integer> firstQuarterMonths = Lists.newArrayList(1, 2, 3);
            int secondQuarter = 2;
            List<Integer> secondQuarterMonths = Lists.newArrayList(4, 5, 6);
            int thirdQuarter = 3;
            List<Integer> thirdQuarterMonths = Lists.newArrayList(7, 8, 9);
            List<Integer> fourQuarterMonths = Lists.newArrayList(10, 11, 12);

            int dataPrecision = BaseConstant.DATA_PRECISION;

            List<BookingWidgetDTO> bookingWidgetDTOS = Lists.newArrayListWithExpectedSize(propertyDTOS.size());
            propertyDTOS.forEach(propertyDTO -> {
                BookingWidgetDTO bookingWidgetDTO = new BookingWidgetDTO();
                Integer propertyId = propertyDTO.getId();
                bookingWidgetDTO.setPropertyId(propertyId);
                bookingWidgetDTO.setPropertyName(propertyDTO.getAbbr());

                InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

                List<BookingMonth> currentPropertyBookingMonths = bookingMonthsByPropertyId.getOrDefault(propertyId, Collections.emptyList());

                bookingWidgetDTO.setData(quarters.stream().map(item -> {
                    BookingWidgetDetailDTO bookingWidgetDetailDTO = new BookingWidgetDetailDTO();
                    bookingWidgetDetailDTO.setAbscissa(item);

                    List<BookingMonth> currentQuarterBookingMonths = currentPropertyBookingMonths.stream()
                            .filter(bookingMonth -> {
                                if (item == firstQuarter) {
                                    return firstQuarterMonths.contains(bookingMonth.getAbscissa());
                                } else if (item == secondQuarter) {
                                    return secondQuarterMonths.contains(bookingMonth.getAbscissa());
                                } else if (item == thirdQuarter) {
                                    return thirdQuarterMonths.contains(bookingMonth.getAbscissa());
                                } else {
                                    return fourQuarterMonths.contains(bookingMonth.getAbscissa());
                                }
                            }).toList();

                    // 订单数量
                    bookingWidgetDetailDTO.setCount(currentQuarterBookingMonths.stream().map(BookingMonth::getCount)
                            .reduce(BaseConstant.ZERO, Integer::sum));

                    // 收入
                    bookingWidgetDetailDTO.setRevenue(currentQuarterBookingMonths.stream().map(BookingMonth::getRevenue)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));

                    // 入住天数
                    bookingWidgetDetailDTO.setDays(currentQuarterBookingMonths.stream().map(BookingMonth::getDays)
                            .reduce(BaseConstant.ZERO, Integer::sum));

                    // 平均单价
                    if (bookingWidgetDetailDTO.getDays() != BaseConstant.ZERO) {
                        bookingWidgetDetailDTO.setAveragePrice(bookingWidgetDetailDTO.getRevenue()
                                .divide(new BigDecimal(bookingWidgetDetailDTO.getDays()), dataPrecision, roundingMode));
                    } else {
                        bookingWidgetDetailDTO.setAveragePrice(BigDecimal.ZERO);
                    }

                    return bookingWidgetDetailDTO;
                }).toList());

                bookingWidgetDTOS.add(bookingWidgetDTO);
            });

            return bookingWidgetDTOS;
        }

        /**
         * 查询 Monthly Trends 数据
         *
         * @param query 查询条件
         * @return Monthly Trends 数据
         * @author yyt
         * @date 2024/7/23
         **/
        @Override
        public List<BookingWidgetDTO> listMonthlyTrends(BookingAnalysisQuery query) {
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(query.getPropertyIds());

            List<BookingMonth> bookingMonths = bookingMonthService.list(query.getPropertyIds(), query.getStartMonth(), query.getEndMonth());
            Map<Integer, Map<Integer, List<BookingMonth>>> bookingMonthsByPropertyId = bookingMonths.stream()
                    .collect(Collectors.groupingBy(BookingMonth::getPropertyId, Collectors.groupingBy(BookingMonth::getAbscissa)));

            Month[] months = Month.values();
            int dataPrecision = BaseConstant.DATA_PRECISION;

            List<BookingWidgetDTO> bookingWidgetDTOS = Lists.newArrayListWithExpectedSize(propertyDTOS.size());
            propertyDTOS.forEach(propertyDTO -> {
                BookingWidgetDTO bookingWidgetDTO = new BookingWidgetDTO();
                Integer propertyId = propertyDTO.getId();
                bookingWidgetDTO.setPropertyId(propertyId);
                bookingWidgetDTO.setPropertyName(propertyDTO.getAbbr());

                InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

                Map<Integer, List<BookingMonth>> bookingsByMonth = bookingMonthsByPropertyId.getOrDefault(propertyId, Collections.emptyMap());

                bookingWidgetDTO.setData(Arrays.stream(months).map(item -> {
                    BookingWidgetDetailDTO bookingWidgetDetailDTO = new BookingWidgetDetailDTO();
                    int month = item.getValue();
                    bookingWidgetDetailDTO.setAbscissa(month);

                    List<BookingMonth> currentMonthBookings = bookingsByMonth.getOrDefault(month, Collections.emptyList());

                    // 订单数量
                    bookingWidgetDetailDTO.setCount(currentMonthBookings.stream().map(BookingMonth::getCount).reduce(BaseConstant.ZERO, Integer::sum));

                    // 收入
                    bookingWidgetDetailDTO.setRevenue(currentMonthBookings.stream().map(BookingMonth::getRevenue).reduce(BigDecimal.ZERO, BigDecimal::add));

                    // 入住天数
                    bookingWidgetDetailDTO.setDays(currentMonthBookings.stream().map(BookingMonth::getDays).reduce(BaseConstant.ZERO, Integer::sum));

                    // 平均单价
                    if (bookingWidgetDetailDTO.getDays() != BaseConstant.ZERO) {
                        bookingWidgetDetailDTO.setAveragePrice(bookingWidgetDetailDTO.getRevenue()
                                .divide(new BigDecimal(bookingWidgetDetailDTO.getDays()), dataPrecision, roundingMode));
                    } else {
                        bookingWidgetDetailDTO.setAveragePrice(BigDecimal.ZERO);
                    }

                    return bookingWidgetDetailDTO;
                }).toList());

                bookingWidgetDTOS.add(bookingWidgetDTO);
            });

            return bookingWidgetDTOS;
        }

        /**
         * 查询 Far-out vs. Close-in 数据
         *
         * @param query 查询条件
         * @return Far-out vs. Close-in 数据
         * @author yyt
         * @date 2024/7/23
         **/
        @Override
        public List<BookingWidgetDTO> listAdvance(BookingAnalysisQuery query) {
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(query.getPropertyIds());

            List<BookingAdvance> bookingAdvances = bookingAdvanceService.list(query.getPropertyIds(), query.getStartMonth(), query.getEndMonth());
            Map<Integer, Map<Integer, List<BookingAdvance>>> bookingAdvancesByPropertyId = bookingAdvances.stream()
                    .collect(Collectors.groupingBy(BookingAdvance::getPropertyId, Collectors.groupingBy(BookingAdvance::getAbscissa)));

            AdvanceAbscissaEnum[] advanceAbscissaEnums = AdvanceAbscissaEnum.values();
            List<BookingWidgetDTO> bookingWidgetDTOS = Lists.newArrayListWithExpectedSize(propertyDTOS.size());

            int dataPrecision = BaseConstant.DATA_PRECISION;

            for (PropertyDTO propertyDTO : propertyDTOS) {
                BookingWidgetDTO bookingWidgetDTO = new BookingWidgetDTO();
                Integer propertyId = propertyDTO.getId();
                bookingWidgetDTO.setPropertyId(propertyId);
                bookingWidgetDTO.setPropertyName(propertyDTO.getAbbr());

                InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

                Map<Integer, List<BookingAdvance>> bookingAdvancesByAbscissa = bookingAdvancesByPropertyId.getOrDefault(propertyId, Collections.emptyMap());

                bookingWidgetDTO.setData(Arrays.stream(advanceAbscissaEnums).map(item -> {
                    BookingWidgetDetailDTO bookingWidgetDetailDTO = new BookingWidgetDetailDTO();
                    bookingWidgetDetailDTO.setAbscissa(item.getAbscissa());

                    List<BookingAdvance> currentBookingAdvances = bookingAdvancesByAbscissa.getOrDefault(item.getAbscissa(), Collections.emptyList());

                    // 订单数量
                    bookingWidgetDetailDTO.setCount(currentBookingAdvances.stream().map(BookingAdvance::getCount)
                            .reduce(BaseConstant.ZERO, Integer::sum));

                    // 收入
                    bookingWidgetDetailDTO.setRevenue(currentBookingAdvances.stream().map(BookingAdvance::getRevenue)
                            .reduce(BigDecimal.ZERO, BigDecimal::add));

                    // 入住天数
                    bookingWidgetDetailDTO.setDays(currentBookingAdvances.stream().map(BookingAdvance::getDays)
                            .reduce(BaseConstant.ZERO, Integer::sum));

                    // 平均单价
                    if (bookingWidgetDetailDTO.getDays() != BaseConstant.ZERO) {
                        bookingWidgetDetailDTO.setAveragePrice(bookingWidgetDetailDTO.getRevenue()
                                .divide(new BigDecimal(bookingWidgetDetailDTO.getDays()), dataPrecision, roundingMode));
                    } else {
                        bookingWidgetDetailDTO.setAveragePrice(BigDecimal.ZERO);
                    }

                    return bookingWidgetDetailDTO;
                }).toList());

                bookingWidgetDTOS.add(bookingWidgetDTO);
            }

            return bookingWidgetDTOS;
        }

        /**
         * 查询 Day Of The Week Trends 数据
         *
         * @param query 查询条件
         * @return Day Of The Week Trends 数据
         * @author yyt
         * @date 2024/7/23
         **/
        @Override
        public List<BookingWidgetDTO> listWeek(BookingAnalysisQuery query) {
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(query.getPropertyIds());

            List<BookingWeek> bookingWeeks = bookingWeekService.list(query.getPropertyIds(), query.getStartMonth(), query.getEndMonth());
            Map<Integer, Map<Integer, List<BookingWeek>>> bookingWeeksByPropertyId = bookingWeeks.stream()
                    .collect(Collectors.groupingBy(BookingWeek::getPropertyId, Collectors.groupingBy(BookingWeek::getAbscissa)));

            int dataPrecision = BaseConstant.DATA_PRECISION;

            List<BookingWidgetDTO> bookingWidgetDTOS = Lists.newArrayListWithExpectedSize(propertyDTOS.size());
            propertyDTOS.forEach(propertyDTO -> {
                BookingWidgetDTO bookingWidgetDTO = new BookingWidgetDTO();
                Integer propertyId = propertyDTO.getId();
                bookingWidgetDTO.setPropertyId(propertyId);
                bookingWidgetDTO.setPropertyName(propertyDTO.getAbbr());

                InternationalizationDTO internationalization = propertyDTO.getInternationalization();
                InternationalizationNumberDTO number = internationalization.getNumber();
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

                Map<Integer, List<BookingWeek>> bookingsByWeek = bookingWeeksByPropertyId.getOrDefault(propertyId, Collections.emptyMap());

                // 当前资产设置的周起始日 开始的一周
                List<DayOfWeek> weeks = WeekUtils.getWeeks(internationalization.getDateTime().getStartOfWeek());
                bookingWidgetDTO.setData(weeks.stream().map(item -> {
                    BookingWidgetDetailDTO bookingWidgetDetailDTO = new BookingWidgetDetailDTO();
                    int week = item.getValue();
                    bookingWidgetDetailDTO.setAbscissa(week);

                    List<BookingWeek> currentBookingWeeks = bookingsByWeek.getOrDefault(week, Collections.emptyList());

                    // 订单数量
                    bookingWidgetDetailDTO.setCount(currentBookingWeeks.stream().map(BookingWeek::getCount).reduce(BaseConstant.ZERO, Integer::sum));

                    // 收入
                    bookingWidgetDetailDTO.setRevenue(currentBookingWeeks.stream().map(BookingWeek::getRevenue).reduce(BigDecimal.ZERO, BigDecimal::add));

                    // 入住天数
                    bookingWidgetDetailDTO.setDays(currentBookingWeeks.stream().map(BookingWeek::getDays).reduce(BaseConstant.ZERO, Integer::sum));

                    // 平均单价
                    if (bookingWidgetDetailDTO.getDays() != BaseConstant.ZERO) {
                        bookingWidgetDetailDTO.setAveragePrice(bookingWidgetDetailDTO.getRevenue()
                                .divide(new BigDecimal(bookingWidgetDetailDTO.getDays()), dataPrecision, roundingMode));
                    } else {
                        bookingWidgetDetailDTO.setAveragePrice(BigDecimal.ZERO);
                    }

                    return bookingWidgetDetailDTO;
                }).toList());

                bookingWidgetDTOS.add(bookingWidgetDTO);
            });

            return bookingWidgetDTOS;
        }

    }