# HotelKpiQuarterlyServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.commonlib.util.YearMonthWeekUtils;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.HotelKpiDateTypeCons;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiMetricsConfig;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiQuarterly;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiWarningComment;
    import cloud.wconnect.workperformance.mapper.hotelkpi.old.HotelKpiQuarterlyMapper;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.HotelKpiDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.HotelKpiDetailDTO;
    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.dto.hotelkpi.old.originkpi.HotelKpiOriginDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.originkpi.HotelKpiOriginValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.setting.actual.HotelKpiMetricsSettingActualDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.setting.target.HotelKpiMetricsSettingTargetDTO;
    import cloud.wconnect.workperformance.pojo.query.hotelkpi.old.HotelKpiQuery;
    import cloud.wconnect.workperformance.service.hotelkpi.old.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.stereotype.Service;

    import jakarta.annotation.Resource;
    import java.time.YearMonth;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Hotel KPI Quarterly 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-06-24
     */
    @Service("hotelKpiQuarterlyServiceImpl_old")
    @DS("work_performance")
    public class HotelKpiQuarterlyServiceImpl extends ServiceImpl<HotelKpiQuarterlyMapper, HotelKpiQuarterly> implements IHotelKpiQuarterlyService {

        @Resource
        private IHotelKpiService hotelKpiService;
        @Resource
        private IHotelKpiMetricsSettingTargetService metricsSettingTargetService;
        @Resource
        private IHotelKpiMetricsSettingActualService metricsSettingActualService;
        @Resource
        private IHotelKpiMetricsConfigService metricsConfigService;
        @Resource
        private IHotelKpiMetricsSettingService metricsSettingService;
        @Resource
        private IHotelKpiWarningCommentService warningCommentService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private BeanMapper beanMapper;

        /**
         * [for update] 根据日期范围、指标类型集合 获取 KPI 原始数据
         *
         * @param propertyId   资产 id
         * @param startDate    开始日期
         * @param endDate      结束日期
         * @param metricsTypes 指标类型
         * @return HotelKpiQuarterly 集合
         * @author zc
         * @date 2024/6/25
         */
        @Override
        public List<HotelKpiQuarterly> listByDateRangeAndMetricsTypes(int propertyId, long startDate, long endDate, List<Integer> metricsTypes) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            return list(new LambdaQueryWrapper<HotelKpiQuarterly>()
                    .between(HotelKpiQuarterly::getMonth, startDate, endDate)
                    .in(HotelKpiQuarterly::getMetricsType, metricsTypes)
                    .eq(HotelKpiQuarterly::getPropertyId, propertyId)
                    .last("for update"));
        }

        /**
         * 获取 Hotel KPI 数据
         *
         * @param propertyDTO 资产
         * @param query       查询条件
         * @return Hotel KPI 数据
         * @author zc
         * @date 2024/6/27
         */
        @Override
        public List<HotelKpiDTO> listHotelKpi(PropertyDTO propertyDTO, HotelKpiQuery query) {
            // 获取有效的指标
            List<HotelKpiMetricsConfig> effectiveMetrics = metricsConfigService.listEffectiveMetricsOfYear(propertyDTO.getId(),
                    query.getDateType(), query.getStartDateLong(), query.getEndDateLong(), Boolean.TRUE);
            Map<Integer, List<HotelKpiMetricsConfig>> metricsParentId2Configs = effectiveMetrics.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsConfig::getParentId));

            // 获取去年最后一个季度的开始日期：年初到结束日期的数据用于计算【递减剩余】类型的指标 Target 值；多获取一季的数据用于指标数据的比较
            long lastQuarterOfLastYear = YearMonthWeekUtils.getFirstQuarterOfCurrentYear(query.getStartDateLong());

            // 提取非父级数据的指标配置
            List<Integer> metricsTypes = effectiveMetrics.stream().map(HotelKpiMetricsConfig::getMetricsType)
                    .filter(metricsType -> metricsType > 0).toList();

            // 获取 KPI 原始数据
            List<HotelKpiMetricsDTO> metricsDTOList = listKpiMetricsDTOList(propertyDTO.getId(), lastQuarterOfLastYear,
                    query.getEndDateLong(), metricsTypes);

            // 组装 Metrics Setting
            packageSettingValue(propertyDTO.getId(), metricsDTOList, metricsTypes, lastQuarterOfLastYear, query.getEndDateLong());

            // 获取操作人姓名
            Map<Integer, String> userId2Name = hotelKpiService.extractOperatorName(propertyDTO.getTenantId(), metricsDTOList);

            Map<Long, Map<Integer, HotelKpiMetricsDTO>> metricsType2KpiMetricsDTOByDate = metricsDTOList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsDTO::getDate,
                            Collectors.toMap(HotelKpiMetricsDTO::getMetricsType, Function.identity())));

            YearMonth startYearMonth = YearMonthUtils.getYearMonth(String.valueOf(query.getStartDateLong()));
            YearMonth endYearMonth = YearMonthUtils.getYearMonth(String.valueOf(query.getEndDateLong()));

            List<HotelKpiDTO> hotelKpiDTOList = Lists.newArrayList();

            // 获取指定日期警示备注数量
            Map<Long, Map<Integer, Long>> metricsType2WarningCountByDate = warningCommentService.listByDateRangeAndMetricsTypes(propertyDTO.getId(),
                            query.getDateType(), query.getStartDateLong(), query.getEndDateLong(), metricsTypes).stream()
                    .collect(Collectors.groupingBy(HotelKpiWarningComment::getDate,
                            Collectors.groupingBy(HotelKpiWarningComment::getMetricsType, Collectors.counting())));

            for (YearMonth indexYearMonth = startYearMonth; !indexYearMonth.isAfter(endYearMonth); indexYearMonth = indexYearMonth.plusMonths(3)) {
                // 获取当前季度的 KPI 原始数据
                long indexDate = YearMonthUtils.parseYearMonthToLong(indexYearMonth);
                Map<Integer, HotelKpiMetricsDTO> metricsType2KpiMetricsDTO = metricsType2KpiMetricsDTOByDate.getOrDefault(indexDate, Collections.emptyMap());

                // 获取上一季度的 KPI 原始数据
                long lastIndexDate = YearMonthWeekUtils.getLastQuarterDate(indexDate);
                Map<Integer, HotelKpiMetricsDTO> metricsType2LastKpiMetricsDTO = metricsType2KpiMetricsDTOByDate.getOrDefault(lastIndexDate, Collections.emptyMap());

                Map<Integer, Long> metricsType2WarningCount = metricsType2WarningCountByDate.getOrDefault(indexDate, Collections.emptyMap());

                // 生成 KPI 详情数据
                List<HotelKpiDetailDTO> kpiDetailDTOList = hotelKpiService.generateKpiDetailDTOList(propertyDTO, userId2Name, effectiveMetrics,
                        metricsParentId2Configs, metricsType2KpiMetricsDTO, metricsType2LastKpiMetricsDTO, metricsType2WarningCount,
                        indexDate, lastIndexDate, HotelKpiDateTypeCons.QUARTERLY);

                hotelKpiDTOList.add(HotelKpiDTO.builder()
                        .date(indexDate)
                        .details(kpiDetailDTOList).build());
            }
            return hotelKpiDTOList;
        }

        /**
         * 获取 KPI 原始数据
         *
         * @param propertyId   资产 id
         * @param startDate    开始日期
         * @param endDate      结束日期
         * @param metricsTypes 指标类型
         * @return HotelKpiMetricsDTO 集合
         * @author zc
         * @date 2024/6/27
         */
        @Override
        public List<HotelKpiMetricsDTO> listKpiMetricsDTOList(int propertyId, long startDate, long endDate, List<Integer> metricsTypes) {
            List<HotelKpiQuarterly> kpiQuarterlyList = listByDateRangeAndMetricsTypes(propertyId, startDate, endDate, metricsTypes);
            Map<Long, Map<Integer, HotelKpiQuarterly>> metricsType2KpiByDate = kpiQuarterlyList.stream()
                    .collect(Collectors.groupingBy(HotelKpiQuarterly::getMonth,
                            Collectors.toMap(HotelKpiQuarterly::getMetricsType, Function.identity())));

            YearMonth startYearMonth = YearMonthUtils.getYearMonth(String.valueOf(startDate));
            YearMonth endYearMonth = YearMonthUtils.getYearMonth(String.valueOf(endDate));

            List<HotelKpiMetricsDTO> kpiMetricsDTOList = Lists.newArrayList();
            for (YearMonth indexYearMonth = startYearMonth; !indexYearMonth.isAfter(endYearMonth); indexYearMonth = indexYearMonth.plusMonths(3)) {
                long indexDate = YearMonthUtils.parseYearMonthToLong(indexYearMonth);
                Map<Integer, HotelKpiQuarterly> metricsType2Kpi = metricsType2KpiByDate.getOrDefault(indexDate, Collections.emptyMap());
                for (Integer metricsType : metricsTypes) {
                    HotelKpiMetricsDTO kpiMetricsDTO = new HotelKpiMetricsDTO();
                    HotelKpiQuarterly kpiQuarterly = metricsType2Kpi.get(metricsType);
                    if (Objects.isNull(kpiQuarterly)) {
                        kpiMetricsDTO.setDate(indexDate);
                        kpiMetricsDTO.setMetricsType(metricsType);
                    } else {
                        HotelKpiOriginDetailDTO originDetailDTO = JsonUtils.convertStringToObject(objectMapper,
                                kpiQuarterly.getDetail(), HotelKpiOriginDetailDTO.class);
                        kpiMetricsDTO = beanMapper.map(kpiQuarterly, HotelKpiMetricsDTO.class, dto -> {
                            dto.setDate(kpiQuarterly.getMonth());
                            HotelKpiOriginValueDTO actual = originDetailDTO.getActual();
                            dto.setActual(beanMapper.map(actual, HotelKpiValueDTO.class, item -> item.setSystem(actual.getCrawl())));
                            HotelKpiOriginValueDTO target = originDetailDTO.getTarget();
                            dto.setTarget(beanMapper.map(target, HotelKpiValueDTO.class, item -> item.setSystem(target.getCrawl())));
                            HotelKpiOriginValueDTO cure = originDetailDTO.getCure();
                            dto.setCure(beanMapper.map(cure, HotelKpiValueDTO.class, item -> item.setSystem(cure.getCrawl())));
                        });
                    }
                    kpiMetricsDTOList.add(kpiMetricsDTO);
                }
            }
            return kpiMetricsDTOList;
        }

        /**
         * 组装 Target, Cure setting 数据
         *
         * @param propertyId     资产 id
         * @param metricsDTOList 指标数据 DTO 容器
         * @param metricsTypes   指标类型集合
         * @param startDate      开始日期
         * @param endDate        结束日期
         * @author zc
         * @date 2024/6/27
         */
        private void packageSettingValue(int propertyId, List<HotelKpiMetricsDTO> metricsDTOList,
                                         List<Integer> metricsTypes, long startDate, long endDate) {
            if (CollectionUtils.isEmpty(metricsDTOList)) {
                return;
            }
            // 获取 Actual 的设置数据
            List<HotelKpiMetricsSettingActualDTO> metricsSettingActualDTOS =
                    metricsSettingActualService.listQuarterlySettingByDateRange(propertyId, metricsTypes, startDate,
                            endDate, metricsDTOList);
            // 获取 Target、Cure 设置数据
            List<HotelKpiMetricsSettingTargetDTO> metricsSettingTargetDTOS =
                    metricsSettingTargetService.listQuarterlySettingByDateRange(propertyId, metricsTypes, startDate, endDate);
            // 组装 Metrics Setting
            metricsSettingService.packageMetricsSetting(metricsDTOList, metricsSettingActualDTOS, metricsSettingTargetDTOS);
        }

    }