# HotelKpiMedalliaResponseServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.exception.ListenerException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.YearMonthWeekUtils;
    import cloud.wconnect.workperformance.cons.S3BucketFunctionCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiDateTypeCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiMetricsEnum;
    import cloud.wconnect.workperformance.entity.hotelkpi.*;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.factorconfig.HotelKpiCalculationFactorConfigDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.factorconfig.HotelKpiCalculationFactorConfigIndexDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.originkpi.HotelKpiOriginCalculationFactorDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.originkpi.HotelKpiOriginDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.originkpi.HotelKpiOriginValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.originkpi.HotelKpiParseValueDTO;
    import cloud.wconnect.workperformance.service.AmazonS3Service;
    import cloud.wconnect.workperformance.service.hotelkpi.*;
    import cloud.wconnect.workperformance.service.hotelkpi.medalliaresponse.IHotelKpiMedalliaResponseParseService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.io.IOException;
    import java.io.InputStream;
    import java.time.DayOfWeek;
    import java.time.LocalDate;
    import java.time.temporal.TemporalAdjusters;
    import java.util.Collection;
    import java.util.Objects;

    /**
     * <p>
     * Hotel KPI Medallia Response
     * </p>
     *
     * @author zc
     * @since 2024-08-06
     */
    @Slf4j
    @Service
    @DS("work_performance")
    public class HotelKpiMedalliaResponseServiceImpl implements IHotelKpiMedalliaResponseService {
        @Resource
        private IHotelKpiService hotelKpiService;
        @Resource
        private IHotelKpiWeeklyService weeklyService;
        @Resource
        private IHotelKpiMonthlyService monthlyService;
        @Resource
        private IHotelKpiQuarterlyService quarterlyService;
        @Resource
        private IHotelKpiYearlyService yearlyService;
        @Resource
        private IHotelKpiMetricsConfigService metricsConfigService;
        @Resource
        private IHotelKpiWeekStartDayService weekStartDayService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private AmazonS3Service amazonS3Service;
        @Resource
        private ApplicationContext applicationContext;

        private static final int METRICS_TYPE = HotelKpiMetricsEnum.MEDALLIA_RESPONSE_DIRECT.getMetricsType();

        /**
         * 获取公式
         *
         * @param propertyId 资产 id
         * @author zc
         * @date 2025/8/24
         */
        @Override
        public String getFormula(int propertyId) {
            // 获取指标配置
            HotelKpiMetricsConfig metricsConfig = metricsConfigService.getMetricsConfigByTypeForUpdate(propertyId, METRICS_TYPE);
            if (Objects.isNull(metricsConfig)) {
                return "";
            }
            HotelKpiCalculationFactorConfigDTO configDTO = JsonUtils.convertStringToObject(objectMapper,
                    metricsConfig.getCalculationFactorConfig(), HotelKpiCalculationFactorConfigDTO.class);
            HotelKpiCalculationFactorConfigIndexDTO actualFactorConfigIndex1DTO = configDTO.getActual().getCalculationFactor().stream()
                    .filter(item -> item.getIndex() == 1).findFirst().orElse(null);
            if (Objects.isNull(actualFactorConfigIndex1DTO)) {
                return "";
            }
            return actualFactorConfigIndex1DTO.getFormula();
        }

        /**
         * 解析报告
         *
         * @param propertyDTO 资产
         * @param fileDate    文件下载时间戳
         * @param in          文件
         * @author zc
         * @date 2025/8/24
         */
        @Override
        public HotelKpiParseValueDTO parseReport(PropertyDTO propertyDTO, long fileDate, InputStream in) {
            String formula = getFormula(propertyDTO.getId());
            Collection<IHotelKpiMedalliaResponseParseService> parserList = applicationContext.getBeansOfType(IHotelKpiMedalliaResponseParseService.class).values();
            for (IHotelKpiMedalliaResponseParseService parser : parserList) {
                if (parser.supports(formula)) {
                    return parser.parse(propertyDTO, fileDate, in, formula);
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理数据
         *
         * @param propertyDTO   资产
         * @param parseValueDTO 报告数据
         * @param fileUrl       文件地址
         * @param dateType      日期类型（1:Weekly;2:Monthly;3:Quarterly;4:Yearly）
         * @param dataDate      数据日期（yyyyMMdd,yyyyMM,yyyy）
         * @author zc
         * @date 2025/8/24
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleData(PropertyDTO propertyDTO, HotelKpiParseValueDTO parseValueDTO, String fileUrl, int dateType, long dataDate) {
            // 获取指标配置
            HotelKpiMetricsConfig metricsConfig = metricsConfigService.getMetricsConfigByTypeForUpdate(propertyDTO.getId(), METRICS_TYPE);
            if (Objects.isNull(metricsConfig)) {
                return;
            }
            // 上传文件到 S3
            uploadReport(fileUrl);
            // 组装文件路径参数
            parseValueDTO.setFileUrl(fileUrl);

            // 保存采集数据
            if (dateType == HotelKpiDateTypeCons.WEEKLY) {
                saveWeekly(propertyDTO, metricsConfig, parseValueDTO, dataDate);
            } else if (dateType == HotelKpiDateTypeCons.MONTHLY) {
                saveMonthly(propertyDTO, metricsConfig, parseValueDTO, dataDate);
            } else if (dateType == HotelKpiDateTypeCons.QUARTERLY) {
                saveQuarterly(propertyDTO, metricsConfig, parseValueDTO, dataDate);
            } else {
                saveYearly(propertyDTO, metricsConfig, parseValueDTO, dataDate);
            }
        }

        /**
         * 保存周数据
         *
         * @param propertyDTO   资产
         * @param metricsConfig 指标配置
         * @param parseValueDTO 解析报告的原始数据
         * @param date          日期(yyyyMMdd)
         * @author zc
         * @date 2024/8/6
         */
        private void saveWeekly(PropertyDTO propertyDTO, HotelKpiMetricsConfig metricsConfig, HotelKpiParseValueDTO parseValueDTO, long date) {
            // 转换为所在周的周起始日
            DayOfWeek dayOfWeek = weekStartDayService.getWeekStartDay(propertyDTO.getId());
            LocalDate weekStartLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(date), BaseConstant.DATE_FORMAT_YYYYMMDD)
                    .with(TemporalAdjusters.previousOrSame(dayOfWeek));
            long week = Long.parseLong(DateTimeUtils.localDateToStr(weekStartLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            // 获取原始数据
            HotelKpiWeekly originKpiWeekly = weeklyService.getByDateAndMetricsType(propertyDTO.getId(),
                    week, metricsConfig.getMetricsType());
            if (Objects.isNull(originKpiWeekly)) {
                originKpiWeekly = new HotelKpiWeekly();
            }

            weeklyService.insertOrUpdate(HotelKpiWeekly.builder()
                    .propertyId(propertyDTO.getId())
                    .week(week)
                    .metricsType(metricsConfig.getMetricsType())
                    .detail(packageOriginDetail(parseValueDTO, originKpiWeekly.getDetail()))
                    .build());
        }

        /**
         * 保存月数据
         *
         * @param propertyDTO   资产
         * @param metricsConfig 指标配置
         * @param parseValueDTO 解析报告的原始数据
         * @param date          日期(yyyyMM)
         * @author zc
         * @date 2024/8/6
         */
        private void saveMonthly(PropertyDTO propertyDTO, HotelKpiMetricsConfig metricsConfig, HotelKpiParseValueDTO parseValueDTO, long date) {
            // 获取原始数据
            HotelKpiMonthly originKpiMonthly = monthlyService.getByDateAndMetricsType(propertyDTO.getId(), date, metricsConfig.getMetricsType());
            if (Objects.isNull(originKpiMonthly)) {
                originKpiMonthly = new HotelKpiMonthly();
            }

            monthlyService.insertOrUpdate(HotelKpiMonthly.builder()
                    .propertyId(propertyDTO.getId())
                    .month(date)
                    .metricsType(metricsConfig.getMetricsType())
                    .detail(packageOriginDetail(parseValueDTO, originKpiMonthly.getDetail()))
                    .build());
        }

        /**
         * 保存季数据
         *
         * @param propertyDTO    资产
         * @param metricsConfig  指标配置
         * @param parseValueDTO  解析报告的原始数据
         * @param date          日期(yyyyMM)
         * @author zc
         * @date 2024/8/6
         */
        private void saveQuarterly(PropertyDTO propertyDTO, HotelKpiMetricsConfig metricsConfig, HotelKpiParseValueDTO parseValueDTO, long date) {
            // 转换为季度首月(如：202401, 202404, 202407, 202410)
            long quarter = YearMonthWeekUtils.getQuarterFirstDate(date);

            // 获取原始数据
            HotelKpiQuarterly originKpiQuarterly = quarterlyService.getByDateAndMetricsType(propertyDTO.getId(), quarter, metricsConfig.getMetricsType());
            if (Objects.isNull(originKpiQuarterly)) {
                originKpiQuarterly = new HotelKpiQuarterly();
            }

            quarterlyService.insertOrUpdate(HotelKpiQuarterly.builder()
                    .propertyId(propertyDTO.getId())
                    .month(quarter)
                    .metricsType(metricsConfig.getMetricsType())
                    .detail(packageOriginDetail(parseValueDTO, originKpiQuarterly.getDetail()))
                    .build());
        }

        /**
         * 保存年数据
         *
         * @param propertyDTO    资产
         * @param metricsConfig  指标配置
         * @param parseValueDTO  解析报告的原始数据
         * @param date          日期(yyyy)
         * @author zc
         * @date 2024/8/6
         */
        private void saveYearly(PropertyDTO propertyDTO, HotelKpiMetricsConfig metricsConfig, HotelKpiParseValueDTO parseValueDTO, long date) {
            // 获取原始数据
            HotelKpiYearly originKpiYearly = yearlyService.getByDateAndMetricsType(propertyDTO.getId(), date, metricsConfig.getMetricsType());
            if (Objects.isNull(originKpiYearly)) {
                originKpiYearly = new HotelKpiYearly();
            }

            yearlyService.insertOrUpdate(HotelKpiYearly.builder()
                    .propertyId(propertyDTO.getId())
                    .year(date)
                    .metricsType(metricsConfig.getMetricsType())
                    .detail(packageOriginDetail(parseValueDTO, originKpiYearly.getDetail()))
                    .build());
        }

        /**
         * 上传报告，用于点击指标下载报告
         *
         * @param fileUrl S3 路径
         * @author zc
         * @date 2025/8/24
         */
        private void uploadReport(String fileUrl) {
            // 上传报告，用于日志下载报告
            try (InputStream in = amazonS3Service.getFile(fileUrl)) {
                amazonS3Service.uploadFile(S3BucketFunctionCons.HOTEL_KPI, in, fileUrl);
            } catch (IOException e) {
                log.error("HotelKpiMedalliaResponseServiceImpl Upload Medallia Response report error. fileUrl: {}", fileUrl, e);
            }
        }

        /**
         * 组装原始指标数据DTO
         *
         * @param parseValueDTO 解析报告的原始数据
         * @param originDetail  原始数据详情
         * @author zc
         * @date 2024/8/8
         */
        private String packageOriginDetail(HotelKpiParseValueDTO parseValueDTO, String originDetail) {
            HotelKpiOriginDetailDTO originDetailDTO = Objects.isNull(originDetail) ? new HotelKpiOriginDetailDTO() :
                    JsonUtils.convertStringToObject(objectMapper, originDetail, HotelKpiOriginDetailDTO.class);

            HotelKpiOriginValueDTO actual = originDetailDTO.getActual();
            // 组装 Actual 文件路径
            actual.setFileUrl(parseValueDTO.getFileUrl());

            HotelKpiOriginCalculationFactorDTO newFactorDTO = new HotelKpiOriginCalculationFactorDTO().setIndex(1)
                    .setCrawl(parseValueDTO.getValue()).setCrawlOrigin(parseValueDTO.getOriginValues());

            // 查找并替换或新增新的指标计算因子原始数据
            hotelKpiService.replaceOriginFactorList(actual.getCalculationFactor(), newFactorDTO);
            return JsonUtils.convertObjToString(objectMapper, originDetailDTO);
        }

    }