# AttendanceActualScheduleHourImportLogServiceImpl.java

    package cloud.wconnect.workperformance.service.sva.actualschedulehour.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.ReportImportResultCons;
    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.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.workperformance.entity.sva.avhours.AttendanceAvsImportLog;
    import cloud.wconnect.workperformance.mapper.sva.avhours.AttendanceAvsImportLogMapper;
    import cloud.wconnect.workperformance.pojo.dto.sva.avhours.AvHoursImportLogDTO;
    import cloud.wconnect.workperformance.pojo.query.sva.avhours.ImportAvHoursLogQuery;
    import cloud.wconnect.workperformance.service.sva.actualschedulehour.IAttendanceActualScheduleHourImportLogService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.lang.Nullable;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

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

    /**
     * <p>
     * 服务实现类：Actual vs Schedule Hours报告导入日志
     * </p>
     *
     * @author zc
     * @since 2023-03-22
     */
    @Service
    @DS("work_performance")
    public class AttendanceActualScheduleHourImportLogServiceImpl extends ServiceImpl<AttendanceAvsImportLogMapper, AttendanceAvsImportLog> implements IAttendanceActualScheduleHourImportLogService {
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private BeanMapper beanMapper;

        /**
         * 忽略指定的报告异常记录
         *
         * @param id         id
         * @param operatorId 操作人 id
         * @author zc
         * @date 2023/3/22
         */
        @Override
        public void ignore(long id, int operatorId) {
            AttendanceAvsImportLog importLog = new AttendanceAvsImportLog();
            importLog.setId(id);
            importLog.setImportResult(ReportImportResultCons.IMPORT_RESULT_IGNORE);
            importLog.setResolvedBy(operatorId);
            importLog.setResolvedTime(DateTimeUtils.nowTimestamp());
            updateById(importLog);
        }

        /**
         * 根据报告数据日期获取导入记录数量
         *
         * @param propertyId   资产 id
         * @param theDate      报告数据日期
         * @param resultStatus 导入结果
         * @return 导入记录数量
         * @author zc
         * @date 2023/10/2
         */
        @Override
        public long count(int propertyId, long theDate, @Nullable Integer resultStatus) {
            return count(new QueryWrapper<AttendanceAvsImportLog>().lambda()
                    .eq(AttendanceAvsImportLog::getTheDate, theDate)
                    .eq(AttendanceAvsImportLog::getPropertyId, propertyId)
                    .eq(Objects.nonNull(resultStatus), AttendanceAvsImportLog::getImportResult, resultStatus));
        }

        /**
         * 批量新增日志
         *
         * @param propertyDTOList      资产集合
         * @param propertyId2DateRange 报告数据日期范围(yyyyMMdd)
         * @param importResult         报告导入结果
         * @param operatorId           操作人 id
         * @param url                  S3 key
         * @param reportType           报告类型（1.实际工时报告，2.预计工时报告）
         * @return 日志 id 集合
         * @author zc
         * @date 2024/8/19
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<Long> addBatch(List<PropertyDTO> propertyDTOList, Map<Integer, List<Long>> propertyId2DateRange,
                                   int importResult, int operatorId, @Nullable String url, int reportType) {
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            List<AttendanceAvsImportLog> importLogList = Lists.newArrayList();
            propertyDTOList.forEach(propertyDTO -> {
                String timezone = propertyDTO.getTimezone();
                List<Long> dateRangeList = propertyId2DateRange.getOrDefault(propertyDTO.getId(), Collections.emptyList());
                if (CollectionUtils.isEmpty(dateRangeList)) {
                    return;
                }
                importLogList.add(new AttendanceAvsImportLog()
                        .setPropertyId(propertyDTO.getId())
                        .setReportType(reportType)
                        .setTheDate(DateTimeUtils.dateNowTimestamp(timezone))
                        .setStartDate(DateTimeUtils.dateStrToEpochSecond(dateRangeList.get(0).toString(),
                                BaseConstant.DATE_FORMAT_YYYYMMDD, timezone))
                        .setEndDate(DateTimeUtils.dateStrToEpochSecond(dateRangeList.get(1).toString(),
                                BaseConstant.DATE_FORMAT_YYYYMMDD, timezone))
                        .setImportResult(importResult)
                        .setUrl(StringUtils.isNotBlank(url) ? url : "")
                        .setCreatedBy(operatorId)
                        .setCreatedTime(nowTimestamp)
                        .setResolvedBy(BaseConstant.ZERO)
                        .setResolvedTime(BaseConstant.LONG_ZERO));
            });
            saveBatch(importLogList);

            return importLogList.stream().map(AttendanceAvsImportLog::getId).toList();
        }

        /**
         * 新增日志
         *
         * @param propertyDTO  资产
         * @param fromDate     报告数据开始日期(yyyyMMdd)
         * @param toDate       报告数据结束日期(yyyyMMdd)
         * @param importResult 报告导入结果
         * @param operatorId   操作人 id
         * @param url          S3 key
         * @param reportType   报告类型（1.实际工时报告，2.预计工时报告）
         * @author zc
         * @date 2024/2/28
         */
        @Override
        public void add(PropertyDTO propertyDTO, long fromDate, long toDate,
                        int importResult, int operatorId, @Nullable String url, int reportType) {
            String timezone = propertyDTO.getTimezone();
            save(new AttendanceAvsImportLog()
                    .setPropertyId(propertyDTO.getId())
                    .setReportType(reportType)
                    .setTheDate(DateTimeUtils.dateNowTimestamp(timezone))
                    .setStartDate(DateTimeUtils.dateStrToEpochSecond(Long.valueOf(fromDate).toString(),
                            BaseConstant.DATE_FORMAT_YYYYMMDD, timezone))
                    .setEndDate(DateTimeUtils.dateStrToEpochSecond(Long.valueOf(toDate).toString(),
                            BaseConstant.DATE_FORMAT_YYYYMMDD, timezone))
                    .setImportResult(importResult)
                    .setUrl(StringUtils.isNotBlank(url) ? url : "")
                    .setCreatedBy(operatorId)
                    .setCreatedTime(DateTimeUtils.nowTimestamp())
                    .setResolvedBy(BaseConstant.ZERO)
                    .setResolvedTime(BaseConstant.LONG_ZERO));
        }

        /**
         * 根据处理结果更新导入日志
         *
         * @param importLogIds 日志 id 集合
         * @param handleResult 处理结果 true：成功 false：失败
         * @param operatorId   操作人 id
         * @param url          S3 key
         * @author zc
         * @date 2023/10/19
         */
        @Override
        public void updateBatch(List<Long> importLogIds, boolean handleResult, int operatorId, @Nullable String url) {
            // 获取日志并判断导入状态
            List<AttendanceAvsImportLog> importLogs = listByIds(importLogIds);
            if (CollectionUtils.isEmpty(importLogs)) {
                return;
            }

            LambdaUpdateWrapper<AttendanceAvsImportLog> updateWrapper = new UpdateWrapper<AttendanceAvsImportLog>().lambda()
                    .in(AttendanceAvsImportLog::getId, importLogIds)
                    .set(StringUtils.isNotBlank(url), AttendanceAvsImportLog::getUrl, url)
                    .set(AttendanceAvsImportLog::getImportResult, handleResult ?
                            ReportImportResultCons.IMPORT_RESULT_SUCCESS : ReportImportResultCons.IMPORT_RESULT_FAIL);
            if (importLogs.get(0).getImportResult() == ReportImportResultCons.IMPORT_RESULT_REPAIRING
                    && handleResult) {
                updateWrapper.set(AttendanceAvsImportLog::getResolvedBy, operatorId)
                        .set(AttendanceAvsImportLog::getResolvedTime, DateTimeUtils.nowTimestamp());
            }
            update(updateWrapper);
        }

        /**
         * 校验日志是否存在
         *
         * @param id 日志 id
         * @return AttendanceAvsImportLog
         * @author zc
         * @date 2023/10/18
         */
        @Override
        public AttendanceAvsImportLog checkExist(long id) {
            AttendanceAvsImportLog importLog = getById(id);
            if (Objects.isNull(importLog)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The importLog is not found; id:" + id);
            }
            return importLog;
        }

        /**
         * 校验日志归属
         *
         * @param propertyId 资产 id
         * @param importLog  日志
         * @author zc
         * @date 2023/10/18
         */
        @Override
        public void checkBelong(int propertyId, AttendanceAvsImportLog importLog) {
            if (!importLog.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("The importLog does not belong to this property; propertyId:{0}; importLogPropertyId:{1}",
                                propertyId, importLog.getPropertyId()));
            }
        }

        /**
         * 查询报告导入记录
         *
         * @param propertyDTO 资产
         * @param query       查询条件
         * @return AvHoursImportLogDTO 集合
         * @author zc
         * @date 2023/10/19
         */
        @Override
        public ListPagedDTO<AvHoursImportLogDTO> list(PropertyDTO propertyDTO, ImportAvHoursLogQuery query) {
            LambdaQueryWrapper<AttendanceAvsImportLog> queryWrapper = new QueryWrapper<AttendanceAvsImportLog>().lambda()
                    .eq(AttendanceAvsImportLog::getPropertyId, propertyDTO.getId())
                    .in(AttendanceAvsImportLog::getReportType, query.getReportType())
                    .in(CollectionUtils.isNotEmpty(query.getStatus()),
                            AttendanceAvsImportLog::getImportResult, query.getStatus())
                    .and(Objects.nonNull(query.getStartOperationDate()) && Objects.nonNull(query.getEndOperationDate()),
                            wrapper -> wrapper.between(AttendanceAvsImportLog::getCreatedTime,
                                            query.getStartOperationDate(), query.getEndOperationDate())
                                    .or().between(AttendanceAvsImportLog::getResolvedTime,
                                            query.getStartOperationDate(), query.getEndOperationDate()))
                    .between(Objects.nonNull(query.getStartFromDataDate()) && Objects.nonNull(query.getStartToDataDate()),
                            AttendanceAvsImportLog::getStartDate, query.getStartFromDataDate(), query.getStartToDataDate())
                    .between(Objects.nonNull(query.getEndFromDataDate()) && Objects.nonNull(query.getEndToDataDate()),
                            AttendanceAvsImportLog::getEndDate, query.getEndFromDataDate(), query.getEndToDataDate());

            List<AttendanceAvsImportLog> importLogList;
            long total;
            if (Objects.nonNull(query.getPage()) && Objects.nonNull(query.getSize())) {
                Page<AttendanceAvsImportLog> page = page(new Page<>(query.getPage(), query.getSize()),
                        queryWrapper.orderByDesc(AttendanceAvsImportLog::getId));
                importLogList = page.getRecords();
                total = page.getTotal();
            } else {
                importLogList = list(queryWrapper);
                total = importLogList.size();
            }
            if (CollectionUtils.isEmpty(importLogList)) {
                return new ListPagedDTO<>();
            }

            Set<Integer> userIds = Sets.newHashSet();
            importLogList.forEach(item -> {
                userIds.add(item.getCreatedBy());
                userIds.add(item.getResolvedBy());
            });
            Map<Integer, String> id2UserName = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);
            return new ListPagedDTO<>(total, beanMapper.mapList(importLogList,
                    AvHoursImportLogDTO.class, (item, dto) -> {
                        dto.setCreatedByName(id2UserName.get(item.getCreatedBy()));
                        dto.setResolvedByName(id2UserName.get(item.getResolvedBy()));
                    }));
        }
    }