# HistoryAndForecastBusinessBlocksFileParseImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueAdrConfig;
    import cloud.wconnect.rateshop.pojo.bo.revenue.TransientGroupAdrBO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueAdrConfigService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.ImmutableList;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    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.math.RoundingMode;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.Collections;
    import java.util.List;
    import java.util.Objects;

    /**
     * @author yyt
     * 2023/11/1
     */
    @Service
    @DS("rate_shop")
    public class HistoryAndForecastBusinessBlocksFileParseImpl implements IHistoryAndForecastBusinessBlocksFileParse {

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

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

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

        /**
         * 解析报告 Transient Group Adr 数据
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param in         文件流
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Transient Group Last Year
         * @author yyt
         * @date 2023/11/2
         */
        @Override
        public List<TransientGroupAdrBO> parseReportForTransientGroupAdr(Integer propertyId, String timeZone, InputStream in,
                                                                         Long startDate, Long endDate) throws Exception {
            // 解析文件数据
            Document document = new SAXReader().read(in);
            return parseDocumentForTransientGroupAdr(propertyId, timeZone, document, startDate, endDate);
        }

        /**
         * 解析 Transient Group Adr 数据
         *
         * @param propertyId 资产 id
         * @param timeZone   时区
         * @param document   文件
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Transient Group Adr
         * @author yyt
         * @date 2023/10/30
         */
        private List<TransientGroupAdrBO> parseDocumentForTransientGroupAdr(Integer propertyId, String timeZone, Document document,
                                                                            Long startDate, Long endDate) {
            List<String> targetModules = ImmutableList.of("Blocks", "Sales", "All Business Block Types");
            String xpathTmpl = "//G_D_BOOKING_TYPE[D_DESCRIPTION=''{0}'']//G_D_CONSIDERED_DATE";
            // 获取 adr 配置
            RevenueAdrConfig revenueAdrConfig = revenueAdrConfigService.get(propertyId);
            boolean checkTransientDeduct = Objects.nonNull(revenueAdrConfig) && revenueAdrConfig.getCheckTransientDeduct();
            boolean checkTransientNonDeduct = Objects.nonNull(revenueAdrConfig) && revenueAdrConfig.getCheckTransientNonDeduct();
            boolean checkGroupDeduct = Objects.nonNull(revenueAdrConfig) && revenueAdrConfig.getCheckGroupDeduct();
            boolean checkGroupNonDeduct = Objects.nonNull(revenueAdrConfig) && revenueAdrConfig.getCheckGroupNonDeduct();

            ZoneId zoneId = ZoneId.of(timeZone);
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_MM_DD_YY_STRIKE);
            DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            InternationalizationNumberDTO number = propertyI18nRedisService.get(propertyId).getInternationalization().getNumber();
            int calculatePrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            for (String targetModule : targetModules) {
                String xpath = MessageFormat.format(xpathTmpl, targetModule);
                List<Node> nodes = document.selectNodes(xpath);
                if (CollectionUtils.isNotEmpty(nodes)) {

                    List<TransientGroupAdrBO> transientGroupList = Lists.newArrayListWithExpectedSize(14);
                    for (Node node : nodes) {
                        Element dataElement = (Element) node;

                        String theDateStr = dataElement.element("D_CHAR_CONSIDERED_DATE").getTextTrim();
                        if (!theDateStr.isEmpty()) {
                            long targetDate = DateTimeUtils.toTimestamp(LocalDate.parse(theDateStr, dateTimeFormatter)
                                    .atStartOfDay(zoneId));
                            if (targetDate >= startDate && targetDate <= endDate) {
                                TransientGroupAdrBO transientGroup = new TransientGroupAdrBO();
                                transientGroup.setTargetDate(DateTimeUtils.toZonedDateTime(targetDate, timeZone).format(dateTimeFormatter2));

                                // 计算并设置 Transient ADR
                                String deductRoomsStr = dataElement.element("D_TTL_IND_DEDUCT_ROOMS").getTextTrim();
                                String deductRoomRevStr = dataElement.element("D_TTL_IND_DEDUCT_ROOM_REV").getTextTrim();
                                String nonDeductRoomsStr = dataElement.element("D_TTL_IND_NON_DEDUCT_ROOMS").getTextTrim();
                                String nonDeductRoomRevStr = dataElement.element("D_TTL_IND_NON_DEDUCT_ROOM_REV").getTextTrim();

                                Integer transientRoomNum = null;
                                BigDecimal transientRoomRevenue = null;
                                if (checkTransientDeduct && !deductRoomsStr.isEmpty()) {
                                    transientRoomNum = Integer.parseInt(deductRoomsStr);
                                }
                                if (checkTransientDeduct && !deductRoomRevStr.isEmpty()) {
                                    transientRoomRevenue = new BigDecimal(deductRoomRevStr);
                                }

                                if (checkTransientNonDeduct && !nonDeductRoomsStr.isEmpty()) {
                                    int nonDeductRooms = Integer.parseInt(nonDeductRoomsStr);
                                    transientRoomNum = Objects.isNull(transientRoomNum) ? nonDeductRooms : transientRoomNum + nonDeductRooms;
                                }
                                if (checkTransientNonDeduct && !nonDeductRoomRevStr.isEmpty()) {
                                    BigDecimal nonDeductRoomRev = new BigDecimal(nonDeductRoomRevStr);
                                    transientRoomRevenue = Objects.isNull(transientRoomRevenue) ? nonDeductRoomRev : transientRoomRevenue.add(nonDeductRoomRev);
                                }

                                if (Objects.nonNull(transientRoomRevenue) && Objects.nonNull(transientRoomNum) && transientRoomNum != BaseConstant.ZERO) {
                                    BigDecimal transientAdr = transientRoomRevenue.divide(BigDecimal.valueOf(transientRoomNum),
                                            calculatePrecision, roundingMode);
                                    transientGroup.setTransientAdr(transientAdr);
                                }

                                // 计算并设置 Group ADR
                                String groupDeductRoomsStr = dataElement.element("D_BLK_DEDUCT_ROOMS").getTextTrim();
                                String groupDeductRoomRevStr = dataElement.element("D_BLK_DEDUCT_ROOM_REV").getTextTrim();
                                String groupNonDeductRoomsStr = dataElement.element("D_BLK_NON_DEDUCT_ROOMS").getTextTrim();
                                String groupNonDeductRoomRevStr = dataElement.element("D_BLK_NON_DEDUCT_ROOM_REV").getTextTrim();

                                Integer groupRoomNum = null;
                                BigDecimal groupRoomRevenue = null;
                                if (checkGroupDeduct && !groupDeductRoomsStr.isEmpty()) {
                                    groupRoomNum = Integer.parseInt(groupDeductRoomsStr);
                                }
                                if (checkGroupDeduct && !groupDeductRoomRevStr.isEmpty()) {
                                    groupRoomRevenue = new BigDecimal(groupDeductRoomRevStr);
                                }

                                if (checkGroupNonDeduct && !groupNonDeductRoomsStr.isEmpty()) {
                                    int groupNonDeductRooms = Integer.parseInt(groupNonDeductRoomsStr);
                                    groupRoomNum = Objects.isNull(groupRoomNum) ? groupNonDeductRooms : groupRoomNum + groupNonDeductRooms;
                                }
                                if (checkGroupNonDeduct && !groupNonDeductRoomRevStr.isEmpty()) {
                                    BigDecimal groupNonDeductRoomRev = new BigDecimal(groupNonDeductRoomRevStr);
                                    groupRoomRevenue = Objects.isNull(groupRoomRevenue) ? groupNonDeductRoomRev : groupRoomRevenue.add(groupNonDeductRoomRev);
                                }

                                if (Objects.nonNull(groupRoomRevenue) && Objects.nonNull(groupRoomNum) && groupRoomNum != BaseConstant.ZERO) {
                                    BigDecimal groupAdr = groupRoomRevenue.divide(BigDecimal.valueOf(groupRoomNum),
                                            calculatePrecision, roundingMode);
                                    transientGroup.setGroupAdr(groupAdr);
                                }

                                transientGroupList.add(transientGroup);

                                // 提早结束循环
                                if (targetDate == endDate) {
                                    break;
                                }
                            }
                        }
                    }

                    return transientGroupList;
                }
            }

            return Collections.emptyList();
        }

    }