# EmployeeByAdp.java

    package cloud.wconnect.workperformance.service.schedule.employee;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.EventBodyParamEnum;
    import cloud.wconnect.commonlib.cons.ReportImportResultCons;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.pojo.bo.SymbolBO;
    import cloud.wconnect.commonlib.pojo.dto.EventBodyDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.service.AttendancePropertyCodeInternalService;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.FileCheckUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.UploadUtils;
    import cloud.wconnect.sns.http.processor.Sender;
    import cloud.wconnect.workperformance.cons.BusinessEnum;
    import cloud.wconnect.workperformance.cons.EventSubjectEnum;
    import cloud.wconnect.workperformance.cons.S3BucketFunctionCons;
    import cloud.wconnect.workperformance.entity.schedule.ScheduleDepartmentEmployeeImportLog;
    import cloud.wconnect.workperformance.entity.schedule.ScheduleImportEmployeeFilterConfig;
    import cloud.wconnect.workperformance.pojo.bo.schedule.EmployeeInfoBO;
    import cloud.wconnect.workperformance.pojo.bo.schedule.ScheduleDepartmentEmployeeBO;
    import cloud.wconnect.workperformance.service.AmazonS3Service;
    import cloud.wconnect.workperformance.service.schedule.IScheduleDepartmentEmployeeImportLogService;
    import cloud.wconnect.workperformance.service.schedule.IScheduleImportEmployeeFilterConfigService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.csv.CSVFormat;
    import org.apache.commons.csv.CSVRecord;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import org.springframework.web.multipart.MultipartFile;

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

    /**
     * Employee By Adp Auto
     *
     * @author yyt
     * 2023/7/7
     */
    @Slf4j
    @Component
    @DS("work_performance")
    public class EmployeeByAdp implements IEmployeeFileParse {
        @Resource
        private AttendancePropertyCodeInternalService attendancePropertyCodeInternalService;
        @Resource
        private PropertyInternalService propertyInternalService;
        @Resource
        private IScheduleImportEmployeeFilterConfigService scheduleImportEmployeeFilterConfigService;
        @Resource
        private IScheduleDepartmentEmployeeImportLogService scheduleDepartmentEmployeeImportLogService;
        @Autowired
        private AmazonS3Service amazonS3Service;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private BeanMapper beanMapper;

        @Value("${aws-s3.third-party.key.schedule.employee}")
        private String employeeS3key;

        @Value("${file-max-size.import-report.schedule}")
        private Long importReportFileMaxSize;

        private static final String SOURCE = SystemPlatformEnum.ADP.getDesc();

        private final Sender snsSender;

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

        /**
         * 判断可导入文件条件
         *
         * @param source 平台数据源
         * @return boolean
         * @author yyt
         * @date 2023/7/7
         */
        @Override
        public boolean supports(String source) {
            return source.equals(SOURCE);
        }

        /**
         * 解析报告
         *
         * @param inputStream 文件流
         * @param propertyIds 资产 id
         * @return department employee
         * @author yyt
         * @date 2023/7/7
         */
        @Override
        public Map<Integer, List<ScheduleDepartmentEmployeeBO>> parsReport(InputStreamReader inputStream, List<Integer> propertyIds) {
            // 查询 code 和 property 的对应关系
            List<PropertyDTO> propertyDTOList = propertyInternalService.getProperty(propertyIds);
            Map<String, PropertyDTO> code2PropertyDTO = attendancePropertyCodeInternalService.getCode2PropertyDTO(propertyDTOList);
            if (code2PropertyDTO.isEmpty()) {
                return Collections.emptyMap();
            }

            // 获取 导入员工过滤配置
            Map<Integer, List<String>> filterPositionIdsByPropertyId =
                    scheduleImportEmployeeFilterConfigService.list(new QueryWrapper<ScheduleImportEmployeeFilterConfig>()
                            .lambda().in(ScheduleImportEmployeeFilterConfig::getPropertyId, propertyIds))
                    .stream().collect(Collectors.groupingBy(ScheduleImportEmployeeFilterConfig::getPropertyId,
                            Collectors.mapping(ScheduleImportEmployeeFilterConfig::getPositionId, Collectors.toList())));

            List<EmployeeInfoBO> employees = Lists.newArrayList();
            try {
                // 解析报告
                Iterable<CSVRecord> records = CSVFormat.EXCEL.parse(inputStream);
                for (CSVRecord record : records) {
                    // 跳过第一行的标题
                    if (record.getRecordNumber() == 1) {
                        continue;
                    }

                    PropertyDTO property = code2PropertyDTO.get(record.get(0));
                    if (Objects.isNull(property)) {
                        continue;
                    }

                    String positionId = record.get(1);
                    if (StringUtils.isNotEmpty(positionId) &&
                            filterPositionIdsByPropertyId.getOrDefault(property.getId(), Collections.emptyList()).contains(positionId)) {
                        continue;
                    }

                    // Department Code 为空的不保存
                    String departmentCode = record.get(3);
                    if (StringUtils.isEmpty(departmentCode)) {
                        continue;
                    }

                    EmployeeInfoBO employee = new EmployeeInfoBO();
                    employee.setPositionId(positionId);
                    employee.setPropertyId(property.getId());
                    String payrollName = record.get(2);
                    employee.setName(payrollName);
                    employee.setDepartmentCode(departmentCode);
                    String departmentDesc = record.get(4);
                    employee.setDepartmentName(departmentDesc);
                    employees.add(employee);
                }
            } catch (Exception e) {
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "parse employee report error", e);
            }

            return employees.stream().collect(Collectors.groupingBy(EmployeeInfoBO::getPropertyId,
                    Collectors.mapping(item -> beanMapper.map(item, ScheduleDepartmentEmployeeBO.class), Collectors.toList())));
        }

        /**
         * 检查文件
         *
         * @param propertyDTO 资产
         * @param file        文件
         * @author yyt
         * @date 2024/8/6
         **/
        @Override
        public void checkFile(PropertyDTO propertyDTO, MultipartFile file) {
            // 限制文件大小
            FileCheckUtils.checkFileSize(file, importReportFileMaxSize);
            // 校验文件格式
            UploadUtils.checkFileFormat(file, Lists.newArrayList("csv"));
        }

        /**
         * 处理导入记录
         *
         * @param tenantBO     租户
         * @param propertyDTOS 资产
         * @param importLog    导入记录
         * @param file         文件
         * @param userId       操作人
         * @return 导入记录
         * @author yyt
         * @date 2024/8/6
         **/
        @Override
        public List<ScheduleDepartmentEmployeeImportLog> handleImportLog(SymbolBO tenantBO, List<PropertyDTO> propertyDTOS, ScheduleDepartmentEmployeeImportLog importLog,
                                                                         MultipartFile file, int userId) {
            List<String> propertyAbbrList = propertyDTOS.stream().map(PropertyDTO::getAbbr).collect(Collectors.toList());
            String key = toS3(tenantBO.getSymbol(), String.join("_", propertyAbbrList), file);
            if (Objects.isNull(importLog)) {
                return scheduleDepartmentEmployeeImportLogService.addBatch(propertyDTOS.stream()
                                .map(PropertyDTO::getId).collect(Collectors.toList()), key,
                        ReportImportResultCons.IMPORT_IN_PROGRESS, userId);
            } else {
                // 更新异常为修复中
                ScheduleDepartmentEmployeeImportLog updateContent = new ScheduleDepartmentEmployeeImportLog();
                updateContent.setId(importLog.getId());
                updateContent.setImportResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING);
                updateContent.setUrl(key);
                scheduleDepartmentEmployeeImportLogService.updateById(updateContent);
                importLog.setImportResult(ReportImportResultCons.IMPORT_RESULT_REPAIRING);
                return Lists.newArrayList(importLog);
            }
        }

        /**
         * 上传文件至 s3
         *
         * @param tenantSymbol   租户标识
         * @param propertySymbol 资产标识
         * @param file           文件
         * @return 文件路径
         * @author yyt
         * @date 2024/8/2
         **/
        private String toS3(String tenantSymbol, String propertySymbol, MultipartFile file) {
            String key = tenantSymbol + "/" + propertySymbol + "/" + employeeS3key +
                    UploadUtils.getRandomFileName(file.getOriginalFilename());
            try {
                // 上传文件
                amazonS3Service.uploadFile(S3BucketFunctionCons.SCHEDULE_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 tenantBO     租户
         * @param propertyDTOS 资产
         * @param file         文件
         * @param importLogs   导入记录
         * @param userId       操作人
         * @author yyt
         * @date 2024/8/6
         **/
        @Override
        public void handleFile(SymbolBO tenantBO, List<PropertyDTO> propertyDTOS, MultipartFile file,
                               List<ScheduleDepartmentEmployeeImportLog> importLogs, Integer userId) {
            try {
                List<String> propertyAbbrs = propertyDTOS.stream().map(PropertyDTO::getAbbr).collect(Collectors.toList());
                // 上传报告到 S3 服务
                String key = tenantBO.getSymbol() + "/" + String.join("_", propertyAbbrs) +
                        "/" + employeeS3key + UploadUtils.getRandomFileName(file.getOriginalFilename());
                amazonS3Service.uploadFile(file, key);

                HashMap<String, Object> parameter = Maps.newHashMapWithExpectedSize(6);
                parameter.put(EventBodyParamEnum.SOURCE.getDesc(), SystemPlatformEnum.ADP.getDesc());
                parameter.put(EventBodyParamEnum.TENANT_SYMBOL.getDesc(), tenantBO.getSymbol());
                parameter.put(EventBodyParamEnum.PROPERTY_ABBR.getDesc(), propertyAbbrs);
                parameter.put(EventBodyParamEnum.UID.getDesc(), userId);
                parameter.put(EventBodyParamEnum.ID.getDesc(), importLogs.stream().map(ScheduleDepartmentEmployeeImportLog::getId)
                        .collect(Collectors.toList()));
                parameter.put(EventBodyParamEnum.ORIGINATE_FROM.getDesc(), BusinessEnum.SCHEDULE_EMPLOYEE.getBusiness());

                // 手动发送事件通知
                snsSender.publish(EventSubjectEnum.ATTENDANCE_EMPLOYEE.getSubject(),
                        JsonUtils.convertObjToString(objectMapper, new EventBodyDTO(key, parameter)));
            } catch (Exception e) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Failed to send report event", e);
            }
        }

    }