# HistoryAndForecastListenerFileParseByPepImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.pojo.dto.RoomsConfigDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.RoomsConfigInternalService;
    import cloud.wconnect.commonlib.util.CsvFileUtils;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.rateshop.cons.revenue.RevenueCons;
    import cloud.wconnect.rateshop.entity.revenue.RevenueAdrConfig;
    import cloud.wconnect.rateshop.pojo.bo.revenue.OtbAndTransientGroupBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.OtbRevenueBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.TransientGroupBO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueAdrConfigService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import org.apache.commons.csv.CSVFormat;
    import org.apache.commons.csv.CSVRecord;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    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.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Objects;

    /**
     * @author yyt
     * 2026/3/3
     */
    @Service
    @DS("rate_shop")
    public class HistoryAndForecastListenerFileParseByPepImpl implements IHistoryAndForecastListenerFileParse {

        @Autowired
        private IRevenueAdrConfigService revenueAdrConfigService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private RoomsConfigInternalService roomsConfigInternalService;

        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);
        }

        /**
         * 解析报告数据
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param in         文件流
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Otb And TransientGroup
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public OtbAndTransientGroupBO parseReport(Integer propertyId, String timeZone, 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 new OtbAndTransientGroupBO();

            // 先加载所有行
            List<CSVRecord> allRows = new ArrayList<>();
            records.forEach(record -> {
                // 跳过第一行的标题
                if (record.getRecordNumber() == 1) {
                    return;
                }
                allRows.add(record);
            });

            OtbRevenueBO otbRevenueBO = parseFileForOtb(propertyId, allRows, startDate, endDate, timeZone);
            List<TransientGroupBO> transientGroups = parseFileForTransientGroup(propertyId, timeZone, allRows, startDate, endDate);
            return new OtbAndTransientGroupBO(otbRevenueBO, transientGroups);
        }

        /**
         * 解析 Otb 数据
         *
         * @param propertyId 资产 id
         * @param records    文件
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @param timeZone   时区
         * @return otb revenue
         * @author yyt
         * @date 2026/3/3
         */
        private OtbRevenueBO parseFileForOtb(Integer propertyId, List<CSVRecord> records, Long startDate, Long endDate, String timeZone) {
            Integer totalRooms = null;
            Integer totalSoldRooms = null;
            BigDecimal totalRevenue = null;

            for (int i = 0; i < records.size(); i++) {
                CSVRecord record = records.get(i);
                // 空行
                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;
                }

                String totalRoomsStr = CsvFileUtils.getAndFormatNumber(record, "C");
                if (StringUtils.isNotEmpty(totalRoomsStr)) {
                    if (totalRooms == null) {
                        totalRooms = Integer.parseInt(totalRoomsStr);
                    } else {
                        totalRooms += Integer.parseInt(totalRoomsStr);
                    }
                }
                String soldRoomsStr = CsvFileUtils.getAndFormatNumber(record, "D");
                if (StringUtils.isNotEmpty(soldRoomsStr)) {
                    if (totalSoldRooms == null) {
                        totalSoldRooms = Integer.parseInt(soldRoomsStr);
                    } else {
                        totalSoldRooms += Integer.parseInt(soldRoomsStr);
                    }
                }
                String adrStr = CsvFileUtils.getAndFormatNumber(record, "L");
                if (StringUtils.isNotEmpty(soldRoomsStr) && StringUtils.isNotEmpty(adrStr)) {
                    BigDecimal revenue = new BigDecimal(soldRoomsStr).multiply(new BigDecimal(adrStr));
                    totalRevenue = totalRevenue == null ? revenue : totalRevenue.add(revenue);
                }
            }

            InternationalizationNumberDTO number = propertyI18nRedisService.get(propertyId).getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            OtbRevenueBO otbRevenue = new OtbRevenueBO();
            otbRevenue.setOtbRevenue(totalRevenue);
            otbRevenue.setOtbAdr(Objects.nonNull(totalRevenue) && Objects.nonNull(totalSoldRooms) && totalSoldRooms > 0 ?
                    totalRevenue.divide(BigDecimal.valueOf(totalSoldRooms), BaseConstant.DATA_PRECISION, roundingMode) : null);
            otbRevenue.setOtbOccPercent(Objects.nonNull(totalSoldRooms) && Objects.nonNull(totalRooms) && totalSoldRooms > 0 ?
                    BigDecimal.valueOf(totalSoldRooms).divide(BigDecimal.valueOf(totalRooms), BaseConstant.DATA_PRECISION, roundingMode) : null);
            return otbRevenue;
        }

        /**
         * 解析 Transient Group 数据
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param records   文件
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Transient And Group
         * @author yyt
         * @date 2026/3/3
         */
        private List<TransientGroupBO> parseFileForTransientGroup(Integer propertyId, String timeZone, List<CSVRecord> records, Long startDate, Long endDate) {
            RoomsConfigDTO roomsConfig = roomsConfigInternalService.getRoomsConfig(propertyId);
            Integer rooms = roomsConfig.getRooms();

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

            // 最多1个月的数据
            List<TransientGroupBO> transientGroups = Lists.newArrayListWithExpectedSize(31);
            DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            InternationalizationNumberDTO number = propertyI18nRedisService.get(propertyId).getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
            BigDecimal oneHundred = BigDecimal.valueOf(100L);

            for (int i = 0; i < records.size(); i++) {
                CSVRecord record = records.get(i);
                // 空行
                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;
                }

                TransientGroupBO transientGroup = new TransientGroupBO();
                transientGroup.setTargetDate(DateTimeUtils.strToLocalDate(date, BaseConstant.DATE_FORMAT_MMMDDYYYY_USA).format(dateTimeFormatter2));

                // 如果配置了房间数，则根据 occ 计算规则判断：1.使用配置的房间数计算 occ，2.直接使用文件中的 occ
                String soldRoomsStr = CsvFileUtils.getAndFormatNumber(record, "D");
                if (roomsConfig.getOccRule() == RevenueCons.USE_ROOMS_TO_CALCULATE) {
                    if (StringUtils.isNotEmpty(soldRoomsStr)) {
                        //  Occ. % = total occ rooms / 总房间数
                        BigDecimal occPercent = new BigDecimal(soldRoomsStr).divide(BigDecimal.valueOf(rooms),
                                BaseConstant.DATA_PRECISION, roundingMode).multiply(oneHundred);
                        transientGroup.setOccPercent(occPercent);
                    }
                } else {
                    String occStr = CsvFileUtils.getAndFormatNumber(record, "M");
                    if (StringUtils.isNotEmpty(occStr)) {
                        transientGroup.setOccPercent(new BigDecimal(occStr).setScale(BaseConstant.DATA_PRECISION, roundingMode));
                    }
                }

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

                // 如果勾选了 Transient deduct
                if (checkTransientDeduct) {
                    if (StringUtils.isNotEmpty(soldRoomsStr)) {
                        transientGroup.setTransientRoomNum(Integer.valueOf(soldRoomsStr));
                    }
                }

                String availableRoomsStr = CsvFileUtils.getAndFormatNumber(record, "E");
                transientGroup.setRoomsAvailable(StringUtils.isNotEmpty(availableRoomsStr) ? Integer.parseInt(availableRoomsStr) : null);

                transientGroups.add(transientGroup);

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

    }