# RFParseCommonServiceImpl.java

    package cloud.wconnect.rateshop.service.revenue.reservationforecast;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegment;
    import cloud.wconnect.rateshop.pojo.bo.revenue.*;
    import cloud.wconnect.rateshop.service.revenue.IRevenueImportService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import lombok.RequiredArgsConstructor;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.time.temporal.TemporalAdjusters;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * @author: yyt
     * 2025-07-07
     */
    @RequiredArgsConstructor
    @Service
    @DS("rate_shop")
    public class RFParseCommonServiceImpl implements IRFParseCommonService{

        private final IRevenueImportService revenueImportService;
        private final PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 封装 SegmentBO
         *
         * @param propertyId                资产 id
         * @param segments                  segment
         * @param startDate                 开始日期
         * @param endDate                   结束日期
         * @param reservationForecastBOList 文件数据
         * @param timeZone                  时区
         * @return segmentBOList
         * @author yyt
         * @date 2025/7/7
         **/
        @Override
        public List<List<SegmentBO>> packageSegmentBO(Integer propertyId, List<RevenueSegment> segments, Long startDate, Long endDate, List<ReservationForecastBO> reservationForecastBOList, String timeZone) {
            if (CollectionUtils.isEmpty(reservationForecastBOList)) return Collections.emptyList();

            // 组装 开始-结束范围内，每天所需的数据日期范围，Map：key 为具体日期；value 为当前日期需要的日期区间集合，第一个元素为开始日期，第二个元素为结束日期
            Map<Long, List<Long>> dateRangeByEveryDay = packageDateRange(propertyId, timeZone, startDate, endDate);

            // 循环处理开始-结束范围内每天所需要的文件中的数据
            return packageSegmentBO(segments, reservationForecastBOList, dateRangeByEveryDay, timeZone);
        }

        /**
         * 组装 开始-结束范围内，每天所需的数据日期范围
         * <p>Map：key 为具体日期; value 为当前日期需要的日期区间集合，第一个元素为开始日期，第二个元素为结束日期</p>
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return 开始-结束范围内，每天所需的数据日期范围
         */
        private Map<Long, List<Long>> packageDateRange(Integer propertyId, String timeZone, Long startDate, Long endDate) {
            ZonedDateTime startDateZoneDateTime = DateTimeUtils.toZonedDateTime(startDate, timeZone);
            ZonedDateTime endDateZoneDateTime = DateTimeUtils.toZonedDateTime(endDate, timeZone);

            // 当前日期
            ZonedDateTime currentDateTime = DateTimeUtils.dateNow(timeZone);
            // 当月
            int monthValue = currentDateTime.getMonthValue();
            // 当前资产 pms 是否是 shiji 平台
            boolean isShiJi = propertyI18nRedisService.get(propertyId).getPmsType().equals(SystemPlatformEnum.SHI_JI.getDesc());

            // 最多90天
            Map<Long, List<Long>> dateRangeByEveryDay = Maps.newHashMapWithExpectedSize(90);
            for (ZonedDateTime date = startDateZoneDateTime; !date.isAfter(endDateZoneDateTime); date = date.plusDays(1)) {
                // 若当前资产是 shiji，则每天的数据范围都是当月一整月
                // 若不是 shiji，则当月的数据范围是（当前日期-1 至 月底），未来月的数据范围是一整月
                if (isShiJi) {
                    dateRangeByEveryDay.put(date.toEpochSecond(), getOneMonthDateRange(date));
                } else {
                    // 若处理的是当月，则数据范围统一为（当天-1 至 月底）
                    if (date.getMonthValue() == monthValue) {
                        dateRangeByEveryDay.put(date.toEpochSecond(), revenueImportService.listImportReservationForecastDateRange(date));
                    } else {
                        // 未来月，则统一是一整月
                        dateRangeByEveryDay.put(date.toEpochSecond(), getOneMonthDateRange(date));
                    }
                }
            }
            return dateRangeByEveryDay;
        }

        /**
         * 获取一个月数据范围
         *
         * @param date 日期
         * @return 一个月数据范围
         * @author yyt
         * @date 2026/2/28
         **/
        private List<Long> getOneMonthDateRange(ZonedDateTime date) {
            return Lists.newArrayList(date.with(TemporalAdjusters.firstDayOfMonth()).toEpochSecond(), date.with(TemporalAdjusters.lastDayOfMonth()).toEpochSecond());
        }

        /**
         * 封装 SegmentBO
         *
         * @param segments                  segment
         * @param reservationForecastBOList 文件数据
         * @param dateRangeByEveryDay       日期区间
         * @param timeZone                  时区
         * @return SegmentBO
         * @author yyt
         * @date 2024/5/22
         **/
        private List<List<SegmentBO>> packageSegmentBO(List<RevenueSegment> segments, List<ReservationForecastBO> reservationForecastBOList,
                                                       Map<Long, List<Long>> dateRangeByEveryDay, String timeZone) {
            // segment pms 系统 segment 名称
            Map<Integer, List<String>> pmsSegmentsById = segments.stream().collect(Collectors.toMap(RevenueSegment::getId,
                    segment -> Lists.newArrayList(StringUtils.split(segment.getPmsSegment(), ","))));

            // 根据 segment 所需日期区间，汇总出 每天每个 segment 的数据集合
            List<SegmentBO> segmentBOForDateRangeList = packageSegmentBOForDateRange(dateRangeByEveryDay, pmsSegmentsById,
                    reservationForecastBOList, timeZone);

            // 根据导入选择的日期区间，汇总每天的 segment 数据
            List<SegmentBO> segmentBOForEveryDayList = packageSegmentBOForEveryDay(dateRangeByEveryDay, pmsSegmentsById,
                    reservationForecastBOList, timeZone);

            return Lists.newArrayList(segmentBOForDateRangeList, segmentBOForEveryDayList);
        }

        /**
         * 根据 segment 所需日期区间，汇总出 每天每个 segment 的数据集合
         *
         * @param dateRangeByEveryDay       每天的日期区间 map
         * @param pmsSegmentsById           pms segment 配置
         * @param reservationForecastBOList 文件数据
         * @param timeZone                  时区
         * @return segment 数据集合
         * @author yyt
         * @date 2024/6/1
         **/
        private List<SegmentBO> packageSegmentBOForDateRange(Map<Long, List<Long>> dateRangeByEveryDay,
                                                             Map<Integer, List<String>> pmsSegmentsById,
                                                             List<ReservationForecastBO> reservationForecastBOList,
                                                             String timeZone) {
            int size = pmsSegmentsById.keySet().size();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            List<SegmentBO> segmentBOForDateRangeList = Lists.newArrayListWithExpectedSize(size);

            // 循环处理每天所需要的文件中的数据
            dateRangeByEveryDay.forEach((date, dateRange) -> {

                SegmentBO segmentBO = new SegmentBO();
                segmentBO.setDate(DateTimeUtils.toZonedDateTime(date, timeZone).format(dateTimeFormatter));

                // 根据当天的日期区间过滤出所需要的文件节点数据
                Long startDate = dateRange.get(0);
                Long endDate = dateRange.get(1);
                Map<String, List<ReservationForecastMarketCodeBO>> marketCodeBOListByMarketCode = Maps.newHashMapWithExpectedSize(size);
                if (CollectionUtils.isNotEmpty(reservationForecastBOList)) {
                    marketCodeBOListByMarketCode = reservationForecastBOList.stream()
                            .filter(item -> item.getDate() >= startDate && item.getDate() <= endDate)
                            .flatMap(item -> item.getMarketCodeBOS().stream())
                            .collect(Collectors.groupingBy(ReservationForecastMarketCodeBO::getMarketCode));
                }

                // 统计每个 segment 当天的 rooms、 revenue 之和
                List<RoomsAndRevenueBO> roomsAndRevenueBOList = Lists.newArrayListWithExpectedSize(size);
                Map<String, List<ReservationForecastMarketCodeBO>> finalMarketCodeBOListByMarketCode = marketCodeBOListByMarketCode;
                pmsSegmentsById.forEach((segmentId, pmsSegments) -> {
                    int totalRooms = 0;
                    BigDecimal totalRevenue = BigDecimal.ZERO;
                    for (String pmsSegment : pmsSegments) {
                        List<ReservationForecastMarketCodesBO> marketCodes = finalMarketCodeBOListByMarketCode.getOrDefault(pmsSegment, Collections.emptyList())
                                .stream().flatMap(item -> item.getDetail().stream()).toList();
                        for (ReservationForecastMarketCodesBO marketCode : marketCodes) {
                            Integer rooms = marketCode.getRooms();
                            if (Objects.nonNull(rooms)) {
                                totalRooms += rooms;
                            }

                            BigDecimal revenue = marketCode.getRevenue();
                            if (Objects.nonNull(revenue)) {
                                totalRevenue = totalRevenue.add(revenue);
                            }
                        }
                    }

                    roomsAndRevenueBOList.add(new RoomsAndRevenueBO(segmentId, totalRooms, totalRevenue));
                });

                segmentBO.setDetail(roomsAndRevenueBOList);
                segmentBOForDateRangeList.add(segmentBO);
            });
            return segmentBOForDateRangeList;
        }

        /**
         * 根据导入选择的日期区间，汇总每天的 segment 数据
         *
         * @param dateRangeByEveryDay       每天的日期区间 map
         * @param pmsSegmentsById           pms segment 配置
         * @param reservationForecastBOList 文件数据
         * @param timeZone                  时区
         * @return segment 数据集合
         * @author yyt
         * @date 2024/6/1
         **/
        private List<SegmentBO> packageSegmentBOForEveryDay(Map<Long, List<Long>> dateRangeByEveryDay,
                                                            Map<Integer, List<String>> pmsSegmentsById,
                                                            List<ReservationForecastBO> reservationForecastBOList, String timeZone) {
            int size = pmsSegmentsById.keySet().size();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            List<SegmentBO> segmentBOForEveryDayList = Lists.newArrayListWithExpectedSize(size);

            Map<Long, Map<String, List<ReservationForecastMarketCodesBO>>> marketCodeBOListByDate = reservationForecastBOList.stream()
                    .collect(Collectors.toMap(ReservationForecastBO::getDate,
                            item -> item.getMarketCodeBOS().stream().collect(Collectors.toMap(ReservationForecastMarketCodeBO::getMarketCode,
                                    ReservationForecastMarketCodeBO::getDetail))));
            for (Long everyDay : dateRangeByEveryDay.keySet()) {

                SegmentBO segmentBO = new SegmentBO();
                segmentBO.setDate(DateTimeUtils.toZonedDateTime(everyDay, timeZone).format(dateTimeFormatter));

                Map<String, List<ReservationForecastMarketCodesBO>> marketCodesBOListByMarketCode = marketCodeBOListByDate.getOrDefault(everyDay, Collections.emptyMap());
                // 统计每个 segment 当天的 rooms、 revenue 之和
                List<RoomsAndRevenueBO> roomsAndRevenueBOList = Lists.newArrayListWithExpectedSize(size);
                pmsSegmentsById.forEach((segmentId, pmsSegments) -> {
                    int totalRooms = 0;
                    BigDecimal totalRevenue = BigDecimal.ZERO;
                    for (String pmsSegment : pmsSegments) {
                        List<ReservationForecastMarketCodesBO> marketCodes = marketCodesBOListByMarketCode.getOrDefault(pmsSegment, Collections.emptyList());
                        for (ReservationForecastMarketCodesBO marketCode : marketCodes) {
                            Integer rooms = marketCode.getRooms();
                            if (Objects.nonNull(rooms)) {
                                totalRooms += rooms;
                            }

                            BigDecimal revenue = marketCode.getRevenue();
                            if (Objects.nonNull(revenue)) {
                                totalRevenue = totalRevenue.add(revenue);
                            }
                        }
                    }

                    roomsAndRevenueBOList.add(new RoomsAndRevenueBO(segmentId, totalRooms, totalRevenue));
                });

                segmentBO.setDetail(roomsAndRevenueBOList);
                segmentBOForEveryDayList.add(segmentBO);
            }
            return segmentBOForEveryDayList;
        }

    }