# HotelKpiMetricsSettingActualServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    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.cons.hotelkpi.old.HotelKpiSettingCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.HotelKpiValueCategoryCons;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiMetricsSetting;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.HotelKpiCalculationFactorDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.HotelKpiMetricsDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.setting.actual.*;
    import cloud.wconnect.workperformance.service.hotelkpi.old.IHotelKpiMetricsSettingActualService;
    import cloud.wconnect.workperformance.service.hotelkpi.old.IHotelKpiMetricsSettingService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.stereotype.Service;

    import jakarta.annotation.Resource;
    import java.math.BigDecimal;
    import java.time.LocalDate;
    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 Metrics Setting Actual 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-08-06
     */
    @Service("hotelKpiMetricsSettingActualServiceImpl_old")
    @DS("work_performance")
    public class HotelKpiMetricsSettingActualServiceImpl implements IHotelKpiMetricsSettingActualService {
        @Resource
        private IHotelKpiMetricsSettingService metricsSettingService;
        @Resource
        private ObjectMapper objectMapper;

        private static final int VALUE_CATEGORY = HotelKpiValueCategoryCons.ACTUAL;

        /**
         * 根据日期范围获取周设置
         *
         * @param propertyId            资产 id
         * @param metricsTypes          指标类型
         * @param startDateLong         开始日期
         * @param endDateLong           结束日期
         * @param metricsDTOMonthlyList Monthly 指标数据集合
         * @return HotelKpiMetricsSettingActualDTO 集合
         * @author zc
         * @date 2024/8/6
         */
        @Override
        public List<HotelKpiMetricsSettingActualDTO> listWeeklySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong, long endDateLong,
                                                                                  List<HotelKpiMetricsDTO> metricsDTOMonthlyList) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            LocalDate startLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(startDateLong), BaseConstant.DATE_FORMAT_YYYYMMDD);
            LocalDate endLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(endDateLong), BaseConstant.DATE_FORMAT_YYYYMMDD);

            List<HotelKpiMetricsSetting> settingList = metricsSettingService.listByYearRangeAndMetricsTypes(propertyId,
                    metricsTypes, startLocalDate.getYear(), endLocalDate.getYear(), VALUE_CATEGORY);
            if (CollectionUtils.isEmpty(settingList)) {
                return Collections.emptyList();
            }
            Map<Long, List<HotelKpiMetricsSetting>> year2SettingList = settingList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsSetting::getYear));

            Map<Integer, List<HotelKpiMetricsDTO>> metricsType2MetricsMonthlyList = metricsDTOMonthlyList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsDTO::getMetricsType));

            List<HotelKpiMetricsSettingActualDTO> settingValueDTOList = Lists.newArrayList();
            for (LocalDate indexLocalDate = startLocalDate; !indexLocalDate.isAfter(endLocalDate); indexLocalDate = (indexLocalDate.getDayOfMonth() >= 22) ?
                    indexLocalDate.plusMonths(1).withDayOfMonth(1) : indexLocalDate.plusDays(7)) {
                List<HotelKpiMetricsSetting> settingListByYear = year2SettingList.get((long) indexLocalDate.getYear());
                if (CollectionUtils.isEmpty(settingListByYear)) {
                    continue;
                }
                for (HotelKpiMetricsSetting metricsSetting : settingListByYear) {
                    List<HotelKpiMetricsDTO> metricsMonthlyList = metricsType2MetricsMonthlyList.getOrDefault(metricsSetting.getMetricsType(), Collections.emptyList());
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.WEEKLY, indexLocalDate, metricsSetting,
                            Collections.emptyList(), metricsMonthlyList));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 根据日期范围获取月设置
         *
         * @param propertyId     资产 id
         * @param metricsTypes   指标类型
         * @param startDateLong  开始日期
         * @param endDateLong    结束日期
         * @param metricsDTOList 指标数据
         * @return HotelKpiMetricsSettingActualDTO 集合
         * @author zc
         * @date 2024/8/6
         */
        @Override
        public List<HotelKpiMetricsSettingActualDTO> listMonthlySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong, long endDateLong,
                                                                                   List<HotelKpiMetricsDTO> metricsDTOList) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            LocalDate startLocalDate = YearMonthUtils.getYearMonth(String.valueOf(startDateLong)).atDay(1);
            LocalDate endLocalDate = YearMonthUtils.getYearMonth(String.valueOf(endDateLong)).atDay(1);

            List<HotelKpiMetricsSetting> settingList = metricsSettingService.listByYearRangeAndMetricsTypes(propertyId,
                    metricsTypes, startLocalDate.getYear(), endLocalDate.getYear(), VALUE_CATEGORY);
            if (CollectionUtils.isEmpty(settingList)) {
                return Collections.emptyList();
            }
            Map<Long, List<HotelKpiMetricsSetting>> year2SettingList = settingList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsSetting::getYear));

            Map<Integer, List<HotelKpiMetricsDTO>> metricsType2MetricsList = metricsDTOList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsDTO::getMetricsType));

            List<HotelKpiMetricsSettingActualDTO> settingValueDTOList = Lists.newArrayList();
            for (LocalDate indexLocalDate = startLocalDate; !indexLocalDate.isAfter(endLocalDate); indexLocalDate = indexLocalDate.plusMonths(1)) {
                List<HotelKpiMetricsSetting> settingListByYear = year2SettingList.get((long) indexLocalDate.getYear());
                if (CollectionUtils.isEmpty(settingListByYear)) {
                    continue;
                }
                for (HotelKpiMetricsSetting metricsSetting : settingListByYear) {
                    List<HotelKpiMetricsDTO> metricsList = metricsType2MetricsList.getOrDefault(metricsSetting.getMetricsType(), Collections.emptyList());
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.MONTHLY, indexLocalDate, metricsSetting,
                            metricsList, Collections.emptyList()));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 根据日期范围获取季设置
         *
         * @param propertyId     资产 id
         * @param metricsTypes   指标类型
         * @param startDateLong  开始日期
         * @param endDateLong    结束日期
         * @param metricsDTOList 指标数据
         * @return HotelKpiMetricsSettingActualDTO 集合
         * @author zc
         * @date 2024/8/6
         */
        @Override
        public List<HotelKpiMetricsSettingActualDTO> listQuarterlySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong, long endDateLong,
                                                                                     List<HotelKpiMetricsDTO> metricsDTOList) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            LocalDate startLocalDate = YearMonthUtils.getYearMonth(String.valueOf(startDateLong)).atDay(1);
            LocalDate endLocalDate = YearMonthUtils.getYearMonth(String.valueOf(endDateLong)).atDay(1);

            List<HotelKpiMetricsSetting> settingList = metricsSettingService.listByYearRangeAndMetricsTypes(propertyId,
                    metricsTypes, startLocalDate.getYear(), endLocalDate.getYear(), VALUE_CATEGORY);
            if (CollectionUtils.isEmpty(settingList)) {
                return Collections.emptyList();
            }
            Map<Long, List<HotelKpiMetricsSetting>> year2SettingList = settingList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsSetting::getYear));

            Map<Integer, List<HotelKpiMetricsDTO>> metricsType2MetricsList = metricsDTOList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsDTO::getMetricsType));

            List<HotelKpiMetricsSettingActualDTO> settingValueDTOList = Lists.newArrayList();
            for (LocalDate indexLocalDate = startLocalDate; !indexLocalDate.isAfter(endLocalDate); indexLocalDate = indexLocalDate.plusMonths(3)) {
                List<HotelKpiMetricsSetting> settingListByYear = year2SettingList.get((long) indexLocalDate.getYear());
                if (CollectionUtils.isEmpty(settingListByYear)) {
                    continue;
                }
                for (HotelKpiMetricsSetting metricsSetting : settingListByYear) {
                    List<HotelKpiMetricsDTO> metricsList = metricsType2MetricsList.getOrDefault(metricsSetting.getMetricsType(), Collections.emptyList());
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.QUARTERLY, indexLocalDate, metricsSetting,
                            metricsList, Collections.emptyList()));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 根据日期范围获取年设置
         *
         * @param propertyId     资产 id
         * @param metricsTypes   指标类型
         * @param startDateLong  开始日期
         * @param endDateLong    结束日期
         * @param metricsDTOList 指标数据
         * @return HotelKpiMetricsSettingActualDTO 集合
         * @author zc
         * @date 2024/8/6
         */
        @Override
        public List<HotelKpiMetricsSettingActualDTO> listYearlySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong, long endDateLong,
                                                                                  List<HotelKpiMetricsDTO> metricsDTOList) {
            if (CollectionUtils.isEmpty(metricsTypes)) {
                return Collections.emptyList();
            }
            LocalDate startLocalDate = YearMonth.of((int) startDateLong, 1).atDay(1);
            LocalDate endLocalDate = YearMonth.of((int) endDateLong, 1).atDay(1);

            List<HotelKpiMetricsSetting> settingList = metricsSettingService.listByYearRangeAndMetricsTypes(propertyId,
                    metricsTypes, startLocalDate.getYear(), endLocalDate.getYear(), VALUE_CATEGORY);
            if (CollectionUtils.isEmpty(settingList)) {
                return Collections.emptyList();
            }
            Map<Long, List<HotelKpiMetricsSetting>> year2SettingList = settingList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsSetting::getYear));

            Map<Integer, List<HotelKpiMetricsDTO>> metricsType2MetricsList = metricsDTOList.stream()
                    .collect(Collectors.groupingBy(HotelKpiMetricsDTO::getMetricsType));

            List<HotelKpiMetricsSettingActualDTO> settingValueDTOList = Lists.newArrayList();
            for (LocalDate indexLocalDate = startLocalDate; !indexLocalDate.isAfter(endLocalDate); indexLocalDate = indexLocalDate.plusYears(1)) {
                List<HotelKpiMetricsSetting> settingListByYear = year2SettingList.get((long) indexLocalDate.getYear());
                if (CollectionUtils.isEmpty(settingListByYear)) {
                    continue;
                }
                for (HotelKpiMetricsSetting targetSetting : settingListByYear) {
                    List<HotelKpiMetricsDTO> metricsList = metricsType2MetricsList.getOrDefault(targetSetting.getMetricsType(), Collections.emptyList());
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.YEARLY, indexLocalDate, targetSetting,
                            metricsList, Collections.emptyList()));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 转换设置值
         *
         * @param dateType              日期类型
         * @param localDate             当前日期
         * @param setting               设置
         * @param metricsDTOList        指标数值集合
         * @param monthlyMetricsDTOList Monthly 指标数值集合
         * @return HotelKpiMetricsSettingActualDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiMetricsSettingActualDTO convertToSettingValueDTO(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting,
                                                                         List<HotelKpiMetricsDTO> metricsDTOList, List<HotelKpiMetricsDTO> monthlyMetricsDTOList) {
            int category = setting.getSettingCategory();
            if (HotelKpiSettingCons.CONSTANT == category) {
                return extractionForConstantOrAccumulation(dateType, localDate, setting);
            } else if (HotelKpiSettingCons.ACCUMULATION == category) {
                return extractionForConstantOrAccumulation(dateType, localDate, setting);
            } else if (HotelKpiSettingCons.DYNAMIC == category) {
                return extractionForDynamic(dateType, localDate, setting);
            } else if (HotelKpiSettingCons.INDEPENDENT == category) {
                return extractionForIndependent(dateType, localDate, setting);
            } else {
                return extractionForDiminishingSurplus(dateType, localDate, setting, metricsDTOList, monthlyMetricsDTOList);
            }
        }

        /**
         * 提取设置值 for Constant or Accumulation（恒定、累加）
         *
         * @param dateType  日期类型
         * @param localDate 日期
         * @param setting   设置值
         * @return HotelKpiMetricsSettingActualDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiMetricsSettingActualDTO extractionForConstantOrAccumulation(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting) {
            HotelKpiMetricsSettingConstantActualDTO settingConstantDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingConstantActualDTO.class);
            HotelKpiMetricsSettingActualValueDTO settingValueDTO;
            long date;
            if (HotelKpiDateTypeCons.WEEKLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                settingValueDTO = settingConstantDTO.getWeek();
            } else if (HotelKpiDateTypeCons.MONTHLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));
                settingValueDTO = settingConstantDTO.getMonth();
            } else if (HotelKpiDateTypeCons.QUARTERLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));
                settingValueDTO = settingConstantDTO.getQuarter();
            } else {
                date = localDate.getYear();
                settingValueDTO = settingConstantDTO.getYear();
            }
            return HotelKpiMetricsSettingActualDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .value(settingValueDTO.getValue())
                    .build();
        }

        /**
         * 提取设置值 for Dynamic（动态）
         *
         * @param setting 设置值
         * @return HotelKpiMetricsSettingActualDTO
         * @author zc
         * @date 2024/6/28
         */
        private HotelKpiMetricsSettingActualDTO extractionForDynamic(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting) {
            long date;
            if (HotelKpiDateTypeCons.WEEKLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            } else if (HotelKpiDateTypeCons.MONTHLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));
            } else if (HotelKpiDateTypeCons.QUARTERLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));
            } else {
                date = localDate.getYear();
            }
            return HotelKpiMetricsSettingActualDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .build();
        }

        /**
         * 提取设置值 for Independent（独立）
         *
         * @param dateType  日期类型
         * @param localDate 日期
         * @param setting   设置值
         * @return HotelKpiMetricsSettingActualDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiMetricsSettingActualDTO extractionForIndependent(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting) {
            HotelKpiMetricsSettingIndependentActualDTO settingIndependentDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingIndependentActualDTO.class);
            int index;
            long date;
            List<HotelKpiMetricsSettingActualValueDTO> settingValueDTOList;
            if (HotelKpiDateTypeCons.WEEKLY == dateType) {
                index = (localDate.getMonthValue() - 1) * 4 + (localDate.getDayOfMonth() / 7) + 1;
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                settingValueDTOList = settingIndependentDTO.getWeek();
            } else if (HotelKpiDateTypeCons.MONTHLY == dateType) {
                index = localDate.getMonthValue();
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));
                settingValueDTOList = settingIndependentDTO.getMonth();
            } else if (HotelKpiDateTypeCons.QUARTERLY == dateType) {
                index = (localDate.getMonthValue() - 1) / 3 + 1;
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));
                settingValueDTOList = settingIndependentDTO.getQuarter();
            } else {
                index = 1;
                date = localDate.getYear();
                settingValueDTOList = settingIndependentDTO.getYear();
            }
            HotelKpiMetricsSettingActualValueDTO settingValueDTO = settingValueDTOList.stream()
                    .collect(Collectors.toMap(HotelKpiMetricsSettingActualValueDTO::getIndex,
                            Function.identity())).getOrDefault(index, new HotelKpiMetricsSettingActualValueDTO());
            return HotelKpiMetricsSettingActualDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .value(settingValueDTO.getValue())
                    .build();
        }

        /**
         * 提取设置值 for DiminishingSurplus（递减剩余）
         *
         * @param dateType              日期类型
         * @param localDate             日期
         * @param setting               设置值
         * @param metricsDTOList        指标数据
         * @param monthlyMetricsDTOList Monthly 指标数值集合
         * @return HotelKpiMetricsSettingActualDTO
         * @author zc
         * @date 2024/8/1
         */
        private HotelKpiMetricsSettingActualDTO extractionForDiminishingSurplus(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting,
                                                                                List<HotelKpiMetricsDTO> metricsDTOList, List<HotelKpiMetricsDTO> monthlyMetricsDTOList) {
            HotelKpiMetricsSettingDiminishingSurplusActualDTO settingDiminishingSurplusDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingDiminishingSurplusActualDTO.class);

            Integer monthRange = settingDiminishingSurplusDTO.getRange();
            BigDecimal totalValue = settingDiminishingSurplusDTO.getValue();
            if (Objects.isNull(monthRange) || Objects.isNull(totalValue)) {
                return new HotelKpiMetricsSettingActualDTO();
            }
            int totalTargetPerCycle = totalValue.intValue();

            long date;
            int calculationTarget;
            if (HotelKpiDateTypeCons.WEEKLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

                Map<Integer, HotelKpiMetricsDTO> month2MetricsDTO = monthlyMetricsDTOList.stream().collect(Collectors.toMap(item ->
                        YearMonthUtils.getYearMonth(item.getDate().toString()).getMonthValue(), Function.identity()));
                // 计算当前周期的计算因子 Target
                calculationTarget = calculateWeekTargetValue(localDate, totalTargetPerCycle, monthRange, month2MetricsDTO);
            } else if (HotelKpiDateTypeCons.MONTHLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));

                Map<Integer, HotelKpiMetricsDTO> month2MetricsDTO = metricsDTOList.stream().collect(Collectors.toMap(item ->
                        YearMonthUtils.getYearMonth(item.getDate().toString()).getMonthValue(), Function.identity()));
                // 计算当前月份的计算因子 Target
                calculationTarget = calculateMonthTargetValue(localDate, totalTargetPerCycle, monthRange, month2MetricsDTO);
            } else if (HotelKpiDateTypeCons.QUARTERLY == dateType) {
                date = Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMM));

                Map<Integer, HotelKpiMetricsDTO> month2MetricsDTO = metricsDTOList.stream().collect(Collectors.toMap(item ->
                        YearMonthUtils.getYearMonth(item.getDate().toString()).getMonthValue(), Function.identity()));
                // 计算当前月份的计算因子 Target
                calculationTarget = calculateQuarterTargetValue(localDate, totalTargetPerCycle, monthRange, month2MetricsDTO);
            } else {
                date = localDate.getYear();
                calculationTarget = totalTargetPerCycle * (12 / monthRange);
            }
            BigDecimal value = new BigDecimal(String.valueOf(calculationTarget));
            return HotelKpiMetricsSettingActualDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    // 若小于0，则置为0
                    .value(value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value)
                    .build();
        }

        /**
         * 计算指定周期的目标值 for DiminishingSurplus（递减剩余）
         *
         * @param localDate           当前日期
         * @param totalTargetPerCycle 周期的总目标值
         * @param cycleLength         周期大小
         * @param month2MetricsDTO    指标数值
         * @return 指定月份目标值
         * @author zc
         * @date 2024/12/23
         */
        private int calculateWeekTargetValue(LocalDate localDate, int totalTargetPerCycle, int cycleLength, Map<Integer, HotelKpiMetricsDTO> month2MetricsDTO) {
            int numberOfMonthlyWeeks = 4;

            // 计算当前月份的目标值
            int targetValue = calculateMonthTargetValue(localDate, totalTargetPerCycle, cycleLength, month2MetricsDTO);
            int avgTarget = targetValue / numberOfMonthlyWeeks;
            int remainder = targetValue % numberOfMonthlyWeeks;
            Map<Integer, Integer> weekly2TargetValue = Maps.newHashMap();
            for (int i = 1; i <= numberOfMonthlyWeeks; i++) {
                weekly2TargetValue.put(i, weekly2TargetValue.getOrDefault(i - 1, 0) + avgTarget + ((remainder > 0) ? 1 : 0));
                remainder--;
            }
            // 计算出第几周
            int weekIndex = YearMonthWeekUtils.calculateCustomWeekOfMonth(localDate);
            return weekly2TargetValue.get(weekIndex);
        }

        /**
         * 计算指定月份的目标值 for DiminishingSurplus（递减剩余）
         *
         * @param localDate           当前日期
         * @param totalTargetPerCycle 周期的总目标值
         * @param cycleLength         周期大小
         * @param month2MetricsDTO    指标数值
         * @return 指定月份目标值
         * @author zc
         * @date 2024/12/20
         */
        private int calculateMonthTargetValue(LocalDate localDate, int totalTargetPerCycle, int cycleLength, Map<Integer, HotelKpiMetricsDTO> month2MetricsDTO) {
            List<Integer> dayOfMonthList = Lists.newArrayList(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

            int month = localDate.getMonthValue();
            int indexOfCycleLength = (month % cycleLength == 0) ? cycleLength : (month % cycleLength);
            // 减去周期内当前月之前已完成的实际值
            for (int i = indexOfCycleLength - 1; i > 0; i--) {
                HotelKpiMetricsDTO metricsDTO = month2MetricsDTO.getOrDefault(month - i, new HotelKpiMetricsDTO());
                BigDecimal actualValue = getCalculationFactorIndex1(metricsDTO);
                totalTargetPerCycle = totalTargetPerCycle - actualValue.intValue();
            }
            totalTargetPerCycle = Math.max(0, totalTargetPerCycle);

            // 周期内剩余月份平均值
            int avgTarget = totalTargetPerCycle / (cycleLength - (indexOfCycleLength - 1));
            // 剩余的目标值除以周期内剩余的月份
            int remainder = totalTargetPerCycle % (cycleLength - (indexOfCycleLength - 1));
            // 截取周期内当前月及之后的月份
            List<Integer> currentMonthDateList = dayOfMonthList.subList(month - 1, month + cycleLength - indexOfCycleLength);
            Map<Integer, Integer> monthly2TargetValue = Maps.newHashMap();
            while (remainder > 0) {
                for (int i = 0; i < currentMonthDateList.size(); i++) {
                    if (remainder == 0) {
                        break;
                    }
                    int monthly = month + i;
                    int currentAvg = monthly2TargetValue.getOrDefault(monthly, avgTarget);
                    if (currentMonthDateList.get(i) == 31) {
                        currentAvg++;
                        remainder--;
                    }
                    monthly2TargetValue.put(monthly, currentAvg);
                }
            }
            return monthly2TargetValue.getOrDefault(month, avgTarget);
        }

        /**
         * 获取计算因子 index 1
         *
         * @param metricsDTO 指标数据
         * @return java.math.BigDecimal
         * @author zc
         * @date 2024/8/11
         */
        private BigDecimal getCalculationFactorIndex1(HotelKpiMetricsDTO metricsDTO) {
            return metricsDTO.getActual().getCalculationFactor().stream().filter(item -> item.getIndex() == 1).findFirst()
                    .map(HotelKpiCalculationFactorDTO::getCrawl).orElse(BigDecimal.ZERO);
        }

        /**
         * 计算指定季度的目标值 for DiminishingSurplus（递减剩余）
         *
         * @param localDate           当前日期
         * @param totalTargetPerCycle 周期的总目标值
         * @param cycleLength         周期大小
         * @param month2MetricsDTO    指标数值
         * @return 指定月份目标值
         * @author zc
         * @date 2024/12/21
         */
        private int calculateQuarterTargetValue(LocalDate localDate, int totalTargetPerCycle, int cycleLength, Map<Integer, HotelKpiMetricsDTO> month2MetricsDTO) {
            int month = localDate.getMonthValue();
            int target;
            if (cycleLength > 3) {
                int quarterCycleLength = cycleLength / 3;
                // 日期所在第几季度
                int quarterIndexOfYear = YearMonthWeekUtils.calculateQuarterOfYear(localDate);
                int indexOfCycleLength = (quarterIndexOfYear % quarterCycleLength == 0) ? quarterCycleLength : (quarterIndexOfYear % quarterCycleLength);
                // 减去周期内当前季度之前已完成的实际值
                for (int i = indexOfCycleLength - 1; i > 0; i--) {
                    HotelKpiMetricsDTO metricsDTO = month2MetricsDTO.getOrDefault(month - i * 3, new HotelKpiMetricsDTO());
                    BigDecimal actualValue = getCalculationFactorIndex1(metricsDTO);
                    totalTargetPerCycle = totalTargetPerCycle - actualValue.intValue();
                }
                totalTargetPerCycle = Math.max(0, totalTargetPerCycle);
                // 周期内剩余季度平均值
                int avgTarget = totalTargetPerCycle / (quarterCycleLength - (indexOfCycleLength - 1));
                // 剩余的目标值除以周期内剩余的季度
                int remainder = totalTargetPerCycle % (quarterCycleLength - (indexOfCycleLength - 1));
                Map<Integer, Integer> quarterly2TargetValue = Maps.newHashMap();
                while (remainder > 0) {
                    for (int i = 0; i < (quarterCycleLength - indexOfCycleLength + 1); i++) {
                        if (remainder == 0) {
                            break;
                        }
                        int quarterly = month + i * 3;
                        int currentAvg = quarterly2TargetValue.getOrDefault(quarterly, avgTarget);
                        currentAvg++;
                        remainder--;
                        quarterly2TargetValue.put(quarterly, currentAvg);
                    }
                }
                target = quarterly2TargetValue.getOrDefault(month, avgTarget);
            } else {
                target = totalTargetPerCycle * (3 / cycleLength);
            }
            return target;
        }
    }