# HotelKpiMetricsSettingTargetServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiDateTypeCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiSettingCategoryEnum;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiValueCategoryCons;
    import cloud.wconnect.workperformance.entity.hotelkpi.HotelKpiMetricsSetting;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.HotelKpiSettingCalculationDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.HotelKpiSettingCalculationFactorDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.HotelKpiSettingCalculationFactorSettingDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.target.HotelKpiMetricsSettingConstantTargetDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.target.HotelKpiMetricsSettingIndependentTargetDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.target.HotelKpiMetricsSettingTargetDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.target.HotelKpiMetricsSettingTargetValueDTO;
    import cloud.wconnect.workperformance.service.hotelkpi.IHotelKpiMetricsSettingService;
    import cloud.wconnect.workperformance.service.hotelkpi.IHotelKpiMetricsSettingTargetService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.time.DayOfWeek;
    import java.time.LocalDate;
    import java.time.YearMonth;
    import java.time.temporal.ChronoUnit;
    import java.time.temporal.TemporalAdjusters;
    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 Target Setting 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-06-24
     */
    @Service
    @DS("work_performance")
    public class HotelKpiMetricsSettingTargetServiceImpl implements IHotelKpiMetricsSettingTargetService {
        @Resource
        private IHotelKpiMetricsSettingService metricsSettingService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        private static final int VALUE_CATEGORY = HotelKpiValueCategoryCons.TARGET;

        /**
         * 根据日期范围获取周设置
         *
         * @param propertyId    资产 id
         * @param metricsTypes  指标类型
         * @param startDateLong 开始日期
         * @param endDateLong   结束日期
         * @param dayOfWeek     周几
         * @return HotelKpiMetricsSettingTargetDTO 集合
         * @author zc
         * @date 2024/6/26
         */
        @Override
        public List<HotelKpiMetricsSettingTargetDTO> listWeeklySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong,
                                                                                  long endDateLong, DayOfWeek dayOfWeek) {
            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));

            List<HotelKpiMetricsSettingTargetDTO> settingValueDTOList = Lists.newArrayList();
            for (LocalDate indexLocalDate = startLocalDate; !indexLocalDate.isAfter(endLocalDate); indexLocalDate = indexLocalDate.plusDays(7)) {
                List<HotelKpiMetricsSetting> settingListByYear = year2SettingList.get((long) indexLocalDate.getYear());
                if (CollectionUtils.isEmpty(settingListByYear)) {
                    continue;
                }
                for (HotelKpiMetricsSetting metricsSetting : settingListByYear) {
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.WEEKLY, indexLocalDate,
                            metricsSetting, dayOfWeek));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 根据日期范围获取月设置
         *
         * @param propertyId    资产 id
         * @param metricsTypes  指标类型
         * @param startDateLong 开始日期
         * @param endDateLong   结束日期
         * @param dayOfWeek     周几
         * @return HotelKpiMetricsSettingTargetDTO 集合
         * @author zc
         * @date 2024/6/27
         */
        @Override
        public List<HotelKpiMetricsSettingTargetDTO> listMonthlySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong,
                                                                                   long endDateLong, DayOfWeek dayOfWeek) {
            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));

            List<HotelKpiMetricsSettingTargetDTO> 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) {
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.MONTHLY, indexLocalDate,
                            metricsSetting, dayOfWeek));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 根据日期范围获取季设置
         *
         * @param propertyId    资产 id
         * @param metricsTypes  指标类型
         * @param startDateLong 开始日期
         * @param endDateLong   结束日期
         * @param dayOfWeek     周几
         * @return HotelKpiMetricsSettingTargetDTO 集合
         * @author zc
         * @date 2024/6/27
         */
        @Override
        public List<HotelKpiMetricsSettingTargetDTO> listQuarterlySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong,
                                                                                     long endDateLong, DayOfWeek dayOfWeek) {
            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));

            List<HotelKpiMetricsSettingTargetDTO> 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) {
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.QUARTERLY, indexLocalDate,
                            metricsSetting, dayOfWeek));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 根据日期范围获取年设置
         *
         * @param propertyId    资产 id
         * @param metricsTypes  指标类型
         * @param startDateLong 开始日期
         * @param endDateLong   结束日期
         * @param dayOfWeek     周几
         * @return HotelKpiMetricsSettingTargetDTO 集合
         * @author zc
         * @date 2024/6/27
         */
        @Override
        public List<HotelKpiMetricsSettingTargetDTO> listYearlySettingByDateRange(int propertyId, List<Integer> metricsTypes, long startDateLong,
                                                                                  long endDateLong, DayOfWeek dayOfWeek) {
            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));

            List<HotelKpiMetricsSettingTargetDTO> 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 metricsSetting : settingListByYear) {
                    settingValueDTOList.add(convertToSettingValueDTO(HotelKpiDateTypeCons.YEARLY, indexLocalDate,
                            metricsSetting, dayOfWeek));
                }
            }
            return settingValueDTOList;
        }

        /**
         * 转换设置值
         *
         * @param dateType  日期类型
         * @param localDate 当前日期
         * @param setting   设置
         * @param dayOfWeek 周几
         * @return HotelKpiMetricsSettingTargetDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiMetricsSettingTargetDTO convertToSettingValueDTO(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting,
                                                                         DayOfWeek dayOfWeek) {
            int category = setting.getSettingCategory();
            if (HotelKpiSettingCategoryEnum.CONSTANT.getCategory() == category) {
                return extractionForConstant(dateType, localDate, setting);
            } else if (HotelKpiSettingCategoryEnum.ACCUMULATION.getCategory() == category) {
                return extractionForAccumulation(dateType, localDate, setting, dayOfWeek);
            } else if (HotelKpiSettingCategoryEnum.DYNAMIC.getCategory() == category) {
                return extractionForDynamic(dateType, localDate, setting);
            } else {
                return extractionForIndependent(dateType, localDate, setting, dayOfWeek);
            }
        }

        /**
         * 提取设置值 for Constant（恒定）
         *
         * @param dateType  日期类型
         * @param localDate 日期
         * @param setting   设置值
         * @return HotelKpiMetricsSettingTargetDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiMetricsSettingTargetDTO extractionForConstant(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting) {
            HotelKpiMetricsSettingConstantTargetDTO settingConstantDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingConstantTargetDTO.class);
            HotelKpiMetricsSettingTargetValueDTO 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 HotelKpiMetricsSettingTargetDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .target(settingValueDTO.getTarget())
                    .cure(settingValueDTO.getCure())
                    .build();
        }

        /**
         * 提取设置值 for Accumulation（累计）
         *
         * @param dateType  日期类型
         * @param localDate 日期
         * @param setting   设置值
         * @param dayOfWeek 周几
         * @return HotelKpiMetricsSettingTargetDTO
         * @author zc
         * @date 2025/2/25
         */
        private HotelKpiMetricsSettingTargetDTO extractionForAccumulation(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting,
                                                                          DayOfWeek dayOfWeek) {
            HotelKpiMetricsSettingConstantTargetDTO settingConstantDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingConstantTargetDTO.class);
            BigDecimal weekTarget = settingConstantDTO.getWeek().getTarget();
            BigDecimal weekCure = settingConstantDTO.getWeek().getCure();

            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();
            }
            int weekStartDayCount = DateTimeUtils.getWeekStartDayCount(localDate, dayOfWeek, dateType);
            BigDecimal targetValue = Objects.isNull(weekTarget) ? null : weekTarget.multiply(new BigDecimal(String.valueOf(weekStartDayCount)));
            BigDecimal cureValue = Objects.isNull(weekCure) ? null : weekCure.multiply(new BigDecimal(String.valueOf(weekStartDayCount)));
            return HotelKpiMetricsSettingTargetDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .target(targetValue)
                    .cure(cureValue)
                    .build();
        }

        /**
         * 提取设置值 for Dynamic（动态）
         *
         * @param setting 设置值
         * @return HotelKpiMetricsSettingTargetDTO
         * @author zc
         * @date 2024/6/28
         */
        private HotelKpiMetricsSettingTargetDTO extractionForDynamic(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting) {
            HotelKpiMetricsSettingConstantTargetDTO settingConstantDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingConstantTargetDTO.class);
            HotelKpiMetricsSettingTargetValueDTO 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 HotelKpiMetricsSettingTargetDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .cure(settingValueDTO.getCure())
                    .build();
        }

        /**
         * 提取设置值 for Independent（独立）
         *
         * @param dateType  日期类型
         * @param localDate 日期
         * @param setting   设置值
         * @param dayOfWeek 周几
         * @return HotelKpiMetricsSettingTargetDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiMetricsSettingTargetDTO extractionForIndependent(int dateType, LocalDate localDate, HotelKpiMetricsSetting setting, DayOfWeek dayOfWeek) {
            HotelKpiMetricsSettingIndependentTargetDTO settingIndependentDTO = JsonUtils.convertStringToObject(objectMapper,
                    setting.getDetail(), HotelKpiMetricsSettingIndependentTargetDTO.class);
            int index;
            long date;
            List<HotelKpiMetricsSettingTargetValueDTO> settingValueDTOList;
            if (HotelKpiDateTypeCons.WEEKLY == dateType) {
                LocalDate firstDayOfWeekInYear = LocalDate.of(localDate.getYear(), 1, 1)
                        .with(TemporalAdjusters.nextOrSame(dayOfWeek));
                index = (int) ChronoUnit.DAYS.between(firstDayOfWeekInYear, localDate) / DayOfWeek.values().length + 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();
            }
            Map<Integer, HotelKpiMetricsSettingTargetValueDTO> index2Setting = settingValueDTOList.stream()
                    .collect(Collectors.toMap(HotelKpiMetricsSettingTargetValueDTO::getIndex, Function.identity()));

            HotelKpiMetricsSettingTargetValueDTO settingValueDTO = index2Setting.getOrDefault(index, new HotelKpiMetricsSettingTargetValueDTO());
            return HotelKpiMetricsSettingTargetDTO.builder()
                    .date(date)
                    .metricsType(setting.getMetricsType())
                    .index(setting.getIndex())
                    .settingCategory(setting.getSettingCategory())
                    .target(settingValueDTO.getTarget())
                    .cure(settingValueDTO.getCure())
                    .build();
        }

        /**
         * 校验 settingValue 的格式
         *
         * @param settingDTO 指标 setting DTO
         * @author zc
         * @date 2024/6/29
         */
        @Override
        public void checkSettingValueFormat(HotelKpiSettingCalculationDTO settingDTO) {
            List<HotelKpiSettingCalculationFactorDTO> calculationFactor = settingDTO.getCalculationFactor();
            try {
                for (HotelKpiSettingCalculationFactorDTO factor : calculationFactor) {
                    HotelKpiSettingCalculationFactorSettingDTO valueSetting = factor.getValueSetting();

                    if (HotelKpiSettingCategoryEnum.CONSTANT.getCategory() == valueSetting.getCategory()
                            || HotelKpiSettingCategoryEnum.ACCUMULATION.getCategory() == valueSetting.getCategory()
                            || HotelKpiSettingCategoryEnum.DYNAMIC.getCategory() == valueSetting.getCategory()) {
                        JsonUtils.convertStringToObject(objectMapper, valueSetting.getSettingValue(),
                                HotelKpiMetricsSettingConstantTargetDTO.class);
                    } else {
                        JsonUtils.convertStringToObject(objectMapper, valueSetting.getSettingValue(),
                                HotelKpiMetricsSettingIndependentTargetDTO.class);
                    }
                }
            } catch (Exception e) {
                throw new BadRequestException(CommonErrorI18nConstant.FORMAT_ERROR,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("settingValue"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The settingValue structure is wrong format, exception:" + e.getMessage());
            }
        }
    }