# AttendanceActualScheduleHourController.java

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

    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.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.cons.sva.SvaReportTypeCons;
    import cloud.wconnect.workperformance.entity.sva.avhours.AttendanceAvsImportLog;
    import cloud.wconnect.workperformance.pojo.dto.sva.avhours.AvHoursImportLogDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.avhours.AvImportReportDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.avhours.SvaActualScheduleHoursDTO;
    import cloud.wconnect.workperformance.pojo.query.sva.avhours.ImportAvHoursLogQuery;
    import cloud.wconnect.workperformance.pojo.query.sva.avhours.SvaActualScheduleHoursQuery;
    import cloud.wconnect.workperformance.service.sva.actualschedulehour.IAttendanceActualScheduleHourImportLogService;
    import cloud.wconnect.workperformance.service.sva.actualschedulehour.ISvaActualScheduleRecordService;
    import cloud.wconnect.workperformance.service.sva.actualschedulehour.actualhour.IActualHourParseService;
    import cloud.wconnect.workperformance.service.sva.actualschedulehour.schedulehour.IScheduleHourParseService;
    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.text.MessageFormat;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 控制器类：员工考勤时数
     * </p>
     *
     * @author zc
     * @since 2023-03-22
     */
    @RestController
    @RequestMapping("/sva/av")
    public class AttendanceActualScheduleHourController {
        @Resource
        private ISvaActualScheduleRecordService actualScheduleRecordService;
        @Resource
        private IAttendanceActualScheduleHourImportLogService importLogService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private ApplicationContext applicationContext;

        /**
         * 获取指定检索条件下的考勤信息集合
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return 分页的考勤信息集合
         * @author zc
         * @date 2025/4/22
         */
        @ApiId(129)
        @GetMapping
        public ListPagedDTO<SvaActualScheduleHoursDTO> list(@RequestParam String property, @Valid SvaActualScheduleHoursQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            return actualScheduleRecordService.listPaged(propertyId, query);
        }

        /**
         * 导入 Actual vs Scheduled Hours 报告
         *
         * @param importReportDTO 导入报告 DTO
         * @return Empty Map
         * @author zc
         * @date 2025/4/21
         */
        @ApiId(131)
        @PostMapping("/report")
        public Map<Objects, Objects> importReport(@Validated(Addition.class) AvImportReportDTO 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);
            }
            // 转换来源标识
            String source = AvImportReportDTO.TYPE_ADP == importReportDTO.getType() ?
                    SystemPlatformEnum.ADP.getDesc() : SystemPlatformEnum.OTHER.getDesc();
            List<PropertyDTO> propertyDTOList = propertyI18nRedisService.list(propertyIds);
            // 组装资产数据范围
            Map<Integer, List<Long>> propertyId2DateRange = propertyDTOList.stream()
                    .collect(Collectors.toMap(PropertyDTO::getId, propertyDTO ->
                            Lists.newArrayList(importReportDTO.getStartDate(), importReportDTO.getEndDate())));
            if (SvaReportTypeCons.ACTUAL_HOUR == importReportDTO.getReportType()) {
                IActualHourParseService parser = getActualHourReportParser(source);
                // 校验文件是否满足需求
                parser.checkFile(propertyDTOList, importReportDTO.getFile());
                // 插入进行中导入记录
                List<Long> importLogIds = importLogService.addBatch(propertyDTOList, propertyId2DateRange,
                        ReportImportResultCons.IMPORT_IN_PROGRESS, sessionContext.getLoginUserId(), null, importReportDTO.getReportType());
                // 处理数据
                parser.handleData(propertyDTOList, importReportDTO.getFile(), propertyId2DateRange,
                        importLogIds, sessionContext.getLoginUserId());
            } else {
                IScheduleHourParseService parser = getScheduleHourReportParser(source);
                // 校验文件是否满足需求
                parser.checkFile(propertyDTOList, importReportDTO.getFile());
                // 插入进行中导入记录
                List<Long> importLogIds = importLogService.addBatch(propertyDTOList, propertyId2DateRange,
                        ReportImportResultCons.IMPORT_IN_PROGRESS, sessionContext.getLoginUserId(), null, importReportDTO.getReportType());
                // 处理数据
                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 2025/4/21
         */
        @ApiId(864)
        @PostMapping("/exception/{id:\\d+}")
        public Map<Object, Object> resolve(@RequestParam String property, @PathVariable long id,
                                           @Validated(Edition.class) AvImportReportDTO importReportDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验日志是否存在
            AttendanceAvsImportLog importLog = importLogService.checkExist(id);
            // 校验日志归属
            importLogService.checkBelong(propertyId, importLog);
            // 校验是否是失败、缺失记录
            checkImportResult(importLog.getImportResult());

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

            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)));

            // 转换来源标识
            String source = AvImportReportDTO.TYPE_ADP == importReportDTO.getType() ?
                    SystemPlatformEnum.ADP.getDesc() : SystemPlatformEnum.OTHER.getDesc();

            if (SvaReportTypeCons.ACTUAL_HOUR == importLog.getReportType()) {
                IActualHourParseService parser = getActualHourReportParser(source);
                // 校验文件是否满足需求
                parser.checkFile(propertyDTOList, importReportDTO.getFile());
                // 更新日志为修复中
                importLogService.updateById(AttendanceAvsImportLog.builder()
                        .id(id).importResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING).build());
                // 处理数据
                parser.handleData(propertyDTOList, importReportDTO.getFile(), propertyId2DateRange,
                        Lists.newArrayList(id), sessionContext.getLoginUserId());
            } else {
                IScheduleHourParseService parser = getScheduleHourReportParser(source);
                // 校验文件是否满足需求
                parser.checkFile(propertyDTOList, importReportDTO.getFile());
                // 更新日志为修复中
                importLogService.updateById(AttendanceAvsImportLog.builder()
                        .id(id).importResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING).build());
                // 处理数据
                parser.handleData(propertyDTOList, importReportDTO.getFile(), propertyId2DateRange,
                        Lists.newArrayList(id), sessionContext.getLoginUserId());
            }
            return Collections.emptyMap();
        }

        /**
         * 查询报告导入记录
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return AvHoursImportLogDTO 集合
         * @author zc
         * @date 2023/10/19
         */
        @ApiId(132)
        @GetMapping("/report/import-log")
        public ListPagedDTO<AvHoursImportLogDTO> listImportLog(@RequestParam String property, @Valid ImportAvHoursLogQuery 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(133)
        @PutMapping("/exceptions/ignore/{id:\\d+}")
        public Map<Object, Object> ignore(@RequestParam String property, @PathVariable long id) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验是否存在
            AttendanceAvsImportLog 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 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));
            }
        }

        /**
         * 获取 Actual 报告解析服务
         *
         * @param source 来源标识
         * @return Actual 报告解析服务
         * @author zc
         * @date 2025/4/21
         */
        private IActualHourParseService getActualHourReportParser(String source) {
            Collection<IActualHourParseService> parserList = applicationContext.getBeansOfType(IActualHourParseService.class).values();
            for (IActualHourParseService parseService : parserList) {
                if (parseService.supports(source)) {
                    return parseService;
                }
            }
            throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                    BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                    "Can not find Sva Actual Hour report parser");
        }

        /**
         * 获取 Schedule 报告解析服务
         *
         * @param source 来源标识
         * @return Schedule 报告解析服务
         * @author zc
         * @date 2025/4/21
         */
        private IScheduleHourParseService getScheduleHourReportParser(String source) {
            Collection<IScheduleHourParseService> parserList = applicationContext.getBeansOfType(IScheduleHourParseService.class).values();
            for (IScheduleHourParseService parseService : parserList) {
                if (parseService.supports(source)) {
                    return parseService;
                }
            }
            throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                    BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                    "Can not find Sva Schedule Hour report parser");
        }
    }