# WeeklyMonthlyRevenueReportServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.MoudleEventSubjectEnum;
    import cloud.wconnect.commonlib.exception.ListenerException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.rateshop.cons.weeklymonthlyrevenue.MonthlyRevenueCons;
    import cloud.wconnect.rateshop.cons.weeklymonthlyrevenue.MonthlyRevenueImportCategoryCons;
    import cloud.wconnect.rateshop.entity.weeklymonthlyrevenue.WeeklyMonthlyRevenueRoom;
    import cloud.wconnect.rateshop.entity.weeklymonthlyrevenue.WeeklyMonthlyRevenueSegment;
    import cloud.wconnect.rateshop.entity.weeklymonthlyrevenue.WeeklyMonthlyRevenueVersionData;
    import cloud.wconnect.rateshop.pojo.bo.moduleevent.monthlyrevenue.MonthlyRevenueDetailEventBO;
    import cloud.wconnect.rateshop.pojo.bo.moduleevent.monthlyrevenue.MonthlyRevenueEventBO;
    import cloud.wconnect.rateshop.pojo.bo.weeklymonthlyrevenue.*;
    import cloud.wconnect.rateshop.pojo.dto.weeklymonthlyrevenue.versiondata.*;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueReportService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueRoomService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueSegmentService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueVersionDataService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.cancellationsummary.IWMRevenueCancellationSummaryParseService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.grouppickup.IWMRevenueGroupPickupParseService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.historyandforecast.IWMRevenueHistoryAndForecastParseService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.managerflash.IWMRevenueManagerFlashParseService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.reservationforecast.IWMRevenueReservationForecastParseService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.reservationstatistics.IWMRevenueReservationStatisticsParseService;
    import cloud.wconnect.sns.http.processor.Sender;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Nullable;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.io.InputStream;
    import java.time.LocalDate;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Weekly Monthly Revenue Report 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-05-16
     */
    @Service
    @DS("rate_shop")
    public class WeeklyMonthlyRevenueReportServiceImpl implements IWeeklyMonthlyRevenueReportService {
        @Resource
        private IWeeklyMonthlyRevenueRoomService roomService;
        @Resource
        private IWeeklyMonthlyRevenueSegmentService segmentService;
        @Resource
        private IWeeklyMonthlyRevenueVersionDataService versionDataService;
        @Resource
        private ApplicationContext applicationContext;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private Executor executor;
        @Resource
        @Qualifier("moduleEventSender")
        private Sender moduleEventSender;

        /**
         * 根据报告分类获取报告日期范围
         *
         * @param businessDate 业务日期（yyyyMMdd）
         * @param timezone     时区
         * @param dateRange    日期范围，例如：-1,-1
         * @return 日期范围[yyyyMMdd(startDate), yyyyMMdd(endDate)]
         * @author zc
         * @date 2024/5/16
         */
        @Override
        public List<Long> getReportDateRangeByCategory(Long businessDate, String timezone, String dateRange) {
            LocalDate localDate = DateTimeUtils.strToLocalDate(businessDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
            List<Long> dateRangeList = Arrays.stream(dateRange.split(",")).map(Long::parseLong).toList();

            return List.of(Long.parseLong(DateTimeUtils.localDateToStr(localDate.plusDays(dateRangeList.get(0)),
                            BaseConstant.DATE_FORMAT_YYYYMMDD)),
                    Long.parseLong(DateTimeUtils.localDateToStr(localDate.plusDays(dateRangeList.get(1)),
                            BaseConstant.DATE_FORMAT_YYYYMMDD)));
        }

        /**
         * 获取指定日期范围内的版本数据
         *
         * @param propertyDTO 资产
         * @param startDate   数据开始日期（yyyyMMdd）
         * @param endDate     数据结束日期（yyyyMMdd）
         * @return WeeklyMonthlyRevenueVersionData 集合
         * @author zc
         * @date 2025/5/22
         */
        private Map<Long, WeeklyMonthlyRevenueVersionData> getVersionDataMap(PropertyDTO propertyDTO, long startDate, long endDate) {
            return versionDataService.listByDateRange(propertyDTO.getId(), startDate, endDate).stream()
                    .collect(Collectors.toMap(WeeklyMonthlyRevenueVersionData::getDate, Function.identity()));
        }

        /**
         * 解析报告 History and Forecast
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   数据日期范围（yyyyMMdd）
         * @return WMRevenueHistoryAndForecastBO 集合
         * @author zc
         * @date 2024/5/18
         */
        @Override
        public List<WMRevenueHistoryAndForecastBO> parseHistoryAndForecastReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IWMRevenueHistoryAndForecastParseService> parserList = applicationContext.getBeansOfType(IWMRevenueHistoryAndForecastParseService.class).values();
            for (IWMRevenueHistoryAndForecastParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyDTO, in, dateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 History and Forecast
         *
         * @param propertyDTO 资产
         * @param boList      报告数据
         * @param versionDate 版本日期（yyyyMMdd）
         * @param operatorId  操作人 id
         * @author zc
         * @date 2024/5/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleHistoryAndForecast(PropertyDTO propertyDTO, List<WMRevenueHistoryAndForecastBO> boList,
                                             @Nullable Long versionDate, int operatorId) {
            if (CollectionUtils.isEmpty(boList)) {
                return;
            }

            ArrayList<WMRevenueHistoryAndForecastBO> forecastList = Lists.newArrayListWithCapacity(boList.size());

            //获取任意一个数据的OperaData类型
            String operaDataType = boList.get(0).getOperaDataType();
            if (StringUtils.isNotBlank(operaDataType)) {
                //operaDataType不等于空时，说明bo数据是从xml文件中解析的
                //根据operaDataType字段筛选出未来数据
                for (WMRevenueHistoryAndForecastBO item : boList) {
                    if (item.getOperaDataType().equals(MonthlyRevenueCons.HISTORY_AND_FORECAST_REPORT_DATA_FORECAST)) {
                        forecastList.add(item);
                    }
                }
            } else {
                assert versionDate != null;
                LocalDate versionLocalDate = DateTimeUtils.strToLocalDate(versionDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                //根据businessDate区分历史和未来数据
                for (WMRevenueHistoryAndForecastBO item : boList) {
                    LocalDate theDay = DateTimeUtils.strToLocalDate(item.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                    if (!theDay.isBefore(versionLocalDate)) {
                        forecastList.add(item);
                    }
                }
            }

            /*
             * 未来 Forecast  0 ~ 100
             */
            assert versionDate != null;
            // 保存预测数据
            saveForecastRevenueOverall(propertyDTO, forecastList, versionDate, operatorId);
            // 保存版本数据
            saveVersionDataOverall(propertyDTO, forecastList, versionDate);
        }

        /**
         * 保存版本数据
         *
         * @param propertyDTO 资产
         * @param boList      报告数据
         * @param versionDate 数据开始日期（yyyyMMdd）
         * @author zc
         * @date 2025/5/22
         */
        private void saveVersionDataOverall(PropertyDTO propertyDTO, List<WMRevenueHistoryAndForecastBO> boList, Long versionDate) {
            if (CollectionUtils.isEmpty(boList)) return;

            // 获取数据日期集合
            List<Long> dateList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                return Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            }).toList();
            long startDate = Collections.min(dateList);
            long endDate = Collections.max(dateList);

            Map<Long, WeeklyMonthlyRevenueVersionData> date2OriginVersionData = getVersionDataMap(propertyDTO, startDate, endDate);

            List<WeeklyMonthlyRevenueVersionData> versionDataList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                WmRevenueVersionDataOverallDTO overall = beanMapper.map(bo, WmRevenueVersionDataOverallDTO.class);

                WeeklyMonthlyRevenueVersionData originVersionData = date2OriginVersionData.get(theDate);
                if (Objects.nonNull(originVersionData)) {
                    List<WMRevenueVersionDataDTO> originVersionDataDTOList = JsonUtils.convertStringToList(objectMapper,
                            originVersionData.getVersionData(), WMRevenueVersionDataDTO.class);
                    Map<Long, WMRevenueVersionDataDTO> version2OriginVersionDataDTO = originVersionDataDTOList.stream()
                            .collect(Collectors.toMap(WMRevenueVersionDataDTO::getVersion, Function.identity()));

                    WMRevenueVersionDataDTO versionDataDTO = version2OriginVersionDataDTO.get(versionDate);
                    if (Objects.nonNull(versionDataDTO)) {
                        versionDataDTO.setOverall(overall);
                    } else {
                        WMRevenueVersionDataDTO newVersionDataDTO = new WMRevenueVersionDataDTO();
                        newVersionDataDTO.setVersion(versionDate);
                        newVersionDataDTO.setOverall(overall);

                        originVersionDataDTOList.add(newVersionDataDTO);
                    }
                    originVersionData.setVersionData(JsonUtils.convertObjListToString(objectMapper, originVersionDataDTOList));

                    return originVersionData;
                } else {
                    WMRevenueVersionDataDTO newVersionDataDTO = new WMRevenueVersionDataDTO();
                    newVersionDataDTO.setVersion(versionDate);
                    newVersionDataDTO.setOverall(overall);

                    return new WeeklyMonthlyRevenueVersionData()
                            .setPropertyId(propertyDTO.getId())
                            .setDate(theDate)
                            .setVersionData(JsonUtils.convertObjListToString(objectMapper,
                                    Lists.newArrayList(newVersionDataDTO)));
                }
            }).collect(Collectors.toList());

            versionDataService.insertOrUpdateBatchByUnique(versionDataList);
        }

        /**
         * 保存预测Revenue Overall 数据
         *
         * @param propertyDTO 资产
         * @param boList      预测数据
         * @param versionDate 版本日期（yyyyMMdd）
         * @param operatorId  操作人 id
         * @author lichen
         * @date 2025/11/4
         */
        private void saveForecastRevenueOverall(PropertyDTO propertyDTO, List<WMRevenueHistoryAndForecastBO> boList, Long versionDate, int operatorId) {
            if (CollectionUtils.isEmpty(boList)) return;
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            // 获取数据日期范围
            List<Long> dateList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                return Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            }).toList();
            long startDate = Collections.min(dateList);
            long endDate = Collections.max(dateList);

            //查询原来的数据
            List<WeeklyMonthlyRevenueRoom> originList = roomService.listByDateRange(propertyDTO.getId(), startDate, endDate);
            Map<Long, WeeklyMonthlyRevenueRoom> theDate2OriginEntity = originList.stream()
                    .collect(Collectors.toMap(WeeklyMonthlyRevenueRoom::getTheDate, Function.identity()));

            ArrayList<WeeklyMonthlyRevenueRoom> batchList = Lists.newArrayListWithCapacity(boList.size());
            for (WMRevenueHistoryAndForecastBO bo : boList) {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                boolean isUpdate = false;

                WeeklyMonthlyRevenueRoom originEntity = theDate2OriginEntity.get(theDate);
                if (Objects.nonNull(originEntity)) {
                    //预测数据的版本>=原来数据的版本，则更新
                    if (versionDate.compareTo(originEntity.getVersion()) >= 0) {
                        isUpdate = true;
                    }
                } else {
                    //原来数据不存在时，更新
                    isUpdate = true;
                }

                if (isUpdate) {
                    WeeklyMonthlyRevenueRoom newRevenueRoom = new WeeklyMonthlyRevenueRoom();
                    newRevenueRoom.setPropertyId(propertyDTO.getId());
                    newRevenueRoom.setTheDate(theDate);
                    newRevenueRoom.setOcc(bo.getOccupancy());
                    newRevenueRoom.setRoomsSold(bo.getTotalOcc());
                    newRevenueRoom.setAdr(bo.getAverageRate());
                    newRevenueRoom.setRoomRevenue(bo.getRoomRevenue());
                    newRevenueRoom.setOoo(bo.getOooRooms());
                    newRevenueRoom.setComplimentaryRooms(bo.getComplimentaryRooms());
                    newRevenueRoom.setNoShowRooms(bo.getNoShowRooms());
                    newRevenueRoom.setUpdatedBy(operatorId);
                    newRevenueRoom.setUpdatedTime(nowTimestamp);
                    newRevenueRoom.setStatus(MonthlyRevenueCons.FORECAST);
                    newRevenueRoom.setVersion(versionDate);

                    if (Objects.nonNull(originEntity) && Objects.nonNull(originEntity.getPcUpdatedBy())
                            && originEntity.getPcUpdatedBy() > 0) {
                        // 优先使用手动设置的 Physical Capacity
                        newRevenueRoom.setPhysicalCapacity(originEntity.getPhysicalCapacity());
                        newRevenueRoom.setPcUpdatedBy(originEntity.getPcUpdatedBy());
                        newRevenueRoom.setPcUpdatedTime(originEntity.getPcUpdatedTime());
                    } else {
                        newRevenueRoom.setPhysicalCapacity(bo.getInventoryRooms());
                        newRevenueRoom.setPcUpdatedBy(0);
                        newRevenueRoom.setPcUpdatedTime(nowTimestamp);
                    }
                    batchList.add(newRevenueRoom);
                }
            }

            if (CollectionUtils.isEmpty(batchList)) {
                return;
            }

            roomService.saveOverallBatch(batchList);

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                sendModuleEvent(propertyDTO.getId(), startDate, endDate);
            });
        }

        /**
         * 解析报告 Group Pickup
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   数据日期范围（yyyyMMdd）
         * @return WMRevenueGroupPickupBO 集合
         * @author zc
         * @date 2024/5/18
         */
        @Override
        public List<WMRevenueGroupPickupBO> parseGroupPickupReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IWMRevenueGroupPickupParseService> parserList =
                    applicationContext.getBeansOfType(IWMRevenueGroupPickupParseService.class).values();
            for (IWMRevenueGroupPickupParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyDTO, in, dateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 Group Pickup
         *
         * @param propertyDTO       资产
         * @param groupPickupBOList 报告数据
         * @param category          0:全部 1:历史 2:未来
         * @param versionDate       版本日期（yyyyMMdd）
         * @param operatorId        操作人 id
         * @author zc
         * @date 2025/5/22
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleGroupPickup(PropertyDTO propertyDTO, List<WMRevenueGroupPickupBO> groupPickupBOList,
                                      int category, Long versionDate, int operatorId) {
            if (CollectionUtils.isEmpty(groupPickupBOList)) {
                return;
            }

            long nowTimestamp = DateTimeUtils.nowTimestamp();

            List<WMRevenueGroupPickupBO> boList = groupPickupBOList.stream().
                    collect(Collectors.groupingBy(WMRevenueGroupPickupBO::getTheDay,
                            Collectors.summingInt(WMRevenueGroupPickupBO::getOrigin))).entrySet().stream()
                    .map(entry -> {
                        WMRevenueGroupPickupBO groupPickupBO = new WMRevenueGroupPickupBO();
                        groupPickupBO.setTheDay(entry.getKey());
                        groupPickupBO.setOrigin(entry.getValue());
                        return groupPickupBO;
                    }).toList();

            LocalDate localDateByVersionDate = DateTimeUtils.strToLocalDate(versionDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);

            //历史数据集合
            ArrayList<WeeklyMonthlyRevenueSegment> historyList = Lists.newArrayListWithCapacity(boList.size());
            //预测数据集合
            ArrayList<WeeklyMonthlyRevenueSegment> forecastList = Lists.newArrayListWithCapacity(boList.size());
            //版本数据集合
            ArrayList<WMRevenueGroupPickupBO> forecastVersionList = Lists.newArrayListWithCapacity(boList.size());
            //预测数据日期集合
            ArrayList<Long> forecastDateList = Lists.newArrayListWithCapacity(boList.size());

            //根据versionDate筛选历史数据和未来数据
            for (WMRevenueGroupPickupBO item : boList) {
                LocalDate localDateByTheDay = DateTimeUtils.strToLocalDate(item.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDateByTheDay, BaseConstant.DATE_FORMAT_YYYYMMDD));

                //构建segment对象
                WeeklyMonthlyRevenueSegment segment = new WeeklyMonthlyRevenueSegment()
                        .setPropertyId(propertyDTO.getId())
                        .setTheDate(theDate)
                        .setGroupContracted(item.getOrigin())
                        .setContractedUpdatedBy(operatorId)
                        .setContractedUpdatedTime(nowTimestamp);

                //小于versionDate的为历史数据
                if (localDateByTheDay.isBefore(localDateByVersionDate)) {
                    //设置版本和状态
                    segment.setGroupContractedVersion(0L).setGroupContractedStatus(MonthlyRevenueCons.HISTORY);
                    //收集历史数据
                    historyList.add(segment);
                } else {
                    //设置版本和状态
                    segment.setGroupContractedVersion(versionDate).setGroupContractedStatus(MonthlyRevenueCons.FORECAST);
                    //收集数据
                    forecastList.add(segment);

                    //收集版本数据
                    forecastVersionList.add(item);

                    //收集预测数据日期
                    forecastDateList.add(theDate);
                }
            }

            if (category == MonthlyRevenueImportCategoryCons.ALL) {
                // 保存历史数据
                segmentService.saveGroupContractedBatch(historyList);
                // 保存预测数据
                segmentService.saveBatchForecastGroupContracted(propertyDTO, forecastList);
                // 保存版本数据
                saveVersionDataGroupContracted(propertyDTO, forecastVersionList, versionDate, forecastDateList);
            } else if (category == MonthlyRevenueImportCategoryCons.HISTORY) {
                // 保存历史数据
                segmentService.saveGroupContractedBatch(historyList);
            } else {
                // 保存预测数据
                segmentService.saveBatchForecastGroupContracted(propertyDTO, forecastList);
                // 保存版本数据
                saveVersionDataGroupContracted(propertyDTO, forecastVersionList, versionDate, forecastDateList);
            }
        }

        /**
         * 保存版本数据 Group Contracted
         *
         * @param propertyDTO 资产
         * @param boList      数据
         * @param versionDate 版本日期（yyyyMMdd）
         * @param dateList    日期集合（yyyyMMdd）
         * @author zc
         * @date 2025/5/22
         */
        private void saveVersionDataGroupContracted(PropertyDTO propertyDTO, List<WMRevenueGroupPickupBO> boList,
                                                    Long versionDate, List<Long> dateList) {
            if (CollectionUtils.isEmpty(boList)) {
                return;
            }

            long startDate = Collections.min(dateList);
            long endDate = Collections.max(dateList);
            Map<Long, WeeklyMonthlyRevenueVersionData> date2OriginVersionData = getVersionDataMap(propertyDTO, startDate, endDate);

            List<WeeklyMonthlyRevenueVersionData> versionDataList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                WeeklyMonthlyRevenueVersionData originVersionData = date2OriginVersionData.get(theDate);
                if (Objects.nonNull(originVersionData)) {
                    List<WMRevenueVersionDataDTO> originVersionDataDTOList = JsonUtils.convertStringToList(objectMapper,
                            originVersionData.getVersionData(), WMRevenueVersionDataDTO.class);
                    Map<Long, WMRevenueVersionDataDTO> version2OriginVersionDataDTO = originVersionDataDTOList.stream()
                            .collect(Collectors.toMap(WMRevenueVersionDataDTO::getVersion, Function.identity()));

                    WMRevenueVersionDataDTO versionDataDTO = version2OriginVersionDataDTO.get(versionDate);
                    if (Objects.nonNull(versionDataDTO)) {
                        versionDataDTO.getGroup().setContracted(bo.getOrigin());
                    } else {
                        WMRevenueVersionDataGroupDTO group = new WMRevenueVersionDataGroupDTO();
                        group.setContracted(bo.getOrigin());

                        WMRevenueVersionDataDTO newVersionDataDTO = new WMRevenueVersionDataDTO();
                        newVersionDataDTO.setVersion(versionDate);
                        newVersionDataDTO.setGroup(group);

                        originVersionDataDTOList.add(newVersionDataDTO);
                    }
                    originVersionData.setVersionData(JsonUtils.convertObjListToString(objectMapper, originVersionDataDTOList));

                    return originVersionData;
                } else {
                    WMRevenueVersionDataGroupDTO group = new WMRevenueVersionDataGroupDTO();
                    group.setContracted(bo.getOrigin());

                    WMRevenueVersionDataDTO newVersionDataDTO = new WMRevenueVersionDataDTO();
                    newVersionDataDTO.setVersion(versionDate);
                    newVersionDataDTO.setGroup(group);

                    return new WeeklyMonthlyRevenueVersionData()
                            .setPropertyId(propertyDTO.getId())
                            .setDate(theDate)
                            .setVersionData(JsonUtils.convertObjListToString(objectMapper,
                                    Lists.newArrayList(newVersionDataDTO)));
                }
            }).collect(Collectors.toList());

            versionDataService.insertOrUpdateBatchByUnique(versionDataList);
        }

        /**
         * 解析报告 Reservation Statistics
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   报告数据日期范围(yyyyMMdd)
         * @return WMRevenueReservationStatisticsBO 集合
         * @author zc
         * @date 2024/5/20
         */
        @Override
        public List<WMRevenueReservationStatisticsBO> parseReservationStatisticsReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IWMRevenueReservationStatisticsParseService> parserList = applicationContext.getBeansOfType(IWMRevenueReservationStatisticsParseService.class).values();
            for (IWMRevenueReservationStatisticsParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyDTO, in, dateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 Reservation Statistics
         *
         * @param boList      报告数据
         * @param propertyDTO 资产
         * @param operatorId  操作人 id
         * @author zc
         * @date 2024/5/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleReservationStatistics(PropertyDTO propertyDTO, List<WMRevenueReservationStatisticsBO> boList, int operatorId) {
            if (CollectionUtils.isEmpty(boList)) {
                return;
            }
            // 保存数据
            saveRevenueSegmentReservationStatistics(propertyDTO, boList, operatorId);
        }

        /**
         * 保存 Segment 数据 for Reservation statistics
         *
         * @param propertyDTO 资产
         * @param boList      数据
         * @param operatorId  操作人 id
         * @author zc
         * @date 2025/5/22
         */
        private void saveRevenueSegmentReservationStatistics(PropertyDTO propertyDTO, List<WMRevenueReservationStatisticsBO> boList, int operatorId) {
            Integer propertyId = propertyDTO.getId();
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            List<WeeklyMonthlyRevenueSegment> revenueSegmentList = boList.stream()
                    .map(bo -> {
                        LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                        long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                        return packageHistorySegmentData(propertyId, theDate, bo, operatorId, nowTimestamp);
                    }).collect(Collectors.toList());
            segmentService.saveRevenueBatch(revenueSegmentList);

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                List<Long> dateList = revenueSegmentList.stream()
                        .map(WeeklyMonthlyRevenueSegment::getTheDate).toList();
                long startDate = Collections.min(dateList);
                long endDate = Collections.max(dateList);
                sendModuleEvent(propertyId, startDate, endDate);
            });
        }

        /**
         * 解析报告 Reservation Forecast
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   报告数据日期范围(yyyyMMdd)
         * @return WMRevenueReservationForecastBO 集合
         * @author zc
         * @date 2024/5/20
         */
        @Override
        public List<WMRevenueReservationForecastBO> parseReservationForecastReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IWMRevenueReservationForecastParseService> parserList = applicationContext.getBeansOfType(IWMRevenueReservationForecastParseService.class).values();
            for (IWMRevenueReservationForecastParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyDTO, in, dateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 Reservation Forecast
         *
         * @param propertyDTO 资产
         * @param boList      报告数据
         * @param versionDate 版本日期（yyyyMMdd）
         * @param operatorId  操作人 id
         * @author zc
         * @date 2024/5/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleReservationForecast(PropertyDTO propertyDTO, List<WMRevenueReservationForecastBO> boList,
                                              Long versionDate, int operatorId) {
            if (CollectionUtils.isEmpty(boList)) {
                return;
            }
            // 提取数据范围
            List<Long> dataDateList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                return Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            }).toList();
            long startDate = Collections.min(dataDateList);
            long endDate = Collections.max(dataDateList);

            // 保存历史数据+预测数据
            saveRevenueSegmentReservationForecast(propertyDTO, boList, versionDate, startDate, endDate, operatorId);
            // 保存版本数据
            saveVersionDataSegmentReservationForecast(propertyDTO, boList, versionDate, startDate, endDate);
        }

        /**
         * 保存 Segment 数据 for Reservation Forecast
         *
         * @param propertyDTO 资产
         * @param boList      数据
         * @param versionDate 版本日期（yyyyMMdd）
         * @param startDate   开始日期(yyyyMMdd)
         * @param endDate     结束日期(yyyyMMdd)
         * @author zc
         * @date 2025/5/22
         */
        private void saveVersionDataSegmentReservationForecast(PropertyDTO propertyDTO, List<WMRevenueReservationForecastBO> boList,
                                                               Long versionDate, long startDate, long endDate) {
            Map<Long, WeeklyMonthlyRevenueVersionData> date2OriginVersionData = getVersionDataMap(propertyDTO, startDate, endDate);

            List<WeeklyMonthlyRevenueVersionData> versionDataList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                WMRevenueVersionDataContractDTO contractDTO = new WMRevenueVersionDataContractDTO();
                contractDTO.setSold(bo.getContractSold());
                contractDTO.setRevenue(bo.getContractRevenue());

                WMRevenueVersionDataTransientDTO transientDTO = new WMRevenueVersionDataTransientDTO();
                transientDTO.setSold(bo.getTransientSold());
                transientDTO.setRevenue(bo.getTransientRevenue());

                WeeklyMonthlyRevenueVersionData originVersionData = date2OriginVersionData.get(theDate);
                if (Objects.nonNull(originVersionData)) {
                    List<WMRevenueVersionDataDTO> originVersionDataDTOList = JsonUtils.convertStringToList(objectMapper,
                            originVersionData.getVersionData(), WMRevenueVersionDataDTO.class);
                    Map<Long, WMRevenueVersionDataDTO> version2OriginVersionDataDTO = originVersionDataDTOList.stream()
                            .collect(Collectors.toMap(WMRevenueVersionDataDTO::getVersion, Function.identity()));

                    WMRevenueVersionDataDTO versionDataDTO = version2OriginVersionDataDTO.get(versionDate);
                    if (Objects.nonNull(versionDataDTO)) {
                        versionDataDTO.getGroup().setSold(bo.getGroupSold());
                        versionDataDTO.getGroup().setRevenue(bo.getGroupRevenue());
                        versionDataDTO.setContract(contractDTO);
                        versionDataDTO.setTransients(transientDTO);
                    } else {
                        WMRevenueVersionDataGroupDTO group = new WMRevenueVersionDataGroupDTO();
                        group.setSold(bo.getGroupSold());
                        group.setRevenue(bo.getGroupRevenue());

                        WMRevenueVersionDataDTO newVersionDataDTO = new WMRevenueVersionDataDTO();
                        newVersionDataDTO.setVersion(versionDate);
                        newVersionDataDTO.setGroup(group);
                        newVersionDataDTO.setContract(contractDTO);
                        newVersionDataDTO.setTransients(transientDTO);

                        originVersionDataDTOList.add(newVersionDataDTO);
                    }
                    originVersionData.setVersionData(JsonUtils.convertObjListToString(objectMapper, originVersionDataDTOList));

                    return originVersionData;
                } else {
                    WMRevenueVersionDataGroupDTO group = new WMRevenueVersionDataGroupDTO();
                    group.setSold(bo.getGroupSold());
                    group.setRevenue(bo.getGroupRevenue());

                    WMRevenueVersionDataDTO newVersionDataDTO = new WMRevenueVersionDataDTO();
                    newVersionDataDTO.setVersion(versionDate);
                    newVersionDataDTO.setGroup(group);
                    newVersionDataDTO.setContract(contractDTO);
                    newVersionDataDTO.setTransients(transientDTO);

                    return new WeeklyMonthlyRevenueVersionData()
                            .setPropertyId(propertyDTO.getId())
                            .setDate(theDate)
                            .setVersionData(JsonUtils.convertObjListToString(objectMapper,
                                    Lists.newArrayList(newVersionDataDTO)));
                }
            }).collect(Collectors.toList());

            versionDataService.insertOrUpdateBatchByUnique(versionDataList);
        }

        /**
         * 保存 Segment 数据 for Reservation Forecast
         *
         * @param propertyDTO 资产
         * @param boList      数据
         * @param versionDate 版本日期（yyyyMMdd）
         * @param startDate   开始日期（yyyyMMdd）
         * @param endDate     结束日期（yyyyMMdd）
         * @param operatorId  操作人 id
         * @author zc
         * @date 2025/5/22
         */
        private void saveRevenueSegmentReservationForecast(PropertyDTO propertyDTO, List<WMRevenueReservationForecastBO> boList,
                                                           Long versionDate, long startDate, long endDate, int operatorId) {
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            //查询指定日期的Segment数据
            Integer propertyId = propertyDTO.getId();
            Map<Long, WeeklyMonthlyRevenueSegment> theDate2OriginEntity = segmentService.listByDateRange(propertyId, startDate, endDate)
                    .stream().collect(Collectors.toMap(WeeklyMonthlyRevenueSegment::getTheDate, Function.identity()));

            ArrayList<WeeklyMonthlyRevenueSegment> revenueSegmentList = Lists.newArrayListWithCapacity(boList.size());

            for (WMRevenueReservationForecastBO bo : boList) {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                // 若数据是历史数据，则更新
                // 否则，根据版本判断是否更新
                if (theDate < versionDate) {
                    revenueSegmentList.add(packageHistorySegmentData(propertyId, theDate, beanMapper.map(bo, WMRevenueReservationStatisticsBO.class), operatorId, nowTimestamp));
                } else {
                    boolean isUpdate = false;

                    WeeklyMonthlyRevenueSegment originEntity = theDate2OriginEntity.get(theDate);
                    if (Objects.nonNull(originEntity) && Objects.nonNull(originEntity.getVersion())) {
                        //预测数据的版本大于原来数据的版本，则更新
                        if (versionDate.compareTo(originEntity.getVersion()) >= 0) {
                            isUpdate = true;
                        }
                    } else {
                        isUpdate = true;
                    }

                    if (isUpdate) {
                        revenueSegmentList.add(packageForecastSegmentData(propertyId, theDate, versionDate, bo, operatorId, nowTimestamp));
                    }
                }
            }

            if (CollectionUtils.isEmpty(revenueSegmentList)) {
                return;
            }

            segmentService.saveRevenueBatch(revenueSegmentList);

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                List<Long> dateList = revenueSegmentList.stream()
                        .map(WeeklyMonthlyRevenueSegment::getTheDate).toList();
                sendModuleEvent(propertyId, Collections.min(dateList), Collections.max(dateList));
            });
        }

        /**
         * 包装历史数据
         *
         * @param propertyId   资产 id
         * @param theDate      日期
         * @param bo           数据
         * @param operatorId   操作人 id
         * @param nowTimestamp 当前时间戳
         * @return WeeklyMonthlyRevenueSegment
         * @author yyt
         * @date 2026/2/27
         */
        private WeeklyMonthlyRevenueSegment packageHistorySegmentData( Integer propertyId,long theDate, WMRevenueReservationStatisticsBO bo,
                                                                       int operatorId, long nowTimestamp) {
            return new WeeklyMonthlyRevenueSegment()
                    .setTheDate(theDate)
                    .setPropertyId(propertyId)
                    .setTransientSold(bo.getTransientSold())
                    .setTransientRevenue(bo.getTransientRevenue())
                    .setGroupSold(bo.getGroupSold())
                    .setGroupRevenue(bo.getGroupRevenue())
                    .setContractSold(bo.getContractSold())
                    .setContractRevenue(bo.getContractRevenue())
                    .setRevenueUpdatedBy(operatorId)
                    .setRevenueUpdatedTime(nowTimestamp)
                    .setStatus(MonthlyRevenueCons.HISTORY)
                    .setVersion(0L);
        }

        /**
         * 包装预测数据
         *
         * @param propertyId   资产 id
         * @param theDate      日期
         * @param versionDate  版本日期
         * @param bo           数据
         * @param operatorId   操作人 id
         * @param nowTimestamp 当前时间戳
         * @return WeeklyMonthlyRevenueSegment
         * @author yyt
         * @date 2026/2/27
         */
        private WeeklyMonthlyRevenueSegment packageForecastSegmentData(Integer propertyId, long theDate, Long versionDate,
                                                                       WMRevenueReservationForecastBO bo, int operatorId, long nowTimestamp) {
            return new WeeklyMonthlyRevenueSegment()
                    .setTheDate(theDate)
                    .setPropertyId(propertyId)
                    .setTransientSold(bo.getTransientSold())
                    .setTransientRevenue(bo.getTransientRevenue())
                    .setGroupSold(bo.getGroupSold())
                    .setGroupRevenue(bo.getGroupRevenue())
                    .setContractSold(bo.getContractSold())
                    .setContractRevenue(bo.getContractRevenue())
                    .setRevenueUpdatedBy(operatorId)
                    .setRevenueUpdatedTime(nowTimestamp)
                    .setStatus(MonthlyRevenueCons.FORECAST)
                    .setVersion(versionDate);
        }

        /**
         * 解析报告 Cancellation Summary
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   报告数据日期范围(yyyyMMdd)
         * @return WMRevenueCancellationSummaryBO 集合
         * @author zc
         * @date 2024/5/20
         */
        @Override
        public List<WMRevenueCancellationSummaryBO> parseCancellationSummaryReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IWMRevenueCancellationSummaryParseService> parserList =
                    applicationContext.getBeansOfType(IWMRevenueCancellationSummaryParseService.class).values();
            for (IWMRevenueCancellationSummaryParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyDTO, in, dateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 Cancellation Summary
         *
         * @param propertyDTO 资产
         * @param boList      报告数据
         * @param operatorId  操作人 id
         * @author zc
         * @date 2024/5/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleCancellationSummary(PropertyDTO propertyDTO, List<WMRevenueCancellationSummaryBO> boList, int operatorId) {
            if (CollectionUtils.isEmpty(boList)) {
                return;
            }
            // 保存数据
            saveRevenueGroupCancelled(propertyDTO, boList, operatorId);
        }

        /**
         * 解析报告 Manager Flash
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   报告数据日期范围(yyyyMMdd)
         * @return java.util.List<cloud.wconnect.rateshop.pojo.bo.weeklymonthlyrevenue.WMRevenueManagerFlashBO>
         * @author lichen
         * @date 2025/12/12
         */
        @Override
        public List<WMRevenueManagerFlashBO> parseManagerFlashReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IWMRevenueManagerFlashParseService> parserList =
                    applicationContext.getBeansOfType(IWMRevenueManagerFlashParseService.class).values();
            for (IWMRevenueManagerFlashParseService item : parserList) {
                if (item.supports(source)) {
                    return item.parse(propertyDTO, in, dateRange);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 Manager Flash
         *
         * @param propertyDTO 资产
         * @param boList      报告数据
         * @param operatorId  操作人 id
         * @author lichen
         * @date 2025/12/12
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleManagerFlash(PropertyDTO propertyDTO, List<WMRevenueManagerFlashBO> boList, int operatorId) {
            if (CollectionUtils.isEmpty(boList)) {
                return;
            }
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            // 获取数据日期范围
            List<Long> dateList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                return Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            }).toList();
            long startDate = Collections.min(dateList);
            long endDate = Collections.max(dateList);

            //查询原来的数据
            Map<Long, WeeklyMonthlyRevenueRoom> theDate2OriginEntity = roomService.listByDateRange(propertyDTO.getId(), startDate, endDate)
                    .stream().collect(Collectors.toMap(WeeklyMonthlyRevenueRoom::getTheDate, Function.identity()));

            //bo转换成entity
            List<WeeklyMonthlyRevenueRoom> newRevenueRoomList = boList.stream().map(bo -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                WeeklyMonthlyRevenueRoom newRevenueRoom = new WeeklyMonthlyRevenueRoom();
                newRevenueRoom.setPropertyId(propertyDTO.getId());
                newRevenueRoom.setTheDate(theDate);
                newRevenueRoom.setOcc(bo.getOcc());
                newRevenueRoom.setRoomsSold(bo.getRoomsSold());
                newRevenueRoom.setAdr(bo.getAdr());
                newRevenueRoom.setRoomRevenue(bo.getRoomRevenue());
                newRevenueRoom.setOoo(bo.getOoo());
                newRevenueRoom.setComplimentaryRooms(bo.getComplimentaryRooms());
                newRevenueRoom.setNoShowRooms(bo.getNoShowRooms());
                newRevenueRoom.setUpdatedBy(operatorId);
                newRevenueRoom.setUpdatedTime(nowTimestamp);
                //状态设置为历史
                newRevenueRoom.setStatus(MonthlyRevenueCons.HISTORY);
                //版本设置为0
                newRevenueRoom.setVersion(0L);

                WeeklyMonthlyRevenueRoom originEntity = theDate2OriginEntity.getOrDefault(theDate, new WeeklyMonthlyRevenueRoom());
                if (Objects.nonNull(originEntity.getPcUpdatedBy()) && originEntity.getPcUpdatedBy() > 0) {
                    // 优先使用手动设置的 Physical Capacity
                    newRevenueRoom.setPhysicalCapacity(originEntity.getPhysicalCapacity());
                    newRevenueRoom.setPcUpdatedBy(originEntity.getPcUpdatedBy());
                    newRevenueRoom.setPcUpdatedTime(originEntity.getPcUpdatedTime());
                } else {
                    newRevenueRoom.setPhysicalCapacity(bo.getTotalRooms());
                    newRevenueRoom.setPcUpdatedBy(0);
                    newRevenueRoom.setPcUpdatedTime(nowTimestamp);
                }
                return newRevenueRoom;
            }).collect(Collectors.toList());

            roomService.saveOverallBatch(newRevenueRoomList);

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                sendModuleEvent(propertyDTO.getId(), startDate, endDate);
            });
        }

        /**
         * 保存 Segment 数据 for Cancellation Summary
         *
         * @param propertyDTO 资产
         * @param boList      数据
         * @param operatorId  操作人 id
         * @author zc
         * @date 2025/5/22
         */
        private void saveRevenueGroupCancelled(PropertyDTO propertyDTO, List<WMRevenueCancellationSummaryBO> boList, int operatorId) {
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            List<WeeklyMonthlyRevenueSegment> revenueSegmentList = boList.stream()
                    .map(bo -> {
                        LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getTheDay(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                        long theDate = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                        return new WeeklyMonthlyRevenueSegment()
                                .setTheDate(theDate)
                                .setPropertyId(propertyDTO.getId())
                                .setGroupCancelled(bo.getRoomsNights())
                                .setCancelledUpdatedBy(operatorId)
                                .setCancelledUpdatedTime(nowTimestamp);
                    }).collect(Collectors.toList());
            segmentService.saveGroupCancelledBatch(revenueSegmentList);
        }

        /**
         * 发送模块事件
         *
         * @param propertyId 资产 id
         * @param startDate  开始日期(yyyyMMdd)
         * @param endDate    结束日期(yyyyMMdd)
         * @author zc
         * @date 2025/10/14
         */
        private void sendModuleEvent(int propertyId, long startDate, long endDate) {
            // 查询指定时间区间的数据
            Map<Long, WeeklyMonthlyRevenueRoom> date2RevenueRoom = roomService.listByDateRange(propertyId,
                    startDate, endDate).stream().collect(Collectors.toMap(WeeklyMonthlyRevenueRoom::getTheDate, Function.identity()));

            Map<Long, WeeklyMonthlyRevenueSegment> date2RevenueSegment = segmentService.listByDateRange(propertyId,
                    startDate, endDate).stream().collect(Collectors.toMap(WeeklyMonthlyRevenueSegment::getTheDate, Function.identity()));

            LocalDate startLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(startDate), BaseConstant.DATE_FORMAT_YYYYMMDD);
            LocalDate endLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(endDate), BaseConstant.DATE_FORMAT_YYYYMMDD);

            List<MonthlyRevenueDetailEventBO> boList = Lists.newArrayListWithExpectedSize(date2RevenueRoom.size());
            for (LocalDate indexLocalDate = startLocalDate; !indexLocalDate.isAfter(endLocalDate);
                 indexLocalDate = indexLocalDate.plusDays(1)) {
                long indexDate = Long.parseLong(DateTimeUtils.localDateToStr(indexLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                MonthlyRevenueDetailEventBO revenueDetailBO = new MonthlyRevenueDetailEventBO();
                revenueDetailBO.setDate(indexDate);
                WeeklyMonthlyRevenueRoom revenueRoom = date2RevenueRoom.get(indexDate);
                if (Objects.nonNull(revenueRoom)) {
                    revenueDetailBO.setRoomsSold(revenueRoom.getRoomsSold());
                    revenueDetailBO.setRoomRevenue(revenueRoom.getRoomRevenue());
                }
                WeeklyMonthlyRevenueSegment revenueSegment = date2RevenueSegment.get(indexDate);
                if (Objects.nonNull(revenueSegment)) {
                    revenueDetailBO.setTransientSold(revenueSegment.getTransientSold());
                    revenueDetailBO.setTransientRevenue(revenueSegment.getTransientRevenue());
                    revenueDetailBO.setGroupSold(revenueSegment.getGroupSold());
                    revenueDetailBO.setGroupRevenue(revenueSegment.getGroupRevenue());
                    revenueDetailBO.setContractSold(revenueSegment.getContractSold());
                    revenueDetailBO.setContractRevenue(revenueSegment.getContractRevenue());
                }
                boList.add(revenueDetailBO);
            }
            MonthlyRevenueEventBO monthlyRevenueEventBO = new MonthlyRevenueEventBO();
            monthlyRevenueEventBO.setPropertyId(propertyId);
            monthlyRevenueEventBO.setData(boList);

            moduleEventSender.publish(MoudleEventSubjectEnum.MONTHLY_REVENUE.getSubject(),
                    JsonUtils.convertObjToString(objectMapper, monthlyRevenueEventBO));
        }
    }