# OvertimeReportController.java

    package cloud.wconnect.workperformance.controller.mor.overtime;

    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.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.workperformance.entity.mor.OvertimeImportLog;
    import cloud.wconnect.workperformance.pojo.dto.mor.overtime.MorImportLogDTO;
    import cloud.wconnect.workperformance.pojo.dto.mor.overtime.OvertimeImportReportDTO;
    import cloud.wconnect.workperformance.pojo.query.mor.overtime.OvertimeImportLogQuery;
    import cloud.wconnect.workperformance.service.mor.IOvertimeImportLogService;
    import cloud.wconnect.workperformance.service.mor.overtime.IMorOvertimeParseService;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.springframework.context.ApplicationContext;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;

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

    /**
     * 控制器类：加班信息 - 报告导入
     *
     * @author zc
     * @date 2023/11/22
     */
    @RestController
    @RequestMapping("/mor/overtime/report")
    public class OvertimeReportController {
        @Resource
        private IOvertimeImportLogService importLogService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private ApplicationContext applicationContext;

        /**
         * 导入报告
         *
         * @param importReportDTO 导入报告 DTO
         * @return Empty Map
         * @author zc
         * @date 2023/4/2
         */
        @ApiId(250)
        @PostMapping
        public Map<Object, Object> importReport(@Validated(Addition.class) OvertimeImportReportDTO importReportDTO) {
            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; propertyIdRange: " + propertyIds);
            }
            List<PropertyDTO> propertyDTOList = propertyI18nRedisService.list(propertyIds);
            if (Objects.isNull(importReportDTO.getStartDate()) || Objects.isNull(importReportDTO.getEndDate())) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The startDate or endDate cannot be null");
            }
            // 转换来源标识
            String source = OvertimeImportReportDTO.TYPE_ADP == importReportDTO.getType() ?
                    SystemPlatformEnum.ADP.getDesc() : SystemPlatformEnum.OTHER.getDesc();

            // 获取解析器
            IMorOvertimeParseService parser = getOvertimeReportParser(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,
                    OvertimeImportReportDTO.ATTENDANCE_WORK_HOUR_REPORT, 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 2024/8/19
         */
        @ApiId(866)
        @PostMapping("/exception/{id:\\d+}/resolve")
        public Map<Object, Object> resolve(@RequestParam String property, @PathVariable long id,
                                           @Validated(Edition.class) OvertimeImportReportDTO importReportDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // 校验日志是否存在
            OvertimeImportLog importLog = importLogService.checkExist(id);
            // 校验日志归属
            importLogService.checkBelong(propertyDTO.getId(), importLog);
            // 校验是否是失败、缺失记录
            checkImportResult(importLog.getImportResult());

            // 转换来源标识
            String source = OvertimeImportReportDTO.TYPE_ADP == importReportDTO.getType() ?
                    SystemPlatformEnum.ADP.getDesc() : SystemPlatformEnum.OTHER.getDesc();
            // 获取解析的类集合
            IMorOvertimeParseService parser = getOvertimeReportParser(source);

            List<PropertyDTO> propertyDTOList = Lists.newArrayList(propertyDTO);
            // 校验文件后缀、内容是否满足需求
            parser.checkFile(propertyDTOList, importReportDTO.getFile());

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

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

            // 处理数据
            parser.handleData(propertyDTOList, importReportDTO.getFile(), propertyId2DateRange,
                    Lists.newArrayList(importLog.getId()), sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 获取导入记录
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return OvertimeImportLogDTO 集合
         * @author zc
         * @date 2023/4/1
         */
        @ApiId(251)
        @GetMapping("/import-log")
        public ListPagedDTO<MorImportLogDTO> listImportLog(@RequestParam String property, @Valid OvertimeImportLogQuery 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 2025/3/17
         */
        @ApiId(252)
        @PutMapping("/exception/{id:\\d+}/ignore")
        public Map<Object, Object> ignoreException(@RequestParam String property, @PathVariable long id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在
            OvertimeImportLog importLog = importLogService.checkExist(id);
            // 校验归属
            importLogService.checkBelong(propertyId, importLog);
            // 校验是否是失败、缺失记录
            checkImportResult(importLog.getImportResult());
            importLogService.ignore(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 source 来源标识
         * @return 报告解析服务
         * @author zc
         * @date 2024/8/16
         */
        private IMorOvertimeParseService getOvertimeReportParser(String source) {
            Collection<IMorOvertimeParseService> parserList =
                    applicationContext.getBeansOfType(IMorOvertimeParseService.class).values();
            IMorOvertimeParseService parser = null;
            for (IMorOvertimeParseService 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 Mor Overtime report parser");
            }
            return parser;
        }

    }