# ReservationStatisticsListenerFileParseByOperaImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.rateshop.cons.revenue.RevenueCons;
    import cloud.wconnect.rateshop.entity.revenue.RevenueCustomersRule;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegment;
    import cloud.wconnect.rateshop.pojo.bo.revenue.*;
    import cloud.wconnect.rateshop.service.AmazonS3Service;
    import cloud.wconnect.rateshop.service.revenue.IRevenueCustomersRuleService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSegmentService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.Node;
    import org.dom4j.io.SAXReader;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.io.InputStream;
    import java.math.BigDecimal;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * @author yyt
     * 2023/11/3
     */
    @Service
    @DS("rate_shop")
    public class ReservationStatisticsListenerFileParseByOperaImpl implements IReservationStatisticsListenerFileParse {

        @Autowired
        private IRevenueSegmentService revenueSegmentService;
        @Autowired
        private IRevenueCustomersRuleService revenueCustomersRuleService;
        @Autowired
        private AmazonS3Service amazonS3Service;

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

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

        /**
         * 解析报告数据
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param path       文件 s3 路径
         * @param date       指定日期
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return SegmentAndCustomersBO
         * @author yyt
         * @date 2023/11/3
         */
        @Override
        public SegmentAndCustomersBO parseReport(Integer propertyId, String timeZone, String path, String date,
                                                 Long startDate, Long endDate) {
            SegmentBO segmentBO = parseSegment(propertyId, timeZone, path, date, startDate, endDate);

            CustomersBO customersBO = parseCustomers(propertyId, timeZone, path, startDate, endDate);

            return new SegmentAndCustomersBO(date, segmentBO, customersBO);
        }

        /**
         * 解析 segment
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param path       文件 s3 路径
         * @param date       指定日期
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Segment
         * @author yyt
         * @date 2024/4/9
         **/
        private SegmentBO parseSegment(Integer propertyId, String timeZone, String path, String date, Long startDate, Long endDate){
            List<RevenueSegment> segments = revenueSegmentService.listActiveSegment(propertyId);
            if (CollectionUtils.isEmpty(segments)) {
                return new SegmentBO(date, Collections.emptyList());
            }

            Map<Integer, List<String>> pmsSegmentsById = segments.stream().collect(Collectors.toMap(RevenueSegment::getId,
                    segment -> Lists.newArrayList(StringUtils.split(segment.getPmsSegment(), ","))));

            ZoneId zoneId = ZoneId.of(timeZone);
            int size = segments.size();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_MM_DD_YY_STRIKE);
            Map<Integer, BigDecimal> revenueBySegmentId = Maps.newHashMapWithExpectedSize(size);
            Map<Integer, Integer> roomsBySegmentId = Maps.newHashMapWithExpectedSize(size);

            try (InputStream inputStream = amazonS3Service.getFile(path)) {
                // 解析文件数据
                Document document = new SAXReader().read(inputStream);
                List<Node> dateNodes = document.selectNodes("//DAY");
                String detailXpathTmpl = "./LIST_MARKET//MARKET[GROUP_NAME=''{0}'']//DETAIL";
                dateNodes.forEach(dateNode -> {
                    String targetDateStr = ((Element) dateNode).elementText("CHAR_BUSINESS_DATE");
                    ZonedDateTime targetDate = LocalDate.parse(targetDateStr, dateTimeFormatter).atStartOfDay(zoneId);
                    long targetDateSecond = targetDate.toEpochSecond();
                    if (targetDateSecond < startDate || targetDateSecond > endDate) {
                        return;
                    }

                    pmsSegmentsById.forEach((segmentId, operaSegments) -> {
                        BigDecimal totalRevenue = BigDecimal.ZERO;
                        int totalRooms = 0;
                        for (String operaSegment : operaSegments) {
                            String detailXpath = MessageFormat.format(detailXpathTmpl, operaSegment);
                            List<Node> detailNodes = dateNode.selectNodes(detailXpath);
                            for (Node detailNode : detailNodes) {
                                Element element = (Element) detailNode;
                                // 累加每天的Segment Revenue
                                String revenueStr = element.elementTextTrim("REVENUE");
                                if (!revenueStr.isEmpty()) {
                                    BigDecimal revenue = new BigDecimal(revenueStr);
                                    totalRevenue = totalRevenue.add(revenue);
                                }

                                // 累加每天售出的Segment房间数
                                String roomsStr = element.elementTextTrim("NO_DEFINITE_ROOMS");
                                if (!roomsStr.isEmpty()) {
                                    int rooms = Integer.parseInt(roomsStr);
                                    totalRooms = totalRooms + rooms;
                                }
                            }
                        }
                        revenueBySegmentId.put(segmentId, revenueBySegmentId.getOrDefault(segmentId, BigDecimal.ZERO).add(totalRevenue));
                        roomsBySegmentId.put(segmentId, roomsBySegmentId.getOrDefault(segmentId, 0) + totalRooms);
                    });
                });

                if (MapUtils.isEmpty(revenueBySegmentId) && MapUtils.isEmpty(roomsBySegmentId)) {
                    return new SegmentBO(date, Collections.emptyList());
                }

                return new SegmentBO(date, segments.stream().map(item -> {
                    Integer id = item.getId();
                    return new RoomsAndRevenueBO(id, roomsBySegmentId.get(id), revenueBySegmentId.get(id));
                }).collect(Collectors.toList()));
            } catch (Exception e) {
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parse reservation statistics report error", e);
            }
        }

        /**
         * 解析 Customers
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param path       文件 s3 路径
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Customers
         * @author yyt
         * @date 2024/4/9
         **/
        private CustomersBO parseCustomers(Integer propertyId, String timeZone, String path, Long startDate, Long endDate) {
            Map<Integer, List<String>> ruleByType = revenueCustomersRuleService.list(propertyId).stream()
                    .collect(Collectors.toMap(RevenueCustomersRule::getType, item -> Arrays.stream(item.getRule().split(",")).toList()));
            if (MapUtils.isEmpty(ruleByType)) {
                return null;
            }

            List<String> groupRules = ruleByType.getOrDefault(RevenueCons.GROUP, Collections.emptyList());
            List<String> contractRules = ruleByType.getOrDefault(RevenueCons.CONTRACT, Collections.emptyList());

            BigDecimal totalRevenue = null;
            Integer totalRooms = null;
            BigDecimal groupRevenue = null;
            Integer groupRooms = null;
            BigDecimal contractRevenue = null;
            Integer contractRooms = null;
            // 报告中的数据在处理范围内
            boolean isInScope = false;

            try (InputStream inputStream = amazonS3Service.getFile(path)) {
                // 解析文件数据
                Document document = new SAXReader().read(inputStream);
                List<Node> dateNodes = document.selectNodes("//DAY");
                for (Node node : dateNodes) {
                    Element element = (Element) node;
                    String targetDateStr = element.elementText("CHAR_BUSINESS_DATE");
                    long dataDate = DateTimeUtils.dateStrToEpochSecond(targetDateStr, BaseConstant.DATE_FORMAT_MM_DD_YY_STRIKE, timeZone);
                    if (dataDate < startDate || dataDate > endDate) {
                        continue;
                    } else {
                        isInScope = true;
                    }

                    List<Node> childNodes = node.selectNodes("./LIST_MARKET/MARKET");
                    for (Node childNode : childNodes) {
                        Element elementChild = (Element) childNode;
                        String marketCode = elementChild.elementText("GROUP_NAME");
                        List<Node> detailNodes = childNode.selectNodes("./LIST_DETAIL/DETAIL");
                        for (Node detalNode : detailNodes) {
                            Element detalElement = (Element) detalNode;
                            String revenueStr = detalElement.elementTextTrim("REVENUE");
                            if (!revenueStr.isEmpty()) {
                                BigDecimal revenue = new BigDecimal(revenueStr);

                                // 累计 total revenue
                                if (Objects.isNull(totalRevenue)) {
                                    totalRevenue = BigDecimal.ZERO;
                                }
                                totalRevenue = totalRevenue.add(revenue);

                                if (groupRules.contains(marketCode)) {
                                    // 累计 group revenue
                                    if (Objects.isNull(groupRevenue)) {
                                        groupRevenue = BigDecimal.ZERO;
                                    }
                                    groupRevenue = groupRevenue.add(revenue);
                                }

                                if (contractRules.contains(marketCode)) {
                                    // 累计 contract revenue
                                    if (Objects.isNull(contractRevenue)) {
                                        contractRevenue = BigDecimal.ZERO;
                                    }
                                    contractRevenue = contractRevenue.add(revenue);
                                }
                            }

                            String roomsStr = detalElement.elementTextTrim("NO_DEFINITE_ROOMS");
                            if (!roomsStr.isEmpty()) {
                                int rooms = Integer.parseInt(roomsStr);

                                // 累计 total rooms
                                if (Objects.isNull(totalRooms)) {
                                    totalRooms = BaseConstant.ZERO;
                                }
                                totalRooms += rooms;

                                if (groupRules.contains(marketCode)) {
                                    // 累计 group rooms
                                    if (Objects.isNull(groupRooms)) {
                                        groupRooms = BaseConstant.ZERO;
                                    }
                                    groupRooms += rooms;
                                }

                                if (contractRules.contains(marketCode)) {
                                    // 累计 contract rooms
                                    if (Objects.isNull(contractRooms)) {
                                        contractRooms = BaseConstant.ZERO;
                                    }
                                    contractRooms += rooms;
                                }
                            }
                        }
                    }
                }

                if (!isInScope) return null;

                CustomersBO customersBO = new CustomersBO();

                if (Objects.nonNull(totalRevenue)) {
                    // Transient = Total - Group - Contract
                    if (Objects.nonNull(groupRevenue)) {
                        totalRevenue = totalRevenue.subtract(groupRevenue);
                    }
                    if (Objects.nonNull(contractRevenue)) {
                        totalRevenue = totalRevenue.subtract(contractRevenue);
                    }
                }
                customersBO.setTransientRevenue(totalRevenue);
                if (Objects.nonNull(totalRooms)) {
                    // Transient = Total - Group - Contract
                    if (Objects.nonNull(groupRooms)) {
                        totalRooms -= groupRooms;
                    }
                    if (Objects.nonNull(contractRooms)) {
                        totalRooms -= contractRooms;
                    }
                }
                customersBO.setTransientRooms(totalRooms);

                customersBO.setGroupRevenue(groupRevenue);
                customersBO.setGroupRooms(groupRooms);

                customersBO.setContractRevenue(contractRevenue);
                customersBO.setContractRooms(contractRooms);

                return customersBO;
            } catch (Exception e) {
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parse reservation statistics report error", e);
            }
        }

    }