# OperatingStatisticsController.java

    package cloud.wconnect.workperformance.controller.sva.operatingstatistics;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.group.Addition;
    import cloud.wconnect.commonlib.pojo.dto.group.Edition;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.workperformance.entity.sva.operatingstatistics.OperStatisImportLog;
    import cloud.wconnect.workperformance.entity.sva.operatingstatistics.OperatingStatistics;
    import cloud.wconnect.workperformance.pojo.dto.sva.operatingstatistics.OperatingStatisticsDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.operatingstatistics.OperatingStatisticsImportLogDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.operatingstatistics.OperatingStatisticsImportReportDTO;
    import cloud.wconnect.workperformance.pojo.query.ImportLogQuery;
    import cloud.wconnect.workperformance.service.sva.operatingstatistics.IOperStatisImportLogService;
    import cloud.wconnect.workperformance.service.sva.operatingstatistics.IOperatingStatisticsService;
    import cloud.wconnect.workperformance.service.sva.operatingstatistics.operatingstatistics.IOperatingStatisticsParseService;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.context.ApplicationContext;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;

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

    /**
     * <p>
     * 控制器类：运营收入分析
     * </p>
     *
     * @author zc
     * @since 2023-03-17
     */
    @RestController
    @RequestMapping("/sva/operating-statistics")
    public class OperatingStatisticsController {
        @Resource
        private IOperatingStatisticsService operatingStatisticsService;
        @Resource
        private IOperStatisImportLogService importLogService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private ApplicationContext applicationContext;
        @Resource
        private BeanMapper beanMapper;

        /**
         * 获取目标月份下的 Operating Statistics 集合
         *
         * @param property   资产标识
         * @param targetDate 目标日期（月初时间戳）
         * @return Operating Statistics 集合
         * @author zc
         * @date 2023/3/17
         */
        @ApiId(105)
        @GetMapping
        public List<OperatingStatisticsDTO> list(@RequestParam String property, @RequestParam long targetDate) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            long fromDate = DateTimeUtils.getFirstDayOfMonth(targetDate, propertyDTO.getTimezone());
            long toDate = DateTimeUtils.getEndDayOfMonth(targetDate, propertyDTO.getTimezone());

            List<OperatingStatistics> result = operatingStatisticsService.list(propertyDTO.getId(), fromDate, toDate);
            if (CollectionUtils.isEmpty(result)) {
                return Collections.emptyList();
            }
            return beanMapper.mapList(result, OperatingStatisticsDTO.class);
        }

        /**
         * 查询报告导入记录
         *
         * @param property       资产标识
         * @param query 查询条件
         * @return AvHoursImportLogDTO 集合
         * @author zc
         * @date 2023/10/19
         */
        @ApiId(106)
        @GetMapping("/report/import-log")
        public ListPagedDTO<OperatingStatisticsImportLogDTO> listImportLog(@RequestParam String property, @Valid ImportLogQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            return importLogService.list(propertyDTO, query);
        }

        /**
         * 忽略指定的报告异常记录
         *
         * @param property 资产标识
         * @param id       id
         * @return empty map
         * @author zc
         * @date 2023/3/17
         */
        @ApiId(107)
        @PutMapping("/exceptions/ignore/{id:\\d+}")
        public Map<Object, Object> ignore(@RequestParam String property, @PathVariable long id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验日志是否存在
            OperStatisImportLog importLog = importLogService.checkExist(id);
            // 校验日志归属
            importLogService.checkBelong(propertyId, importLog);
            // 校验是否是失败、缺失记录
            checkImportResult(importLog.getImportResult());

            importLogService.ignore(id, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 导入 Operating Statistics 报告
         *
         * @param importReportDTO 导入报告 DTO
         * @return Empty Map
         * @author zc
         * @date 2024/8/20
         */
        @ApiId(108)
        @PostMapping("/report")
        public Map<Objects, Objects> importReport(@Validated(Addition.class) OperatingStatisticsImportReportDTO importReportDTO) {
            // 校验日期范围
            dateParamCheck(importReportDTO.getStartDate(), importReportDTO.getEndDate());
            // 校验是否有权限处理资产数据
            List<Integer> propertyIds = importReportDTO.getPropertyIds();
            if (!Sets.newHashSet(sessionContext.getPropertyIds()).containsAll(propertyIds)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        "Cannot be operated others property data; propertyIds: " + propertyIds);
            }
            List<PropertyDTO> propertyDTOList = propertyI18nRedisService.list(propertyIds);

            // 转换来源标识
            String source = OperatingStatisticsImportReportDTO.TYPE_M3 == importReportDTO.getType() ?
                    SystemPlatformEnum.M3.getDesc() : SystemPlatformEnum.OTHER.getDesc();

            IOperatingStatisticsParseService parser = getOperatingStatisticsReportParser(source);

            // 校验文件是否满足需求
            parser.checkFile(propertyDTOList, importReportDTO.getFile());

            // 组装资产数据范围
            Map<Integer, List<Long>> propertyId2DateRange = propertyDTOList.stream()
                    .collect(Collectors.toMap(PropertyDTO::getId, propertyDTO ->
                            Lists.newArrayList(importReportDTO.getStartDate(), importReportDTO.getEndDate())));

            // 插入进行中导入记录
            List<Long> importLogIds = importLogService.addBatch(propertyDTOList, propertyId2DateRange,
                    ReportImportResultCons.IMPORT_IN_PROGRESS, sessionContext.getLoginUserId(), null);

            // 处理数据
            parser.handleData(propertyDTOList, importReportDTO.getFile(), propertyId2DateRange,
                    importLogIds, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 解决导入异常
         *
         * @param property        资产标识
         * @param id              异常 id
         * @param importReportDTO 导入报告 DTO
         * @return Empty Map
         * @author zc
         * @date 2023/10/19
         */
        @ApiId(865)
        @PostMapping("/exception/{id:\\d+}")
        public Map<Object, Object> resolve(@RequestParam String property, @PathVariable long id,
                                           @Validated(Edition.class) OperatingStatisticsImportReportDTO importReportDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验日志是否存在
            OperStatisImportLog importLog = importLogService.checkExist(id);
            // 校验日志归属
            importLogService.checkBelong(propertyId, importLog);
            // 校验是否是失败、缺失记录
            checkImportResult(importLog.getImportResult());

            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            List<PropertyDTO> propertyDTOList = Lists.newArrayList(propertyDTO);

            // 转换来源标识
            String source = OperatingStatisticsImportReportDTO.TYPE_M3 == importReportDTO.getType() ?
                    SystemPlatformEnum.M3.getDesc() : SystemPlatformEnum.OTHER.getDesc();

            IOperatingStatisticsParseService parser = getOperatingStatisticsReportParser(source);

            // 校验文件是否满足需求
            parser.checkFile(propertyDTOList, importReportDTO.getFile());

            // 更新日志为修复中
            importLogService.updateById(OperStatisImportLog.builder()
                    .id(id).importResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING).build());

            long startDate = Long.parseLong(DateTimeUtils.localDateToStr(DateTimeUtils.longToLocalDate(importLog.getStartDate(),
                    propertyDTO.getTimezone()), BaseConstant.DATE_FORMAT_YYYYMMDD));
            long endDate = Long.parseLong(DateTimeUtils.localDateToStr(DateTimeUtils.longToLocalDate(importLog.getEndDate(),
                    propertyDTO.getTimezone()), BaseConstant.DATE_FORMAT_YYYYMMDD));
            // 组装资产数据范围
            Map<Integer, List<Long>> propertyId2DateRange = propertyDTOList.stream()
                    .collect(Collectors.toMap(PropertyDTO::getId, item ->
                            Lists.newArrayList(startDate, endDate)));

            // 处理数据
            parser.handleData(propertyDTOList, importReportDTO.getFile(), propertyId2DateRange,
                    Lists.newArrayList(id), sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 校验是否是失败、缺失记录
         *
         * @param importResult 导入结果
         * @author zc
         * @date 2024/6/13
         */
        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");
            }
        }

        /**
         * 校验日期查询条件
         *
         * @param startDate 开始时间
         * @param endDate   结束时间
         * @author zc
         * @date 2024/2/29
         */
        private void dateParamCheck(long startDate, long endDate) {
            if (startDate > endDate) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The startDate cannot be greater than endDate; startDate:{0}, endDate:{1}",
                                startDate, endDate));
            }
        }

        /**
         * 获取报告解析服务
         *
         * @param source 来源标识
         * @return 报告解析服务
         * @author zc
         * @date 2024/8/20
         */
        private IOperatingStatisticsParseService getOperatingStatisticsReportParser(String source) {
            Collection<IOperatingStatisticsParseService> parserList =
                    applicationContext.getBeansOfType(IOperatingStatisticsParseService.class).values();
            IOperatingStatisticsParseService parser = null;
            for (IOperatingStatisticsParseService 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 Sva Operating Statistics report parser");
            }
            return parser;
        }
    }