package jnpf.attendance.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.util.StringUtil;
import com.google.common.collect.Maps;
import jnpf.attendance.antifreeze.UserAntifreeze;
import jnpf.attendance.mapper.AttendanceClockInMapper;
import jnpf.attendance.mapper.AttendanceDayStatisticsMapper;
import jnpf.attendance.mapper.AttendanceLeaveApproveMapper;
import jnpf.attendance.mapper.StatisticsMapper;
import jnpf.attendance.service.*;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.base.vo.PageListVO;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.AttendanceBaseSetting;
import jnpf.entity.attendance.AttendanceClockInPic;
import jnpf.entity.attendance.AttendanceClockInResult;
import jnpf.entity.attendance.FtbAttendanceDailyRule;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.enums.attendance.ClockInStatusEnum;
import jnpf.enums.attendance.StatisticsEnumUtil;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.model.*;
import jnpf.model.attendance.vo.*;
import jnpf.model.attendance.vo.attendance.*;
import jnpf.model.common.DateRangeDto;
import jnpf.model.personnels.dto.staff.roster.FtbPersonnelsStaffRosterDto;
import jnpf.model.personnels.req.roster.StaffRosterListReq;
import jnpf.permission.V2UserApi;
import jnpf.permission.eum.v2.TargetAuthEnums;
import jnpf.permission.eum.v2.UserWorkStatusEnums;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.permission.vo.v2.TargetAuthIdsVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.personnels.FtbPersonnelsRosterManagerApi;
import jnpf.util.*;
import jnpf.util.attendance.DayStatisticsUtils;
import jnpf.util.auth.V2AuthPermissionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.hutool.core.collection.CollUtil.newArrayList;
import static jnpf.enums.attendance.StatisticsEnumUtil.TabDataTypeEnum.JD;
import static jnpf.enums.attendance.StatisticsEnumUtil.TabDataTypeEnum.getTabDataTypeEnum;
import static jnpf.util.DateUtil.stringToDates;
import static jnpf.util.attendance.DayStatisticsUtils.getCollect;

@Slf4j
@Service
public class AppStatisticsServiceImpl implements AppStatisticsService {
    @Resource
    private V2UserApi v2UserApi;
    @Autowired
    private UserAntifreeze userApi;
    @Resource
    private StatisticsMapper statisticsMapper;
    @Resource
    private StatisticsUtilService utilService;
    @Resource
    private AttendanceGroupService groupService;
    @Resource
    private AttendanceUserService userService;
    @Resource
    private AttendanceClockInMapper clockInMapper;
    @Resource
    private AttendanceClockInService clockInService;
    @Resource
    private V2AuthPermissionUtils authPermissionUtils;
    @Resource
    private AttendanceDailyRuleService dailyRuleService;
    @Resource
    private AttendanceClockInPicService clockInPicService;
    @Resource
    private AttendanceBaseSettingService baseSettingService;
    @Resource
    private AttendanceLeaveApproveMapper leaveApproveMapper;
    @Resource
    private AttendanceDayStatisticsMapper dayStatisticsMapper;
    @Resource
    private AttendanceDayStatisticsService dayStatisticsService;


