# YesterdayListenerFileParseByPepImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.PropertyRoomSoldTypeCons;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.query.PropertyRoomSoldQuery;
    import cloud.wconnect.commonlib.service.PropertyRoomSoldInternalService;
    import cloud.wconnect.commonlib.util.CsvFileUtils;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueAdrConfig;
    import cloud.wconnect.rateshop.entity.revenue.RevenueDailySummary;
    import cloud.wconnect.rateshop.pojo.bo.revenue.DailySummaryBO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.HistoryAndForecastDailySummaryDTO;
    import cloud.wconnect.rateshop.service.revenue.IHandleHistoryAndForecastReportCommonService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueAdrConfigService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueDailySummaryService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.csv.CSVFormat;
    import org.apache.commons.csv.CSVRecord;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.time.LocalDate;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * History And Forecast Shiji来源报告解析
     *
     * @author yyt
     * @date 2026/3/4
     */
    @RequiredArgsConstructor
    @Service
    @Slf4j
    @DS("rate_shop")
    public class YesterdayListenerFileParseByPepImpl implements IYesterdayListenerFileParse {

        private final IRevenueAdrConfigService revenueAdrConfigService;
        private final IRevenueDailySummaryService revenueDailySummaryService;
        private final IHandleHistoryAndForecastReportCommonService handleHFYesterdayAndNinetyCommonService;
        private final PropertyRoomSoldInternalService propertyRoomSoldInternalService;

        /**
         * 支持的报告来源
         *
         * @param source 来源
         * @return boolean
         * @author yyt
         * @date 2026/3/4
         */
        @Override
        public boolean supports(String source) {
            return SystemPlatformEnum.PEP.getDesc().equals(source);
        }

        /**
         * 解析报告数据
         *
         * @param propertyDTO 资产
         * @param in          文件流
         * @param startDate   开始日期
         * @param endDate     结束日期
         * @return HistoryAndForecastDailySummaryDTO
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public List<HistoryAndForecastDailySummaryDTO> parseReport(PropertyDTO propertyDTO, InputStream in, Long startDate, Long endDate) throws Exception {
            // 解析文件数据
            Iterable<CSVRecord> records = CSVFormat.EXCEL.parse(new InputStreamReader(in));
            Iterator<CSVRecord> iterator = records.iterator();
            if (!iterator.hasNext()) return Collections.emptyList();

            Integer propertyId = propertyDTO.getId();
            List<String> marketSymbols = propertyRoomSoldInternalService.list(new PropertyRoomSoldQuery(propertyId))
                    .stream().filter(item -> item.getType() == PropertyRoomSoldTypeCons.GROUP)
                    .map(item-> item.getMarketSymbol().toLowerCase())
                    .toList();
            if (CollectionUtils.isEmpty(marketSymbols)) return Collections.emptyList();

            RevenueAdrConfig revenueAdrConfig = revenueAdrConfigService.get(propertyId);
            boolean checkGroupDeduct = Objects.nonNull(revenueAdrConfig) && revenueAdrConfig.getCheckGroupDeduct();
            if (!checkGroupDeduct) return Collections.emptyList();

            String timezone = propertyDTO.getTimezone();
            List<HistoryAndForecastDailySummaryDTO> historyAndForecastDTOs = new ArrayList<>();

            // 跳过标题
            iterator.next();
            while (iterator.hasNext()) {
                CSVRecord record = iterator.next();
                // 空行
                if (record.size() == BaseConstant.ZERO) continue;

                // 解析每天数据
                String date = CsvFileUtils.get(record, "A");
                if (StringUtils.isEmpty(date)) continue;

                LocalDate localDate = DateTimeUtils.strToLocalDate(date, BaseConstant.DATE_FORMAT_MMMDDYYYY_USA);
                long dataDate = DateTimeUtils.localDateToLong(localDate, timezone);
                if (dataDate < startDate || dataDate > endDate) {
                    continue;
                }

                HistoryAndForecastDailySummaryDTO historyAndForecastDailySummaryDTO = new HistoryAndForecastDailySummaryDTO();
                historyAndForecastDailySummaryDTO.setTheDate(dataDate);
                //小时数
                int hour = DateTimeUtils.dateTimeNow(propertyDTO.getTimezone()).getHour();
                historyAndForecastDailySummaryDTO.setTheHour(hour);
                //同步时间
                historyAndForecastDailySummaryDTO.setSyncTime(DateTimeUtils.nowTimestamp());

                String marketSegment = CsvFileUtils.get(record, "C");
                if(StringUtils.isEmpty(marketSegment)) continue;
                if(!marketSymbols.contains(marketSegment.toLowerCase())){
                    continue;
                }

                String staysStr = CsvFileUtils.getAndFormatNumber(record, "H");
                if (StringUtils.isNotEmpty(staysStr)) {
                    historyAndForecastDailySummaryDTO.setGroupRooms(Integer.parseInt(staysStr));
                }
                historyAndForecastDTOs.add(historyAndForecastDailySummaryDTO);
            }

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

            // 数据按照日期汇总，每天的数据汇总 group room number 作为一天的数据
            List<HistoryAndForecastDailySummaryDTO> result = Lists.newArrayListWithExpectedSize(30);
            Map<Long, List<Integer>> groupRoomNumbersByDate = historyAndForecastDTOs.stream().collect(Collectors.groupingBy(HistoryAndForecastDailySummaryDTO::getTheDate,
                    Collectors.collectingAndThen(Collectors.toList(), list -> list.stream().map(HistoryAndForecastDailySummaryDTO::getGroupRooms)
                            .filter(Objects::nonNull).toList())));
            int hour = DateTimeUtils.dateTimeNow(propertyDTO.getTimezone()).getHour();
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            groupRoomNumbersByDate.forEach((date, groupRoomNumbers) -> {
                HistoryAndForecastDailySummaryDTO historyAndForecastDailySummaryDTO = new HistoryAndForecastDailySummaryDTO();
                historyAndForecastDailySummaryDTO.setTheDate(date);
                historyAndForecastDailySummaryDTO.setTheHour(hour);
                historyAndForecastDailySummaryDTO.setSyncTime(nowTimestamp);
                historyAndForecastDailySummaryDTO.setGroupRooms(groupRoomNumbers.stream().mapToInt(item -> item).sum());
                result.add(historyAndForecastDailySummaryDTO);
            });

            return result;
        }

        /**
         * 保存报告数据
         *
         * @param propertyDTO        资产
         * @param dailySummaryBOList 报告数据
         * @param operatorId         操作人 id
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveReportData(PropertyDTO propertyDTO, List<HistoryAndForecastDailySummaryDTO> dailySummaryBOList, int operatorId, Long logId) {
            List<DailySummaryBO> dailySummaryBOS = handleHFYesterdayAndNinetyCommonService.filterAndTransform(propertyDTO, operatorId, logId, dailySummaryBOList);
            if (CollectionUtils.isNotEmpty(dailySummaryBOS)) {
                // 查询已存在的数据，然后重新计算相关数据
                List<Long> dates = dailySummaryBOS.stream().map(DailySummaryBO::getTheDate).sorted().toList();
                Long startDate = dates.get(0);
                Long endDate = dates.get(dates.size() - 1);
                Map<Long, Map<Integer, RevenueDailySummary>> existRevenueDailySummaryByDate = revenueDailySummaryService.listDailySummary(propertyDTO.getId(), startDate, endDate)
                        .stream().collect(Collectors.groupingBy(RevenueDailySummary::getTheDate, Collectors.toMap(RevenueDailySummary::getTheHour, v -> v)));

                for (DailySummaryBO dailySummaryBO : dailySummaryBOS) {

                    RevenueDailySummary existRevenueDailySummary = existRevenueDailySummaryByDate.getOrDefault(dailySummaryBO.getTheDate(), Collections.emptyMap())
                            .get(dailySummaryBO.getTheHour());
                    if (Objects.nonNull(existRevenueDailySummary)) {
                        dailySummaryBO.setRoomsAvailable(existRevenueDailySummary.getRoomsAvailable());
                        dailySummaryBO.setTotalRoomsSold(existRevenueDailySummary.getTotalRoomsSold());
                        dailySummaryBO.setTotalOcc(existRevenueDailySummary.getTotalOcc());
                        dailySummaryBO.setOooRooms(existRevenueDailySummary.getOooRooms());
                        dailySummaryBO.setOcc(existRevenueDailySummary.getOcc());
                        dailySummaryBO.setTotalRooms(existRevenueDailySummary.getTotalRooms());
                        dailySummaryBO.setCompRooms(existRevenueDailySummary.getCompRooms());
                    } else {
                        dailySummaryBO.setRoomsAvailable(BaseConstant.ZERO);
                        dailySummaryBO.setTotalRoomsSold(BaseConstant.ZERO);
                        dailySummaryBO.setTotalOcc(BaseConstant.ZERO);
                        dailySummaryBO.setOooRooms(BaseConstant.ZERO);
                    }

                    // 计算散客
                    dailySummaryBO.setTransientRooms(revenueDailySummaryService.calculateTransientRooms(dailySummaryBO));
                }

                revenueDailySummaryService.saveOrUpdate(propertyDTO, dailySummaryBOS);
            }
        }

    }