# HotelKpiAnalysisServiceImpl.java

    package cloud.wconnect.workperformance.service.hotelkpi.impl.old;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiExplainConfig;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiMetricsConfig;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.analysis.HotelKpiAnalysisDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.analysis.HotelKpiAnalysisDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.explain.HotelKpiExplainDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.HotelKpiMetricsDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.HotelKpiValueDTO;
    import cloud.wconnect.workperformance.pojo.query.hotelkpi.old.HotelKpiAnalysisQuery;
    import cloud.wconnect.workperformance.service.hotelkpi.old.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Hotel KPI Calculate 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-06-27
     */
    @Service("hotelKpiAnalysisServiceImpl_old")
    @DS("work_performance")
    public class HotelKpiAnalysisServiceImpl implements IHotelKpiAnalysisService {

        @Resource
        private IHotelKpiExplainMonthlyService explainMonthlyService;
        @Resource
        private IHotelKpiExplainConfigService explainConfigService;
        @Resource
        private IHotelKpiService hotelKpiService;
        @Resource
        private IHotelKpiMonthlyService monthlyService;

        /**
         * 获取指标对比分析数据
         *
         * @param propertyDTO   资产
         * @param query         查询条件
         * @param metricsConfig 指标配置
         * @return HotelKpiAnalysisDTO 集合
         * @author zc
         * @date 2024/6/27
         */
        @Override
        public List<HotelKpiAnalysisDTO> listAnalysis(PropertyDTO propertyDTO, HotelKpiAnalysisQuery query, HotelKpiMetricsConfig metricsConfig) {
            // 获取指标 Actual 分类的 Explain Config 公式
            HotelKpiExplainConfig explainConfig = explainConfigService.getByMetricsType(propertyDTO.getId(), query.getMetricsType());

            // 获取指标 Actual 分类的 Explain 数据
            Map<Long, HotelKpiExplainDTO> date2ExplainMonthlyDTO = explainMonthlyService.listByYearAndMetricsType(propertyDTO.getId(),
                            query.getTargetYear(), query.getMetricsType()).stream()
                    .collect(Collectors.toMap(HotelKpiExplainDTO::getDate, Function.identity()));

            // 提取查询 KPI 数据的年份
            List<Long> queryYears = Lists.newArrayList(query.getTargetYear());
            queryYears.addAll(query.getComparisonYears());
            queryYears.sort(Comparator.naturalOrder());

            // 获取指标数据
            List<HotelKpiMetricsDTO> metricsDTOList = monthlyService.listKpiMetricsDTOListByYears(propertyDTO.getId(), queryYears, query.getMetricsType());

            // 以月分组
            Map<Integer, Map<Integer, HotelKpiMetricsDTO>> year2KpiMetricsDTOByMonth = metricsDTOList.stream()
                    .collect(Collectors.groupingBy(item -> YearMonthUtils.getYearMonth(item.getDate().toString()).getMonthValue(),
                            Collectors.toMap(item -> YearMonthUtils.getYearMonth(item.getDate().toString()).getYear(), Function.identity())));

            YearMonth startYearMonthForTarget = YearMonth.of(query.getTargetYear().intValue(), 1);
            YearMonth endYearMonthForTarget = startYearMonthForTarget.withMonth(12);
            long startMonthForTarget = YearMonthUtils.parseYearMonthToLong(startYearMonthForTarget);
            long endMonthForTarget = YearMonthUtils.parseYearMonthToLong(endYearMonthForTarget);

            // 提取 Target Year KPI 数据
            List<HotelKpiMetricsDTO> targetYearKpiMetricsDTOList = metricsDTOList.stream()
                    .filter(item -> item.getDate() >= startMonthForTarget && item.getDate() <= endMonthForTarget).toList();
            // 组装 Target setting 数据
            monthlyService.packageSettingValue(propertyDTO.getId(), targetYearKpiMetricsDTOList,
                    List.of(query.getMetricsType()), startMonthForTarget, endMonthForTarget);
            Map<Long, HotelKpiMetricsDTO> date2TargetKpiMetricsDTO = targetYearKpiMetricsDTOList.stream()
                    .collect(Collectors.toMap(HotelKpiMetricsDTO::getDate, Function.identity()));

            List<HotelKpiAnalysisDTO> analysisDTOList = Lists.newArrayList();
            for (YearMonth indexYearMonth = startYearMonthForTarget; !indexYearMonth.isAfter(endYearMonthForTarget);
                 indexYearMonth = indexYearMonth.plusMonths(1)) {
                HotelKpiAnalysisDTO analysisDTO = packageKpiAnalysisDTO(propertyDTO, metricsConfig, indexYearMonth, explainConfig,
                        queryYears, year2KpiMetricsDTOByMonth, date2TargetKpiMetricsDTO, date2ExplainMonthlyDTO);
                analysisDTOList.add(analysisDTO);
            }

            // 组装 KPI Avg 数据
            packageKpiAnalysisAvgDTO(propertyDTO, analysisDTOList);

            // 整理顺序
            HotelKpiAnalysisDTO q1q2AnalysisAvgDTO = analysisDTOList.remove(12);
            analysisDTOList.add(6, q1q2AnalysisAvgDTO);

            return analysisDTOList;
        }

        /**
         * 组装 KPI Avg 数据
         *
         * @param propertyDTO     资产
         * @param analysisDTOList 1 - 12 月 KPI 数据集合
         * @author zc
         * @date 2024/7/27
         */
        private void packageKpiAnalysisAvgDTO(PropertyDTO propertyDTO, List<HotelKpiAnalysisDTO> analysisDTOList) {
            List<BigDecimal> targetList = Lists.newArrayList();
            Map<Long, List<BigDecimal>> year2ActualList = Maps.newHashMap();
            for (HotelKpiAnalysisDTO analysisDTO : analysisDTOList) {
                targetList.add(analysisDTO.getTarget());

                List<HotelKpiAnalysisDetailDTO> actualList = analysisDTO.getActuals();
                for (HotelKpiAnalysisDetailDTO actual : actualList) {
                    List<BigDecimal> actualListOfYear = year2ActualList.getOrDefault(actual.getYear(), Lists.newArrayList());
                    actualListOfYear.add(actual.getActual());
                    year2ActualList.put(actual.getYear(), actualListOfYear);
                }
            }
            // 13: Q1-Q2 Avg; 14: Q3-Q4 Avg; 15: Q1-Q4 Avg
            for (int monthIndex = 13; monthIndex <= 15; monthIndex++) {
                analysisDTOList.add(calculateKpiAnalysisAvgDTO(propertyDTO, targetList, year2ActualList, monthIndex));
            }
        }

        /**
         * 计算指标对比分析数据平均值
         *
         * @param propertyDTO     资产
         * @param targetList      指标目标值
         * @param year2ActualList 实际值
         * @param monthIndex      月份坐标
         * @return HotelKpiAnalysisDTO
         * @author zc
         * @date 2024/7/27
         */
        private HotelKpiAnalysisDTO calculateKpiAnalysisAvgDTO(PropertyDTO propertyDTO, List<BigDecimal> targetList,
                                                               Map<Long, List<BigDecimal>> year2ActualList, int monthIndex) {
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                    .getInternationalization().getNumber().getCalculationRule());

            int startIndex = 0;
            int endIndex = 12;
            if (monthIndex == 13) {
                endIndex = 6;
            } else if (monthIndex == 14) {
                startIndex = 6;
            }

            HotelKpiAnalysisDTO q1q2AvgDTO = new HotelKpiAnalysisDTO();
            q1q2AvgDTO.setMonth(monthIndex);

            // 计算 Target Avg
            List<BigDecimal> targetValues = targetList.subList(startIndex, endIndex).stream().filter(Objects::nonNull).toList();
            BigDecimal totalTargetValue = targetValues.stream().reduce(BigDecimal::add).orElse(null);
            BigDecimal avgTargetValue = Objects.isNull(totalTargetValue) ? null :
                    totalTargetValue.divide(new BigDecimal(targetValues.size()), BaseConstant.DATA_PRECISION, roundingMode);
            q1q2AvgDTO.setTarget(avgTargetValue);

            // 计算 Actual Avg
            List<HotelKpiAnalysisDetailDTO> actualList = Lists.newArrayList();
            for (Map.Entry<Long, List<BigDecimal>> entry : year2ActualList.entrySet()) {
                List<BigDecimal> actualValues = entry.getValue().subList(startIndex, endIndex).stream().filter(Objects::nonNull).toList();
                BigDecimal totalActualValue = actualValues.stream().reduce(BigDecimal::add).orElse(null);
                BigDecimal avgActualValue = Objects.isNull(totalActualValue) ? null :
                        totalActualValue.divide(new BigDecimal(actualValues.size()), BaseConstant.DATA_PRECISION, roundingMode);

                HotelKpiAnalysisDetailDTO avgActualDTO = new HotelKpiAnalysisDetailDTO();
                avgActualDTO.setYear(entry.getKey());
                avgActualDTO.setActual(avgActualValue);
                actualList.add(avgActualDTO);
            }
            q1q2AvgDTO.setActuals(actualList);
            return q1q2AvgDTO;
        }

        /**
         * 组装 KPI 分析 DTO
         *
         * @param propertyDTO               资产
         * @param metricsConfig             指标配置
         * @param yearMonth                 年月
         * @param explainConfig             Explain 配置
         * @param years                     比较的年份
         * @param year2KpiMetricsDTOByMonth KPI 数据
         * @param date2TargetKpiMetricsDTO  目标年份的 KPI 数据
         * @param date2ExplainMonthlyDTO    Explain 数据
         * @return HotelKpiAnalysisDTO
         * @author zc
         * @date 2024/6/28
         */
        private HotelKpiAnalysisDTO packageKpiAnalysisDTO(PropertyDTO propertyDTO, HotelKpiMetricsConfig metricsConfig,
                                                          YearMonth yearMonth, HotelKpiExplainConfig explainConfig, List<Long> years,
                                                          Map<Integer, Map<Integer, HotelKpiMetricsDTO>> year2KpiMetricsDTOByMonth,
                                                          Map<Long, HotelKpiMetricsDTO> date2TargetKpiMetricsDTO,
                                                          Map<Long, HotelKpiExplainDTO> date2ExplainMonthlyDTO) {
            HotelKpiAnalysisDTO analysisDTO = new HotelKpiAnalysisDTO();
            // Month
            analysisDTO.setMonth(yearMonth.getMonthValue());

            // 当前年月
            long indexDateLong = YearMonthUtils.parseYearMonthToLong(yearMonth);

            // KPI 指标数据
            HotelKpiMetricsDTO targetKpiMetricsDTO = date2TargetKpiMetricsDTO.getOrDefault(indexDateLong, new HotelKpiMetricsDTO());
            // 组装目标年份的指标数值
            hotelKpiService.packageMetricsValue(propertyDTO, metricsConfig, targetKpiMetricsDTO);
            HotelKpiValueDTO targetKpiValueDTO = targetKpiMetricsDTO.getTarget();
            BigDecimal target = hotelKpiService.getEffectiveValue(targetKpiValueDTO.getManual(),
                    targetKpiValueDTO.getSystem(), targetKpiValueDTO.getUpdatedBy());
            analysisDTO.setTarget(target);

            // Show Explain
            HotelKpiExplainDTO explainMonthly = date2ExplainMonthlyDTO.get(indexDateLong);
            Boolean isShow = explainConfigService.getExplainIsShow(explainConfig, explainMonthly);
            // 获取 Target Explain Value
            String targetExplainValue = explainConfigService.getTargetExplainValue(explainConfig, explainMonthly);

            // KPI 指标数据
            Map<Integer, HotelKpiMetricsDTO> year2KpiMetricsDTO = year2KpiMetricsDTOByMonth
                    .getOrDefault(yearMonth.getMonthValue(), Collections.emptyMap());

            // 组装 Target Value Content
            HotelKpiMetricsDTO targetYearMetricsDTO = year2KpiMetricsDTO.get(yearMonth.getYear());
            if (Objects.nonNull(targetYearMetricsDTO)) {
                analysisDTO.setValueContent(targetYearMetricsDTO.getExplain().getTarget().getValueContent());
            }

            List<HotelKpiAnalysisDetailDTO> actualList = Lists.newArrayList();
            for (long year : years) {
                HotelKpiAnalysisDetailDTO analysisDetailDTO = new HotelKpiAnalysisDetailDTO();
                analysisDetailDTO.setYear(year);
                HotelKpiMetricsDTO metricsDTO = year2KpiMetricsDTO.get((int) year);
                if (Objects.nonNull(metricsDTO)) {
                    // 组装其他年份的指标数值
                    hotelKpiService.packageMetricsValue(propertyDTO, metricsConfig, metricsDTO);

                    HotelKpiValueDTO actualKpiValueDTO = metricsDTO.getActual();
                    BigDecimal actual = hotelKpiService.getEffectiveValue(actualKpiValueDTO.getManual(),
                            actualKpiValueDTO.getSystem(), actualKpiValueDTO.getUpdatedBy());
                    analysisDetailDTO.setActual(actual);

                    // 设置 Actual 值内容
                    analysisDetailDTO.setValueContent(metricsDTO.getExplain().getActual().getValueContent());

                    // 获取 KPI 是否达标
                    Boolean isPass = explainConfigService.getKpiComparison(metricsConfig.getComparisonRule(),
                            actualKpiValueDTO, analysisDetailDTO.getActual(), analysisDTO.getTarget());
                    if (Objects.nonNull(isPass) && Objects.nonNull(isShow)) {
                        analysisDetailDTO.setPass(isPass && !isShow);
                        analysisDetailDTO.setExplainType(explainConfig.getFormula());
                        analysisDetailDTO.setExplainValue(targetExplainValue);
                        analysisDetailDTO.setShowExplain(isShow);
                    }
                }
                actualList.add(analysisDetailDTO);
            }
            analysisDTO.setActuals(actualList);

            return analysisDTO;
        }
    }