    @Override
    public AppStatisticsListVo getAppHomeData(AppStatisticsListDto req) {
        Date start = DateDetail.getMonthBeginDate(req.getMonth());
        Date end = DateDetail.getMonthEndDate(req.getMonth());
        List<AttendanceGroupUser> groupUserList = userService.getAttendanceGroupUsersOfSecondment(start, end,
                newArrayList(req.getUserId()), null);
        AppStatisticsListVo appStatisticsListVo = new AppStatisticsListVo();
        if (CollUtil.isEmpty(groupUserList)) {
            return appStatisticsListVo;
        }
        //月份格式化返回月份开始结束日期
        DateRangeDto selectDateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        DateRangeDto showDateRangeDto = new DateRangeDto(req.getMonth(), Boolean.FALSE);
        MonthStatisticsDataQueryDto monthStatisticsQueryDto = MonthStatisticsDataQueryDto.builder()
                .startDate(selectDateRangeDto.getStartDate())
                .endDate(selectDateRangeDto.getEndDate())
                .userIds(Collections.singletonList(req.getUserId()))
                .queryType(1)
                .build();
        List<MonthStatisticsQueryVo> pageList = dayStatisticsMapper.getMonthPageList(monthStatisticsQueryDto);
        if (CollUtil.isEmpty(pageList)) {
            return appStatisticsListVo;
        }
        //获取用户的应出勤天数
        appStatisticsListVo.setActualAttendDays(pageList.stream()
                .map(MonthStatisticsQueryVo::getEffectiveAttendDays).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
        appStatisticsListVo.setLateTimes(pageList.stream().map(MonthStatisticsQueryVo::getLateTimes)
                .reduce(Integer::sum).orElse(0));
        appStatisticsListVo.setEarlyLeaveTimes(pageList.stream()
                .map(MonthStatisticsQueryVo::getEarlyLeaveTimes).reduce(Integer::sum).orElse(0));
        appStatisticsListVo.setMissingCardTimes(pageList.stream()
                .map(MonthStatisticsQueryVo::getAbsenceCardTimes).reduce(Integer::sum).orElse(0));
        appStatisticsListVo.setAbsentTimes(pageList.stream()
                .map(MonthStatisticsQueryVo::getAbsenceTimes).reduce(Integer::sum).orElse(0));
        //生成每月考勤展示数据
        List<Date> dateArrayList = new ArrayList<>();
        LocalDate startDate = LocalDate.parse(showDateRangeDto.getStartDate());
        LocalDate endDate = LocalDate.parse(showDateRangeDto.getEndDate());
        while (startDate.isBefore(endDate) || startDate.isEqual(endDate)) {
            dateArrayList.add(Date.from(startDate.atTime(LocalTime.MIDNIGHT).atZone(ZoneId.systemDefault()).toInstant()));
            startDate = startDate.plusDays(1);
        }
        //获取日统计数据
        DayStatisticsDataPageListQueryDto dayStatisticsQueryDto = DayStatisticsDataPageListQueryDto.builder()
                .startDate(selectDateRangeDto.getStartDate())
                .endDate(selectDateRangeDto.getEndDate())
                .userIds(Collections.singletonList(req.getUserId()))
                .queryType(1)
                .build();
        List<DayStatisticsQueryVo> dayStatisticsQueryVoList = dayStatisticsMapper.getDayPageList(dayStatisticsQueryDto);
        //获取每日排班信息-app首页点点展示
        appStatisticsListVo.setClockInfoList(utilService.getUserClockDateArrayList(req.getUserId(),
                selectDateRangeDto, dateArrayList, dayStatisticsQueryVoList));
        return appStatisticsListVo;
    }

    /**
     * 添加原始数据到列表
     *
     * @param clockIns   打卡详情
     * @param allRuleIds 今日所有出勤规则
     * @param ruleIds    需要排除的记录
     */
    private void setOldData(List<ClockClassRecord> clockIns, List<String> allRuleIds, List<String> ruleIds, String userId, Date day) {
        // 查询今日所有打卡
        List<ClockClassRecord> clockInList = clockInMapper.getClockInByStatistics(userId, day);
        if (clockInList.isEmpty()) {
            return;
        }
        List<ClockClassRecord> finalSorted = getClockInListSort(clockIns);
        List<ClockClassRecord> collect = clockInList.stream().filter(v -> allRuleIds.contains(v.getRuleId())).collect(Collectors.toList());
        if (!collect.isEmpty()) {
            List<Integer> indexList = new ArrayList<>();
            collect.forEach(v -> indexList.add(clockInList.indexOf(v)));
            Integer minDelIndex = null;
            Integer maxDelIndex = null;
            // 获取最大 和 最小的 index
            int minIndex = indexList.get(0);
            int maxIndex = indexList.get(indexList.size() - 1);
            if (minIndex > 0) {
                // 往上查找不在 allRuleIds 中的 ruleId 不为空的记录 并移除往上所有记录
                for (int i = indexList.get(0) - 1; i > 0; i--) {
                    ClockClassRecord vo = clockInList.get(i);
                    if (vo.getRuleId() != null && !allRuleIds.contains(vo.getRuleId())) {
                        minDelIndex = i;
                        break;
                    }
                }
            }
            if (maxIndex < clockInList.size() - 1) {
                // 往下查找不在 allRuleIds 中的 ruleId 不为空的记录 并移除往下所有记录
                for (int i = indexList.get(0) + 1; i < clockInList.size(); i++) {
                    ClockClassRecord vo = clockInList.get(i);
                    if (vo.getRuleId() != null && !allRuleIds.contains(vo.getRuleId())) {
                        maxDelIndex = i;
                        break;
                    }
                }
            }
            // 排除 ruleId 在 ruleIds 中的记录 和 minDelIndex 往上的记录 和 maxDelIndex 往下的记录
            List<ClockClassRecord> removeList = new ArrayList<>();
            if (null != minDelIndex) {
                removeList.addAll(clockInList.subList(0, minDelIndex));
            }
            if (null != maxDelIndex) {
                removeList.addAll(clockInList.subList(maxDelIndex, clockInList.size() - 1));
            }
            clockInList.removeAll(removeList);
            clockInList.removeIf(v -> ruleIds.contains(v.getRuleId()));
        }
        if (!clockInList.isEmpty()) {
            // 转换数据 clockInTime 格式化, 打卡方式 转换成文字
            clockInList.forEach(v -> {
                String str = DateDetail.getDate2Str(day, DateDetail.DF).equals(DateDetail.getDate2Str(v.getClockInTime(), DateDetail.DF)) ? "" : "次日";
                v.setClockInTimeStr(str + DateDetail.getDate2Str(v.getClockInTime(), DateDetail.DF10));
                v.setClockInMethod(DayStatisticsUtils.getClockInMethod(null == v.getClockInMethod() ?
                        ConstantUtil.DEVICE_UNKNOWN : Integer.parseInt(v.getClockInMethod())));
            });

            // 将clockInList插入到今日出勤中[插入到两个班次时间之间]
            if (finalSorted.isEmpty()) {
                finalSorted.addAll(clockInList.stream().sorted(Comparator.comparing(ClockClassRecord::getClockInTime)).collect(Collectors.toList()));
            } else {
                Map<ClockClassRecord, List<ClockClassRecord>> map = new HashMap<>();
                Date beginTime = null;
                AtomicReference<String> ruleId = new AtomicReference<>(null);
                for (int i = 0; i < finalSorted.size(); i++) {
                    ClockClassRecord v1 = finalSorted.get(i);
                    ruleId.set(v1.getRuleId());
                    if (Objects.isNull(v1.getWorkDate())) {
                        continue;
                    }
                    ClockClassRecord v2 = null;
                    if (i + 1 < finalSorted.size()) {
                        v2 = finalSorted.get(i + 1);
                    }
                    if (i == 0) {
                        // 判断小于v1的clockInList
                        List<ClockClassRecord> list = clockInList.stream()
                                .filter(clockIn -> clockIn.getClockInTime().before(v1.getWorkDate()))
                                .peek(v -> {
                                    if (StringUtils.isEmpty(v.getRuleId())) {
                                        v.setRuleId(ruleId.get());
                                    }
                                })
                                .collect(Collectors.toList());
                        map.put(null, list);
                    }
                    if (null != v2 && v2.getWorkDate() != null) {
                        Date afterTime;
                        if (null != beginTime) {
                            afterTime = beginTime;
                            beginTime = null;
                            ruleId.set(v2.getRuleId());
                        } else {
                            afterTime = v1.getWorkDate();
                        }
                        Date beforeTime;
                        if (null != v2.getClockInStatus() && v2.getClockInStatus().equals(ClockInStatusEnum.NEED_NOT.getValue())) {
                            beforeTime = v1.getLackTime();
                            beginTime = v1.getLackTime();
                        } else {
                            beforeTime = v2.getWorkDate();
                        }
                        // 如果v2不为空, 判断哪些clockInList在v1, v2班次之间
                        List<ClockClassRecord> list = clockInList.stream()
                                .filter(clockIn -> clockIn.getClockInTime().after(afterTime) && clockIn.getClockInTime().before(beforeTime))
                                .peek(v -> {
                                    if (StringUtils.isEmpty(v.getRuleId())) {
                                        v.setRuleId(ruleId.get());
                                    }
                                })
                                .collect(Collectors.toList());
                        map.putIfAbsent(v1, list);
                    } else {
                        // 如果v2为空, 判断哪些clockInList大于v1班次
                        List<ClockClassRecord> list = clockInList.stream().filter(clockIn -> clockIn.getClockInTime()
                                .after(v1.getWorkDate())).collect(Collectors.toList());
                        map.putIfAbsent(v1, list);
                    }
                }
                map.forEach((key, valueList) -> {
                    List<ClockClassRecord> list = valueList.stream().sorted(Comparator.comparing(ClockClassRecord::getClockInTime))
                            .collect(Collectors.toList());
                    if (key == null) {
                        finalSorted.addAll(0, list);
                    } else {
                        int index = finalSorted.indexOf(key);
                        if (index + 1 > finalSorted.size() - 1) {
                            finalSorted.addAll(list);
                        } else {
                            finalSorted.addAll(index + 1, list);
                        }
                    }
                });
                clockIns.clear();
                clockIns.addAll(finalSorted);
            }
        }
    }

    private List<ClockClassRecord> getClockInListSort(List<ClockClassRecord> clockIns) {

        if (clockIns.isEmpty()) {
            return new ArrayList<>();
        }
        // 1. 按 ruleId 分组，得到 Map<ruleId, List<...>>
        Map<String, List<ClockClassRecord>> grouped =
                clockIns.stream()
                        .collect(Collectors.groupingBy(ClockClassRecord::getRuleId));
        // 2. 对每个分组，按照组内 workDate 升序排序（保证组内顺序：上班(早)在前）
        grouped.replaceAll((ruleId, l) -> l.stream()
                .filter(clockIn -> clockIn.getWorkDate() != null)
                .sorted(Comparator.comparing(ClockClassRecord::getWorkDate))
                .collect(Collectors.toList()));
        // 3. 构造一个 List of groups (Entry)，并按每组的最小 workDate 排序
        List<Map.Entry<String, List<ClockClassRecord>>> groupsSortedByMinDate =
                grouped.entrySet().stream()
                        .sorted(Comparator.comparing(e -> e.getValue().get(0).getWorkDate()))
                        .collect(Collectors.toList());
        // 4. 将排序好的组合并回一个最终列表（组内保持升序）
        List<ClockClassRecord> finalSorted = new ArrayList<>();
        for (Map.Entry<String, List<ClockClassRecord>> entry : groupsSortedByMinDate) {
            finalSorted.addAll(entry.getValue());
        }
        return finalSorted;
    }

    /**
     * 获取打卡记录
     *
     * @param clockIn 打卡记录
     * @param rule    出勤规则
     * @return jnpf.model.attendance.model.ClockClassRecord
     */
    private ClockClassRecord getDailyClockInVo(ClockInVo clockIn, AttendanceRuleVo rule, Map<String, AttendanceBaseSetting> enableBaseSetting) {
        ClockClassRecord dailyClockIn = BeanUtil.toBean(clockIn, ClockClassRecord.class);
        dailyClockIn.setOutStepOutType(rule.getOutStepOutType());
        dailyClockIn.setInStepOutType(rule.getInStepOutType());
        dailyClockIn.setDay(rule.getDay());
        dailyClockIn.setIsEffectiveClock(1);
        dailyClockIn.setCouldRepair(ConstantUtil.NUM_FALSE);
        dailyClockIn.setRemark(clockIn.getRemark());
        dailyClockIn.setAssociationApplyId(clockIn.getAssociationApplyId());
        dailyClockIn.setApprovalStatus(StringUtils.isEmpty(clockIn.getApplyId()) ? 0 : 1);
        AttendanceBaseSetting attendanceBaseSetting = enableBaseSetting.get(rule.getGroupId());
        // 能否补卡判断
        if (Objects.nonNull(attendanceBaseSetting) && !clockIn.getClockInStatus().equals(jnpf.enums.ClockInStatusEnum.WORK_NORMAL.getValue()) &&
                !clockIn.getClockInStatus().equals(jnpf.enums.ClockInStatusEnum.HOME_NORMAL.getValue())) {
            AttendanceClockInResult clockInResult = new AttendanceClockInResult();
            clockInResult.setUserId(clockIn.getUserId());
            clockInResult.setRuleId(clockIn.getRuleId());
            clockInResult.setClockInStatus(clockIn.getClockInStatus());
            clockInResult.setAbsence(clockIn.getAbsence());
            try {
                GroupRuleVo bean = BeanUtil.toBean(attendanceBaseSetting, GroupRuleVo.class);
                bean.setSelfGroupId(rule.getGroupId());
                MutablePair<Date, Date> datePair = clockInService.couldRepairRecord(dailyClockIn.getDay(),
                        clockInResult, dailyClockIn.getApprovalStatus(), bean);
                if (DateDetail.checkTimeBetween(dailyClockIn.getDay(), datePair.getLeft(), datePair.getRight())) {
                    dailyClockIn.setCouldRepair(ConstantUtil.NUM_TRUE);
                }
            } catch (Exception e) {
                log.error("获取统计默认数据时发生异常", e);
            }
        }
        dailyClockIn.setWorkDate(clockIn.getClockInType() == 1 ? rule.getInPoint() : rule.getOutPoint());
        dailyClockIn.setLackTime(clockIn.getClockInType() == 1 ? rule.getInLackPoint() : rule.getOutLackPoint());
        return dailyClockIn;
    }

    private List<ClockClassRecord> clockInAllRecord(String userId, Date day, List<AttendanceRuleVo> dailyRuleList) {
        if (CollUtil.isEmpty(dailyRuleList)) {
            return newArrayList();
        }
        List<String> allRuleIdList = dailyRuleList.stream().map(AttendanceRuleVo::getId).collect(Collectors.toList());
        List<ClockInVo> clockInResultList = clockInMapper.getClockInResultByRuleList(allRuleIdList);
        List<String> collect1 = dailyRuleList.stream().map(AttendanceRuleVo::getGroupId).distinct().collect(Collectors.toList());
        Map<String, AttendanceBaseSetting> enableBaseSetting = baseSettingService.getEnableBaseSetting(collect1);
        Map<String, AttendanceRuleVo> collect = dailyRuleList.stream().collect(Collectors.toMap(AttendanceRuleVo::getId, Function.identity()));
        // 需要展示打卡记录的出勤规则
        dailyRuleList = dailyRuleList.stream().filter(v -> v.getInPoint() != null && v.getOutPoint() != null).collect(Collectors.toList());
        List<String> ruleIdList = dailyRuleList.stream().map(AttendanceRuleVo::getId).collect(Collectors.toList());
        List<ClockClassRecord> clockIns = clockInResultList.stream().map(clockIn ->
                getDailyClockInVo(clockIn, collect.getOrDefault(clockIn.getRuleId(), new AttendanceRuleVo()),
                        enableBaseSetting)).collect(Collectors.toList());
        setOldData(clockIns, allRuleIdList, ruleIdList, userId, day);
        List<String> userIds = clockIns.stream().map(ClockClassRecord::getAbsenceLeader).filter(StringUtil::isNotEmpty)
                .distinct().collect(Collectors.toList());
        List<UserBoundVO> userList = CollUtil.isNotEmpty(userIds) ? v2UserApi.userNameAndCopy(userIds) : new ArrayList<>();
        Map<String, UserBoundVO> userMap = CollUtil.isNotEmpty(userList) ? userList.stream().collect(Collectors.toMap(UserBoundVO::getId, Function.identity())) : new HashMap<>();
        clockIns.stream().filter(item -> item.getAbsenceLeader() != null).forEach(clockIn ->
                clockIn.setAbsenceLeaderName(StringUtil.isNotEmpty(clockIn.getAbsenceLeader()) ?
                        userMap.get(clockIn.getAbsenceLeader()).getUserName() : null));
        return clockIns;
    }

    @Override
    public List<AppStatisticsRecordVo> getRecordData(AppStatisticsRecordDto req) throws Exception {
        List<AppStatisticsRecordVo> statisticsRecordVoList = new ArrayList<>();
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(req.getUserId());
        Date day = stringToDates(req.getDate());
        List<GroupInfoVo> groupInfoList = clockInService.getClockInMainInfo(stringToDates(req.getDate()), userInfo, ConstantUtil.NUM_TRUE);
        if (CollectionUtil.isNotEmpty(groupInfoList)) {
            // 排除掉不是今天的出勤规则
            groupInfoList.forEach(v -> {
                v.getAttendanceRuleList().removeIf(v1 -> v1.getDay().compareTo(day) != 0 || v1.getAttendanceType().equals(AttendanceTypeEnum.DEFAULT.getCode()));
            });
            groupInfoList.removeIf(v -> CollUtil.isEmpty(v.getAttendanceRuleList()));
            if (CollectionUtil.isEmpty(groupInfoList)) {
                return statisticsRecordVoList;
            }
            List<AttendanceRuleVo> attendanceRuleVos = groupInfoList.stream().map(GroupInfoVo::getAttendanceRuleList)
                    .flatMap(Collection::stream).collect(Collectors.toList());
            //查询打卡记录
            List<ClockClassRecord> dailyClockInVos = clockInAllRecord(req.getUserId(), day, attendanceRuleVos);
            //查询打卡记录的拍照图片集合
            Map<String, List<AttendanceClockInPic>> clockInPicMap = CollUtil.isNotEmpty(dailyClockInVos) ?
                    clockInPicService.getClockInPicByIds(dailyClockInVos.stream().map(ClockClassRecord::getClockInId).
                            filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList()), dailyClockInVos.stream()
                            .map(ClockClassRecord::getApplyId).filter(StringUtil::isNotEmpty).distinct()
                            .collect(Collectors.toList())) : new HashMap<>();
            AtomicReference<String> ruleId = new AtomicReference<>(dailyClockInVos.stream().map(ClockClassRecord::getRuleId)
                    .filter(StringUtil::isNotEmpty).findFirst().orElse(null));
            // 查询用户是否封账
            Map<String, Boolean> sealMap = dayStatisticsService.selectUserIsSeal(List.of(req.getUserId()), req.getDate().substring(0, 7));
            Map<String, List<ClockClassRecord>> clockClassRecordList = Maps.newHashMap();
            dailyClockInVos.forEach(vo -> {
                vo.setSeal(sealMap.getOrDefault(vo.getUserId(), false) ? 1 : 0);
                if (StringUtil.isNotEmpty(vo.getRuleId())) {
                    ruleId.set(vo.getRuleId());
                }
                if (!clockClassRecordList.containsKey(ruleId.get())) {
                    clockClassRecordList.put(ruleId.get(), newArrayList());
                }
                clockClassRecordList.get(ruleId.get()).add(DayStatisticsUtils.clock2ClockClassRecord(vo, clockInPicMap));
            });
            for (GroupInfoVo groupInfoVo : groupInfoList) {
                AppStatisticsRecordVo statisticsRecordVo = new AppStatisticsRecordVo();
                BeanUtils.copyProperties(groupInfoVo, statisticsRecordVo);
                statisticsRecordVo.setDay(day);
                List<ClockGroupRecord> attendanceRuleList = new ArrayList<>();
                //排除出勤类型为-1和0的排班记录
                List<AttendanceRuleVo> filterAttendanceRuleList = groupInfoVo.getAttendanceRuleList().stream().filter(m ->
                        !Arrays.asList(AttendanceTypeEnum.CLEAR.getCode(),
                                AttendanceTypeEnum.DEFAULT.getCode()).contains(m.getAttendanceType())).collect(Collectors.toList());
                //获取更新打卡的历史数据
                filterAttendanceRuleList.forEach(rule -> {
                    ClockGroupRecord bean = BeanUtil.toBean(rule, ClockGroupRecord.class);
                    bean.setClockList(clockClassRecordList.getOrDefault(rule.getId(), newArrayList()));
                    attendanceRuleList.add(bean);
                });
                statisticsRecordVo.setAttendanceRuleList(attendanceRuleList);
                statisticsRecordVoList.add(statisticsRecordVo);
            }
            return statisticsRecordVoList;
        }
        Date start = DateDetail.getStr2Date10(req.getDate());
        Date end = cn.hutool.core.date.DateUtil.endOfDay(start);
        List<AttendanceGroupUser> groupUserList = userService.getAttendanceGroupUsersOfSecondment(start, end,
                newArrayList(req.getUserId()), null);
        if (CollUtil.isEmpty(groupUserList)) {
            throw new QueryException("暂无数据");
        }
        String groupId = groupUserList.stream().filter(m -> m.getType().equals(ConstantUtil.NUM_TRUE))
                .collect(Collectors.toList()).get(0).getGroupId();
        AttendanceGroup group = groupService.getById(groupId);
        if (Objects.isNull(group)) {
            throw new QueryException("暂无数据");
        }
        AppStatisticsRecordVo statisticsRecordVo = new AppStatisticsRecordVo();
        statisticsRecordVo.setDay(stringToDates(req.getDate()));
        statisticsRecordVo.setGroupId(group.getId());
        statisticsRecordVo.setGroupName(group.getGroupName());
        statisticsRecordVo.setSelfGroup(1);
        statisticsRecordVo.setUserId(req.getUserId());
        statisticsRecordVoList.add(statisticsRecordVo);
        return statisticsRecordVoList;
    }

