# HotelKpiMonthlyServiceImpl.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.HotelKpiMonthly;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiWarningComment;
    import cloud.wconnect.workperformance.mapper.hotelkpi.old.HotelKpiMonthlyMapper;
    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;
    import java.util.stream.IntStream;

    /**
     * <p>
     * Hotel KPI Monthly 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-06-24
     */
    @Service("hotelKpiMonthlyServiceImpl_old")
    @DS("work_performance")
    public class HotelKpiMonthlyServiceImpl extends ServiceImpl<HotelKpiMonthlyMapper, HotelKpiMonthly> implements IHotelKpiMonthlyService {

        @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    开始日期(yyyyMM)
         * @param endDate      结束日期(yyyyMM)
         * @param metricsTypes 指标类型集合
         * @return HotelKpiMonthly 集合
         * @author zc
         * @date 2024/6/25
         */
        @Override
        public List<HotelKpiMonthly> listByDateRangeAndMetricsTypes(int propertyId, long startDate, long endDate, List<Integer> metricsTypes) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            return list(new LambdaQueryWrapper<HotelKpiMonthly>()
                    .between(HotelKpiMonthly::getMonth, startDate, endDate)
                    .in(HotelKpiMonthly::getMetricsType, metricsTypes)
                    .eq(HotelKpiMonthly::getPropertyId, propertyId)
                    .last("for update"));
        }

        /**
         * 获取 Hotel KPI 数据
         *
         * @param propertyDTO 资产
         * @param query       查询条件
         * @return Hotel KPI 数据
         * @author zc
         * @date 2024/6/26
         */
        @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 lastMonthOfLastYear = YearMonthWeekUtils.getFirstMonthOfCurrentYear(query.getStartDateLong());

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

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

            // 组装 Metrics Setting
            packageSettingValue(propertyDTO.getId(), metricsDTOList, metricsTypes, lastMonthOfLastYear, 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(1)) {
                // 获取当前月的 KPI 原始数据
                long indexDate = YearMonthUtils.parseYearMonthToLong(indexYearMonth);
                Map<Integer, HotelKpiMetricsDTO> metricsType2KpiMetricsDTO = metricsType2KpiMetricsDTOByDate.getOrDefault(indexDate, Collections.emptyMap());

                // 获取上一月的 KPI 原始数据
                long lastIndexDate = YearMonthWeekUtils.getLastMonthDate(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.MONTHLY);

                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<HotelKpiMonthly> kpiMonthlyList = listByDateRangeAndMetricsTypes(propertyId, startDate, endDate, metricsTypes);
            // 转换数据为 DTO
            return convertToMetricsDTOList(kpiMonthlyList, startDate, endDate, metricsTypes);
        }

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

        /**
         * 根据年份集合、指标类型查询指标数据
         *
         * @param propertyId  资产 id
         * @param years       年份集合(yyyy)
         * @param metricsType 指标类型
         * @return HotelKpiMetricsDTO 集合
         * @author zc
         * @date 2024/6/28
         */
        @Override
        public List<HotelKpiMetricsDTO> listKpiMetricsDTOListByYears(int propertyId, List<Long> years, int metricsType) {
            if (CollectionUtils.isEmpty(years)) {
                return Collections.emptyList();
            }
            List<Long> monthlyDates = years.stream().flatMap(year -> IntStream.rangeClosed(1, 12).mapToObj(month ->
                    YearMonth.of(year.intValue(), month))).map(YearMonthUtils::parseYearMonthToLong).toList();

            List<HotelKpiMonthly> kpiMonthlyList = list(new LambdaQueryWrapper<HotelKpiMonthly>()
                    .in(HotelKpiMonthly::getMonth, monthlyDates)
                    .eq(HotelKpiMonthly::getMetricsType, metricsType)
                    .eq(HotelKpiMonthly::getPropertyId, propertyId));

            // 转换数据为 DTO
            long startDate = Collections.min(monthlyDates);
            long endDate = Collections.max(monthlyDates);
            return convertToMetricsDTOList(kpiMonthlyList, startDate, endDate, List.of(metricsType));
        }

        /**
         * 转换数据为 DTO
         *
         * @param kpiMonthlyList 指标数据
         * @param startDate      开始日期(yyyyMM)
         * @param endDate        结束日期(yyyyMM)
         * @param metricsTypes   指标类型
         * @return HotelKpiMetricsDTO 集合
         * @author zc
         * @date 2024/6/28
         */
        private List<HotelKpiMetricsDTO> convertToMetricsDTOList(List<HotelKpiMonthly> kpiMonthlyList,
                                                                 long startDate, long endDate, List<Integer> metricsTypes) {
            Map<Long, Map<Integer, HotelKpiMonthly>> metricsType2KpiByDate = kpiMonthlyList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMonthly::getMonth,
                            Collectors.toMap(HotelKpiMonthly::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(1)) {
                long indexDate = YearMonthUtils.parseYearMonthToLong(indexYearMonth);
                Map<Integer, HotelKpiMonthly> metricsType2Kpi = metricsType2KpiByDate.getOrDefault(indexDate, Collections.emptyMap());
                for (Integer metricsType : metricsTypes) {
                    HotelKpiMetricsDTO kpiMetricsDTO = new HotelKpiMetricsDTO();
                    HotelKpiMonthly kpiMonthly = metricsType2Kpi.get(metricsType);
                    if (Objects.isNull(kpiMonthly)) {
                        kpiMetricsDTO.setDate(indexDate);
                        kpiMetricsDTO.setMetricsType(metricsType);
                    } else {
                        HotelKpiOriginDetailDTO originDetailDTO = JsonUtils.convertStringToObject(objectMapper,
                                kpiMonthly.getDetail(), HotelKpiOriginDetailDTO.class);
                        kpiMetricsDTO = beanMapper.map(kpiMonthly, HotelKpiMetricsDTO.class, dto -> {
                            dto.setDate(kpiMonthly.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;
        }
    }