# AdjReportController.java

    package cloud.wconnect.frontoffice.controller.adjustment;

    import cloud.wconnect.commonlib.annotation.ApiId;
    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.Edition;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.frontoffice.cons.adjustment.AdjNotifyConfigTypeEnum;
    import cloud.wconnect.frontoffice.entity.adjustment.AdJournalLog;
    import cloud.wconnect.frontoffice.parsereport.adjustment.IParseJournalByCashierAndTransactionCodeService;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.AdjImportReportDTO;
    import cloud.wconnect.frontoffice.pojo.group.Addition;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.ImportLogQuery;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.AdjImportLogVO;
    import cloud.wconnect.frontoffice.service.adjustment.IAdJournalLogService;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.ApplicationContext;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;

    import jakarta.validation.Valid;

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

    /**
     * @author lichen
     * @description adjustment模块异常前端控制器
     * @date 2023/4/5 11:26
     */
    @Slf4j
    @RestController
    @RequestMapping("/adjustment/import-report-log")
    public class AdjReportController {

        @Autowired
        private IAdJournalLogService adJournalLogService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private ApplicationContext applicationContext;
        @Value("${file-max-size.import-report.adjustment}")
        private long importReportFileMaxSize;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询导入记录
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdjImportLogVO>
         * @author lichen
         * @date 2023/9/28
         */
        @ApiId(279)
        @GetMapping
        public ListPagedDTO<AdjImportLogVO> page(String property, @Valid ImportLogQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();

            // 资产的PMS类型不等于Shiji时，reportType字段必须为空
            if (!propertyDTO.getPmsType().equals(SystemPlatformEnum.SHI_JI.getDesc()) && CollectionUtils.isNotEmpty(query.getReportTypes())) {
                throw new BadRequestException(CommonErrorI18nConstant.MUST_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("reportType"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The reportType must empty");
            }else{
                List<Integer> reportTypes = query.getReportTypes();
                if(CollectionUtils.isNotEmpty(reportTypes)){
                    ArrayList<Integer> validReportTypes = Lists.newArrayList(AdjNotifyConfigTypeEnum.JOURNAL_REPORT_MISSED.getType(),
                            AdjNotifyConfigTypeEnum.JOURNAL_VOIDED_REPORT_MISSED.getType());
                    if(reportTypes.stream().anyMatch(reportType -> !validReportTypes.contains(reportType))){
                        throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                                i18nMessageUtils.getErrorParamMapNotNeedTranslate("reportType", reportTypes),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The reportType must between in [3:journal 10:journal voided]");
                    }
                }

            }

            return adJournalLogService.pageList(tenantId, propertyId, query);
        }

        /**
         * 导入报告
         *
         * @param property           资产
         * @param adjImportReportDTO 导入DTO
         * @return java.util.Map
         * @author lichen
         * @date 2023/9/28
         */
        @ApiId(775)
        @PostMapping("/import")
        public Map<String, String> importReport(String property, @Validated(Addition.class) AdjImportReportDTO adjImportReportDTO) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));

            // 校验最多选择1天的数据
            Long startDate = adjImportReportDTO.getStartDate();
            Long endDate = adjImportReportDTO.getEndDate();
            long interval = DateTimeUtils.dateDiffOfDays(startDate, endDate, propertyDTO.getTimezone());
            if (interval > 0) {
                throw new BadRequestException(CommonErrorI18nConstant.SELECT_DATE_LIMIT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate(1),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The selected date exceeds the limit");
            }

            //校验source是否符合预期
            checkSource(propertyDTO, adjImportReportDTO.getSource());

            //校验reportType是否符合预期
            checkReportType(propertyDTO, adjImportReportDTO.getReportType());

            MultipartFile file = adjImportReportDTO.getFile();
            //校验文件大小
            checkFileSize(file);

            //获取解析的类集合
            Collection<IParseJournalByCashierAndTransactionCodeService> parserList = applicationContext.getBeansOfType(IParseJournalByCashierAndTransactionCodeService.class).values();
            for (IParseJournalByCashierAndTransactionCodeService parser : parserList) {
                if (parser.supports(adjImportReportDTO.getSource())) {
                    //校验文件后缀、内容是否满足需求
                    parser.checkFile(file, propertyDTO);

                    //插入一条处理中的导入记录
                    AdJournalLog insertEntity = adJournalLogService.insertInProgress(propertyDTO, adjImportReportDTO, sessionContext.getLoginUserId());

                    //处理文件数据
                    parser.handleData(propertyDTO, file, insertEntity, sessionContext.getLoginUserId());
                    break;
                }
            }
            return Collections.emptyMap();
        }

        /**
         * 解决异常的日志
         *
         * @param logId              日志id
         * @param property           资产标识
         * @param adjImportReportDTO 导入报告DTO
         * @return java.util.Map
         * @author lichen
         * @date 2023/9/29
         */
        @ApiId(280)
        @PutMapping("/{id:\\d+}/resolved")
        public Map resolve(@PathVariable(name = "id") long logId, String property,
                           @Validated(Edition.class) AdjImportReportDTO adjImportReportDTO) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(sessionContext.getPropertyId(property));
            //校验source是否符合预期
            checkSource(propertyDTO, adjImportReportDTO.getSource());

            MultipartFile file = adjImportReportDTO.getFile();
            //校验文件大小
            checkFileSize(file);

            //检查是否存在 且 归属于当前资产
            AdJournalLog oriJournalLog = checkExistAndBelongProperty(logId, propertyDTO.getId());
            //校验状态是否合法
            if (!Lists.newArrayList(ReportImportResultCons.IMPORT_RESULT_FAIL, ReportImportResultCons.IMPORT_RESULT_MISSED)
                    .contains(oriJournalLog.getStatus())) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INTEGRATION_ONLY_HANDLE_EXCEPTION,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Cannot operate status is not importResultFail or importResultMissed log");
            }

            //获取解析的类集合
            Collection<IParseJournalByCashierAndTransactionCodeService> parserList = applicationContext.getBeansOfType(IParseJournalByCashierAndTransactionCodeService.class).values();
            for (IParseJournalByCashierAndTransactionCodeService parser : parserList) {
                if (parser.supports(adjImportReportDTO.getSource())) {
                    //校验文件后缀、内容是否满足需求
                    parser.checkFile(file, propertyDTO);

                    //更新为修复中
                    adJournalLogService.updateById(new AdJournalLog().setId(logId).setStatus(ReportImportResultCons.IMPORT_RESULT_REPAIRING));

                    //处理文件数据
                    parser.handleData(propertyDTO, file, oriJournalLog, sessionContext.getLoginUserId());
                    break;
                }
            }
            return Collections.emptyMap();
        }

        /**
         * 忽略Journal by Cashier and Transaction Code/service 导入异常
         *
         * @param property 资产标识
         * @param logId    日志id
         * @return java.util.Map
         * @author lichen
         * @date 2023/9/29
         */
        @ApiId(774)
        @PutMapping("{id:\\d+}/ignore-exception")
        public Map ignoreException(String property, @PathVariable(name = "id") Long logId) {
            int propertyId = sessionContext.getPropertyId(property);
            AdJournalLog adJournalLog = checkExistAndBelongProperty(logId, propertyId);
            //可以忽略的状态
            List<Integer> canIgnoreStatus = Lists.newArrayList(ReportImportResultCons.IMPORT_RESULT_FAIL, ReportImportResultCons.IMPORT_RESULT_MISSED);
            if (!canIgnoreStatus.contains(adJournalLog.getStatus())) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INTEGRATION_ONLY_HANDLE_EXCEPTION,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Cannot operate status is not importResultFail or importResultMissed log");
            }
            //设置为忽略
            adJournalLog.setStatus(ReportImportResultCons.IMPORT_RESULT_IGNORE);
            adJournalLog.setResolvedBy(sessionContext.getLoginUserId());
            adJournalLog.setResolvedTime(DateTimeUtils.nowTimestamp());
            //更新
            adJournalLogService.updateById(adJournalLog);
            return Collections.emptyMap();
        }

        /**
         * 检查是否存在 且 归属于当前资产
         *
         * @param id         日志id
         * @param propertyId 资产id
         * @return cloud.wconnect.frontoffice.entity.adjustment.AdJournalLog
         * @author lichen
         * @date 2023/9/29
         */
        private AdJournalLog checkExistAndBelongProperty(Long id, Integer propertyId) {
            AdJournalLog byId = adJournalLogService.getById(id);
            if (Objects.isNull(byId)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Adjustment Requests journal log not found, id = " + id);
            }
            if (byId.getPropertyId().compareTo(propertyId) != 0) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("Adjustment Requests journal log not belong current property, journalLogId:{0}, " +
                                "journalLogBelongPropertyId:{1}, currentPropertyId:{2}", id, byId.getPropertyId(), propertyId));
            }
            return byId;
        }

        /**
         * 校验文件大小
         *
         * @param file 文件
         * @return void
         * @author lichen
         * @date 2024/2/22
         */
        private void checkFileSize(MultipartFile file) {
            if (file.getSize() > importReportFileMaxSize) {
                throw new BadRequestException(CommonErrorI18nConstant.FILE_SIZE_ERROR,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The fileSize max is " + importReportFileMaxSize);
            }
        }

        /**
         * 校验source是否符合预期
         *
         * @param propertyDTO 资产DTO
         * @param source      来源
         * @return void
         * @author lichen
         * @date 2025/7/26
         */
        private void checkSource(PropertyDTO propertyDTO, String source) {
            String pmsType = propertyDTO.getPmsType();
            //校验source是否符合预期
            if (!Lists.newArrayList(pmsType, SystemPlatformEnum.OTHER.getDesc()).contains(source)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INTEGRATION_SOURCE_SYSTEM_NOT_SUPPORTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The source must be in:" + pmsType + "|" + SystemPlatformEnum.OTHER.getDesc());
            }
        }

        /**
         * 校验reportType是否符合预期
         *
         * @param propertyDTO 资产DTO
         * @param reportType  报告类型
         * @author lichen
         * @date 2026/3/27
         */
        private void checkReportType(PropertyDTO propertyDTO, Integer reportType) {
            String pmsType = propertyDTO.getPmsType();
            //校验reportType是否符合预期
            if (pmsType.equals(SystemPlatformEnum.SHI_JI.getDesc())) {
                ArrayList<Integer> reportTypes = Lists.newArrayList(AdjNotifyConfigTypeEnum.JOURNAL_REPORT_MISSED.getType(),
                        AdjNotifyConfigTypeEnum.JOURNAL_VOIDED_REPORT_MISSED.getType());
                if (!reportTypes.contains(reportType)) {
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("reportType", reportTypes),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The reportType must between in [3:journal 10:journal voided]");
                }
            } else {
                if (Objects.nonNull(reportType)) {
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_EMPTY,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("reportType"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The reportType must be null");
                }
            }
        }

    }