    @Override
    public AppStatisticsMoreVo getMoreDefaultData(AppStatisticsMoreDto req) throws Exception {
        DateRangeDto selectDateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        MonthStatisticsDataQueryDto dataQueryDto = MonthStatisticsDataQueryDto.builder()
                .startDate(selectDateRangeDto.getStartDate())
                .endDate(selectDateRangeDto.getEndDate())
                .userIds(Collections.singletonList(req.getUserId()))
                .queryType(1)
                .build();
        List<MonthStatisticsQueryVo> monthStatisticsQueryVoList = dayStatisticsMapper.getMonthPageList(dataQueryDto);
        if (CollUtil.isEmpty(monthStatisticsQueryVoList)) {
            throw new QueryException("暂无数据");
        }
        AppStatisticsMoreVo statisticsMore = new AppStatisticsMoreVo();
        BigDecimal actualAttendDays = BigDecimal.ZERO;
        BigDecimal leaveDays = BigDecimal.ZERO;
        BigDecimal leaveHours = BigDecimal.ZERO;
        int lateTimes = 0;
        BigDecimal lateAccumulatedMinutes = BigDecimal.ZERO;
        int earlyLeaveTimes = 0;
        BigDecimal earlyLeaveAccumulatedMinutes = BigDecimal.ZERO;
        int absenceCardTimes = 0;
        int absenceTimes = 0;
        int makeUpCardTimes = 0;
        int outworkClockTimes = 0;
        int overtimeTimes = 0;
        int busTimesTotal = 0;
        for (MonthStatisticsQueryVo item : monthStatisticsQueryVoList) {
            actualAttendDays = DayStatisticsUtils.safeAdd(actualAttendDays, item.getEffectiveAttendDays());
            leaveDays = DayStatisticsUtils.safeAdd(leaveDays, item.getLeaveDays());
            leaveHours = DayStatisticsUtils.safeAdd(leaveHours, item.getLeaveHours());
            lateTimes += DayStatisticsUtils.defaultIfNull(item.getLateTimes(), 0);
            lateAccumulatedMinutes = DayStatisticsUtils.safeAdd(lateAccumulatedMinutes, item.getLateMinutes());
            earlyLeaveTimes += DayStatisticsUtils.defaultIfNull(item.getEarlyLeaveTimes(), 0);
            earlyLeaveAccumulatedMinutes = DayStatisticsUtils.safeAdd(earlyLeaveAccumulatedMinutes, item.getEarlyLeaveMinutes());
            absenceCardTimes += DayStatisticsUtils.defaultIfNull(item.getAbsenceCardTimes(), 0);
            absenceTimes += DayStatisticsUtils.defaultIfNull(item.getAbsenceTimes(), 0);
            makeUpCardTimes += DayStatisticsUtils.defaultIfNull(item.getMakeUpCardTimes(), 0);
            outworkClockTimes += DayStatisticsUtils.defaultIfNull(item.getOutworkTimes(), 0);
            overtimeTimes += DayStatisticsUtils.defaultIfNull(item.getOvertimeTimes(), 0);
            busTimesTotal += DayStatisticsUtils.countSplitElements(item.getBusTimes());
        }
        List<BatchNumberResult> outTimesList = monthStatisticsQueryVoList.stream()
                .map(item -> MonthStatisticsQueryVo.getOutTimesList(item.getOutTimes()))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        statisticsMore.setActualAttendDays(actualAttendDays);
        statisticsMore.setLeaveDays(leaveDays);
        statisticsMore.setLeaveHours(leaveHours);
        statisticsMore.setLateTimes(lateTimes);
        statisticsMore.setLateAccumulatedMinutes(lateAccumulatedMinutes);
        statisticsMore.setEarlyLeaveTimes(earlyLeaveTimes);
        statisticsMore.setEarlyLeaveAccumulatedMinutes(earlyLeaveAccumulatedMinutes);
        statisticsMore.setAbsenceCardTimes(absenceCardTimes);
        statisticsMore.setAbsenceTimes(absenceTimes);
        statisticsMore.setMakeUpCardTimes(makeUpCardTimes);
        statisticsMore.setOutworkClockTimes(outworkClockTimes);
        statisticsMore.setOvertimeTimes(overtimeTimes);
        statisticsMore.setOutTimes(Math.toIntExact(outTimesList.stream().map(BatchNumberResult::getBatchNumberId).distinct().count()));
        statisticsMore.setBusTimes(busTimesTotal);
        // 获取借调次数
        List<UserSecondRecord> userSecondRecordList = statisticsMapper.getUserSecondRecordList(List.of(req.getUserId()), null);
        if (CollUtil.isNotEmpty(userSecondRecordList)) {
            statisticsMore.setSecondTimes(DayStatisticsUtils.handleDateJsonObject(selectDateRangeDto, userSecondRecordList));
        }
        return statisticsMore;
    }

