# HotelKpiServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.HotelKpiDateTypeCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.HotelKpiMetricsEnum;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.comparison.HotelKpiJudgmentCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.comparison.HotelKpiTrendCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.old.formula.HotelKpiFormulaActualCons;
    import cloud.wconnect.workperformance.entity.hotelkpi.old.HotelKpiMetricsConfig;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.*;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.explain.HotelKpiExplainMainDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.kpi.explain.HotelKpiExplainValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.metricsconfig.HotelKpiCalculationFactorConfigDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.metricsconfig.HotelKpiCalculationFactorConfigIndexDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.old.metricsconfig.HotelKpiCalculationFactorConfigItemDTO;
    import cloud.wconnect.workperformance.service.hotelkpi.old.IHotelKpiService;
    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.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * Service: Hotel KPI
     *
     * @author zc
     * @date 2024/06/24
     */
    @Service("hotelKpiServiceImpl_old")
    @DS("work_performance")
    public class HotelKpiServiceImpl implements IHotelKpiService {

        @Resource
        private UserInternalService userInternalService;
        @Resource
        private ObjectMapper objectMapper;

        /**
         * 提取有效值
         *
         * @param valueByManual 手动录入值
         * @param valueBySystem 系统录入值
         * @param operatorId    操作人 id
         * @return 有效值
         * @author zc
         * @date 2024/7/3
         */
        @Override
        public BigDecimal getEffectiveValue(BigDecimal valueByManual, BigDecimal valueBySystem, Integer operatorId) {
            if (Objects.isNull(operatorId) || operatorId == 0) {
                return Objects.nonNull(valueByManual) ? valueByManual : valueBySystem;
            } else {
                return valueByManual;
            }
        }

        /**
         * 获取 KPI 对比结果
         *
         * @param currentMetricsDTO 当前周期的 kpi DTO
         * @param lastMetricsDTO    上个周期的 kpi DTO
         * @param comparisonRule    对比规则
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/6/26
         */
        @Override
        public HotelKpiComparisonDTO getKpiComparison(HotelKpiMetricsDTO currentMetricsDTO, HotelKpiMetricsDTO lastMetricsDTO, String comparisonRule) {
            HotelKpiComparisonRuleDTO rule = JsonUtils.convertStringToObject(objectMapper,
                    comparisonRule, HotelKpiComparisonRuleDTO.class);
            // 若比较规则关闭，则不参与比较
            if (!rule.getOn()) {
                return new HotelKpiComparisonDTO();
            }
            String actualGreaterTarget = rule.getActualGreaterTarget();
            String actualGreaterLast = rule.getActualGreaterLast();

            if (StringUtils.isBlank(actualGreaterTarget) && StringUtils.isBlank(actualGreaterLast)) {
                return new HotelKpiComparisonDTO();
            } else if (StringUtils.isBlank(actualGreaterLast)) {
                if (HotelKpiJudgmentCons.GOOD.equals(actualGreaterTarget)) {
                    return getKpiComparisonActualVSTargetGreater(currentMetricsDTO);
                } else {
                    return getKpiComparisonActualVSTargetLess(currentMetricsDTO);
                }
            } else if (StringUtils.isBlank(actualGreaterTarget)) {
                if (HotelKpiJudgmentCons.GOOD.equals(actualGreaterLast)) {
                    return getKpiComparisonActualVSLastGreater(currentMetricsDTO, lastMetricsDTO);
                } else {
                    return getKpiComparisonActualVSLastLess(currentMetricsDTO, lastMetricsDTO);
                }
            } else {
                if (HotelKpiJudgmentCons.GOOD.equals(actualGreaterTarget)) {
                    if (HotelKpiJudgmentCons.GOOD.equals(actualGreaterLast)) {
                        return getKpiComparisonActualVSTargetGreater(currentMetricsDTO);
                    } else {
                        return getKpiComparisonActualVSTargetGreaterAndLastLess(currentMetricsDTO, lastMetricsDTO);
                    }
                } else {
                    if (HotelKpiJudgmentCons.GOOD.equals(actualGreaterLast)) {
                        return getKpiComparisonActualVSTargetLessAndLastGreater(currentMetricsDTO, lastMetricsDTO);
                    } else {
                        return getKpiComparisonActualVSTargetLess(currentMetricsDTO);
                    }
                }
            }
        }

        /**
         * 获取对比结果 for 【Actual vs Target greater and Actual vs Last less】
         *
         * @param currentMetricsDTO 当前周期的 kpi DTO
         * @param lastMetricsDTO    上个周期的 kpi DTO
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/8/7
         */
        private HotelKpiComparisonDTO getKpiComparisonActualVSTargetGreaterAndLastLess(HotelKpiMetricsDTO currentMetricsDTO, HotelKpiMetricsDTO lastMetricsDTO) {
            HotelKpiValueDTO currentActualValueDTO = currentMetricsDTO.getActual();
            BigDecimal currentActual = getEffectiveValue(currentActualValueDTO.getManual(), currentActualValueDTO.getSystem(), currentActualValueDTO.getUpdatedBy());

            HotelKpiValueDTO currentTargetValueDTO = currentMetricsDTO.getTarget();
            BigDecimal currentTarget = getEffectiveValue(currentTargetValueDTO.getManual(), currentTargetValueDTO.getSystem(), currentTargetValueDTO.getUpdatedBy());

            HotelKpiValueDTO currentCureValueDTO = currentMetricsDTO.getCure();
            BigDecimal currentCure = getEffectiveValue(currentCureValueDTO.getManual(), currentCureValueDTO.getSystem(), currentCureValueDTO.getUpdatedBy());

            HotelKpiComparisonDTO kpiComparisonDTO = new HotelKpiComparisonDTO();
            if (Objects.isNull(currentActual) || Objects.isNull(currentTarget)) {
                return kpiComparisonDTO;
            }
            if (currentActual.compareTo(currentTarget) > 0) {
                HotelKpiValueDTO lastActualValueDTO = lastMetricsDTO.getActual();
                BigDecimal lastActual = getEffectiveValue(lastActualValueDTO.getManual(), lastActualValueDTO.getSystem(), lastActualValueDTO.getUpdatedBy());
                if (Objects.isNull(lastActual)) {
                    return kpiComparisonDTO;
                }

                if (currentActual.compareTo(lastActual) < 0) {
                    kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                    kpiComparisonDTO.setTrend(HotelKpiTrendCons.DECREASE);
                } else if (currentActual.compareTo(lastActual) == 0) {
                    kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                    kpiComparisonDTO.setTrend(HotelKpiTrendCons.STABLE);
                } else {
                    kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
                    kpiComparisonDTO.setTrend(HotelKpiTrendCons.INCREASE);
                }
            } else if (currentActual.compareTo(currentTarget) == 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
            } else if (Objects.nonNull(currentCure) && currentActual.compareTo(currentCure) >= 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.PASS);
            } else {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
            }
            return kpiComparisonDTO;
        }

        /**
         * 获取对比结果 for 【Actual vs Target less and Actual vs Last greater】
         *
         * @param currentMetricsDTO 当前周期的 kpi DTO
         * @param lastMetricsDTO    上个周期的 kpi DTO
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/7/18
         */
        private HotelKpiComparisonDTO getKpiComparisonActualVSTargetLessAndLastGreater(HotelKpiMetricsDTO currentMetricsDTO, HotelKpiMetricsDTO lastMetricsDTO) {
            HotelKpiValueDTO currentActualValueDTO = currentMetricsDTO.getActual();
            BigDecimal currentActual = getEffectiveValue(currentActualValueDTO.getManual(), currentActualValueDTO.getSystem(), currentActualValueDTO.getUpdatedBy());

            HotelKpiValueDTO currentTargetValueDTO = currentMetricsDTO.getTarget();
            BigDecimal currentTarget = getEffectiveValue(currentTargetValueDTO.getManual(), currentTargetValueDTO.getSystem(), currentTargetValueDTO.getUpdatedBy());

            HotelKpiValueDTO currentCureValueDTO = currentMetricsDTO.getCure();
            BigDecimal currentCure = getEffectiveValue(currentCureValueDTO.getManual(), currentCureValueDTO.getSystem(), currentCureValueDTO.getUpdatedBy());

            HotelKpiComparisonDTO kpiComparisonDTO = new HotelKpiComparisonDTO();
            if (Objects.isNull(currentActual) || Objects.isNull(currentTarget)) {
                return kpiComparisonDTO;
            }
            if (currentActual.compareTo(currentTarget) < 0) {
                HotelKpiValueDTO lastActualValueDTO = lastMetricsDTO.getActual();
                BigDecimal lastActual = getEffectiveValue(lastActualValueDTO.getManual(), lastActualValueDTO.getSystem(), lastActualValueDTO.getUpdatedBy());
                if (Objects.isNull(lastActual)) {
                    return kpiComparisonDTO;
                }

                if (currentActual.compareTo(lastActual) > 0) {
                    kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                    kpiComparisonDTO.setTrend(HotelKpiTrendCons.INCREASE);
                } else if (currentActual.compareTo(lastActual) == 0) {
                    kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                    kpiComparisonDTO.setTrend(HotelKpiTrendCons.STABLE);
                } else {
                    kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
                    kpiComparisonDTO.setTrend(HotelKpiTrendCons.DECREASE);
                }
            } else if (currentActual.compareTo(currentTarget) == 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
            } else if (Objects.nonNull(currentCure) && currentActual.compareTo(currentCure) <= 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.PASS);
            } else {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
            }
            return kpiComparisonDTO;
        }

        /**
         * 获取对比结果 for 【Actual vs Target greater】
         *
         * @param metricsDTO 指标 kpi DTO
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiComparisonDTO getKpiComparisonActualVSTargetGreater(HotelKpiMetricsDTO metricsDTO) {
            HotelKpiValueDTO actualValueDTO = metricsDTO.getActual();
            BigDecimal actual = getEffectiveValue(actualValueDTO.getManual(), actualValueDTO.getSystem(), actualValueDTO.getUpdatedBy());

            HotelKpiValueDTO targetValueDTO = metricsDTO.getTarget();
            BigDecimal target = getEffectiveValue(targetValueDTO.getManual(), targetValueDTO.getSystem(), targetValueDTO.getUpdatedBy());

            HotelKpiValueDTO cureValueDTO = metricsDTO.getCure();
            BigDecimal cure = getEffectiveValue(cureValueDTO.getManual(), cureValueDTO.getSystem(), cureValueDTO.getUpdatedBy());

            HotelKpiComparisonDTO kpiComparisonDTO = new HotelKpiComparisonDTO();
            if (Objects.isNull(target)) {
                return kpiComparisonDTO;
            }
            if (Objects.isNull(actual)) {
                kpiComparisonDTO.setJudgment(getJudgmentWhenActualIsNull(metricsDTO.getMetricsType(), actualValueDTO.getCalculationFactor()));
                return kpiComparisonDTO;
            }
            if (actual.compareTo(target) >= 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
            } else if (Objects.nonNull(cure) && actual.compareTo(cure) >= 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.PASS);
            } else {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
            }
            return kpiComparisonDTO;
        }

        /**
         * 获取对比结果 for 【Actual vs Target less】
         *
         * @param metricsDTO 指标 kpi DTO
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiComparisonDTO getKpiComparisonActualVSTargetLess(HotelKpiMetricsDTO metricsDTO) {
            HotelKpiValueDTO actualValueDTO = metricsDTO.getActual();
            BigDecimal actual = getEffectiveValue(actualValueDTO.getManual(), actualValueDTO.getSystem(), actualValueDTO.getUpdatedBy());

            HotelKpiValueDTO targetValueDTO = metricsDTO.getTarget();
            BigDecimal target = getEffectiveValue(targetValueDTO.getManual(), targetValueDTO.getSystem(), targetValueDTO.getUpdatedBy());

            HotelKpiValueDTO cureValueDTO = metricsDTO.getCure();
            BigDecimal cure = getEffectiveValue(cureValueDTO.getManual(), cureValueDTO.getSystem(), cureValueDTO.getUpdatedBy());

            HotelKpiComparisonDTO kpiComparisonDTO = new HotelKpiComparisonDTO();
            if (Objects.isNull(target)) {
                return kpiComparisonDTO;
            }
            if (Objects.isNull(actual)) {
                kpiComparisonDTO.setJudgment(getJudgmentWhenActualIsNull(metricsDTO.getMetricsType(), actualValueDTO.getCalculationFactor()));
                return kpiComparisonDTO;
            }
            if (actual.compareTo(target) <= 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
            } else if (Objects.nonNull(cure) && actual.compareTo(cure) <= 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.PASS);
            } else {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
            }
            return kpiComparisonDTO;
        }

        /**
         * 获取判断结果 when actual is null
         *
         * @param metricsType       指标类型
         * @param calculationFactor 计算因子
         * @return java.lang.String
         * @author zc
         * @date 2024/12/19
         */
        private String getJudgmentWhenActualIsNull(int metricsType, List<HotelKpiCalculationFactorDTO> calculationFactor) {
            HotelKpiCalculationFactorDTO calculationFactorDTO = calculationFactor.stream()
                    .filter(item -> item.getIndex() == 2).findFirst().orElse(null);
            if (Objects.nonNull(calculationFactorDTO) && Objects.nonNull(calculationFactorDTO.getCrawl())) {
                if (calculationFactorDTO.getCrawl().compareTo(BigDecimal.ZERO) == 0) {
                    List<Integer> meetTypeList = Lists.newArrayList(HotelKpiMetricsEnum.QUORE_COMPLAINT.getMetricsType(),
                            HotelKpiMetricsEnum.QUORE_GR_PM.getMetricsType(), HotelKpiMetricsEnum.QUORE_DEEP_CLEAN.getMetricsType());
                    if (meetTypeList.contains(metricsType)) {
                        return HotelKpiJudgmentCons.GOOD;
                    }
                }
            }
            return null;
        }

        /**
         * 获取对比结果 for 【Actual vs Last greater】
         *
         * @param currentMetricsDTO 当前周期的 kpi DTO
         * @param lastMetricsDTO    上个周期的 kpi DTO
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/6/26
         */
        private HotelKpiComparisonDTO getKpiComparisonActualVSLastGreater(HotelKpiMetricsDTO currentMetricsDTO, HotelKpiMetricsDTO lastMetricsDTO) {
            HotelKpiValueDTO currentActualValueDTO = currentMetricsDTO.getActual();
            BigDecimal currentActual = getEffectiveValue(currentActualValueDTO.getManual(), currentActualValueDTO.getSystem(), currentActualValueDTO.getUpdatedBy());

            HotelKpiValueDTO lastActualValueDTO = lastMetricsDTO.getActual();
            BigDecimal lastActual = getEffectiveValue(lastActualValueDTO.getManual(), lastActualValueDTO.getSystem(), lastActualValueDTO.getUpdatedBy());

            HotelKpiComparisonDTO kpiComparisonDTO = new HotelKpiComparisonDTO();
            if (Objects.isNull(currentActual) || Objects.isNull(lastActual)) {
                return kpiComparisonDTO;
            }

            if (currentActual.compareTo(lastActual) > 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                kpiComparisonDTO.setTrend(HotelKpiTrendCons.INCREASE);
            } else if (currentActual.compareTo(lastActual) == 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                kpiComparisonDTO.setTrend(HotelKpiTrendCons.STABLE);
            } else {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
                kpiComparisonDTO.setTrend(HotelKpiTrendCons.DECREASE);
            }
            return kpiComparisonDTO;
        }

        /**
         * 获取对比结果 for 【Actual vs Last less】
         *
         * @param currentMetricsDTO 当前周期的 kpi DTO
         * @param lastMetricsDTO    上个周期的 kpi DTO
         * @return HotelKpiComparisonDTO
         * @author zc
         * @date 2024/7/25
         */
        private HotelKpiComparisonDTO getKpiComparisonActualVSLastLess(HotelKpiMetricsDTO currentMetricsDTO, HotelKpiMetricsDTO lastMetricsDTO) {
            HotelKpiValueDTO currentActualValueDTO = currentMetricsDTO.getActual();
            BigDecimal currentActual = getEffectiveValue(currentActualValueDTO.getManual(), currentActualValueDTO.getSystem(), currentActualValueDTO.getUpdatedBy());

            HotelKpiValueDTO lastActualValueDTO = lastMetricsDTO.getActual();
            BigDecimal lastActual = getEffectiveValue(lastActualValueDTO.getManual(), lastActualValueDTO.getSystem(), lastActualValueDTO.getUpdatedBy());

            HotelKpiComparisonDTO kpiComparisonDTO = new HotelKpiComparisonDTO();
            if (Objects.isNull(currentActual) || Objects.isNull(lastActual)) {
                return kpiComparisonDTO;
            }

            if (currentActual.compareTo(lastActual) < 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                kpiComparisonDTO.setTrend(HotelKpiTrendCons.DECREASE);
            } else if (currentActual.compareTo(lastActual) == 0) {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.GOOD);
                kpiComparisonDTO.setTrend(HotelKpiTrendCons.STABLE);
            } else {
                kpiComparisonDTO.setJudgment(HotelKpiJudgmentCons.BAD);
                kpiComparisonDTO.setTrend(HotelKpiTrendCons.INCREASE);
            }
            return kpiComparisonDTO;
        }

        /**
         * 提取操作人名称
         *
         * @param tenantId       租户 id
         * @param metricsDTOList kpi 指标数据
         * @return 操作人名称
         * @author zc
         * @date 2024/6/25
         */
        @Override
        public Map<Integer, String> extractOperatorName(int tenantId, List<HotelKpiMetricsDTO> metricsDTOList) {
            if (CollectionUtils.isEmpty(metricsDTOList)) {
                return Collections.emptyMap();
            }
            Set<Integer> userIds = metricsDTOList.stream().flatMap(dto ->
                    dto.summaryUserIds().stream()).collect(Collectors.toSet());
            return userInternalService.getNameMapByIds(tenantId, userIds);
        }

        /**
         * 生成指标 KPI 数据
         *
         * @param propertyDTO                   资产
         * @param userId2Name                   操作人名称
         * @param metricsConfigs                指标配置
         * @param metricsType2KpiMetricsDTO     当前 KPI 数据
         * @param metricsType2LastKpiMetricsDTO 上一 KPI 数据
         * @param metricsType2WarningCount      警示备注数量数据
         * @param currentDate                   当前日期
         * @param lastDate                      上一日期
         * @param dateType                      日期类型（1:week 2:month 3:quarter 4:year）
         * @return HotelKpiMetricsDTO
         * @author zc
         * @date 2024/6/26
         */
        @Override
        public List<HotelKpiDetailDTO> generateKpiDetailDTOList(PropertyDTO propertyDTO, Map<Integer, String> userId2Name,
                                                                List<HotelKpiMetricsConfig> metricsConfigs,
                                                                Map<Integer, List<HotelKpiMetricsConfig>> metricsParentId2Configs,
                                                                Map<Integer, HotelKpiMetricsDTO> metricsType2KpiMetricsDTO,
                                                                Map<Integer, HotelKpiMetricsDTO> metricsType2LastKpiMetricsDTO,
                                                                Map<Integer, Long> metricsType2WarningCount,
                                                                long currentDate, long lastDate, int dateType) {
            return metricsConfigs.stream()
                    .filter(metrics -> metrics.getParentId() == 0)
                    .sorted(Comparator.comparing(HotelKpiMetricsConfig::getSort))
                    .map(parent0Metrics -> {
                        List<HotelKpiItemDTO> items = metricsParentId2Configs.getOrDefault(parent0Metrics.getId(), Collections.emptyList()).stream()
                                .sorted(Comparator.comparing(HotelKpiMetricsConfig::getSort))
                                .map(parent1Metrics -> {
                                    List<HotelKpiMetricsDTO> metrics = metricsParentId2Configs.getOrDefault(parent1Metrics.getId(), Collections.emptyList()).stream()
                                            .sorted(Comparator.comparing(HotelKpiMetricsConfig::getSort))
                                            .map(childMetrics -> {
                                                HotelKpiMetricsDTO currentMetricsDTO = metricsType2KpiMetricsDTO.getOrDefault(childMetrics.getMetricsType(), new HotelKpiMetricsDTO());
                                                // 组装指标 DTO
                                                packageMetricsDTO(propertyDTO, currentDate, dateType, childMetrics, currentMetricsDTO);

                                                HotelKpiMetricsDTO lastMetricsDTO = metricsType2LastKpiMetricsDTO.getOrDefault(childMetrics.getMetricsType(), new HotelKpiMetricsDTO());
                                                // 组装指标 DTO
                                                packageMetricsDTO(propertyDTO, lastDate, dateType, childMetrics, lastMetricsDTO);

                                                // 组装警示备注数量
                                                currentMetricsDTO.setWarningCount(metricsType2WarningCount.getOrDefault(childMetrics.getMetricsType(), 0L));
                                                // 当警示备注数量为0时，才需要设置对比结果
                                                if (currentMetricsDTO.getWarningCount() == 0) {
                                                    // 获取对比结果
                                                    HotelKpiComparisonDTO comparison = getKpiComparison(currentMetricsDTO, lastMetricsDTO, childMetrics.getComparisonRule());
                                                    currentMetricsDTO.setComparison(comparison);
                                                }

                                                // 组装操作人名称
                                                HotelKpiValueDTO actual = currentMetricsDTO.getActual();
                                                actual.setUpdatedName(userId2Name.get(actual.getUpdatedBy()));
                                                HotelKpiValueDTO target = currentMetricsDTO.getTarget();
                                                target.setUpdatedName(userId2Name.get(target.getUpdatedBy()));
                                                HotelKpiValueDTO cure = currentMetricsDTO.getCure();
                                                cure.setUpdatedName(userId2Name.get(cure.getUpdatedBy()));

                                                return currentMetricsDTO;
                                            }).toList();
                                    return HotelKpiItemDTO.builder()
                                            .id(parent1Metrics.getId())
                                            .title(parent1Metrics.getTitle())
                                            .metrics(metrics)
                                            .build();
                                }).toList();
                        return HotelKpiDetailDTO.builder()
                                .id(parent0Metrics.getId())
                                .title(parent0Metrics.getTitle())
                                .items(items).build();
                    }).toList();
        }

        /**
         * 组装指标 DTO
         *
         * @param propertyDTO   资产
         * @param date          日期
         * @param dateType      日期类型（1:week 2:month 3:quarter 4:year）
         * @param metricsConfig 指标配置
         * @param metricsDTO    待组装 DTO
         * @author zc
         * @date 2024/12/14
         */
        private void packageMetricsDTO(PropertyDTO propertyDTO, long date, int dateType,
                                       HotelKpiMetricsConfig metricsConfig, HotelKpiMetricsDTO metricsDTO) {
            metricsDTO.setDate(date);
            metricsDTO.setMetricsType(metricsConfig.getMetricsType());
            metricsDTO.setTitle(metricsConfig.getTitle());
            metricsDTO.setDescription(metricsConfig.getDescription());
            metricsDTO.setDisabled(metricsDisabled(dateType, date, metricsConfig.getDisabledDate()));
            metricsDTO.setUnit(metricsConfig.getUnit());
            // 组装指标数值
            packageMetricsValue(propertyDTO, metricsConfig, metricsDTO);
        }

        /**
         * 根据日期类型校验指标是否禁用
         *
         * @param dateType            日期类型（1:week 2:month 3:quarter 4:year）
         * @param date                日期(yyyyMMdd,yyyyMM,yyyy)
         * @param metricsDisabledDate 指标禁用日期（yyyyMMdd）
         * @return java.lang.Boolean
         * @author zc
         * @date 2024/7/4
         */
        private Boolean metricsDisabled(int dateType, long date, long metricsDisabledDate) {
            if (HotelKpiDateTypeCons.WEEKLY == dateType) {
                return date > metricsDisabledDate;
            } else if (HotelKpiDateTypeCons.MONTHLY == dateType
                    || HotelKpiDateTypeCons.QUARTERLY == dateType) {
                long dateLong = Long.parseLong(DateTimeUtils.localDateToStr(YearMonthUtils.getYearMonth(String.valueOf(date)).atDay(1),
                        BaseConstant.DATE_FORMAT_YYYYMMDD));
                return dateLong > metricsDisabledDate;
            } else {
                long dateLong = Long.parseLong(DateTimeUtils.localDateToStr(YearMonth.of((int) date, 1)
                        .atDay(1), BaseConstant.DATE_FORMAT_YYYYMMDD));
                return dateLong > metricsDisabledDate;
            }
        }

        /**
         * 组装指标数值
         *
         * @param propertyDTO   资产
         * @param metricsConfig 指标配置
         * @param metricsDTO    指标数值
         * @author zc
         * @date 2024/12/14
         */
        @Override
        public void packageMetricsValue(PropertyDTO propertyDTO, HotelKpiMetricsConfig metricsConfig, HotelKpiMetricsDTO metricsDTO) {
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                    .getInternationalization().getNumber().getCalculationRule());

            // 计算因子配置
            HotelKpiCalculationFactorConfigDTO configDTO = JsonUtils.convertStringToObject(objectMapper,
                    metricsConfig.getCalculationFactorConfig(), HotelKpiCalculationFactorConfigDTO.class);

            // 计算 Actual System Value
            setSystemValue(configDTO.getActual(), metricsDTO.getActual(), roundingMode);
            // 计算 Target System Value
            setSystemValue(configDTO.getTarget(), metricsDTO.getTarget(), roundingMode);
            // 计算 Cure System Value
            setSystemValue(configDTO.getTarget(), metricsDTO.getCure(), roundingMode);

            HotelKpiExplainMainDTO explainMainDTO = metricsDTO.getExplain();
            // 设置指标 Actual Explain Value
            packageKpiExplainValue(configDTO.getActual(), metricsDTO.getActual(), explainMainDTO.getActual());
            // 设置指标 Target Explain Value
            packageKpiExplainValue(configDTO.getTarget(), metricsDTO.getTarget(), explainMainDTO.getTarget());
        }

        /**
         * 设置系统值
         *
         * @param factorConfigItemDTO 计算因子配置
         * @param hotelKpiValueDTO    指标值
         * @param roundingMode        舍入模式
         * @author zc
         * @date 2025/1/14
         */
        private void setSystemValue(HotelKpiCalculationFactorConfigItemDTO factorConfigItemDTO,
                                    HotelKpiValueDTO hotelKpiValueDTO, RoundingMode roundingMode) {
            List<HotelKpiCalculationFactorDTO> calculationFactorDTOList = hotelKpiValueDTO.getCalculationFactor();
            BigDecimal systemValue = null;
            if (factorConfigItemDTO.getCalculationFactor().size() == 1) {
                systemValue = calculationFactorDTOList.stream().filter(item ->
                        item.getIndex() == 1).findFirst().orElse(new HotelKpiCalculationFactorDTO()).getCrawl();
            } else {
                BigDecimal index1Value = calculationFactorDTOList.stream().filter(item ->
                        item.getIndex() == 1).findFirst().orElse(new HotelKpiCalculationFactorDTO()).getCrawl();
                BigDecimal index2Value = calculationFactorDTOList.stream().filter(item ->
                        item.getIndex() == 2).findFirst().orElse(new HotelKpiCalculationFactorDTO()).getCrawl();
                if (Objects.nonNull(index1Value) && Objects.nonNull(index2Value) && index2Value.compareTo(BigDecimal.ZERO) != 0) {
                    if (factorConfigItemDTO.getPercent()) {
                        systemValue = index1Value.multiply(new BigDecimal("100")).divide(index2Value, BaseConstant.DATA_PRECISION, roundingMode);
                    } else {
                        systemValue = index1Value.divide(index2Value, BaseConstant.DATA_PRECISION, roundingMode);
                    }
                }
            }
            hotelKpiValueDTO.setSystem(systemValue);
        }

        /**
         * 组装 KPI Explain Value
         *
         * @param factorConfigItemDTO 计算因子配置
         * @param hotelKpiValueDTO    指标值
         * @param explainValueDTO     待组装DTO
         * @author zc
         * @date 2024/12/24
         */
        private void packageKpiExplainValue(HotelKpiCalculationFactorConfigItemDTO factorConfigItemDTO, HotelKpiValueDTO hotelKpiValueDTO,
                                            HotelKpiExplainValueDTO explainValueDTO) {
            List<HotelKpiCalculationFactorConfigIndexDTO> calculationFactorList = factorConfigItemDTO.getCalculationFactor();
            if (calculationFactorList.size() != 2) {
                return;
            }
            // 计算因子 Title
            Map<Integer, HotelKpiCalculationFactorConfigIndexDTO> index2FactorIndex = calculationFactorList.stream()
                    .collect(Collectors.toMap(HotelKpiCalculationFactorConfigIndexDTO::getIndex, Function.identity()));

            // 计算因子数值
            Map<Integer, HotelKpiCalculationFactorDTO> index2CalculationDTO = hotelKpiValueDTO.getCalculationFactor().stream()
                    .collect(Collectors.toMap(HotelKpiCalculationFactorDTO::getIndex, Function.identity()));

            List<HotelKpiCalculationFactorValueDTO> calculationFactor = calculationFactorList.stream().map(item -> {
                HotelKpiCalculationFactorDTO calculationFactorDTO = index2CalculationDTO.getOrDefault(item.getIndex(),
                        new HotelKpiCalculationFactorDTO());

                HotelKpiCalculationFactorConfigIndexDTO factorIndexDTO = index2FactorIndex.getOrDefault(item.getIndex(),
                        new HotelKpiCalculationFactorConfigIndexDTO());

                return HotelKpiCalculationFactorValueDTO.builder()
                        .index(item.getIndex())
                        .title(factorIndexDTO.getTitle())
                        .value(calculationFactorDTO.getCrawl())
                        .build();
            }).collect(Collectors.toList());

            // 获取当计算因子分母等于 0 时，显示的内容
            String valueContent = getActualValueContent(calculationFactor, factorConfigItemDTO.getValueContent(),
                    hotelKpiValueDTO.getUpdatedBy());

            explainValueDTO.setCalculationFactor(calculationFactor);
            explainValueDTO.setValueContent(valueContent);
            explainValueDTO.setFormula(HotelKpiFormulaActualCons.MOLECULE_DIVIDE_DENOMINATOR);
        }

        /**
         * 获取 Actual 值内容
         *
         * <p>备注: 当 Actual 由两个计算因子计算得来时，若除数等于 0，则需要展示设置的 Actual 内容来代替计算的数值</p>
         *
         * @param calculationFactor  Actual 计算因子
         * @param actualValueContent Actual 内容
         * @return Actual 值内容
         * @author zc
         * @date 2024/12/14
         */
        private String getActualValueContent(List<HotelKpiCalculationFactorValueDTO> calculationFactor, String actualValueContent, int updatedBy) {
            if (updatedBy > 0) {
                return null;
            }
            HotelKpiCalculationFactorValueDTO calculationFactorDTO = calculationFactor.stream()
                    .filter(item -> item.getIndex() == 2).findFirst().orElse(null);
            if (Objects.nonNull(calculationFactorDTO) && Objects.nonNull(calculationFactorDTO.getValue())) {
                if (calculationFactorDTO.getValue().compareTo(BigDecimal.ZERO) == 0) {
                    return actualValueContent;
                }
            }
            return null;
        }
    }