# HotelKpiReportController.java

    package cloud.wconnect.workperformance.controller.hotelkpi;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.ReportImportResultCons;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiCons;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiReportCategoryEnum;
    import cloud.wconnect.workperformance.entity.hotelkpi.HotelKpiImportLog;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.report.HotelKpiImportLogDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.report.HotelKpiImportReportDTO;
    import cloud.wconnect.workperformance.pojo.query.hotelkpi.HotelKpiImportLogQuery;
    import cloud.wconnect.workperformance.service.hotelkpi.IHotelKpiImportLogService;
    import cloud.wconnect.workperformance.service.hotelkpi.historyandforecast.IHotelKpiHistoryAndForecastParseService;
    import cloud.wconnect.workperformance.service.hotelkpi.managerflash.IHotelKpiManagerFlashParseService;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;

    import java.text.MessageFormat;
    import java.util.*;

    /**
     * <p>
     * Hotel KPI Report 前端控制器
     * </p>
     *
     * @author zc
     * @since 2024-07-01
     */
    @RestController
    @RequestMapping("/hotel-kpi/report")
    public class HotelKpiReportController {
        @Resource
        private IHotelKpiImportLogService importLogService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private ApplicationContext applicationContext;

        /**
         * 获取报告导入记录
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return HotelKpiImportLogDTO 集合
         * @author zc
         * @date 2024/7/1
         */
        @ApiId(1222)
        @GetMapping("/import-log")
        public ListPagedDTO<HotelKpiImportLogDTO> listImportLog(@RequestParam String property, @Valid HotelKpiImportLogQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            // 校验报告类型参数
            checkReportTypeParams(propertyDTO.getPmsType(), query.getReportType());

            return importLogService.list(propertyDTO, query);
        }

        /**
         * 解决导入异常记录
         *
         * @param property  资产标识
         * @param id        异常记录 id
         * @param reportDTO Import Report DTO
         * @return empty map
         * @author zc
         * @date 2024/7/16
         */
        @ApiId(1223)
        @PutMapping("/import-log/{id:\\d+}/resolve")
        public Map<Object, Object> resolve(@RequestParam String property, @PathVariable("id") long id,
                                           HotelKpiImportReportDTO reportDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在
            HotelKpiImportLog importLog = importLogService.checkExist(id);
            // 校验归属
            importLogService.checkBelong(propertyId, importLog);
            // 校验是否是失败、缺失记录
            checkImportResult(importLog.getImportResult());

            MultipartFile file = reportDTO.getFile();
            String source = reportDTO.getSource();
            int loginUserId = sessionContext.getLoginUserId();

            // 解决异常
            if (Objects.nonNull(file) && Objects.nonNull(source)) {
                PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
                // 校验数据集成参数
                checkDataIntegrationParams(propertyDTO.getPmsType(), source, importLog.getReportType());

                List<Long> dateRange = Lists.newArrayList(importLog.getStartDate(), importLog.getEndDate());

                int reportType = importLog.getReportType();
                if (reportType == HotelKpiReportCategoryEnum.HISTORY_AND_FORECAST.getCategory()) {
                    IHotelKpiHistoryAndForecastParseService parser = getHistoryAndForecastParser(source);
                    // 校验文件是否满足需求
                    parser.checkFile(propertyDTO, file);
                    // 更新记录为修复中
                    importLogService.updateById(new HotelKpiImportLog().setId(id)
                            .setImportResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING));
                    // 处理数据
                    parser.handleData(propertyDTO, file, dateRange, importLog.getId(), sessionContext.getLoginUserId());
                } else {
                    IHotelKpiManagerFlashParseService parser = getManagerFlashParser(source);
                    // 校验文件是否满足需求
                    parser.checkFile(propertyDTO, file);
                    // 更新记录为修复中
                    importLogService.updateById(new HotelKpiImportLog().setId(id)
                            .setImportResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING));
                    // 处理数据
                    parser.handleData(propertyDTO, file, dateRange, importLog.getId(), sessionContext.getLoginUserId());
                }
            } else {
                // 更新记录为忽略
                importLogService.updateById(new HotelKpiImportLog().setId(id)
                        .setImportResult(ReportImportResultCons.IMPORT_RESULT_IGNORE)
                        .setResolvedBy(loginUserId)
                        .setResolvedTime(DateTimeUtils.nowTimestamp()));
            }
            return Collections.emptyMap();
        }

        /**
         * 导入报告
         *
         * @param property  资产标识
         * @param reportDTO Import Report DTO
         * @return empty map
         * @author zc
         * @date 2024/7/16
         */
        @ApiId(1224)
        @PostMapping
        public Map<Object, Object> importReport(@RequestParam String property, @Valid HotelKpiImportReportDTO reportDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String source = reportDTO.getSource();
            int reportType = reportDTO.getReportType();
            // 校验数据集成参数
            checkDataIntegrationParams(propertyDTO.getPmsType(), source, reportType);

            int loginUserId = sessionContext.getLoginUserId();
            MultipartFile file = reportDTO.getFile();
            if (reportType == HotelKpiReportCategoryEnum.HISTORY_AND_FORECAST.getCategory()) {
                List<Long> dateRange = Lists.newArrayList(reportDTO.getStartDate(), reportDTO.getEndDate());
                IHotelKpiHistoryAndForecastParseService parser = getHistoryAndForecastParser(source);
                // 校验文件是否满足需求
                parser.checkFile(propertyDTO, file);
                // 创建处理中的导入记录
                HotelKpiImportLog importLog = importLogService.saveInProgress(propertyDTO, reportType, dateRange, loginUserId);
                // 处理数据
                parser.handleData(propertyDTO, file, dateRange, importLog.getId(), loginUserId);
            } else {
                // Manager Flash 报告只需要一个日期参数 startDate
                List<Long> dateRange = Lists.newArrayList(reportDTO.getStartDate(), reportDTO.getStartDate());
                IHotelKpiManagerFlashParseService parser = getManagerFlashParser(source);
                // 校验文件是否满足需求
                parser.checkFile(propertyDTO, file);
                // 创建处理中的导入记录
                HotelKpiImportLog importLog = importLogService.saveInProgress(propertyDTO, reportType, dateRange, loginUserId);
                // 处理数据
                parser.handleData(propertyDTO, file, dateRange, importLog.getId(), loginUserId);
            }
            return Collections.emptyMap();
        }

        /**
         * 校验是否是失败、缺失记录
         *
         * @param importResult 导入结果
         * @author zc
         * @date 2024/7/1
         */
        private void checkImportResult(int importResult) {
            if (importResult != ReportImportResultCons.IMPORT_RESULT_FAIL
                    && importResult != ReportImportResultCons.IMPORT_RESULT_MISSED) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INTEGRATION_ONLY_HANDLE_EXCEPTION,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Cannot operate status is not importResultFail or importResultMissed log");
            }
        }

        /**
         * 获取报告解析服务 - History and Forecast
         *
         * @param source 来源标识
         * @return 报告解析服务
         * @author zc
         * @date 2024/8/21
         */
        private IHotelKpiHistoryAndForecastParseService getHistoryAndForecastParser(String source) {
            Collection<IHotelKpiHistoryAndForecastParseService> parserList =
                    applicationContext.getBeansOfType(IHotelKpiHistoryAndForecastParseService.class).values();
            IHotelKpiHistoryAndForecastParseService parser = null;
            for (IHotelKpiHistoryAndForecastParseService parseService : parserList) {
                if (parseService.supports(source)) {
                    parser = parseService;
                    break;
                }
            }
            if (Objects.isNull(parser)) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        "Can not find Hotel KPI History and forecast report parser");
            }
            return parser;
        }

        /**
         * 获取报告解析服务 - Manager Flash
         *
         * @param source 来源标识
         * @return 报告解析服务
         * @author zc
         * @date 2025/7/24
         */
        private IHotelKpiManagerFlashParseService getManagerFlashParser(String source) {
            Collection<IHotelKpiManagerFlashParseService> parserList =
                    applicationContext.getBeansOfType(IHotelKpiManagerFlashParseService.class).values();
            IHotelKpiManagerFlashParseService parser = null;
            for (IHotelKpiManagerFlashParseService parseService : parserList) {
                if (parseService.supports(source)) {
                    parser = parseService;
                    break;
                }
            }
            if (Objects.isNull(parser)) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        "Can not find Hotel KPI Manager flash report parser");
            }
            return parser;
        }

        /**
         * 校验数据集成参数
         *
         * @param propertyPmsType 属性PMS类型
         * @param sourceSystem    来源系统
         * @param reportType      报告类型
         * @author zc
         * @date 2025/7/23
         */
        private void checkDataIntegrationParams(String propertyPmsType, String sourceSystem, int reportType) {
            List<String> effectSourceList = List.of(propertyPmsType, SystemPlatformEnum.OTHER.getDesc());
            if (!effectSourceList.contains(sourceSystem)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INTEGRATION_SOURCE_SYSTEM_NOT_SUPPORTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The currently selected source system is not supported, " +
                                "sourceSystem must be in {0}", effectSourceList));
            }
            // 校验报告类型参数
            checkReportTypeParams(propertyPmsType, List.of(reportType));
        }

        /**
         * 校验报告类型参数
         *
         * @param propertyPmsType 属性PMS类型
         * @param reportTypeList  报告类型集合
         * @author zc
         * @date 2025/7/23
         */
        private void checkReportTypeParams(String propertyPmsType, List<Integer> reportTypeList) {
            if (CollectionUtils.isEmpty(reportTypeList)) {
                return;
            }
            List<Integer> effectReportTypeList;
            if (SystemPlatformEnum.OPERA.getDesc().equals(propertyPmsType)) {
                effectReportTypeList = HotelKpiCons.OPERA_PMS_TYPE_LIST;
            } else {
                effectReportTypeList = HotelKpiCons.SHIJI_PMS_TYPE_LIST;
            }
            if (!new HashSet<>(effectReportTypeList).containsAll(reportTypeList)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INTEGRATION_REPORT_TYPE_NOT_SUPPORTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The currently selected report type is not supported, " +
                                "reportType must be in {0}", effectReportTypeList));
            }
        }
    }