    @Override
    public AppStatisticsMoreInfoVo getMoreExpandData(AppStatisticsMoreInfoDto req) throws Exception {
        AppStatisticsMoreInfoVo moreInfoVo = new AppStatisticsMoreInfoVo();
        //月份格式化返回月份开始结束日期
        DateRangeDto rangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        StatisticsEnumUtil.DataTypeEnum dataTypeEnum = StatisticsEnumUtil.DataTypeEnum.getDataTypeEnum(req.getType());
        if (Objects.isNull(dataTypeEnum)) {
            throw new QueryException("记录类型不正确");
        }
        //获取用户信息
        List<UserBoundVO> userList = v2UserApi.userNameAndCopy(List.of(req.getUserId()));
        Map<String, UserBoundVO> userMap = CollUtil.isNotEmpty(userList) ? userList.stream().collect(Collectors.toMap(UserBoundVO::getId, Function.identity())) : new HashMap<>();
        //获取用户月度考勤统计数据
        MonthStatisticsDataQueryDto monthStatisticsQueryDto = MonthStatisticsDataQueryDto.builder()
                .startDate(rangeDto.getStartDate())
                .endDate(rangeDto.getEndDate())
                .userIds(Collections.singletonList(req.getUserId()))
                .queryType(1)
                .build();
        List<MonthStatisticsQueryVo> monthStatisticsQueryVoList = dayStatisticsMapper.getMonthPageList(monthStatisticsQueryDto);
        if (CollUtil.isEmpty(monthStatisticsQueryVoList)) {
            throw new QueryException("暂无数据");
        }
        Integer enumTimes = dataTypeEnum.getTimes(monthStatisticsQueryVoList);
        // 获取每个考勤组的出勤换算比
        Map<String, BigDecimal> ratioMap = monthStatisticsQueryVoList.stream().collect(
                Collectors.toMap(MonthStatisticsQueryVo::getGroupId, MonthStatisticsQueryVo::getAttendanceRatio));
        // 查询用户是否封账
        Map<String, Boolean> sealMap = dayStatisticsService.selectUserIsSeal(List.of(req.getUserId()), req.getMonth());
        if (enumTimes > 0) {
            switch (dataTypeEnum) {
                case QJ:
                    Map<String, List<LeaveRecord>> userLeaveMap = utilService.getLeaveMap(userMap, ratioMap, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setLeaveRecordList(userLeaveMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case CD:
                    Map<String, List<LateRecord>> userLateMap = utilService.getLateMap(userMap, sealMap, null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setLateRecordList(userLateMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case ZT:
                    Map<String, List<EarlyLeaveRecord>> earlyLeaveMap = utilService.getEarlyMap(userMap, sealMap, null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setEarlyLeaveRecordList(earlyLeaveMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case QK:
                    Map<String, List<AbsenceCardRecord>> absenceCardMap = utilService.getAbsenceCardMap(userMap, sealMap, null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setAbsenceCardRecordList(absenceCardMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case QQ:
                    Map<String, List<AbsenceRecord>> absenceMap = utilService.getAbsenceMap(null, sealMap, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setAbsenceRecordList(absenceMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case BK:
                    Map<String, List<MakeUpCardRecord>> makeUpCardMap = utilService.getMakeUpCardMap(null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setMakeUpCardRecordList(makeUpCardMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case WQ:
                    Map<String, List<OutworkRecord>> outworkMap = utilService.getOutworkRecordList(null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setOutworkRecordList(outworkMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case JB:
                    Map<String, List<OvertimeRecord>> overtimeMap = utilService.getOvertimeRecordList(null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setOvertimeRecordList(overtimeMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case JD:
                    Map<String, List<SecondRecord>> secondMap = utilService.getSecondRecordList(null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setSecondRecordList(secondMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case CC:
                    Map<String, List<BusOrOutRecord>> busMap = utilService.getBusRecordList(null, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setBusRecordList(busMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
                case WC:
                    Map<String, List<BusOrOutRecord>> outMap = utilService.getOutRecordList(ratioMap, List.of(req.getUserId()), rangeDto);
                    moreInfoVo.setOutRecordList(outMap.values().stream().flatMap(List::stream).collect(Collectors.toList()));
                    break;
            }
        }
        return moreInfoVo;
    }

    @Override
    public AppStatisticsTeamListVo getTeamHomeData(AppStatisticsTeamListDto req) {
        AppStatisticsTeamListVo teamListVo = new AppStatisticsTeamListVo();
        //月份格式化返回月份开始结束日期
        DateRangeDto selectDateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        //获取用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), selectDateRangeDto.getStartDate(), selectDateRangeDto.getEndDate(), null, String.valueOf(0));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return teamListVo;
        }
        List<String> groupIds = req.getFilterList().stream()
                .map(GroupFilterDto::getGroupId)
                .distinct().collect(Collectors.toList());
        StatisticsDataQueryDto monthStatisticsQueryDto = StatisticsDataQueryDto.builder()
                .groupIds(CollUtil.isNotEmpty(groupIds) ? groupIds : null)
                .startDate(selectDateRangeDto.getStartDate())
                .endDate(selectDateRangeDto.getEndDate())
                .userIds(userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList()))
                .build();
        StatisticsDataQueryVo statisticsDataQueryVo = dayStatisticsMapper.getMonthStatisticsDataQuery(monthStatisticsQueryDto, Boolean.FALSE);
        if (Objects.isNull(statisticsDataQueryVo)) {
            return teamListVo;
        }
        teamListVo.setLeaveNumber(statisticsDataQueryVo.getLeaveCount());
        teamListVo.setLateNumber(statisticsDataQueryVo.getLateCount());
        teamListVo.setEarlyLeaveNumber(statisticsDataQueryVo.getEarlyLeaveCount());
        teamListVo.setAbsenceCardNumber(statisticsDataQueryVo.getAbsenceCardCount());
        teamListVo.setAbsenceNumber(statisticsDataQueryVo.getAbsenceCount());
        return teamListVo;
    }

    /**
     * 获取有权限的用户列表
     *
     * @param filterList 筛选条件
     * @param startDate 开始时间
     * @param endDate 结束时间
     * @param workStatus 用户状态
     * @return 用户列表
     */
    public List<UserBoundVO> getUserIdArr(List<GroupFilterDto> filterList, String startDate, String endDate, List<String> userIdsFilter, String workStatus) {
        List<String> groupIds = filterList.stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        List<String> teamIds = filterList.stream().filter(item -> item.getTeam() == 1).map(GroupFilterDto::getTeamId).distinct().collect(Collectors.toList());
        Map<String, List<String>> groupTeamMap = Map.of();
        Map<String, List<Integer>> groupTeamStaMap = Map.of();
        if (CollUtil.isNotEmpty(teamIds)) {
            // 按照考勤组分组，获取不同的班次id集合
            groupTeamMap = filterList.stream().filter(item -> item.getTeam() == 1 && jnpf.util.StringUtil.isNotEmpty(item.getTeamId())).collect(Collectors.groupingBy(GroupFilterDto::getGroupId, Collectors.mapping(GroupFilterDto::getTeamId, Collectors.toList())));
            groupTeamStaMap = filterList.stream().collect(Collectors.groupingBy(GroupFilterDto::getGroupId, Collectors.mapping(GroupFilterDto::getTeam, Collectors.toList())));
        }
        StatisticsEnumUtil.WorkStatusEnum workStatusEnum = StatisticsEnumUtil.WorkStatusEnum.getWorkStatusEnum(workStatus);
        Assert.notNull(workStatusEnum, "工作状态不正确");
        List<UserWorkStatusEnums> workStatusEnums = DayStatisticsUtils.getWorkStatusEnumList(Objects.requireNonNull(workStatusEnum));
        List<UserBoundVO> userBoundVoList = userService.batchGetUserBoundVO(groupIds, workStatusEnums);
        if (CollUtil.isEmpty(userBoundVoList)) {
            userBoundVoList = new ArrayList<>();
        }
        // 判断是否有人员过滤，有的话需要取交集
        List<String> userIds = CollUtil.isNotEmpty(userBoundVoList) ? userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList()) : List.of();
        // 查询用户所属权限
        TargetAuthIdsVO targetAuthIdsVO = authPermissionUtils.processAuthIds();
        // 是否配置仅下属
        List<String> underlingUserId = null;
        if (Objects.nonNull(targetAuthIdsVO) && Objects.equals(targetAuthIdsVO.getTargetAuthEnums(), TargetAuthEnums.USER)) {
            if (CollUtil.isEmpty(userBoundVoList)) {
                return CollUtil.newArrayList();
            }
            underlingUserId = userIds;
        }
        // 查询用户考勤组用户
        DateRangeDto dateRangeDto = new DateRangeDto(startDate, endDate);
        Date start = jnpf.util.DateUtil.stringToDate(dateRangeDto.getStartDate());
        Date end = jnpf.util.DateUtil.stringToDate(dateRangeDto.getEndDate());
        List<AttendanceGroupUser> groupUsers = userService.queryByUsersAndGroup(start, end, underlingUserId, groupIds);
        if (CollUtil.isEmpty(groupUsers)) {
            return CollUtil.newArrayList();
        }
        List<String> queryUserIds = new ArrayList<>();
        Map<String, List<AttendanceGroupUser>> groupUserMap = CollUtil.isNotEmpty(groupUsers) ? groupUsers.stream().collect(Collectors.groupingBy(AttendanceGroupUser::getUserId)) : new HashMap<>();
        for (Map.Entry<String, List<AttendanceGroupUser>> listEntry : groupUserMap.entrySet()) {
            if (dailyRuleService.findUserIsExistsByDay(listEntry.getValue(), start, end) != 0) {
                queryUserIds.add(listEntry.getKey());
            }
        }
        if (CollUtil.isEmpty(queryUserIds)) {
            return CollUtil.newArrayList();
        }
        // 获取未在权限用户里面的用户集合
        List<String> noPermissionUserIds = new ArrayList<>(CollUtil.subtract(queryUserIds, userIds));
        if (CollUtil.isNotEmpty(noPermissionUserIds)) {
            List<PartUserInfoVo> userBoundVos = userApi.getStaffRosterListInfoByIds(noPermissionUserIds);
            Map<String, PartUserInfoVo> userMap = userBoundVos.stream().collect(Collectors.toMap(PartUserInfoVo::getUserId, Function.identity(), (r1, r2) -> r1));
            if (CollUtil.isNotEmpty(userMap)) {
                List<UserBoundVO> finalUserBoundVoList = userBoundVoList;
                userBoundVos.forEach(item -> {
                    PartUserInfoVo partUserInfoVo = userMap.get(item.getUserId());
                    if (partUserInfoVo != null && finalUserBoundVoList.stream().noneMatch(userBoundVO -> userBoundVO.getId().equals(partUserInfoVo.getUserId()))) {
                        UserBoundVO userBoundVO = new UserBoundVO();
                        BeanUtils.copyProperties(partUserInfoVo, userBoundVO);
                        userBoundVO.setId(partUserInfoVo.getUserId());
                        userBoundVO.setUserName(partUserInfoVo.getRealName());
                        userBoundVO.setWorkStatusEnums(UserWorkStatusEnums.getUserWorkStatusEnumsByValue(item.getWorkerStatus()));
                        finalUserBoundVoList.add(userBoundVO);
                    }
                });
            }
        }
        if (CollUtil.isEmpty(userBoundVoList)) {
            return CollUtil.newArrayList();
        }
        Map<String, UserBoundVO> userIdToUserBoundVoMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, item -> item));
        List<UserBoundVO> result = new ArrayList<>();
        if (CollUtil.isNotEmpty(teamIds)) {
            Map<String, List<UserBoundVO>> groupUserListMap = groupUsers.stream().collect(Collectors.groupingBy(AttendanceGroupUser::getGroupId)).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().stream().map(AttendanceGroupUser::getUserId).map(userIdToUserBoundVoMap::get).filter(Objects::nonNull).collect(Collectors.toList())));
            Map<String, List<String>> finalGroupTeamMap = groupTeamMap;
            Map<String, List<Integer>> finalGroupTeamStaMap = groupTeamStaMap;
            groupUserListMap.forEach((groupId, groupUserList) -> {
                boolean flag = finalGroupTeamMap.containsKey(groupId) && (finalGroupTeamStaMap.containsKey(groupId) && !finalGroupTeamStaMap.get(groupId).contains(0));
                result.addAll(flag ? groupUserList.stream().filter(item -> teamIds.contains(item.getStoreTeamId())).collect(Collectors.toList()) : groupUserList);
            });
            userBoundVoList = result.stream().distinct().collect(Collectors.toList());
        }
        if (!workStatusEnum.equals(StatisticsEnumUtil.WorkStatusEnum.QB)) {
            userBoundVoList = userBoundVoList.stream().filter(item -> item.getWorkStatusEnums().getCode().equals(workStatusEnum.getCode())).collect(Collectors.toList());
        }
        if (CollUtil.isNotEmpty(userIdsFilter)) {
            userBoundVoList = userBoundVoList.stream().filter(item -> userIdsFilter.contains(item.getId())).collect(Collectors.toList());
        }
        return userBoundVoList;
    }

    @Override
    public List<AppStatisticsTeamTabVo> getTabListData(AppTeamStatisticsTabDto req) {
        DateRangeDto dateRangeDto = new DateRangeDto(req.getDate(), req.getDate());
        //根据枚举生成出勤类型的TabList
        List<AppStatisticsTeamTabVo> teamTabVoList = DayStatisticsUtils.createTabDataDirectory().stream().sorted(Comparator.comparing(AppStatisticsTeamTabVo::getDataType)).collect(Collectors.toList());
        //获取用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), req.getDate(), req.getDate(), null, String.valueOf(0));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return teamTabVoList;
        }
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        Map<String, UserBoundVO> userBoundMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        //获取考勤组当天打卡数据
        AppStatisticsTeamTabListVo teamTabListVo = statisticsTeamTabListVo(userBoundMap, groupIds, dateRangeDto);
        // 构建枚举到数据列表的映射关系
        Map<StatisticsEnumUtil.TabDataTypeEnum, List<ClockUser>> dataMap = new EnumMap<>(StatisticsEnumUtil.TabDataTypeEnum.class);
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.QB, teamTabListVo.getAllDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.XX, teamTabListVo.getRestDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.CD, teamTabListVo.getLateDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.ZT, teamTabListVo.getEarlyLeaveDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.QJ, teamTabListVo.getLeaveDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.QK, teamTabListVo.getAbsenceCardDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.QQ, teamTabListVo.getAbsenceDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.JD, teamTabListVo.getSecondDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.BK, teamTabListVo.getMakeUpCardDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.WQ, teamTabListVo.getOutworkDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.JB, teamTabListVo.getOvertimeDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.CC, teamTabListVo.getBusDataList());
        dataMap.put(StatisticsEnumUtil.TabDataTypeEnum.WC, teamTabListVo.getGoOutDataList());
        for (AppStatisticsTeamTabVo teamTabVo : teamTabVoList) {
            StatisticsEnumUtil.TabDataTypeEnum dataTypeEnum = getTabDataTypeEnum(teamTabVo.getDataType());
            if (Objects.isNull(dataTypeEnum)) {
                continue;
            }
            teamTabVo.setDataList(dataMap.get(dataTypeEnum));
        }
        return teamTabVoList;
    }

    @Override
    public AppTeamStatisticsVo getStatisticsData(AppTeamStatisticsDto req) {
        AppTeamStatisticsVo statisticsVo = new AppTeamStatisticsVo();
        DateRangeDto selectDateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        //获取用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), selectDateRangeDto.getStartDate(), selectDateRangeDto.getEndDate(), null, String.valueOf(0));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return statisticsVo;
        }
        List<String> groupIds = req.getFilterList().stream()
                .map(GroupFilterDto::getGroupId)
                .distinct().collect(Collectors.toList());
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        MonthStatisticsDataQueryDto monthStatisticsQueryDto = MonthStatisticsDataQueryDto.builder()
                .groupIds(CollUtil.isNotEmpty(groupIds) ? groupIds : null)
                .startDate(selectDateRangeDto.getStartDate())
                .endDate(selectDateRangeDto.getEndDate())
                .userIds(userIds)
                .queryType(1)
                .build();
        List<MonthStatisticsQueryVo> monthStatisticsQueryVoList = dayStatisticsMapper.getMonthPageList(monthStatisticsQueryDto);
        if (CollUtil.isNotEmpty(monthStatisticsQueryVoList)) {
            statisticsVo.setRestNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getRestTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setLeaveNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> StringUtil.isNotEmpty(item.getLeaveTimes())).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setLateNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getLateTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setEarlyLeaveNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getEarlyLeaveTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setAbsenceCardNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getAbsenceCardTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setAbsenceNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getAbsenceTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setMakeUpCardNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getMakeUpCardTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setOutworkNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getOutworkTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setOvertimeNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> item.getOvertimeTimes() > 0).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setOutNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> CollUtil.isNotEmpty(MonthStatisticsQueryVo.getOutTimesList(item.getOutTimes()))).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
            statisticsVo.setBusNumber(Math.toIntExact(monthStatisticsQueryVoList.stream().filter(item -> StringUtil.isNotEmpty(item.getBusTimes())).map(MonthStatisticsQueryVo::getUserId).distinct().count()));
        }
        // 获取借调人数
        Map<String, List<SecondRecord>> secondMap = utilService.getSecondRecordList(groupIds, userIds, new DateRangeDto(selectDateRangeDto.getStartDate(), selectDateRangeDto.getEndDate()));
        Integer uniqueUserCount = Math.toIntExact(secondMap.keySet().stream().map(key -> key.split("&")[0]).distinct().count());
        statisticsVo.setSecondNumber(uniqueUserCount);
        return statisticsVo;
    }

    @Override
    public List<AppTeamStatisticsListVo> getStatisticsListData(AppTeamStatisticsListDto req) throws QueryException {
        StatisticsEnumUtil.TabDataTypeEnum dataTypeEnum = getTabDataTypeEnum(req.getDataType());
        if (Objects.isNull(dataTypeEnum)) {
            throw new QueryException("查询记录类型不正确");
        }
        DateRangeDto selectDateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        List<AppTeamStatisticsListVo> statisticsListVoList = new ArrayList<>();
        //获取用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), selectDateRangeDto.getStartDate(), selectDateRangeDto.getEndDate(), null, String.valueOf(0));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return statisticsListVoList;
        }
        Map<String, UserBoundVO> userBoundMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        if (dataTypeEnum.equals(JD)) {
            //借调查询
            secondedMethod(selectDateRangeDto, groupIds, userBoundMap, statisticsListVoList);
            return statisticsListVoList;
        }
        //查询月统计数据
        MonthStatisticsDataQueryDto monthStatisticsQueryDto = MonthStatisticsDataQueryDto.builder()
                .groupIds(CollUtil.isNotEmpty(groupIds) ? groupIds : null)
                .startDate(selectDateRangeDto.getStartDate())
                .endDate(selectDateRangeDto.getEndDate())
                .userIds(userIds)
                .queryType(1)
                .build();
        List<MonthStatisticsQueryVo> monthPageList = dayStatisticsMapper.getMonthPageList(monthStatisticsQueryDto);
        if (CollUtil.isEmpty(monthPageList)) {
            return statisticsListVoList;
        }
        List<MonthStatisticsQueryVo> filterList = dataTypeEnum.dataFilter(monthPageList);
        if (CollUtil.isEmpty(filterList)) {
            return statisticsListVoList;
        }
        Map<String, List<Object>> dataDetailsMap = new HashMap<>();
        Map<String, BigDecimal> ratioMap = monthPageList.stream().collect(Collectors.toMap(MonthStatisticsQueryVo::getGroupId,
                MonthStatisticsQueryVo::getAttendanceRatio, (existing, replacement) -> replacement));
        // 查询用户是否封账
        Map<String, Boolean> sealMap = dayStatisticsService.selectUserIsSeal(userIds, req.getMonth());
        switch (dataTypeEnum) {
            case QJ:
                Map<String, List<LeaveRecord>> userLeaveMap = utilService.getLeaveMap(userBoundMap, ratioMap, userIds, selectDateRangeDto);
                dataDetailsMap = userLeaveMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case CD:
                Map<String, List<LateRecord>> userLateMap = utilService.getLateMap(userBoundMap, sealMap, groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = userLateMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case ZT:
                Map<String, List<EarlyLeaveRecord>> earlyLeaveMap = utilService.getEarlyMap(userBoundMap, sealMap, groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = earlyLeaveMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case QK:
                Map<String, List<AbsenceCardRecord>> absenceCardMap = utilService.getAbsenceCardMap(userBoundMap, sealMap, groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = absenceCardMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case QQ:
                Map<String, List<AbsenceRecord>> absenceMap = utilService.getAbsenceMap(groupIds, sealMap, userIds, selectDateRangeDto);
                dataDetailsMap = absenceMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case BK:
                Map<String, List<MakeUpCardRecord>> makeUpCardMap = utilService.getMakeUpCardMap(groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = makeUpCardMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case WQ:
                Map<String, List<OutworkRecord>> outworkMap = utilService.getOutworkRecordList(groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = outworkMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case JB:
                Map<String, List<OvertimeRecord>> overtimeMap = utilService.getOvertimeRecordList(groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = overtimeMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case CC:
                Map<String, List<BusOrOutRecord>> busMap = utilService.getBusRecordList(groupIds, userIds, selectDateRangeDto);
                dataDetailsMap = busMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
            case WC:
                Map<String, List<BusOrOutRecord>> outMap = utilService.getOutRecordList(ratioMap, userIds, selectDateRangeDto);
                dataDetailsMap = outMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
                break;
        }
        statisticsListVoList = DayStatisticsUtils.dateEncapsulationV2(dataTypeEnum, filterList, dataDetailsMap, userBoundMap);
        return statisticsListVoList;
    }

    /**
     * 借调统计
     * @param rangeDto 查询参数
     * @param groupIds 考勤组id
     * @param userBoundMap 用户信息
     * @param statisticsListVoList 统计数据
     */
    private void secondedMethod(DateRangeDto rangeDto, List<String> groupIds, Map<String, UserBoundVO> userBoundMap, List<AppTeamStatisticsListVo> statisticsListVoList) {
        List<String> userIds = newArrayList(userBoundMap.keySet());
        Map<String, List<SecondRecord>> secondMap = utilService.getSecondRecordList(groupIds, userIds, new DateRangeDto(rangeDto.getStartDate(), rangeDto.getEndDate()));
        Map<String, List<Object>> dataDetailsMap = secondMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue())));
        if (CollUtil.isEmpty(secondMap)) {
            return;
        }
        dataDetailsMap.forEach((userAndGroupKey, secondList) -> {
            String userId = userAndGroupKey.split("&")[0];
            AppTeamStatisticsListVo statisticsListVo = new AppTeamStatisticsListVo();
            statisticsListVo.setUserId(userId);
            UserBoundVO userInfoVo = userBoundMap.get(userId);
            statisticsListVo.setUserHeadSculpture(StringUtil.isNotEmpty(userInfoVo.getHeadIcon()) ?
                    UploaderUtil.uploaderImg(userInfoVo.getHeadIcon()) : null);
            statisticsListVo.setUserName(StringUtil.isNotEmpty(userInfoVo.getUserName()) ? userInfoVo.getUserName() :
                    StringUtil.isNotEmpty(userInfoVo.getNickname()) ? userInfoVo.getNickname() : null);
            statisticsListVo.setIsSecond(0);
            statisticsListVo.setNumber(secondList.size());
            statisticsListVo.setDetalList(secondList);
            statisticsListVoList.add(statisticsListVo);
        });
    }

    public void setClockNew(List<ClockInExportVo> usersByGroupVos, DateRangeDto dateRangeDto, Map<String, ClockInExportVo> keyMap) {
        //所有的需要的出勤规则 通过日期，考勤组，时间 过滤的数据
        List<FtbAttendanceDailyRule> allUserRule = clockInService.getCurrentDailyRuleListOfDay(dateRangeDto, usersByGroupVos);
        List<AttendanceRuleVo> allRule = JsonUtil.getJsonToList(allUserRule, AttendanceRuleVo.class);
        if (null == allRule || allRule.isEmpty()) {
            // 没排班
            return;
        }
        allRule.forEach(v -> v.setDayStr(DateDetail.getDate2Str(v.getDay(), DateDetail.DF)));
        Map<String, AttendanceRuleVo> ruleIdMap = allRule.stream().collect(Collectors.toMap(AttendanceRuleVo::getId, Function.identity()));
        // 通过 用户-日期-考勤组 分组
        Map<String, List<AttendanceRuleVo>> userIdByRuleMap = allRule.stream().collect(Collectors.groupingBy(rule -> rule.getUserId() + "-" + rule.getDayStr() + "-" + rule.getGroupId()));
        // 所有的用户
        List<String> allUserIds = usersByGroupVos.stream().map(ClockInExportVo::getUserId).distinct().collect(Collectors.toList());
        // 批量查询所有打卡记录
        List<ClockClassRecord> clockInList = clockInMapper.getClockInList(allUserIds, dateRangeDto.getStartDate(), dateRangeDto.getEndDate());
        clockInList.forEach(v -> v.setDayStr(DateDetail.getDate2Str(v.getDay(), DateDetail.DF)));
        // 通过 用户-日期 分组
        Map<String, List<ClockClassRecord>> userIdByClickMap = clockInList.stream().collect(Collectors.groupingBy(clockClassRecord -> clockClassRecord.getUserId() + "-" + clockClassRecord.getDayStr()));
        SimpleDateFormat hms = new SimpleDateFormat("HH:mm");
        SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
        // 以打卡结果表为主 查出所有的打卡结果记录
        List<ClockInVo> clockResultResultList = clockInMapper.getClockInResultByRuleList(allRule.stream().map(AttendanceRuleVo::getId).distinct().collect(Collectors.toList()));
        List<ClockClassRecord> records = newArrayList();
        clockResultResultList.forEach(v -> {
            ClockClassRecord dailyClockIn = BeanUtil.toBean(v, ClockClassRecord.class);
            dailyClockIn.setIsEffectiveClock(1);
            dailyClockIn.setCouldRepair(ConstantUtil.NUM_FALSE);
            AttendanceRuleVo attendanceRuleVo = ruleIdMap.get(v.getRuleId());
            if (null != attendanceRuleVo) {
                dailyClockIn.setWorkDate(v.getClockInType() == 1 ? attendanceRuleVo.getInPoint() : attendanceRuleVo.getOutPoint());
            }
            records.add(dailyClockIn);
        });
        Map<String, List<ClockClassRecord>> ruleIdByMap = records.stream().collect(Collectors.groupingBy(ClockClassRecord::getRuleId));
        usersByGroupVos.forEach(v -> {
            // 以下代码目的是为了找到当日的所有打卡记录
            // 获取所有和该用户当天且与本考勤组相关的出勤规则
            List<AttendanceRuleVo> ftbAttendanceDailyRules = userIdByRuleMap.get(v.getKeyString());
            if (null != ftbAttendanceDailyRules) {
                // 当天的出勤规则集合
                List<ClockClassRecord> recordList = newArrayList();
                List<String> todayRuleIds = ftbAttendanceDailyRules.stream().map(AttendanceRuleVo::getId).distinct().collect(Collectors.toList());
                todayRuleIds.forEach(value -> {
                    List<ClockClassRecord> clockClassRecords = ruleIdByMap.get(value);
                    if (CollUtil.isNotEmpty(clockClassRecords)) {
                        recordList.addAll(clockClassRecords);
                    }
                });
                // 获取所有和该用户本日期相关的打卡记录
                List<ClockClassRecord> clockClassRecords = userIdByClickMap.get(v.getUserId() + "-" + v.getDate());
                if (null != recordList || null != clockClassRecords) {
                    List<ClockClassRecord> dayClock = DayStatisticsUtils.setOldDataNew(ftbAttendanceDailyRules.stream().map(AttendanceRuleVo::getId).collect(Collectors.toList()), stringToDates(v.getDate()), clockClassRecords, recordList);
                    // 处理打卡信息
                    DayStatisticsUtils.setClockTime(v.getGroupId(), keyMap, v.getUserId(), v.getDate(), dayClock, ymd, hms);
                }
            }
        });

    }

    /**
     * 考勤组指定日期考勤打卡及时段
     *
     * @param usersByGroupVos 用户集合
     * @param tenantId        租户id
     * @param dateRangeDto    日期范围
     */
    @Override
    public List<ClockInExportVo> getDayClockInPageListExport(List<ClockInExportVo> usersByGroupVos, DateRangeDto
            dateRangeDto, String tenantId) {
        // 将传入的用户信息clockInExportVoList根据 userId分组
        List<String> groupIds = usersByGroupVos.stream().map(ClockInExportVo::getGroupId).distinct().collect(Collectors.toList());
        Map<String, List<ClockInExportVo>> groupClockMap = usersByGroupVos.stream().collect(Collectors.groupingBy(ClockInExportVo::getGroupId));
        Map<String, ClockInExportVo> keyMap = usersByGroupVos.stream().collect(Collectors.toMap(ClockInExportVo::getKeyString, Function.identity()));
        // 获取明天的日期
        for (String groupId : groupIds) {
            List<ClockInExportVo> groupClockList = groupClockMap.get(groupId);
            if (CollUtil.isEmpty(groupClockList)) {
                continue;
            }
            List<String> userIds = groupClockList.stream().map(ClockInExportVo::getUserId).distinct().collect(Collectors.toList());
            // 查询打卡规则
            List<UserRuleListVo> userRuleList = statisticsMapper.getUserRuleList(userIds, dateRangeDto, groupId, List.of(
                    AttendanceTypeEnum.ORDINARY.getCode(), AttendanceTypeEnum.REST.getCode(), AttendanceTypeEnum.LEAVE.getCode(),
                    AttendanceTypeEnum.WORKOVERTIME.getCode(), AttendanceTypeEnum.HOLIDAYS.getCode(), AttendanceTypeEnum.DEDUCTION_HOLIDAYS.getCode(),
                    AttendanceTypeEnum.EXCHANGE_HOLIDAYS.getCode(), AttendanceTypeEnum.SECONDMENT.getCode(), AttendanceTypeEnum.BUSINESS_TRIP.getCode(),
                    AttendanceTypeEnum.STEP_OUT.getCode()));
            // 根据人分组
            Map<String, List<UserRuleListVo>> userRuleMap = userRuleList.stream().collect(Collectors.groupingBy(UserRuleListVo::getUserId));
            for (Map.Entry<String, List<UserRuleListVo>> entry : userRuleMap.entrySet()) {
                if (CollUtil.isEmpty(entry.getValue())) {
                    continue;
                }
                // 根据日期分组
                Map<String, List<UserRuleListVo>> dayRuleMap = entry.getValue().stream().collect(Collectors.groupingBy(UserRuleListVo::getDay));
                for (Map.Entry<String, List<UserRuleListVo>> e : dayRuleMap.entrySet()) {
                    if (CollUtil.isEmpty(e.getValue()) || !keyMap.containsKey(entry.getKey() + "-" + e.getKey() + "-" + groupId)) {
                        continue;
                    }
                    // 组装班次信息
                    RemarkVo remarkVo = getShiftPeriod(e.getValue());
                    ClockInExportVo clockInExportVo = keyMap.get(entry.getKey() + "-" + e.getKey() + "-" + groupId);
                    // 处理班次时间
                    clockInExportVo.setShiftTimeStr(remarkVo.getShiftPeriod().toString());
                    // 是全天假，外出，出差时 remarkVo.getClockTime() 有值
                    clockInExportVo.setClockTime(0 != remarkVo.getClockTime().length() ? remarkVo.getClockTime().toString() : "");
                    clockInExportVo.setRuleIds(remarkVo.getRuleIds());
                }
            }
        }
        // 新的逻辑
        setClockNew(usersByGroupVos, dateRangeDto, keyMap);
        return usersByGroupVos;
    }

    private RemarkVo getShiftPeriod(List<UserRuleListVo> dayList) {
        if (CollUtil.isEmpty(dayList)) {
            return new RemarkVo();
        }
        RemarkVo remarkVo = new RemarkVo();
        // 取出dayList中的RuleId 并去重，然后通过英文逗号拼接
        remarkVo.setRuleIds(dayList.stream()
                .filter(item -> item.getSchedulesType() != null)
                .sorted(Comparator.comparing(UserRuleListVo::getSchedulesType))
                .map(UserRuleListVo::getRuleId)
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .distinct()
                .collect(Collectors.joining(",")));
        // 过滤出正常排班，且时段一样的数据，然后根据班次类型进行正序排序
        DayStatisticsUtils.checkOrdinary(dayList, remarkVo);
        // 请假
        DayStatisticsUtils.checkLeave(dayList, remarkVo);
        // 外出等情况
        checkGoOut(dayList, remarkVo);
        // 出差
        DayStatisticsUtils.checkBusinessTrip(dayList, remarkVo);
        return remarkVo;
    }

    /**
     * 处理外出
     *
     * @param dayList  每日出勤规则
     * @param remarkVo 组装信息
     */
    private void checkGoOut(List<UserRuleListVo> dayList, RemarkVo remarkVo) {
        //举例：
        //1.班次时间为9-18，外出时间为8-20 最终班次时间栏为：9-18（外出时间：8-20） 打卡结果栏为：无需打卡-无需打卡
        //2.班次为9-12 | 14-18，外出时间为8-20，最终班次时间为9-12 | 14-18（外出时间8-20）打卡结果栏为：无需打卡-无需打卡|无需打卡-无需打卡
        //3.班次时间为9-18，外出时间9-10，16-18，最终班次时间栏为9-18（外出时间：9-10）（外出时间：16-18）打卡结果栏为：无需打卡-无需打卡
        //4.班次时间为9-18，外出选择当天（整天外出）最终班次时间栏为9-18（当天外出），打卡结果栏为：无需打卡-无需打卡
        //5.班次时间为9-12 | 14-18，外出选择当天（整天外出）最终班次时间栏为9-12 | 14-18（当天外出），打卡结果栏为：无需打卡-无需打卡|无需打卡-无需打卡
        List<UserRuleListVo> gouOut = dayList.stream().filter(dayRule -> Objects.equals(AttendanceTypeEnum.STEP_OUT.getCode(), dayRule.getApplyViewEnable())).collect(Collectors.toList()).stream()
                .sorted(Comparator.comparing(UserRuleListVo::getInPoint))
                .collect(Collectors.toList());
        // 组装请假
        if (!gouOut.isEmpty()) {
            SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");
            // 如果当天全是外出，那么通过外出命中的时段组装当天上班时段
            if (remarkVo.getShiftPeriod().toString().isEmpty()) {
                for (int i = 0; i < gouOut.size(); i++) {
                    UserRuleListVo vo = gouOut.get(i);
                    if (i == 0) {
                        remarkVo.getShiftPeriod().append(formatter.format(vo.getInPoint())).append("-").append(formatter.format(vo.getOutPoint()));
                    } else {
                        remarkVo.getShiftPeriod().append(" | ").append(formatter.format(vo.getInPoint())).append("-").append(formatter.format(vo.getOutPoint()));
                    }
                }
            }
            // 组装外出时段 09:00-18:00（上班外出） 09:00-18:00（下班外出）
            List<String> list = new ArrayList<>();
            // 班次日期
            String day = dayList.get(0).getDay();
            SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
            gouOut.forEach(v -> {
                // 通过单位判断组装小时外出还是整天外出
                if (v.getAttendanceType() == 1) {
                    if (null != v.getInStepOutApplyId() && !list.contains(v.getInStepOutApplyId())) {
                        list.add(v.getInStepOutApplyId());
                        // 通过applyId查询外出审批详情
                        GoOutVo goOut = leaveApproveMapper.getGoOut(v.getInStepOutApplyId());
                        if (2 == goOut.getUnit()) {
                            remarkVo.getShiftPeriod().append("（当天外出）");
                        } else {
                            // 小时外出跨天次日处理
                            // 比对班次时间和申请时间匹配范围
                            remarkVo.getShiftPeriod().append("（");
                            if (!day.equals(ymd.format(goOut.getStartTime()))) {
                                remarkVo.getShiftPeriod().append("次日");
                            }
                            remarkVo.getShiftPeriod().append(formatter.format(goOut.getStartTime())).append("-");
                            if (!day.equals(ymd.format(goOut.getEndTime()))) {
                                remarkVo.getShiftPeriod().append("次日");
                            }
                            remarkVo.getShiftPeriod().append(formatter.format(goOut.getEndTime())).append("外出）");
                        }
                    }
                    if (null != v.getOutStepOutApplyId() && !list.contains(v.getOutStepOutApplyId())) {
                        list.add(v.getOutStepOutApplyId());
                        // 通过applyId查询外出审批详情
                        GoOutVo goOut = leaveApproveMapper.getGoOut(v.getOutStepOutApplyId());
                        if (2 == goOut.getUnit()) {
                            remarkVo.getShiftPeriod().append("（当天外出）");
                        } else {
                            // 小时外出跨天次日处理
                            remarkVo.getShiftPeriod().append("（");
                            if (!day.equals(ymd.format(goOut.getStartTime()))) {
                                remarkVo.getShiftPeriod().append("次日");
                            }
                            remarkVo.getShiftPeriod().append(formatter.format(goOut.getStartTime())).append("-");
                            if (!day.equals(ymd.format(goOut.getEndTime()))) {
                                remarkVo.getShiftPeriod().append("次日");
                            }
                            remarkVo.getShiftPeriod().append(formatter.format(goOut.getEndTime())).append("外出）");
                        }
                    }
                }
            });
        }
    }

    /**
     * 获取考勤组当天打卡数据
     *
     * @param userBoundVoMap 用户集合
     * @param groupIdList    考勤组ID集合
     * @param dateRangeDto   日期范围
     * @return AppStatisticsTeamTabListVo
     */
    private AppStatisticsTeamTabListVo statisticsTeamTabListVo(Map<String, UserBoundVO> userBoundVoMap,
                                                               List<String> groupIdList, DateRangeDto dateRangeDto) {
        List<ClockUser> allDataList = newArrayList(), restDataList = newArrayList(), lateDataList = newArrayList(),
                earlyLeaveDataList = newArrayList(), leaveDataList = newArrayList(), absenceCardDataList = newArrayList(),
                absenceDataList = newArrayList(), makeUpCardDataList = newArrayList(), outworkDataList = newArrayList(),
                overtimeDataList = newArrayList(), secondDataList = newArrayList(), busDataList = newArrayList(),
                goOutDataList = newArrayList();
        //获取用户排班数据
        List<UserRuleRecord> userRuleRecordList = statisticsMapper.getUserRuleRecordList(new ArrayList<>(userBoundVoMap.keySet()),
                dateRangeDto, groupIdList, List.of(
                        AttendanceTypeEnum.ORDINARY.getCode(), AttendanceTypeEnum.REST.getCode(), AttendanceTypeEnum.LEAVE.getCode(),
                        AttendanceTypeEnum.WORKOVERTIME.getCode(), AttendanceTypeEnum.HOLIDAYS.getCode(), AttendanceTypeEnum.DEDUCTION_HOLIDAYS.getCode(),
                        AttendanceTypeEnum.EXCHANGE_HOLIDAYS.getCode(), AttendanceTypeEnum.SECONDMENT.getCode(), AttendanceTypeEnum.BUSINESS_TRIP.getCode(),
                        AttendanceTypeEnum.STEP_OUT.getCode()));
        userRuleRecordList.forEach(record -> record.getClockInResultList().removeIf(result -> Objects.equals(result.getDeleteMark(), 1) || StringUtil.isEmpty(result.getId())));
        Map<String, List<UserRuleRecord>> userRuleRecordMap = CollUtil.isNotEmpty(userRuleRecordList) ? userRuleRecordList.stream().collect(Collectors.groupingBy(UserRuleRecord::getUserId)) : new HashMap<>();
        //查询手动变更结果的用户信息
        List<String> adminUserIdList = userRuleRecordList.stream()
                .filter(record -> CollUtil.isNotEmpty(record.getClockInResultList()))
                .flatMap(record -> record.getClockInResultList().stream())
                .map(ClockInResultRecordVo::getAbsenceLeader)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        List<UserBoundVO> userList = CollUtil.isNotEmpty(adminUserIdList) ? v2UserApi.userNameAndCopy(adminUserIdList) : new ArrayList<>();
        Map<String, UserBoundVO> adminUserMap = CollUtil.isNotEmpty(userList) ? userList.stream().collect(Collectors.toMap(UserBoundVO::getId, Function.identity())) : new HashMap<>();
        // 用户+考勤组分组
        Map<String, Integer> userBusCountMap = this.dailyRuleService.getUserBusAndOutCount(
                new ArrayList<>(userBoundVoMap.keySet()), groupIdList,
                DateUtil.stringToDate(dateRangeDto.getStartDate()),
                DateUtil.stringToDate(dateRangeDto.getEndDate()),
                AttendanceTypeEnum.BUSINESS_TRIP
        );
        // 用户+考勤组分组
        Map<String, Integer> userOutCountMap = this.dailyRuleService.getUserBusAndOutCount(
                new ArrayList<>(userBoundVoMap.keySet()), groupIdList,
                DateUtil.stringToDate(dateRangeDto.getStartDate()),
                DateUtil.stringToDate(dateRangeDto.getEndDate()),
                AttendanceTypeEnum.STEP_OUT
        );
        for (Map.Entry<String, UserBoundVO> userBoundVo : userBoundVoMap.entrySet()) {
            List<UserRuleRecord> orDefault = userRuleRecordMap.getOrDefault(userBoundVo.getKey(), newArrayList());
            if (orDefault.isEmpty()) {
                continue;
            }
            ClockUser clockUser = new ClockUser();
            UserBoundVO userInfoVo = userBoundVo.getValue();
            // 获取用户头像
            clockUser.setUserId(userInfoVo.getId());
            clockUser.setUserHeadSculpture(StrUtil.isNotEmpty(userInfoVo.getHeadIcon()) ?
                    UploaderUtil.uploaderImg(userInfoVo.getHeadIcon()) : null);
            clockUser.setUserName(userInfoVo.getUserName());
            clockUser.setIdentifying(StrUtil.isNotEmpty(userInfoVo.getUserName()) ?
                    PingYinUtil.getPinYinHeadChar(userInfoVo.getUserName().substring(0, 1)).toUpperCase() : "");
            // 更改为返回多个对象，按照用户+考勤组分组返回
            Map<String, MutablePair<List<Integer>, List<UserClockTimeSpan>>> groupByGroupMap = getGroupByUserList(adminUserMap, orDefault);
            if (CollUtil.isEmpty(groupByGroupMap)) {
                continue;
            }
            // 判断每个月用户在每个考勤组是否有数据
            Map<String, List<Integer>> attendanceTypeMap = orDefault.stream()
                    .collect(Collectors.groupingBy(record -> record.getUserId() + "&" + record.getGroupId(),
                            Collectors.mapping(UserRuleRecord::getAttendanceType, Collectors.toList())));
            groupByGroupMap.forEach((userAndGroupKey, value) -> {
                ClockUser clockUserCopy = new ClockUser();
                BeanUtils.copyProperties(clockUser, clockUserCopy);
                String groupId = userAndGroupKey.split("&")[1];
                // 借调
                boolean anyMatch = orDefault.stream().anyMatch(item -> item.getSelfGroup() != 0);
                if (anyMatch) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    secondDataList.add(clockUserCopy);
                }
                if (!attendanceTypeMap.containsKey(userAndGroupKey) || CollUtil.isEmpty(orDefault)) {
                    return;
                }
                clockUserCopy.setClockTimeSpanList(value.right);
                allDataList.add(clockUserCopy);
                // 休息日
                if (attendanceTypeMap.get(userAndGroupKey).contains(AttendanceTypeEnum.REST.getCode())) {
                    restDataList.add(clockUserCopy);
                }
                // 请假
                if (attendanceTypeMap.get(userAndGroupKey).contains(AttendanceTypeEnum.LEAVE.getCode())) {
                    leaveDataList.add(clockUserCopy);
                }
                // 加班
                if (attendanceTypeMap.get(userAndGroupKey).contains(AttendanceTypeEnum.WORKOVERTIME.getCode())) {
                    overtimeDataList.add(clockUserCopy);
                }
                // 所有打卡状态
                List<Integer> allStatusList = orDefault.stream()
                        .filter(item -> item.getGroupId().equals(groupId) && CollUtil.isNotEmpty(item.getClockInResultList()) &&
                                !DayStatisticsUtils.isHeadAbsence(item.getClockInResultList()))
                        .flatMap(item -> item.getClockInResultList().stream())
                        .map(ClockInResultRecordVo::getClockInStatus)
                        .filter(Objects::nonNull)
                        .distinct()
                        .collect(Collectors.toList());
                // 所有旷工状态
                List<Integer> allAbsenceList = orDefault.stream()
                        .filter(item -> item.getGroupId().equals(groupId) && CollUtil.isNotEmpty(item.getClockInResultList()) &&
                                DayStatisticsUtils.isHeadAbsence(item.getClockInResultList()))
                        .flatMap(item -> item.getClockInResultList().stream())
                        .map(ClockInResultRecordVo::getAbsence)
                        .filter(Objects::nonNull)
                        .distinct()
                        .collect(Collectors.toList());
                // 所有补卡状态
                List<Integer> allRepairedList = orDefault.stream()
                        .filter(item -> item.getGroupId().equals(groupId) && CollUtil.isNotEmpty(item.getClockInResultList()))
                        .flatMap(item -> item.getClockInResultList().stream())
                        .map(ClockInResultRecordVo::getRepaired)
                        .filter(Objects::nonNull)
                        .distinct()
                        .collect(Collectors.toList());
                // 所有外勤状态
                List<Integer> allClockInKindList = orDefault.stream()
                        .filter(item -> item.getGroupId().equals(groupId) && CollUtil.isNotEmpty(item.getClockInResultList()))
                        .flatMap(item -> item.getClockInResultList().stream())
                        .map(ClockInResultRecordVo::getClockInKind)
                        .filter(Objects::nonNull)
                        .distinct()
                        .collect(Collectors.toList());
                // 迟到
                if (CollUtil.isNotEmpty(allStatusList) && allStatusList.contains(ClockInStatusEnum.WORK_LATE.getValue())) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    lateDataList.add(clockUserCopy);
                }
                // 早退
                if (CollUtil.isNotEmpty(allStatusList) && allStatusList.contains(ClockInStatusEnum.HOME_EARLY.getValue())) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    earlyLeaveDataList.add(clockUserCopy);
                }
                // 缺卡
                if (CollUtil.isNotEmpty(allStatusList) && allStatusList.contains(ClockInStatusEnum.NO_CLOCK.getValue())) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    absenceCardDataList.add(clockUserCopy);
                }
                // 旷工
                if (CollUtil.isNotEmpty(allAbsenceList) && allAbsenceList.contains(1)) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    absenceDataList.add(clockUserCopy);
                }
                // 补卡
                if (CollUtil.isNotEmpty(allRepairedList) && allRepairedList.contains(1)) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    makeUpCardDataList.add(clockUserCopy);
                }
                // 外勤
                if (CollUtil.isNotEmpty(allClockInKindList) && allClockInKindList.contains(ConstantUtil.KIND_OUTSIDE)) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    outworkDataList.add(clockUserCopy);
                }
                // 出差
                if (CollUtil.isNotEmpty(userBusCountMap) && userBusCountMap.getOrDefault(userAndGroupKey, 0) > 0) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    busDataList.add(clockUserCopy);
                }
                // 外出
                if (CollUtil.isNotEmpty(userOutCountMap) && userOutCountMap.getOrDefault(userAndGroupKey, 0) > 0) {
                    clockUserCopy.setClockTimeSpanList(value.right);
                    goOutDataList.add(clockUserCopy);
                }
            });
        }
        AppStatisticsTeamTabListVo teamTabListVo = new AppStatisticsTeamTabListVo();
        teamTabListVo.setAllDataList(getCollect(allDataList));
        teamTabListVo.setRestDataList(getCollect(restDataList));
        teamTabListVo.setLateDataList(getCollect(lateDataList));
        teamTabListVo.setEarlyLeaveDataList(getCollect(earlyLeaveDataList));
        teamTabListVo.setLeaveDataList(getCollect(leaveDataList));
        teamTabListVo.setAbsenceCardDataList(getCollect(absenceCardDataList));
        teamTabListVo.setMakeUpCardDataList(getCollect(makeUpCardDataList));
        teamTabListVo.setAbsenceDataList(getCollect(absenceDataList));
        teamTabListVo.setSecondDataList(getCollect(secondDataList));
        teamTabListVo.setOutworkDataList(getCollect(outworkDataList));
        teamTabListVo.setOvertimeDataList(getCollect(overtimeDataList));
        teamTabListVo.setBusDataList(getCollect(busDataList));
        teamTabListVo.setGoOutDataList(getCollect(goOutDataList));
        return teamTabListVo;
    }

    /**
     * 封装用户排班信息
     *
     * @param adminUserMap       管理员用户信息
     * @param userRuleRecordList 排班数据
     */
    private Map<String, MutablePair<List<Integer>, List<UserClockTimeSpan>>> getGroupByUserList(Map<String, UserBoundVO> adminUserMap, List<UserRuleRecord> userRuleRecordList) {
        Map<String, MutablePair<List<Integer>, List<UserClockTimeSpan>>> groupByUserMap = new HashMap<>();
        Map<String, List<UserRuleRecord>> userAndGroupMap = userRuleRecordList.stream().collect(Collectors.groupingBy(record -> record.getUserId() + "&" + record.getGroupId()));
        userAndGroupMap.forEach((userAndGroupKey, ruleRecordList) -> {
            List<UserClockTimeSpan> clockTimeSpanList = new ArrayList<>();
            List<Integer> attendanceTypeList = ruleRecordList.stream().map(UserRuleRecord::getAttendanceType).distinct().collect(Collectors.toList());
            for (UserRuleRecord userRuleRecord : ruleRecordList) {
                //判断当前班次是否旷工
                boolean isAbsence = userRuleRecord.getClockInResultList().stream().anyMatch(item ->
                        Objects.nonNull(item.getAbsence()) && item.getAbsence().equals(ConstantUtil.NUM_TRUE));
                UserClockTimeSpan userClockTimeSpan = new UserClockTimeSpan();
                if (!userRuleRecord.getAttendanceType().equals(AttendanceTypeEnum.BUSINESS_TRIP.getCode()) &&
                        !userRuleRecord.getAttendanceType().equals(AttendanceTypeEnum.STEP_OUT.getCode())) {
                    userClockTimeSpan.setStartTime(userRuleRecord.getInPoint());
                    userClockTimeSpan.setEndTime(userRuleRecord.getOutPoint());
                    userClockTimeSpan.setSecond(userRuleRecord.getSelfGroup());
                    List<UserClockRecord> inRecordList = new ArrayList<>();
                    if (CollUtil.isNotEmpty(userRuleRecord.getClockInResultList())) {
                        userRuleRecord.getClockInResultList().forEach(clockInResult -> {
                            UserClockRecord clockRecord = new UserClockRecord();
                            BeanUtils.copyProperties(clockInResult, clockRecord);
                            //是否外勤打卡
                            if (Objects.nonNull(clockInResult.getClockInKind()) && clockInResult.getClockInKind().equals(ConstantUtil.KIND_OUTSIDE)) {
                                clockRecord.setEffectiveTime(clockInResult.getClockTime());
                            } else {
                                clockRecord.setEffectiveTime(clockInResult.getEffectiveTime());
                            }
                            //是否补卡
                            if (Objects.nonNull(clockInResult.getRepaired()) && clockInResult.getRepaired().equals(ConstantUtil.NUM_TRUE)) {
                                clockRecord.setClockInKind(3);
                            }
                            //增加是否审批中字段
                            clockRecord.setClockInTime(clockInResult.getClockTime());
                            clockRecord.setIsApprove(clockInResult.getApprovalStatus());
                            clockRecord.setAbsence(isAbsence ? 1 : 0);
                            if (StringUtil.isNotEmpty(clockInResult.getAbsenceLeader()) && !userRuleRecord.getUserId().equals(clockInResult.getAbsenceLeader())) {
                                clockRecord.setAbsenceLeaderName(adminUserMap.containsKey(clockInResult.getAbsenceLeader()) ? adminUserMap.get(clockInResult.getAbsenceLeader()).getUserName() : "用户不存在");
                            }
                            inRecordList.add(clockRecord);
                        });
                        if (CollUtil.isNotEmpty(inRecordList)) {
                            userClockTimeSpan.setInRecordList(inRecordList.stream().sorted(Comparator.comparing(UserClockRecord::getClockInType)).collect(Collectors.toList()));
                        }
                    }
                }
                userClockTimeSpan.setAttendanceType(userRuleRecord.getAttendanceType());
                userClockTimeSpan.setApplyViewEnable(userRuleRecord.getApplyViewEnable());
                userClockTimeSpan.setInStepOutType(userRuleRecord.getInStepOutType());
                userClockTimeSpan.setOutStepOutType(userRuleRecord.getOutStepOutType());
                clockTimeSpanList.add(userClockTimeSpan);
            }
            List<UserClockTimeSpan> collect = clockTimeSpanList.stream().filter(userClockTimeSpan ->
                    !Objects.equals(userClockTimeSpan.getAttendanceType(), AttendanceTypeEnum.LEAVE.getCode())).collect(Collectors.toList());
            MutablePair<List<Integer>, List<UserClockTimeSpan>> mutablePair = new MutablePair<>(attendanceTypeList, (CollUtil.isEmpty(collect) ?
                    clockTimeSpanList : collect).stream().sorted(Comparator.comparing(span -> Objects.isNull(span.getStartTime()) ? new Date() :
                    span.getStartTime())).collect(Collectors.toList()));
            groupByUserMap.put(userAndGroupKey, mutablePair);
        });
        return groupByUserMap;
    }
}
