# HFHistoryDataListenerFileParseByPepImpl.java

    package cloud.wconnect.rateshop.service.revenue.historyandforecast.history;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.util.CsvFileUtils;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueAdrConfig;
    import cloud.wconnect.rateshop.entity.revenue.RevenueHistoryAndForecast;
    import cloud.wconnect.rateshop.pojo.bo.revenue.HistoryAndForecastBO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueAdrConfigService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueHistoryAndForecastService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import lombok.RequiredArgsConstructor;
    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.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * @author yyt
     * 2026/3/3
     */
    @RequiredArgsConstructor
    @Service
    @DS("rate_shop")
    public class HFHistoryDataListenerFileParseByPepImpl implements IHFHistoryDataListenerFileParse {

        private final IRevenueHistoryAndForecastService revenueHistoryAndForecastService;
        private final IRevenueAdrConfigService revenueAdrConfigService;

        private static final String SOURCE = SystemPlatformEnum.PEP.getDesc();

        /**
         * 判断支持的平台
         *
         * @param source 平台
         * @return boolean
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public boolean supports(String source) {
            return SOURCE.equals(source);
        }

        /**
         * 解析 History and Forecast 报告数据
         *
         * @param propertyDTO 资产
         * @param in          文件流
         * @param startDate   开始日期
         * @param endDate     结束日期
         * @return java.util.List<cloud.wconnect.rateshop.pojo.bo.revenue.HistoryAndForecastBO>
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public List<HistoryAndForecastBO> 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();

            // 最多31天数据
            List<HistoryAndForecastBO> historyAndForecastBOS = Lists.newArrayListWithExpectedSize(31);
            String timeZone = propertyDTO.getTimezone();
            DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            // 跳过标题
            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;
                }

                HistoryAndForecastBO historyAndForecastBO = new HistoryAndForecastBO();
                historyAndForecastBO.setTargetDate(DateTimeUtils.strToLocalDate(date, BaseConstant.DATE_FORMAT_MMMDDYYYY_USA).format(dateTimeFormatter2));

                String totalRoomsStr = CsvFileUtils.getAndFormatNumber(record, "C");
                String soldRoomsStr = CsvFileUtils.getAndFormatNumber(record, "D");
                if(StringUtils.isNotEmpty(soldRoomsStr)){
                    historyAndForecastBO.setSoldRooms(Integer.valueOf(soldRoomsStr));

                    if(StringUtils.isNotEmpty(totalRoomsStr) && Integer.parseInt(totalRoomsStr) > 0){
                        historyAndForecastBO.setOccPercent(new BigDecimal(soldRoomsStr).divide(new BigDecimal(totalRoomsStr), BaseConstant.DATA_PRECISION, roundingMode));
                    }
                }

                String adrStr = CsvFileUtils.getAndFormatNumber(record, "L");
                if (StringUtils.isNotEmpty(adrStr)) {
                    historyAndForecastBO.setAdr(new BigDecimal(adrStr).setScale(BaseConstant.DATA_PRECISION, roundingMode));
                }

                historyAndForecastBOS.add(historyAndForecastBO);

                // 提前结束循环
                if (dataDate == endDate) {
                    break;
                }
            }
            return historyAndForecastBOS;
        }

        /**
         * 保存报告数据
         *
         * @param propertyDTO 资产
         * @param boList      报告数据
         * @param operatorId  操作人
         * @author yyt
         * @date 2026/3/3
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveReportData(PropertyDTO propertyDTO, List<HistoryAndForecastBO> boList, int operatorId) {
            if(CollectionUtils.isEmpty(boList)) return;

            Integer propertyId = propertyDTO.getId();
            // 查询已存在的数据，根据团客房间数，计算散客房间数
            List<Integer> dates = boList.stream().map(item -> Integer.parseInt(item.getTargetDate().replaceAll("-", "")))
                    .sorted().toList();
            Integer startDate = dates.get(0);
            Integer endDate = dates.get(dates.size() - 1);
            Map<Integer, RevenueHistoryAndForecast> existHistoryAndForecastByDate = revenueHistoryAndForecastService.listByDate(propertyId, startDate, endDate)
                    .stream().collect(Collectors.toMap(RevenueHistoryAndForecast::getTargetDate, v -> v));

            long now = DateTimeUtils.nowTimestamp();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            RevenueAdrConfig revenueAdrConfig = revenueAdrConfigService.get(propertyDTO.getId());
            boolean checkTransientDeduct = Objects.nonNull(revenueAdrConfig) && revenueAdrConfig.getCheckTransientDeduct();

            List<RevenueHistoryAndForecast> historyAndForecasts = boList.stream().map(item -> {
                int date = Integer.parseInt(StringUtils.replace(item.getTargetDate(), "-", ""));
                RevenueHistoryAndForecast existHistoryAndForecast = existHistoryAndForecastByDate.getOrDefault(date, new RevenueHistoryAndForecast());
                item.setGroupRoomNum(existHistoryAndForecast.getGroupRoomNum());

                return new RevenueHistoryAndForecast()
                        .setPropertyId(propertyId)
                        .setTargetDate(date)
                        .setOccPercent(Objects.isNull(item.getOccPercent()) ? null : item.getOccPercent().setScale(dataPrecision, roundingMode))
                        .setSoldRooms(item.getSoldRooms())
                        .setTransientRoomNum(checkTransientDeduct ? revenueHistoryAndForecastService.calculateTransientRoomNum(item) : null)
                        .setAdr(Objects.isNull(item.getAdr()) ? null : item.getAdr().setScale(dataPrecision, roundingMode))
                        .setUpdateBy(operatorId)
                        .setUpdateTime(now);
            }).collect(Collectors.toList());

            revenueHistoryAndForecastService.saveDataForTransientData(historyAndForecasts);
        }

    }