# RevenueReportCommonServiceImpl.java

    package cloud.wconnect.rateshop.service.revenue.report;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.pojo.dto.EventBodyDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.rateshop.cons.BusinessEnum;
    import cloud.wconnect.rateshop.cons.S3BucketFunctionCons;
    import cloud.wconnect.rateshop.entity.revenue.RevenueImportLog;
    import cloud.wconnect.rateshop.pojo.bo.revenue.OtbAndTransientGroupBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.OtbRevenueBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.OtbRevenueFutureBO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.ResolveExceptionDTO;
    import cloud.wconnect.rateshop.service.AmazonS3Service;
    import cloud.wconnect.rateshop.service.revenue.IRevenueImportLogService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueImportService;
    import cloud.wconnect.rateshop.service.revenue.report.historyandforecast.IParseHFFutureExcelService;
    import cloud.wconnect.sns.http.processor.Sender;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.IOException;
    import java.io.InputStream;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Objects;

    /**
     * @author: yyt
     * 2024-08-14
     */
    @Service
    @DS("rate_shop")
    public class RevenueReportCommonServiceImpl implements IRevenueReportCommonService {

        @Resource
        private IRevenueImportLogService revenueImportLogService;
        @Resource
        private IRevenueImportService revenueImportService;
        @Autowired
        private ApplicationContext applicationContext;
        @Autowired
        private AmazonS3Service amazonS3Service;
        @Autowired
        private BeanMapper  beanMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        private final Sender snsSender;

        public RevenueReportCommonServiceImpl(@Qualifier("3pSender") Sender snsSender) {
            this.snsSender = snsSender;
        }

        /**
         * 处理导入记录
         *
         * @param propertyDTO         资产
         * @param importLog           导入记录
         * @param resolveExceptionDTO 选择条件
         * @param businessKey         business key
         * @param file                文件
         * @param userId              操作人
         * @return 导入记录
         * @author yyt
         * @date 2024/8/14
         **/
        @Override
        public RevenueImportLog handleImportLog(PropertyDTO propertyDTO, RevenueImportLog importLog,
                                                ResolveExceptionDTO resolveExceptionDTO, String businessKey, MultipartFile file, int userId) {
            String key = toS3(propertyDTO.getTenantSymbol(), propertyDTO.getAbbr(), businessKey, file);
            if (Objects.isNull(importLog)) {
                importLog = revenueImportLogService.saveInProgressRecord(propertyDTO.getId(), resolveExceptionDTO,
                        ReportImportResultCons.IMPORT_IN_PROGRESS, key, userId);
            } else {
                // 更新异常为修复中
                RevenueImportLog updateContent = new RevenueImportLog();
                updateContent.setId(importLog.getId());
                updateContent.setStatus(ReportImportResultCons.IMPORT_RESULT_REPAIRING);
                updateContent.setUrl(key);
                revenueImportLogService.updateById(updateContent);
                importLog.setStatus(ReportImportResultCons.IMPORT_RESULT_REPAIRING);
            }
            return importLog;
        }

        /**
         * 上传文件到 s3
         *
         * @param path 文件路径
         * @author yyt
         * @date 2025/9/4
         **/
        @Override
        public void toS3(String path) throws IOException {
            try (InputStream inputStream = amazonS3Service.getFile(path)) {
                amazonS3Service.uploadFile(S3BucketFunctionCons.REVENUE_IMPORT_RECORD, inputStream, path);
            }
        }

        /**
         * 上传文件至 s3
         *
         * @param tenantSymbol   租户标识
         * @param propertySymbol 资产标识
         * @param businessKey    business key
         * @param file           文件
         * @return 文件路径
         * @author yyt
         * @date 2024/8/14
         **/
        @Override
        public String toS3(String tenantSymbol, String propertySymbol, String businessKey, MultipartFile file) {
            String key = tenantSymbol + "/" + propertySymbol + "/" + businessKey +
                    UploadUtils.getRandomFileName(file.getOriginalFilename());
            try {
                // 上传文件
                amazonS3Service.uploadFile(S3BucketFunctionCons.REVENUE_IMPORT_RECORD, file, key);
            } catch (Exception e) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Uploading to s3 failure", e);
            }
            return key;
        }

        /**
         * 发送报告事件
         *
         * @param propertyDTO 资产信息
         * @param subject     subject
         * @param businessKey business key
         * @param file        文件
         * @param importLog   导入记录
         * @param userId      操作人
         * @author yyt
         * @date 2024/8/14
         **/
        @Override
        public void handleSendReportEvent(PropertyDTO propertyDTO, String subject, String businessKey,
                                          MultipartFile file, RevenueImportLog importLog, Integer userId) {
            // 发送事件
            try {
                // 设置事件体
                Map<String, Object> params = Maps.newHashMapWithExpectedSize(9);
                params.put(EventBodyParamEnum.TENANT_SYMBOL.getDesc(), propertyDTO.getTenantSymbol());
                params.put(EventBodyParamEnum.PROPERTY_ABBR.getDesc(), Lists.newArrayList(propertyDTO.getAbbr()));
                params.put(EventBodyParamEnum.SOURCE.getDesc(), propertyDTO.getPmsType());
                params.put(EventBodyParamEnum.ID.getDesc(), importLog.getId());
                params.put(EventBodyParamEnum.UID.getDesc(), userId);
                params.put(EventBodyParamEnum.ORIGINATE_FROM.getDesc(), BusinessEnum.REVENUE.getBusiness());
                params.put(EventBodyParamEnum.START_DATE.getDesc(), importLog.getStartDate());
                params.put(EventBodyParamEnum.END_DATE.getDesc(), importLog.getEndDate());
                params.put(EventBodyParamEnum.DATE.getDesc(), importLog.getTheDate());

                // 上传文件
                String key = propertyDTO.getTenantSymbol() + "/" + propertyDTO.getAbbr() + "/" + businessKey +
                        UploadUtils.getRandomFileName(file.getOriginalFilename());
                amazonS3Service.uploadFile(file, key);

                snsSender.publish(subject, JsonUtils.convertObjToString(objectMapper, new EventBodyDTO(key, params)));
            } catch (Exception e) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Failed to send report event", e);
            }
        }

        /**
         * 修改导入状态
         *
         * @param importLog    导入记录
         * @param importResult 导入结果（true：导入成功，false：导入失败）
         * @param userId       操作人
         * @author yyt
         * @date 2024/8/15
         **/
        @Override
        public void updateImportStatus(RevenueImportLog importLog, boolean importResult, int userId) {
            if (importResult) {
                importSuccess(importLog, userId);
            } else {
                importFail(importLog);
            }
        }

        /**
         * 导入成功
         *
         * @param importLog 导入记录
         * @param userId    操作人
         * @author yyt
         * @date 2024/8/15
         **/
        private void importSuccess(RevenueImportLog importLog, int userId) {
            TransactionUtils.afterCommitSyncExecute(() -> {
                // 更新导入记录
                RevenueImportLog updateLog = new RevenueImportLog();
                updateLog.setId(importLog.getId());
                updateLog.setStatus(ReportImportResultCons.IMPORT_RESULT_SUCCESS);
                if (importLog.getStatus() == ReportImportResultCons.IMPORT_RESULT_REPAIRING) {
                    updateLog.setResolvedBy(userId).setResolvedTime(DateTimeUtils.nowTimestamp());
                }
                revenueImportLogService.updateById(updateLog);
            });
        }

        /**
         * 导入失败
         *
         * @param importLog 导入记录
         * @author yyt
         * @date 2024/8/15
         **/
        private void importFail(RevenueImportLog importLog) {
            TransactionUtils.afterCompletionRollbackSyncExecute(() -> {
                // 更新导入记录
                RevenueImportLog updateLog = new RevenueImportLog();
                updateLog.setId(importLog.getId());
                updateLog.setStatus(ReportImportResultCons.IMPORT_RESULT_FAIL);
                revenueImportLogService.updateById(updateLog);
            });
        }

        /**
         * 处理 history and forecast future report
         *
         * @param propertyDTO 资产
         * @param file        文件
         * @param importLog   导入记录
         * @param userId      操作人
         * @author yyt
         * @date 2024/8/16
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleHistoryAndForecastFutureReport(PropertyDTO propertyDTO, MultipartFile file,
                                                         RevenueImportLog importLog, Integer userId) {
            boolean importSuccess = true;
            try (InputStream inputStream = file.getInputStream()) {
                Map<String, IParseHFFutureExcelService> beans = applicationContext.getBeansOfType(IParseHFFutureExcelService.class);
                for (IParseHFFutureExcelService bean : beans.values()) {
                    if (bean.supports(propertyDTO.getPmsType())) {
                        OtbAndTransientGroupBO otbAndTransientGroupBO = bean.handleFile(propertyDTO, inputStream, importLog.getStartDate(), importLog.getEndDate());

                        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM);

                        // 保存文件数据
                        OtbRevenueBO otbRevenue = otbAndTransientGroupBO.getOtbRevenue();
                        OtbRevenueFutureBO otbRevenueFutureBO = beanMapper.map(otbRevenue, OtbRevenueFutureBO.class);
                        otbRevenueFutureBO.setMonth(DateTimeUtils.toZonedDateTime(importLog.getStartDate(), propertyDTO.getTimezone()).format(dateTimeFormatter));
                        revenueImportService.saveOtbAndTransientGroupFutureData(propertyDTO, otbRevenueFutureBO,
                                otbAndTransientGroupBO.getTransientGroups(), userId);
                        return;
                    }
                }
            } catch (Exception e) {
                importSuccess = false;
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parse report error", e);
            } finally {
                updateImportStatus(importLog, importSuccess, userId);
            }
        }

        /**
         * 校验 history and forecast yesterday and ninety days report 日期
         *
         * @param propertyDTO         资产
         * @param resolveExceptionDTO 导入报告DTO
         * @author lichen
         * @date 2025/11/19
         */
        @Override
        public void checkHistoryAndForecastYesterdayAndNinetyDaysReportDate(PropertyDTO propertyDTO, ResolveExceptionDTO resolveExceptionDTO) {
            String timezone = propertyDTO.getTimezone();
            //校验businessDate不能为null
            Long businessDate = resolveExceptionDTO.getDate();
            if (Objects.isNull(businessDate)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.BUSINESS_DATE),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The business Date cannot empty.");
            }

            LocalDate currentDate = LocalDate.now(ZoneId.of(timezone));
            LocalDate businessLocalDate = DateTimeUtils.longToLocalDate(businessDate, timezone);

            //校验businessDate不能大于当前日期
            if (businessLocalDate.isAfter(currentDate)) {
                String targetDate = DateTimeUtils.localDateToStr(currentDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                map.put(CommonFieldI18nConstant.BUSINESS_DATE, true);
                map.put(targetDate, false);

                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                        i18nMessageUtils.getErrorParamMap(map),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The businessDate cannot greater than {0}.", targetDate));
            }

            LocalDate startLocalDate = DateTimeUtils.longToLocalDate(resolveExceptionDTO.getStartDate(), timezone);
            LocalDate endLocalDate = DateTimeUtils.longToLocalDate(resolveExceptionDTO.getEndDate(), timezone);
            //校验开始日期不能大于结束日期
            if (startLocalDate.isAfter(endLocalDate)) {
                String targetDate = DateTimeUtils.localDateToStr(endLocalDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                map.put(CommonFieldI18nConstant.START_DATE, true);
                map.put(targetDate, false);

                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                        i18nMessageUtils.getErrorParamMap(map),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The startDate cannot greater than {0}.", targetDate));
            }

            //校验开始日期必须>=businessDate - 1天
            LocalDate minStartLocalDate = businessLocalDate.minusDays(1);
            if (startLocalDate.isBefore(minStartLocalDate)) {
                String targetDate = DateTimeUtils.localDateToStr(minStartLocalDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                map.put(CommonFieldI18nConstant.START_DATE, true);
                map.put(targetDate, false);

                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_LESS_THAN,
                        i18nMessageUtils.getErrorParamMap(map),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The startDate cannot less than {0}.", targetDate));
            }

            //校验结束日期必须<=businessDate + 90天
            LocalDate maxEndLocalDate = businessLocalDate.plusDays(90);
            if (endLocalDate.isAfter(maxEndLocalDate)) {
                String targetDate = DateTimeUtils.localDateToStr(maxEndLocalDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                map.put(CommonFieldI18nConstant.END_DATE, true);
                map.put(targetDate, false);

                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                        i18nMessageUtils.getErrorParamMap(map),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The endDate cannot greater than {0}.", targetDate));
            }
        }

    }