# HotelKpiYearlyServiceImpl.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.workperformance.cons.hotelkpi.old.HotelKpiDateTypeCons;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiMetricsConfig;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiWarningComment;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiYearly;
    import cloud.wconnect.workperformance.mapper.hotelkpi.old.HotelKpiYearlyMapper;
    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.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 Yearly 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-06-24
     */
    @Service("hotelKpiYearlyServiceImpl_old")
    @DS("work_performance")
    public class HotelKpiYearlyServiceImpl extends ServiceImpl<HotelKpiYearlyMapper, HotelKpiYearly> implements IHotelKpiYearlyService {
        @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    开始日期(yyyy)
         * @param endDate      结束日期(yyyy)
         * @param metricsTypes 指标类型集合
         * @return HotelKpiYearly 集合
         * @author zc
         * @date 2024/8/6
         */
        @Override
        public List<HotelKpiYearly> listByDateRangeAndMetricsTypes(int propertyId, long startDate, long endDate, List<Integer> metricsTypes) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            return list(new LambdaQueryWrapper<HotelKpiYearly>()
                    .between(HotelKpiYearly::getYear, startDate, endDate)
                    .in(HotelKpiYearly::getMetricsType, metricsTypes)
                    .eq(HotelKpiYearly::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));

            // 多获取前一年的数据用于设置对比结果
            long lastYear = query.getStartDateLong() - 1;

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

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

            // 组装 Metrics Setting
            packageSettingValue(propertyDTO.getId(), metricsDTOList, metricsTypes, lastYear, 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())));

            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 (long indexYear = query.getStartDateLong(); indexYear <= query.getEndDateLong(); indexYear++) {
                // 获取当前年的 KPI 原始数据
                Map<Integer, HotelKpiMetricsDTO> metricsType2KpiMetricsDTO = metricsType2KpiMetricsDTOByDate.getOrDefault(indexYear, Collections.emptyMap());

                // 获取上一年的 KPI 原始数据
                long lastIndexYear = indexYear - 1;
                Map<Integer, HotelKpiMetricsDTO> metricsType2LastKpiMetricsDTO = metricsType2KpiMetricsDTOByDate.getOrDefault(lastIndexYear, Collections.emptyMap());

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

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

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

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

            List<HotelKpiMetricsDTO> kpiMetricsDTOList = Lists.newArrayList();
            for (long indexYear = startDate; indexYear <= endDate; indexYear++) {
                Map<Integer, HotelKpiYearly> metricsType2Kpi = metricsType2KpiByDate.getOrDefault(indexYear, Collections.emptyMap());
                for (Integer metricsType : metricsTypes) {
                    HotelKpiMetricsDTO kpiMetricsDTO = new HotelKpiMetricsDTO();
                    HotelKpiYearly kpiYearly = metricsType2Kpi.get(metricsType);
                    if (Objects.isNull(kpiYearly)) {
                        kpiMetricsDTO.setDate(indexYear);
                        kpiMetricsDTO.setMetricsType(metricsType);
                    } else {
                        HotelKpiOriginDetailDTO originDetailDTO = JsonUtils.convertStringToObject(objectMapper,
                                kpiYearly.getDetail(), HotelKpiOriginDetailDTO.class);
                        kpiMetricsDTO = beanMapper.map(kpiYearly, HotelKpiMetricsDTO.class, dto -> {
                            dto.setDate(kpiYearly.getYear());
                            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/26
         */
        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.listYearlySettingByDateRange(propertyId, metricsTypes, startDate,
                            endDate, metricsDTOList);
            // 获取 Target、Cure 设置数据
            List<HotelKpiMetricsSettingTargetDTO> metricsSettingTargetDTOS =
                    metricsSettingTargetService.listYearlySettingByDateRange(propertyId, metricsTypes, startDate, endDate);
            // 组装 Metrics Setting
            metricsSettingService.packageMetricsSetting(metricsDTOList, metricsSettingActualDTOS, metricsSettingTargetDTOS);
        }

    }