package jnpf.attendance.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.antifreeze.UserAntifreeze;
import jnpf.attendance.dto.*;
import jnpf.attendance.mapper.AttendanceBalanceRecordMapper;
import jnpf.attendance.mapper.AttendanceDayStatisticsMapper;
import jnpf.attendance.mapper.PublicHolidayRulesMapper;
import jnpf.attendance.service.*;
import jnpf.attendance.service.handle.notice.AttendanceNoticeHandler;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.constants.MessageTopicConstants;
import jnpf.controller.util.ExcelExportTemplate;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.*;
import jnpf.enums.attendance.*;
import jnpf.exception.LoginException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.event.StatisticsBatchClearDto;
import jnpf.model.attendance.event.StatisticsSingleDto;
import jnpf.model.attendance.event.StatisticsSingleHistoryDto;
import jnpf.model.attendance.model.*;
import jnpf.model.attendance.vo.AttendanceUserVo;
import jnpf.model.attendance.vo.CreateDayStatistics;
import jnpf.model.attendance.vo.SchedulesItemVo;
import jnpf.model.attendance.vo.UserSettingVo;
import jnpf.model.attendance.vo.attendance.*;
import jnpf.model.common.DateRangeDto;
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.util.*;
import jnpf.util.attendance.DayStatisticsUtils;
import jnpf.util.auth.V2AuthPermissionUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.alibaba.fastjson.JSON.parseArray;
import static jnpf.constants.AttendanceConstant.HOLIDAYS_COLOR;
import static jnpf.enums.attendance.UserSettingEnum.*;
import static jnpf.util.attendance.DayStatisticsUtils.initSalaryUserData;

/**
 * 考勤日度统计表
 *
 * @author shitou
 * @email shitou@niujiekeji.com
 * @date 2024-06-24 09:33:43
 */
@Service
@Slf4j
public class AttendanceDayStatisticsServiceImpl extends ServiceImpl<AttendanceDayStatisticsMapper, AttendanceDayStatistics> implements AttendanceDayStatisticsService {
    @Resource
    private V2UserApi v2UserApi;
    @Autowired
    private UserAntifreeze userApi;
    @Resource
    private RocketMQTemplate rocketMqTemplate;
    @Resource
    private AttendanceUserService userService;
    @Resource
    private AttendanceGroupService groupService;
    @Resource
    private AttendanceNoticeHandler noticeHandler;
    @Resource(name = "ioIntensiveThreadPool")
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private PublicHolidayRulesService rulesService;
    @Resource
    private AttendanceClockInService clockInService;
    @Resource
    private ExcelExportTemplate excelExportTemplate;
    @Resource
    private V2AuthPermissionUtils authPermissionUtils;
    @Resource
    private EnableBalanceService enableBalanceService;
    @Resource
    private AttendanceUserService attendanceUserService;
    @Resource
    private AttendanceDailyRuleService dailyRuleService;
    @Resource
    private AttendanceLeaveTypeService leaveTypeService;
    @Resource
    private PublicHolidayRulesService holidayRulesService;
    @Resource
    private AttendanceDailyRuleService dailyRuleService1;
    @Resource
    private AttendanceSuperAdminService superAdminService;
    @Resource
    private AttendanceBaseSettingService baseSettingService;
    @Resource
    private AttendanceUserSettingService userSettingService;
    @Resource
    private AttendanceDayStatisticsUtilImpl dayStatisticsUtil;
    @Resource
    private PublicHolidayRulesMapper publicHolidayRulesMapper;
    @Resource
    private AttendanceBalanceRecordMapper balanceRecordMapper;
    @Resource
    private AttendanceFestivalRulesService festivalRulesService;
    @Resource
    private AttendanceCustomizeTableService customizeTableService;

    private static ImmutableTriple<String, String, String> getShiftInfo(DayStatisticsShiftsJsonVo shiftInfo, AttendanceShiftNameEntity shiftNameEntity) {
        String shiftName, shiftShortName, shiftShortColour;
        // 判断当天是否为划线排班
        if (Objects.nonNull(shiftInfo.getFixedMark()) && shiftInfo.getFixedMark() == 2) {
            shiftName = "划线排班";
            shiftShortName = "划线排班";
            shiftShortColour = HOLIDAYS_COLOR;
        } else {
            shiftName = Objects.nonNull(shiftNameEntity) && StringUtil.isNotEmpty(shiftNameEntity.getName()) ? shiftNameEntity.getName() : "";
            shiftShortName = Objects.nonNull(shiftNameEntity) && StringUtil.isNotEmpty(shiftNameEntity.getShortName()) ? shiftNameEntity.getShortName() : "";
            shiftShortColour = Objects.nonNull(shiftNameEntity) && StringUtil.isNotEmpty(shiftNameEntity.getColour()) ? shiftNameEntity.getColour() : "";
        }
        return ImmutableTriple.of(shiftName, shiftShortName, shiftShortColour);
    }

    private static List<DayStatisticsShiftsJsonVo> getShiftsTimeList(DayStatisticsNoticeQueryVo item, List<DayStatisticsShiftsJsonVo> shiftsJsonVoList, List<String> shiftTimeList) {
        List<DayStatisticsShiftsJsonVo> shiftsJsonVoFilterList = shiftsJsonVoList.stream().filter(m -> m.getAttendanceType().equals(AttendanceTypeEnum.ORDINARY.getCode()) || m.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode())).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(shiftsJsonVoFilterList)) {
            shiftsJsonVoFilterList = shiftsJsonVoFilterList.stream().sorted(Comparator.comparing(DayStatisticsShiftsJsonVo::getInPoint)).collect(Collectors.toList());
        }
        shiftsJsonVoFilterList.forEach(shiftsJsonVo -> {
            StringBuilder shiftTimeStr = new StringBuilder(DateUtil.format(shiftsJsonVo.getInPoint(), "HH:mm")).append("至");
            if (!DayStatisticsUtils.isSameDay(item.getDate(), shiftsJsonVo.getOutPoint())) {
                shiftTimeStr.append("(次日)").append(DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm"));
            } else {
                shiftTimeStr.append(DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm"));
            }
            shiftTimeList.add(shiftTimeStr.toString());
        });
        return shiftsJsonVoFilterList;
    }

    @Override
    @SneakyThrows
    public List<DayStatisticsDataVo> getDayStatisticsData(DayStatisticsDataDto req) {
        StatisticsDataQueryDto queryDto = new StatisticsDataQueryDto();
        BeanUtils.copyProperties(req, queryDto);
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), req.getStartDate(), req.getEndDate(), req.getUserIds(), req.getWorkStatus());
        if (CollUtil.isEmpty(userBoundVoList)) {
            return DayStatisticsUtils.getStatisticsDataPackage(null);
        }
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        queryDto.setUserIds(userIds);
        queryDto.setGroupIds(req.getFilterList().stream().map(GroupFilterDto::getGroupId).collect(Collectors.toList()));
        return DayStatisticsUtils.getStatisticsDataPackage(this.baseMapper.getDayStatisticsDataQuery(queryDto));
    }

    /**
     * 获取有权限的用户列表
     *
     * @param filterList 筛选条件
     * @param startDate 开始时间
     * @param endDate 结束时间
     * @param userIdsFilter 用户ID
     * @param workStatus 用户状态
     * @return 用户列表
     */
    @Override
    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 && 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 = attendanceUserService.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 = attendanceUserService.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
    @SneakyThrows
    public PageInfo<DayStatisticsPageListVo> getDayPageList(DayStatisticsPageListDto req) {
        DayStatisticsDataPageListQueryDto queryDto = new DayStatisticsDataPageListQueryDto();
        BeanUtils.copyProperties(req, queryDto);
        PageInfo<DayStatisticsPageListVo> pageInfo = new PageInfo<>();
        pageInfo.setPageSize((int) req.getPageSize());
        pageInfo.setPageNum((int) req.getCurrentPage());
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), req.getStartDate(), req.getEndDate(), req.getUserIds(), req.getWorkStatus());
        Map<String, UserBoundVO> userBoundVoMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return pageInfo;
        }
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        //获取考勤组信息
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(groupIds);
        if (CollUtil.isEmpty(groupList)) {
            log.info("未查询到考勤组数据:{}", groupIds);
            return pageInfo;
        }
        Map<String, AttendanceGroup> groupMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        queryDto.setUserIds(userIds);
        req.setUserIds(userIds);
        req.setGroupMap(groupMap);
        req.setUserBoundMap(userBoundVoMap);
        queryDto.setGroupIds(groupIds);
        if (Objects.nonNull(req.getTypeId())) {
            StatisticsEnumUtil.TabStaBlockEnum tabDataTypeEnum = StatisticsEnumUtil.TabStaBlockEnum.getTabDataTypeEnum(req.getTypeId());
            Assert.notNull(tabDataTypeEnum, "统计类型不正确");
            if (req.getTypeId().equals(StatisticsEnumUtil.TabStaBlockEnum.QB.getCode())) {
                queryDto.setQueryType(1);
            } else if (req.getTypeId().equals(StatisticsEnumUtil.TabStaBlockEnum.ZC.getCode())) {
                queryDto.setQueryType(2);
            } else {
                queryDto.setQueryType(3);
                queryDto.setFieldName(Objects.requireNonNull(tabDataTypeEnum).getFieldName());
            }
        }
        PageHelper.startPage(Math.toIntExact(req.getCurrentPage()), Math.toIntExact(req.getPageSize()));
        List<DayStatisticsQueryVo> pageListVoList = this.baseMapper.getDayPageList(queryDto);
        if (CollUtil.isEmpty(pageListVoList)) {
            return pageInfo;
        }
        pageInfo = new PageInfo<>(dayStatisticsAssemble(pageListVoList, groupMap, userBoundVoMap));
        pageInfo.setTotal(new PageInfo<>(pageListVoList).getTotal());
        pageInfo.setPageSize(Math.toIntExact(req.getPageSize()));
        pageInfo.setPageNum(Math.toIntExact(req.getCurrentPage()));
        return pageInfo;
    }

    /**
     * 日统计数据封装
     *
     * @param statisticsQueryVoList 日统计数据
     * @param groupMap              考勤数据
     * @param staffRosterMap        用户花名册数据
     * @return List<DayStatisticsPageListVo>
     */
    private List<DayStatisticsPageListVo> dayStatisticsAssemble(List<DayStatisticsQueryVo> statisticsQueryVoList, Map<String, AttendanceGroup> groupMap, Map<String, UserBoundVO> staffRosterMap) {
        Map<String, List<DayStatisticsShiftsJsonVo>> shiftsJsonMap = new HashMap<>();
        Set<String> shiftIdSet = new HashSet<>();
        for (DayStatisticsQueryVo item : statisticsQueryVoList) {
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = parseArray(item.getShiftsJson(), DayStatisticsShiftsJsonVo.class);
            if (CollUtil.isEmpty(shiftsJsonVoList)) {
                shiftsJsonVoList = new ArrayList<>();
            }
            shiftsJsonMap.put(item.getId(), shiftsJsonVoList);
            shiftsJsonVoList.stream().map(DayStatisticsShiftsJsonVo::getShiftId).filter(Objects::nonNull).forEach(shiftIdSet::add);
        }
        List<String> shiftIdList = new ArrayList<>(shiftIdSet);
        Map<String, AttendanceShiftNameEntity> shiftNameMap = CollUtil.isEmpty(shiftIdList) ? new HashMap<>() : dailyRuleService1.getShiftByShiftIds(shiftIdList);

        return statisticsQueryVoList.stream().map(item -> {
            DayStatisticsPageListVo dayStatistics = DayStatisticsPageListVo.builder().build();
            BeanUtils.copyProperties(item, dayStatistics);
            // 组装用户信息
            UserBoundVO staffRosterDto = staffRosterMap.get(item.getUserId());
            if (staffRosterDto != null) {
                dayStatistics.setDeptName(StringUtil.isNotEmpty(staffRosterDto.getOrganizeName()) ? staffRosterDto.getOrganizeName() : "");
                dayStatistics.setPostName(StringUtil.isNotEmpty(staffRosterDto.getPositionName()) ? staffRosterDto.getPositionName() : "");
                dayStatistics.setUserName(StringUtil.isNotEmpty(staffRosterDto.getName()) ? staffRosterDto.getName() : "");
                dayStatistics.setWorkStatus(Objects.nonNull(staffRosterDto.getWorkStatusEnums()) ? staffRosterDto.getWorkStatusEnums().getCode() : null);
                StatisticsEnumUtil.WorkStatusEnum workStatusEnum = StatisticsEnumUtil.WorkStatusEnum.getWorkStatusEnum(dayStatistics.getWorkStatus());
                dayStatistics.setWorkStatusStr(Objects.nonNull(workStatusEnum) ? workStatusEnum.getMsg() : "");
            }
            // 计算各类请假数据
            dayStatistics.setCustomLeaveList(DayStatisticsUtils.getDayCustomLeaveList(item.getCustomLeaveList()));
            // 组装班组名称
            AttendanceGroup group = groupMap.get(item.getGroupId());
            dayStatistics.setGroupName(group != null ? group.getGroupName() : null);
            dayStatistics.setDate(DateUtil.formatDate(item.getDate()));
            // 处理班次、班次时间、打卡时间
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = shiftsJsonMap.getOrDefault(item.getId(), new ArrayList<>());
            List<SchedulesItemVo> schedulesItemVos = getSchedulesItemVos(shiftNameMap, shiftsJsonVoList);
            dayStatistics.setShiftList(schedulesItemVos);
            String shiftStr = CollUtil.isNotEmpty(schedulesItemVos) ? StringUtil.join(schedulesItemVos.stream().map(SchedulesItemVo::getShortName).filter(StringUtil::isNotBlank).distinct().collect(Collectors.toList()), "|") : "";
            dayStatistics.setShiftStr(shiftStr);
            // 处理班次时间
            boolean containsOrdinary = shiftsJsonVoList.stream().anyMatch(json -> Objects.equals(AttendanceTypeEnum.ORDINARY.getCode(), json.getAttendanceType()));
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoFilterList = shiftsJsonVoList.stream().filter(m -> m.getAttendanceType() != null && (m.getAttendanceType().equals(AttendanceTypeEnum.ORDINARY.getCode()) || m.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode()) || (!containsOrdinary && m.getAttendanceType().equals(AttendanceTypeEnum.LEAVE.getCode())))).sorted(Comparator.comparing(DayStatisticsShiftsJsonVo::getInPoint)).collect(Collectors.toList());
            List<String> shiftTimeList = new ArrayList<>();
            StringBuilder shiftTimeStrList = new StringBuilder();
            for (DayStatisticsShiftsJsonVo shiftsJsonVo : shiftsJsonVoFilterList) {
                String inTime = DateUtil.format(shiftsJsonVo.getInPoint(), "HH:mm");
                String outTime = DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm");
                StringBuilder shiftTimeStr = new StringBuilder(inTime).append("~");
                shiftTimeStrList.append(inTime).append("~");
                if (!DayStatisticsUtils.isSameDay(item.getDate(), shiftsJsonVo.getOutPoint())) {
                    String timeRange = "(次日)".concat(outTime);
                    shiftTimeStr.append(timeRange);
                    shiftTimeStrList.append(timeRange).append("|");
                } else {
                    shiftTimeStr.append(outTime);
                    shiftTimeStrList.append(outTime).append("|");
                }
                shiftTimeList.add(shiftTimeStr.toString());
            }
            dayStatistics.setShiftTimeList(shiftTimeList);
            dayStatistics.setShiftTimeStr(shiftTimeStrList.length() > 0 ? shiftTimeStrList.substring(0, shiftTimeStrList.length() - 1) : "");
            // 处理打卡时间
            List<String> clockTimeList = new ArrayList<>();
            StringBuilder clockTimeStr = new StringBuilder();
            for (DayStatisticsShiftsJsonVo shiftsJsonVo : shiftsJsonVoFilterList) {
                DayStatisticsUtils.enclosureData(item, shiftsJsonVo, clockTimeList, clockTimeStr);
            }
            // 处理实际出勤工时
            if (dayStatistics.getActualAttendHours() != null && dayStatistics.getActualAttendHours().compareTo(BigDecimal.ZERO) < 0) {
                dayStatistics.setActualAttendHours(BigDecimal.ZERO);
            }
            dayStatistics.setClockTimeList(clockTimeList);
            dayStatistics.setClockTimeStr(clockTimeStr.length() > 0 ? clockTimeStr.substring(0, clockTimeStr.length() - 1) : "");
            return dayStatistics;
        }).collect(Collectors.toList());
    }

    /**
     * 班次数据封装
     *
     * @param shiftNameMap 用户班次信息
     * @param shiftsJsonVoList   用户班次数据
     * @return List<SchedulesItemVo>
     */
    private List<SchedulesItemVo> getSchedulesItemVos(Map<String, AttendanceShiftNameEntity> shiftNameMap, List<DayStatisticsShiftsJsonVo> shiftsJsonVoList) {
        if (CollUtil.isEmpty(shiftsJsonVoList)) {
            return new ArrayList<>();
        }
        // 判断当天是否为划线排班
        boolean isUnderlineScheduling = shiftsJsonVoList.stream().anyMatch(item -> Objects.nonNull(item.getFixedMark()) && item.getFixedMark().equals(2));
        List<SchedulesItemVo> collect = new ArrayList<>();
        for (DayStatisticsShiftsJsonVo shift : shiftsJsonVoList) {
            String shiftId = shift.getShiftId();
            Integer type = DayStatisticsUtils.getType(shift.getAttendanceType());
            if (Objects.isNull(type)) {
                continue;
            }
            // 划线排班只需要显示一个班次
            if (isUnderlineScheduling && !collect.isEmpty()) {
                continue;
            }
            AttendanceShiftNameEntity shiftName = shiftNameMap.get(shiftId);
            if (Objects.equals(shift.getAttendanceType(), AttendanceTypeEnum.BUSINESS_TRIP.getCode()) || Objects.equals(shift.getAttendanceType(), AttendanceTypeEnum.STEP_OUT.getCode())) {
                shiftName = null;
            }
            ImmutableTriple<String, String, String> shiftInfo = getShiftInfo(shift, shiftName);
            // 是否展示班次信息
            boolean isSpecialType = !Objects.equals(shift.getAttendanceType(), AttendanceTypeEnum.ORDINARY.getCode()) && !Objects.equals(shift.getAttendanceType(), AttendanceTypeEnum.LEAVE.getCode());
            // 是否计薪假或者不计薪假
            boolean isHolidayType = Objects.equals(SchedulesTypeEnum.HOLIDAYS.getCode(), type) || Objects.equals(SchedulesTypeEnum.PAID_HOLIDAYS.getCode(), type);
            String nameValue = isSpecialType ? "" : shiftInfo.left;
            String shortNameValue = isSpecialType ? AttendanceTypeEnum.getMsg(shift.getAttendanceType()) : shiftInfo.middle;
            String colourValue = isHolidayType ? HOLIDAYS_COLOR : shiftInfo.right;
            SchedulesItemVo schedulesItemVo = SchedulesItemVo.builder().type(type).name(nameValue).shortName(shortNameValue).colour(colourValue).schedulesType(shift.getSchedulesType()).build();
            collect.add(schedulesItemVo);
        }
        // 按名称分组处理重复的班次信息
        Map<String, List<SchedulesItemVo>> groupedByName = collect.stream().filter(vo -> vo != null && StringUtil.isNotEmpty(vo.getName())).collect(Collectors.groupingBy(SchedulesItemVo::getName));
        groupedByName.forEach((name, items) -> {
            if (StringUtil.isEmpty(name) || items.size() < 2) {
                return;
            }
            for (int i = 1; i < items.size(); i++) {
                SchedulesItemVo item = items.get(i);
                item.setName("");
                item.setShortName("");
                item.setIsShow(0);
            }
        });
        return collect.stream().filter(Objects::nonNull).sorted(Comparator.comparing(SchedulesItemVo::getSchedulesType, Comparator.nullsFirst(Comparator.naturalOrder()))).collect(Collectors.toList());
    }

    private PageInfo<ClockInExportVo> getDayClockInPageListExport(DayStatisticsPageListDto req) {
        // 过滤了指定用户以及指定工作状态筛选
        GroupUserQueryDto build = GroupUserQueryDto.builder().filterList(req.getFilterList()).startTime(req.getStartDate()).endTime(req.getEndDate()).tenantId(StringUtil.isNotEmpty(req.getTenantId()) ? req.getTenantId() : UserProvider.getUser().getTenantId()).userIds(req.getUserIds()).workStatusEnum(req.getWorkStatusEnum()).staffRosterMap(req.getUserBoundMap()).build();
        List<ClockInExportVo> exportVos = userService.getUsersDayByGroupIds(build, req);
        PageInfo<ClockInExportVo> pageInfo = new PageInfo<>();
        pageInfo.setPageSize((int) req.getPageSize());
        pageInfo.setPageNum((int) req.getCurrentPage());
        pageInfo.setTotal(req.getTotal());
        pageInfo.setList(exportVos);
        if (CollUtil.isEmpty(pageInfo.getList())) {
            return pageInfo;
        }
        List<String> userIdList = pageInfo.getList().stream().map(ClockInExportVo::getUserId).distinct().collect(Collectors.toList());
        if (CollUtil.isEmpty(userIdList)) {
            return pageInfo;
        }
        // 将pageInfo1.getList()的数据先根据人分组排序在根据日期排序
        // 查询指定时间的打卡数据
        List<ClockInExportVo> clockInExportVoList = userService.getClockInExportVoList(build, pageInfo.getList());
        pageInfo.setList(clockInExportVoList);
        // 得到用户  日期  考勤组 集合
        pageInfo.getList().forEach(v -> {
            //  考勤组名称
            AttendanceGroup attendanceGroup = req.getGroupMap().get(v.getGroupId());
            v.setGroupName(null != attendanceGroup ? attendanceGroup.getGroupName() : "");
        });
        pageInfo.setPageSize((int) req.getPageSize());
        return pageInfo;

    }

    @Override
    @SneakyThrows
    public void dayDataExport(DayStatisticsExportDto req) {
        String tenantId = UserProvider.getUser().getTenantId();
        DayStatisticsPageListDto dayStatisticsPageListDto = BeanUtil.toBean(req, DayStatisticsPageListDto.class);
        List<AttendanceCustomizeTableVo> list = customizeTableService.findList(null, 1, 1);
        // 排除掉已删除的请假类型
        if (CollUtil.isNotEmpty(list)) {
            Date start = DateDetail.getStr2Date(req.getStartDate());
            Date end = DateDetail.getStr2Date(req.getEndDate());
            list.removeIf(v -> Objects.nonNull(v.getDeleteTime()) && (v.getDeleteTime().compareTo(start) < 0 || v.getDeleteTime().compareTo(end) > 0));
        }
        Assert.notEmpty(list, "导出失败，未查询到表头数据");
        ExcelExportTemplate.MoreSheetDto moreSheetDto = new ExcelExportTemplate.MoreSheetDto();
        moreSheetDto.setUserId(UserProvider.getLoginUserId());
        moreSheetDto.setFilename(req.getStartDate() + "-" + req.getEndDate() + "考勤数据导出");
        // 获取动态表头相关信息
        List<AttendanceCustomizeTableVo> customizeTableVos = getAttendanceCustomizeTableVos(req.getStartDate(), req.getEndDate(), 1);
        // 获取固定表头名称
        List<List<String>> headerNameList = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().map(vo -> CollUtil.newArrayList(vo.getHeadName())).collect(Collectors.toList()) : new ArrayList<>();
        // 获取固定表头字段
        List<String> headerFieldList = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().map(AttendanceCustomizeTableVo::getField).collect(Collectors.toList()) : new ArrayList<>();
        // 获取固定表头字段宽度
        List<Integer> headerFieldWidthsList = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().map(AttendanceCustomizeTableVo::getWidth).collect(Collectors.toList()) : new ArrayList<>();
        // 导出字段的顺序
        LinkedHashMap<String, Integer> dynamicHeadersMap = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().sorted(Comparator.comparing(AttendanceCustomizeTableVo::getSort)).collect(Collectors.toMap(AttendanceCustomizeTableVo::getField, AttendanceCustomizeTableVo::getSort, (e1, e2) -> e1, LinkedHashMap::new)) : new LinkedHashMap<>();
        dayStatisticsPageListDto.setCurrentPage(1);
        dayStatisticsPageListDto.setPageSize(20000);
        dayStatisticsPageListDto.setTenantId(tenantId);
        List<DayStatisticsPageListVo> dayPageList = this.getDayPageList(dayStatisticsPageListDto).getList();
        List<Map<Integer, Object>> data = CollUtil.newArrayList();
        if (CollUtil.isNotEmpty(dayPageList)) {
            dayPageList.forEach(dayStaVo -> {
                data.add(DayStatisticsUtils.getDayStaDataList(dayStaVo, dynamicHeadersMap));
            });
        }
        ExcelExportTemplate.MoreSheetDetailDto moreSheetDetailDto = new ExcelExportTemplate.MoreSheetDetailDto("考勤数据导出", "统计数据", null, headerNameList, headerFieldList, headerFieldWidthsList, 2000, (page, size) -> data, null);
        moreSheetDto.getMoreSheetDetailDto().add(moreSheetDetailDto);
        String key = "day" + UserProvider.getLoginUserId() + System.currentTimeMillis();
        List<String> head1 = DayStatisticsUtils.getHead();
        ExcelExportTemplate.MoreSheetDetailDto moreSheetDetailDto1 = new ExcelExportTemplate.MoreSheetDetailDto("打卡数据导出", "打卡数据", ClockInExportVo.class, head1.stream().map(vo -> CollUtil.newArrayList(req.getStartDate() + "至" + req.getEndDate() + "成员打卡记录", vo)).collect(Collectors.toList()), null, DayStatisticsUtils.getWidths(), 2000, (page, size) -> {
            dayStatisticsPageListDto.setCurrentPage(page);
            dayStatisticsPageListDto.setPageSize(size);
            dayStatisticsPageListDto.setTenantId(tenantId);
            dayStatisticsPageListDto.setKey(key);
            return getDayClockInPageListExport(dayStatisticsPageListDto);
        }, null);
        moreSheetDto.getMoreSheetDetailDto().add(moreSheetDetailDto1);
        excelExportTemplate.exportMoreExcelModule(moreSheetDto);
    }

    /**
     * 获取可显示的请假类型
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @param type      1-日统计 2-月统计
     * @return 请假类型
     */
    private List<AttendanceCustomizeTableVo> getAttendanceCustomizeTableVos(String startDate, String endDate, Integer type) {
        // 实时查询自定义请假类型
        List<AttendanceCustomizeTableVo> customizeTableVos = customizeTableService.findList(null, 1, type);
        // 排除掉已删除的请假类型
        if (CollUtil.isNotEmpty(customizeTableVos)) {
            Date start = DateDetail.getStr2Date(startDate);
            Date end = DateDetail.getStr2Date(endDate);
            customizeTableVos.removeIf(v -> Objects.nonNull(v.getDeleteTime()) && (v.getDeleteTime().compareTo(start) < 0 || v.getDeleteTime().compareTo(end) > 0));
        }
        // 过滤掉不展示的数据
        customizeTableVos = customizeTableVos.stream().sorted(Comparator.comparing(AttendanceCustomizeTableVo::getSort)).collect(Collectors.toList());
        return customizeTableVos;
    }

    @Override
    @SneakyThrows
    public List<DayStatisticsDataVo> getMonthStatisticsData(MouthStatisticsDataDto req) {
        DateRangeDto dateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        StatisticsDataQueryDto queryDto = new StatisticsDataQueryDto();
        BeanUtils.copyProperties(req, queryDto);
        queryDto.setStartDate(dateRangeDto.getStartDate());
        queryDto.setEndDate(dateRangeDto.getEndDate());
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), req.getUserIds(), req.getWorkStatus());
        if (CollUtil.isEmpty(userBoundVoList)) {
            return DayStatisticsUtils.getStatisticsDataPackage(null);
        }
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        queryDto.setUserIds(userIds);
        queryDto.setGroupIds(groupIds);
        return DayStatisticsUtils.getStatisticsDataPackage(this.baseMapper.getMonthStatisticsDataQuery(queryDto, Boolean.TRUE));
    }

    @Override
    public PageInfo<MonthStatisticsPageListVo> getMonthPageList(MonthStatisticsPageListDto req) throws Exception {
        MonthStatisticsDataQueryDto queryDto = new MonthStatisticsDataQueryDto();
        BeanUtils.copyProperties(req, queryDto);
        DateRangeDto dateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        queryDto.setStartDate(dateRangeDto.getStartDate());
        queryDto.setEndDate(dateRangeDto.getEndDate());
        //获取考勤组信息
        PageInfo<MonthStatisticsPageListVo> pageInfo = new PageInfo<>();
        pageInfo.setPageSize((int) req.getPageSize());
        pageInfo.setPageNum((int) req.getCurrentPage());
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), req.getUserIds(), req.getWorkStatus());
        Map<String, UserBoundVO> userBoundVoMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return pageInfo;
        }
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(groupIds);
        if (CollUtil.isEmpty(groupList)) {
            log.info("未查询到考勤组数据:{}", groupIds);
            return pageInfo;
        }
        Map<String, AttendanceGroup> groupMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        queryDto.setUserIds(userIds);
        req.setUserIds(userIds);
        req.setGroupMap(groupMap);
        req.setUserBoundMap(userBoundVoMap);
        queryDto.setGroupIds(groupIds);
        if (Objects.nonNull(req.getTypeId())) {
            StatisticsEnumUtil.TabStaBlockMonthEnum tabDataTypeEnum = StatisticsEnumUtil.TabStaBlockMonthEnum.getTabDataTypeEnum(req.getTypeId());
            Assert.notNull(tabDataTypeEnum, "统计类型不正确");
            if (req.getTypeId().equals(StatisticsEnumUtil.TabStaBlockMonthEnum.QB.getCode())) {
                queryDto.setQueryType(1);
            } else if (req.getTypeId().equals(StatisticsEnumUtil.TabStaBlockMonthEnum.ZC.getCode())) {
                queryDto.setQueryType(2);
                queryDto.setFieldName(Objects.requireNonNull(tabDataTypeEnum).getFieldName());
            } else {
                queryDto.setQueryType(3);
                queryDto.setFieldName(Objects.requireNonNull(tabDataTypeEnum).getFieldName());
            }
        }
        PageHelper.startPage(Math.toIntExact(req.getCurrentPage()), Math.toIntExact(req.getPageSize()));
        List<MonthStatisticsQueryVo> pageListVoList = this.baseMapper.getMonthPageList(queryDto);
        if (CollUtil.isEmpty(pageListVoList)) {
            return pageInfo;
        }
        pageInfo = new PageInfo<>(monthStatisticsAssemble(req.getMonth(), queryDto.getStartDate(), queryDto.getEndDate(), pageListVoList, groupMap, userBoundVoMap));
        pageInfo.setTotal(new PageInfo<>(pageListVoList).getTotal());
        pageInfo.setPageSize(Math.toIntExact(req.getPageSize()));
        pageInfo.setPageNum(Math.toIntExact(req.getCurrentPage()));
        return pageInfo;
    }

    /**
     * 月统计数据封装
     *
     * @param month                 月份
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @param statisticsQueryVoList 日统计汇总数据
     * @param groupMap              考勤组信息
     * @param staffRosterMap        用户花名册数据
     * @return List<MonthStatisticsPageListVo>
     * @throws Exception 异常问题
     */
    private List<MonthStatisticsPageListVo> monthStatisticsAssemble(String month, String startDate, String endDate, List<MonthStatisticsQueryVo> statisticsQueryVoList, Map<String, AttendanceGroup> groupMap, Map<String, UserBoundVO> staffRosterMap) throws Exception {
        List<String> userIds = statisticsQueryVoList.stream().map(MonthStatisticsQueryVo::getUserId).distinct().collect(Collectors.toList());
        List<String> groupIds = statisticsQueryVoList.stream().map(MonthStatisticsQueryVo::getGroupId).distinct().collect(Collectors.toList());
        //批量获取用户加入考勤组周期
        DateRangeDto rangeDto = new DateRangeDto();
        rangeDto.setStartDate(startDate);
        rangeDto.setEndDate(endDate);
        Map<String, Map<String, List<String>>> userCycleMap = userService.batchGetUserCycleList(rangeDto, groupIds, userIds);
        //批量获取用户应休天数
        List<AttendancePublicHolidayBalance> holidayBalances = rulesService.getBalanceList(month, userIds);
        Map<String, BigDecimal> holidayBalanceMap = CollUtil.isNotEmpty(holidayBalances) ? holidayBalances.stream().collect(Collectors.toMap(AttendancePublicHolidayBalance::getUserId, AttendancePublicHolidayBalance::getTotal)) : new HashMap<>();
        //批量获取用户已休天数
        Map<String, BigDecimal> usePublicHoliday = dailyRuleService.getUserPublicHoliday(month, userIds);
        // 实时查询自定义请假类型
        return statisticsQueryVoList.stream().map(item -> {
            MonthStatisticsPageListVo monthStatistics = MonthStatisticsPageListVo.builder().build();
            BeanUtils.copyProperties(item, monthStatistics);
            monthStatistics.setMonth(month);
            monthStatistics.setGroupName(groupMap.get(item.getGroupId()).getGroupName());
            // 设置应休|已休
            monthStatistics.setRetired(usePublicHoliday.getOrDefault(item.getUserId(), BigDecimal.ZERO).stripTrailingZeros().toPlainString() + "|" + holidayBalanceMap.getOrDefault(item.getUserId(), BigDecimal.ZERO).stripTrailingZeros().toPlainString());
            // 计算各类请假数据
            monthStatistics.setCustomLeaveList(DayStatisticsUtils.getMonthCustomLeaveList(item.getCustomLeaveList()));
            //设置用户基本信息
            if (staffRosterMap.containsKey(item.getUserId())) {
                UserBoundVO staffRosterDto = staffRosterMap.get(item.getUserId());
                monthStatistics.setDeptName(StringUtil.isNotEmpty(staffRosterDto.getOrganizeName()) ? staffRosterDto.getOrganizeName() : "");
                monthStatistics.setPostName(StringUtil.isNotEmpty(staffRosterDto.getPositionName()) ? staffRosterDto.getPositionName() : "");
                monthStatistics.setUserName(StringUtil.isNotEmpty(staffRosterDto.getName()) ? staffRosterDto.getName() : "");
                monthStatistics.setWorkStatus(Objects.nonNull(staffRosterDto.getWorkStatusEnums()) ? staffRosterDto.getWorkStatusEnums().getCode() : null);
                StatisticsEnumUtil.WorkStatusEnum workStatusEnum = StatisticsEnumUtil.WorkStatusEnum.getWorkStatusEnum(monthStatistics.getWorkStatus());
                monthStatistics.setWorkStatusStr(Objects.nonNull(workStatusEnum) ? workStatusEnum.getMsg() : "");
            }
            //设置考勤组周期
            if (userCycleMap.containsKey(item.getGroupId()) && userCycleMap.get(item.getGroupId()).containsKey(item.getUserId())) {
                monthStatistics.setCycleList(userCycleMap.get(item.getGroupId()).get(item.getUserId()));
                StringBuilder cycleStr = new StringBuilder();
                userCycleMap.get(item.getGroupId()).get(item.getUserId()).forEach(cycle -> cycleStr.append(cycle).append(","));
                monthStatistics.setCycleStr(cycleStr.length() > 0 ? cycleStr.substring(0, cycleStr.length() - 1) : "");
            }
            return monthStatistics;
        }).collect(Collectors.toList());
    }

    /**
     * 计薪月统计数据封装
     *
     * @param month                 月份
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @param statisticsQueryVoList 日统计汇总数据
     * @param groupMap              考勤组信息
     * @param staffRosterMap        用户花名册数据
     * @return List<MonthStatisticsPageListVo>
     * @throws Exception 异常问题
     */
    private List<MonthPayrollStatisticsPageListVo> monthPayrollStatisticsAssemble(String month, String startDate, String endDate, List<MonthPayrollStatisticsQueryVo> statisticsQueryVoList, Map<String, AttendanceGroup> groupMap, Map<String, UserBoundVO> staffRosterMap) throws Exception {
        List<String> userIds = statisticsQueryVoList.stream().map(MonthPayrollStatisticsQueryVo::getUserId).distinct().collect(Collectors.toList());
        List<String> groupIds = statisticsQueryVoList.stream().map(MonthPayrollStatisticsQueryVo::getGroupId).distinct().collect(Collectors.toList());
        //批量获取用户加入考勤组周期
        DateRangeDto rangeDto = new DateRangeDto();
        rangeDto.setStartDate(startDate);
        rangeDto.setEndDate(endDate);
        Map<String, Map<String, List<String>>> userCycleMap = userService.batchGetUserCycleList(rangeDto, groupIds, userIds);
        // 实时查询自定义请假类型
        return statisticsQueryVoList.stream().map(item -> {
            MonthPayrollStatisticsPageListVo monthStatistics = MonthPayrollStatisticsPageListVo.builder().build();
            BeanUtils.copyProperties(item, monthStatistics);
            monthStatistics.setMonth(month);
            monthStatistics.setGroupName(groupMap.get(item.getGroupId()).getGroupName());
            // 计算各类请假数据
            monthStatistics.setCustomLeaveList(DayStatisticsUtils.getMonthCustomLeaveList(item.getCustomLeaveList()));
            //设置用户基本信息
            if (staffRosterMap.containsKey(item.getUserId())) {
                UserBoundVO staffRosterDto = staffRosterMap.get(item.getUserId());
                monthStatistics.setDeptName(StringUtil.isNotEmpty(staffRosterDto.getOrganizeName()) ? staffRosterDto.getOrganizeName() : "");
                monthStatistics.setPostName(StringUtil.isNotEmpty(staffRosterDto.getPositionName()) ? staffRosterDto.getPositionName() : "");
                monthStatistics.setUserName(StringUtil.isNotEmpty(staffRosterDto.getName()) ? staffRosterDto.getName() : "");
                monthStatistics.setWorkStatus(Objects.nonNull(staffRosterDto.getWorkStatusEnums()) ? staffRosterDto.getWorkStatusEnums().getCode() : null);
                StatisticsEnumUtil.WorkStatusEnum workStatusEnum = StatisticsEnumUtil.WorkStatusEnum.getWorkStatusEnum(monthStatistics.getWorkStatus());
                monthStatistics.setWorkStatusStr(Objects.nonNull(workStatusEnum) ? workStatusEnum.getMsg() : "");
            }
            //设置考勤组周期
            if (userCycleMap.containsKey(item.getGroupId()) && userCycleMap.get(item.getGroupId()).containsKey(item.getUserId())) {
                monthStatistics.setCycleList(userCycleMap.get(item.getGroupId()).get(item.getUserId()));
                StringBuilder cycleStr = new StringBuilder();
                userCycleMap.get(item.getGroupId()).get(item.getUserId()).forEach(cycle -> cycleStr.append(cycle).append(","));
                monthStatistics.setCycleStr(cycleStr.length() > 0 ? cycleStr.substring(0, cycleStr.length() - 1) : "");
            }
            return monthStatistics;
        }).collect(Collectors.toList());
    }

    /**
     * 考勤确认月统计数据封装
     *
     * @param month                 月份
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @param statisticsQueryVoList 日统计汇总数据
     * @return List<MonthStatisticsPageListVo>
     * @throws Exception 异常问题
     */
    private List<MonthConfirmStatisticsListVo> monthConfirmStatisticsAssemble(String month, String startDate, String endDate, List<MonthStatisticsQueryVo> statisticsQueryVoList) throws Exception {
        List<String> userIds = statisticsQueryVoList.stream().map(MonthStatisticsQueryVo::getUserId).distinct().collect(Collectors.toList());
        List<String> groupIds = statisticsQueryVoList.stream().map(MonthStatisticsQueryVo::getGroupId).distinct().collect(Collectors.toList());
        if (CollUtil.isEmpty(statisticsQueryVoList) || CollUtil.isEmpty(userIds) || CollUtil.isEmpty(groupIds)) {
            return new ArrayList<>();
        }
        //批量获取用户加入考勤组周期
        DateRangeDto rangeDto = new DateRangeDto();
        rangeDto.setStartDate(startDate);
        rangeDto.setEndDate(endDate);
        Map<String, Map<String, List<String>>> userCycleMap = userService.batchGetUserCycleList(rangeDto, groupIds, userIds);
        //批量获取用户应休天数
        List<AttendancePublicHolidayBalance> holidayBalances = rulesService.getBalanceList(month, userIds);
        Map<String, BigDecimal> holidayBalanceMap = CollUtil.isNotEmpty(holidayBalances) ? holidayBalances.stream().collect(Collectors.toMap(AttendancePublicHolidayBalance::getUserId, AttendancePublicHolidayBalance::getTotal)) : new HashMap<>();
        //批量获取用户已休天数
        Map<String, BigDecimal> usePublicHoliday = dailyRuleService.getUserPublicHoliday(month, userIds);
        return statisticsQueryVoList.stream().map(item -> {
            MonthConfirmStatisticsListVo confirmStatisticsListVo = MonthConfirmStatisticsListVo.builder().build();
            BeanUtils.copyProperties(item, confirmStatisticsListVo);
            confirmStatisticsListVo.setMonth(month);
            // 设置应休|已休
            confirmStatisticsListVo.setRetired(usePublicHoliday.getOrDefault(item.getUserId(), BigDecimal.ZERO).stripTrailingZeros().toPlainString() + "|" + holidayBalanceMap.getOrDefault(item.getUserId(), BigDecimal.ZERO).stripTrailingZeros().toPlainString());
            // 计算各类请假数据
            confirmStatisticsListVo.setCustomLeaveList(DayStatisticsUtils.getMonthCustomLeaveList(item.getCustomLeaveList()));
            //设置考勤组周期
            if (userCycleMap.containsKey(item.getGroupId()) && userCycleMap.get(item.getGroupId()).containsKey(item.getUserId())) {
                confirmStatisticsListVo.setCycleList(userCycleMap.get(item.getGroupId()).get(item.getUserId()));
            }
            return confirmStatisticsListVo;
        }).collect(Collectors.toList());
    }

    @Override
    @SneakyThrows
    public void monthDataExport(MonthStatisticsExportDto req) {
        String tenantId = UserProvider.getUser().getTenantId();
        // 筛选后的用户
        req.setTenantId(tenantId);
        DateRangeDto dateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        MonthStatisticsPageListDto monthStatisticsPageListDto = BeanUtil.toBean(req, MonthStatisticsPageListDto.class);
        List<AttendanceCustomizeTableVo> list = customizeTableService.findList(null, 1, 2);
        Assert.notEmpty(list, "导出失败，未查询到表头数据");
        ExcelExportTemplate.MoreSheetDto moreSheetDto = new ExcelExportTemplate.MoreSheetDto();
        moreSheetDto.setUserId(UserProvider.getLoginUserId());
        moreSheetDto.setFilename(req.getMonth() + "考勤数据导出");
        // 获取动态表头相关信息
        List<AttendanceCustomizeTableVo> customizeTableVos = getAttendanceCustomizeTableVos(dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), 2);
        // 获取固定表头名称
        List<List<String>> headerNameList = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().map(vo -> CollUtil.newArrayList(vo.getHeadName())).collect(Collectors.toList()) : new ArrayList<>();
        // 获取固定表头字段
        List<String> headerFieldList = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().map(AttendanceCustomizeTableVo::getField).collect(Collectors.toList()) : new ArrayList<>();
        // 获取固定表头字段宽度
        List<Integer> headerFieldWidthsList = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().map(AttendanceCustomizeTableVo::getWidth).collect(Collectors.toList()) : new ArrayList<>();
        // 导出字段的顺序
        LinkedHashMap<String, Integer> dynamicHeadersMap = CollUtil.isNotEmpty(customizeTableVos) ? customizeTableVos.stream().sorted(Comparator.comparing(AttendanceCustomizeTableVo::getSort)).collect(Collectors.toMap(AttendanceCustomizeTableVo::getField, AttendanceCustomizeTableVo::getSort, (e1, e2) -> e1, LinkedHashMap::new)) : new LinkedHashMap<>();
        monthStatisticsPageListDto.setCurrentPage(1);
        monthStatisticsPageListDto.setPageSize(20000);
        List<MonthStatisticsPageListVo> monthPageList = getMonthPageList(monthStatisticsPageListDto).getList();
        List<Map<Integer, Object>> data = CollUtil.newArrayList();
        ExcelExportTemplate.MoreSheetDetailDto moreSheetDetailDto = new ExcelExportTemplate.MoreSheetDetailDto("考勤数据导出", "统计数据", MonthStatisticsPageListVo.class, headerNameList, headerFieldList, headerFieldWidthsList, 2000, (page, size) -> {
            if (CollUtil.isNotEmpty(monthPageList)) {
                monthPageList.forEach(monthStaVo -> {
                    data.add(DayStatisticsUtils.getMonthStaDataList(monthStaVo, dynamicHeadersMap));
                });
            }
            return data;
        }, null);
        moreSheetDto.getMoreSheetDetailDto().add(moreSheetDetailDto);
        DayStatisticsPageListDto dayStatisticsPageListDto = BeanUtil.toBean(req, DayStatisticsPageListDto.class);
        BeanUtils.copyProperties(monthStatisticsPageListDto, dayStatisticsPageListDto);
        dayStatisticsPageListDto.setStartDate(req.getMonth() + "-01");
        // 获取当月最后一天
        DateDetail dateDetail = new DateDetail();
        String lastDayOfMonth = dateDetail.getCutoffDate(req.getMonth());
        String key = "month" + UserProvider.getLoginUserId() + System.currentTimeMillis();
        dayStatisticsPageListDto.setEndDate(lastDayOfMonth);
        List<String> head1 = DayStatisticsUtils.getHead();
        ExcelExportTemplate.MoreSheetDetailDto moreSheetDetailDto1 = new ExcelExportTemplate.MoreSheetDetailDto("打卡数据导出", "打卡数据", ClockInExportVo.class, head1.stream().map(vo -> CollUtil.newArrayList(req.getMonth() + "成员打卡记录", vo)).collect(Collectors.toList()), null, DayStatisticsUtils.getWidths(), 2000, (page, size) -> {
            dayStatisticsPageListDto.setCurrentPage(page);
            dayStatisticsPageListDto.setPageSize(size);
            dayStatisticsPageListDto.setKey(key);
            return getDayClockInPageListExport(dayStatisticsPageListDto);
        }, null);
        moreSheetDto.getMoreSheetDetailDto().add(moreSheetDetailDto1);
        excelExportTemplate.exportMoreExcelModule(moreSheetDto);


    }

    @Override
    public LatticeStatisticsVo getLatticeStatistics(LatticeStatisticsVoDto req) {
        AttendanceDayStatistics dayStatistics = this.lambdaQuery().eq(AttendanceDayStatistics::getUserId, req.getUserId()).eq(AttendanceDayStatistics::getDate, req.getDay()).eq(AttendanceDayStatistics::getGroupId, req.getGroupId()).last("limit 1").one();
        LatticeStatisticsVo latticeStatisticsVo = LatticeStatisticsVo.builder().build();
        if (Objects.nonNull(dayStatistics)) {
            BeanUtils.copyProperties(dayStatistics, latticeStatisticsVo);
            latticeStatisticsVo.setActualAttendHours(latticeStatisticsVo.getEffectiveAttendHours());
        }
        return latticeStatisticsVo;
    }

    /**
     * 用户加入时生成空白统计数据
     */
    @Override
    @SneakyThrows
    public void userJoinHandleData(String tenantId, String groupId, List<String> userIds) {
        if (StringUtil.isEmpty(groupId)) {
            log.info("用户加入时生成空白统计数据，传入的考勤组id为空");
            return;
        }
        if (CollUtil.isEmpty(userIds)) {
            log.info("用户加入时生成空白统计数据，传入的用户id为空");
            return;
        }
        DateTime dateTime = DateUtil.beginOfDay(new Date());
        CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS).execute(() -> {
            try {
                TenantDataSourceUtil.switchTenant(tenantId);
                dayStatisticsUtil.handleNailData(groupId, userIds, dateTime);
            } catch (Exception e) {
                log.error("用户加入时生成空白统计数据异常", e);
            }
        });
    }

    @Override
    @SneakyThrows
    public void userJoinHandleData(String tenantId, Date date, String groupId, List<String> userIds) {
        if (StringUtil.isEmpty(groupId)) {
            return;
        }
        if (CollUtil.isEmpty(userIds)) {
            return;
        }
        CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS).execute(() -> {
            try {
                TenantDataSourceUtil.switchTenant(tenantId);
                Date today = new Date();
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(today); // 修复变量名错误
                Date currentDate;
                while (true) {
                    currentDate = calendar.getTime();
                    if (currentDate.after(today) || currentDate.equals(today)) {
                        break;
                    }
                    dayStatisticsUtil.handleNailData(groupId, userIds, DateUtil.beginOfDay(currentDate));
                    calendar.add(Calendar.DAY_OF_MONTH, 1);
                }
            } catch (Exception e) {
                log.error("用户加入时生成空白统计数据异常", e);
            }
        });
    }

    @Override
    public Boolean handleDataForJob(String tenantId) {
        Date day = DateUtil.beginOfDay(new Date());
        //已存在数据的考勤组人员不生成空白统计数据
        List<AttendanceDayStatistics> list = lambdaQuery().eq(AttendanceDayStatistics::getDate, day).list();
        Map<String, List<String>> userIdByGroupIdMap = list.stream().collect(Collectors.groupingBy(AttendanceDayStatistics::getGroupId, Collectors.mapping(AttendanceDayStatistics::getUserId, Collectors.toList())));
        List<AttendanceGroupUser> attendanceGroupUsers = attendanceUserService.queryAll();
        Map<String, List<AttendanceGroupUser>> collect = attendanceGroupUsers.stream().collect(Collectors.groupingBy(AttendanceGroupUser::getGroupId));
        collect.forEach((groupId, userIds) -> {
            List<String> createUserId = Lists.newArrayList();
            Map<String, List<AttendanceGroupUser>> userCollect = userIds.stream().collect(Collectors.groupingBy(AttendanceGroupUser::getUserId));
            userCollect.forEach((userId, userList) -> {
                if (dailyRuleService1.isExistStatus(userList, day) > 0) {
                    createUserId.add(userId);
                }
            });
            createUserId.removeAll(userIdByGroupIdMap.getOrDefault(groupId, CollUtil.newArrayList()));
            if (CollUtil.isNotEmpty(createUserId)) {
                dayStatisticsUtil.handleNailData(groupId, createUserId, day);
            }
        });
        return Boolean.TRUE;
    }

    @SneakyThrows
    @Override
    public void batchStatisticDataClear(StatisticsBatchClearDto courseEventDTO) {
        TenantDataSourceUtil.switchTenant(courseEventDTO.getTenantId());
        if (Objects.nonNull(courseEventDTO.getStartDay())) {
            lambdaUpdate().eq(AttendanceDayStatistics::getGroupId, courseEventDTO.getGroupId())
                    .in(AttendanceDayStatistics::getUserId, courseEventDTO.getUserIdList())
                    .ge(AttendanceDayStatistics::getDate, DateUtil.date(courseEventDTO.getStartDay()))
                    .remove();
            return;
        }
        lambdaUpdate().eq(AttendanceDayStatistics::getGroupId, courseEventDTO.getGroupId())
                .in(AttendanceDayStatistics::getUserId, courseEventDTO.getUserIdList())
                .eq(AttendanceDayStatistics::getDate, DateUtil.date(courseEventDTO.getDay()))
                .remove();
    }

    // 该方法只会执行一次，用于处理历史数据
    @Override
    @Transactional
    public void regenerateDayData(String tenantId, Date start) {
        try {
            TenantDataSourceUtil.switchTenant(tenantId);
            // 从开始排班的时间开始
            Date startDay = dailyRuleService.getEarliestSchedulingDate(start);
            LocalDate startDate = startDay.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            LocalDate currentDate = LocalDate.now();
            // 限制最多10个并发线程
            final Semaphore semaphore = new Semaphore(5);
            List<CompletableFuture<Void>> allFutures = new ArrayList<>();
            // 按月处理数据
            while (!startDate.isAfter(currentDate)) {
                // 计算当前月的最后一天
                LocalDate endDate = startDate.with(TemporalAdjusters.lastDayOfMonth());
                // 如果当前月的最后一天超过今天，则结束日期为今天
                if (!endDate.isBefore(currentDate)) {
                    endDate = currentDate;
                }
                // 创建处理当前月份数据的任务
                LocalDate finalStartDate = startDate;
                LocalDate finalEndDate = endDate;
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    try {
                        semaphore.acquire();
                        try {
                            TenantDataSourceUtil.switchTenant(tenantId);
                            // 调用处理方法处理当前月份的数据
                            List<CreateDayStatistics> createDataList = dailyRuleService.getDayRuleByMonth(finalStartDate, finalEndDate);
                            // 事务提交后执行后续操作
                            if (CollUtil.isNotEmpty(createDataList)) {
                                this.handlePostTransactionOperations(tenantId, createDataList);
                            }
                        } finally {
                            semaphore.release();
                        }
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        log.warn("线程因获取信号量被中断", e);
                    } catch (Exception e) {
                        log.error("处理历史数据异常，时间范围: {} 至 {}", finalStartDate, finalEndDate, e);
                    }
                }, threadPoolExecutor);
                allFutures.add(future);
                // 移动到下一个月
                startDate = startDate.plusMonths(1).withDayOfMonth(1);
            }
            // 等待所有任务完成
            if (!allFutures.isEmpty()) {
                CompletableFuture<Void> allDone = CompletableFuture.allOf(allFutures.toArray(new CompletableFuture[0]));
                try {
                    allDone.join();
                } catch (Exception e) {
                    log.error("等待所有任务完成时发生异常", e);
                }
            }
        } catch (Exception e) {
            log.error("重新生成日统计数据异常", e);
        }
    }

    // 该方法只会执行一次，用于处理历史数据
    @Override
    public void processHistoricalData(String tenantId, Date start, Date end) {
        try {
            TenantDataSourceUtil.switchTenant(tenantId);
            // 从开始排班的时间开始
            Date startDay = dailyRuleService.getEarliestSchedulingDate(start);
            LocalDate startDate = startDay.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            LocalDate endDateParam = end.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            // 限制最多5个并发线程
            final Semaphore semaphore = new Semaphore(5);
            List<CompletableFuture<Void>> allFutures = new ArrayList<>();
            // 按月处理数据
            while (startDate.isBefore(endDateParam) || startDate.isEqual(endDateParam)) {
                // 计算当前月的最后一天
                LocalDate endDate = startDate.with(TemporalAdjusters.lastDayOfMonth());
                // 如果当前月的最后一天超过指定结束日期，则结束日期为指定日期
                if (endDate.isAfter(endDateParam)) {
                    endDate = endDateParam;
                }
                // 创建处理当前月份数据的任务
                LocalDate finalStartDate = startDate;
                LocalDate finalEndDate = endDate;
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    try {
                        semaphore.acquire();
                        try {
                            TenantDataSourceUtil.switchTenant(tenantId);
                            // 生成指定范围内的所有日期集合
                            List<LocalDate> dateRange = new ArrayList<>();
                            LocalDate currentDate = finalStartDate;
                            while (currentDate.isBefore(finalEndDate) || currentDate.isEqual(finalEndDate)) {
                                dateRange.add(currentDate);
                                currentDate = currentDate.plusDays(1);
                            }
                            // 调用处理方法处理当前月份的数据
                            List<CreateDayStatistics> createDataList = dailyRuleService.getDayRuleByMonth(finalStartDate, finalEndDate);
                            // **关键修改：即使没有排班数据，也要调用统计接口**
                            this.handlePostTransactionOperations1(tenantId, createDataList, dateRange);
                        } finally {
                            semaphore.release();
                        }
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        log.warn("线程因获取信号量被中断", e);
                    } catch (Exception e) {
                        log.error("处理历史数据异常，时间范围: {} 至 {}", finalStartDate, finalEndDate, e);
                    }
                }, threadPoolExecutor);
                allFutures.add(future);
                // 移动到下一个月
                startDate = startDate.plusMonths(1).withDayOfMonth(1);
            }
            // 等待所有任务完成
            if (!allFutures.isEmpty()) {
                CompletableFuture<Void> allDone = CompletableFuture.allOf(allFutures.toArray(new CompletableFuture[0]));
                try {
                    allDone.join();
                } catch (Exception e) {
                    log.error("等待所有任务完成时发生异常", e);
                }
            }
        } catch (Exception e) {
            log.error("重新生成日统计数据异常", e);
        }
    }

    private void handlePostTransactionOperations(String tenantId, List<CreateDayStatistics> createDayStatisticsList) {
        if (CollUtil.isEmpty(createDayStatisticsList)) {
            return;
        }
        try {
            Map<String, Map<Date, List<CreateDayStatistics>>> careateDayMap = createDayStatisticsList.stream().collect(Collectors.groupingBy(CreateDayStatistics::getGroupId, Collectors.groupingBy(CreateDayStatistics::getDay)));
            careateDayMap.forEach((groupId, dayListMap) -> {
                dayListMap.forEach((date, createList) -> {
                    //调用生成日统计数据接口
                    StatisticsSingleDto courseEventDTO = StatisticsSingleDto.builder()
                            .tenantId(tenantId)
                            .groupId(groupId)
                            .day(date)
                            .triggerSceneEnum(TriggerSceneEnum.MANUAL_TRIGGER)
                            .build();
                    List<String> userIds = createList.stream().map(CreateDayStatistics::getUserId).distinct().collect(Collectors.toList());
                    userIds.forEach(item -> {
                        courseEventDTO.setUserId(item);
                        if (StrUtil.isBlank(tenantId) || StrUtil.isBlank(groupId)) {
                            log.error("AttendanceDayStatisticsServiceImpl#handlePostTransactionOperations 触发日统计租户ID或者考勤组ID为空：{}", JSON.toJSONString(courseEventDTO));
                        }
                        Message<StatisticsSingleDto> message = MessageBuilder.withPayload(courseEventDTO).build();
                        rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_TOPIC, message, 3000L, 2);
                    });
                });
            });
        } catch (Exception e) {
            log.error("事务提交后处理操作异常", e);
        }
    }

    private void handlePostTransactionOperations1(String tenantId, List<CreateDayStatistics> createDayStatisticsList, List<LocalDate> dateRange) {
        if (CollUtil.isEmpty(createDayStatisticsList)) {
            return;
        }
        try {
            Map<String, List<CreateDayStatistics>> groupMap = createDayStatisticsList.stream()
                    .collect(Collectors.groupingBy(CreateDayStatistics::getGroupId));
            groupMap.forEach((groupId, createList) -> {
                // 按dateRange日期顺序遍历
                for (LocalDate date : dateRange) {
                    Date currentDate = Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
                    // 过滤出当前日期的数据
                    List<CreateDayStatistics> currentDayList = createList.stream()
                            .filter(item -> DateUtils.isSameDay(item.getDay(), currentDate))
                            .collect(Collectors.toList());
                    // 调用生成日统计数据接口
                    StatisticsSingleHistoryDto courseEventDTO = StatisticsSingleHistoryDto.builder()
                            .tenantId(tenantId)
                            .groupId(groupId)
                            .day(currentDate)
                            .triggerSceneEnum(TriggerSceneEnum.MANUAL_TRIGGER)
                            .build();
                    // 获取当前日期的用户ID列表
                    List<String> userIds = currentDayList.stream()
                            .map(CreateDayStatistics::getUserId)
                            .distinct()
                            .collect(Collectors.toList());
                    // 发送消息
                    userIds.forEach(item -> {
                        courseEventDTO.setUserId(item);
                        Message<StatisticsSingleHistoryDto> message = MessageBuilder.withPayload(courseEventDTO).build();
                        rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_HISTORY_TOPIC, message, 3000L, 1);
                    });
                }
            });
        } catch (Exception e) {
            log.error("事务提交后处理操作异常", e);
        }
    }

    @Override
    @DSTransactional
    public void statisticDataChange(StatisticsSingleDto singleDto) throws LoginException {
        // 执行业务（异常直接向上抛出）
        this.dayStatisticsUtil.createDayStatisticsData(singleDto);
    }

    @Override
    @DSTransactional
    public void statisticDataChangeHistory(StatisticsSingleHistoryDto singleDto) throws LoginException {
        // 执行业务（异常直接向上抛出）
        this.dayStatisticsUtil.createDayStatisticsDataHistory(singleDto);
    }


    @Override
    @SneakyThrows
    public Boolean dayStatisticsNotice(String tenantId) {
        Integer globalNoticeSetting = getGlobalNoticeSetting(ATTENDANCE_GROUP_DAILY_REPORT_SEND);
        String startTime = null;
        String endTime = null;
        if (Objects.equals(globalNoticeSetting, 1)) {
            Date date = jnpf.util.DateUtil.localDateTimeToDate(LocalDateTime.now().minusHours(1));
            startTime = DateDetail.getDate2Str(date, DateDetail.DF2);
            endTime = DateDetail.getDate2Str(date, DateDetail.DF19);
        } else if (Objects.equals(globalNoticeSetting, 2)) {
            Date date = new Date();
            startTime = DateDetail.getDate2Str(date, DateDetail.DF2);
            endTime = DateDetail.getDate2Str(date, DateDetail.DF19);
        } else if (Objects.equals(globalNoticeSetting, 3)) {
            Date date = jnpf.util.DateUtil.localDateTimeToDate(LocalDateTime.now().minusDays(1));
            DateTime dateTime = DateUtil.beginOfDay(date);
            DateTime dateTime1 = DateUtil.beginOfMinute(date);
            if (dateTime.compareTo(dateTime1) != 0) {
                return Boolean.TRUE;
            }
            startTime = DateDetail.getDateTime2Str(dateTime);
            DateTime dateTime2 = DateUtil.offsetDay(dateTime, 1);
            endTime = DateDetail.getDateTime2Str(dateTime2);
        }
        List<DayStatisticsNoticeQueryVo> dayStatisticsList = this.baseMapper.getDayStatisticsNotice(startTime, endTime);
        if (CollUtil.isEmpty(dayStatisticsList)) {
            log.info("当前时间：{} 个人日度统计提醒：未查询到统计数据", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        Map<String, DayNoticeModel> dayNoticeModelMap = getDayNoticeModelMap(dayStatisticsList);
        //获取考勤组信息
        List<String> groupIdList = dayStatisticsList.stream().map(DayStatisticsNoticeQueryVo::getGroupId).distinct().collect(Collectors.toList());
        List<AttendanceGroup> groupList = this.groupService.queryListByIds(groupIdList);
        if (CollUtil.isEmpty(groupList)) {
            log.info("当前时间：{} 个人日度统计提醒：未查询到考勤组信息", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        Map<String, AttendanceGroup> groupInfoMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        Map<String, List<DayStatisticsNoticeQueryVo>> userDayStatisticsMap = dayStatisticsList.stream().collect(Collectors.groupingBy(DayStatisticsNoticeQueryVo::getUserId));
        for (Map.Entry<String, List<DayStatisticsNoticeQueryVo>> userEntry : userDayStatisticsMap.entrySet()) {
            Date date = userEntry.getValue().get(0).getDate();
            Map<String, List<DayStatisticsNoticeQueryVo>> groupDayStatisticsMap = userEntry.getValue().stream().collect(Collectors.groupingBy(DayStatisticsNoticeQueryVo::getGroupId));
            List<DayNoticeModel> dayNoticeModelList = Lists.newArrayList();
            for (Map.Entry<String, List<DayStatisticsNoticeQueryVo>> groupEntry : groupDayStatisticsMap.entrySet()) {
                if (!groupInfoMap.containsKey(groupEntry.getKey())) {
                    continue;
                }
                for (DayStatisticsNoticeQueryVo dayStatistics : groupEntry.getValue()) {
                    if (!dayNoticeModelMap.containsKey(dayStatistics.getId())) {
                        continue;
                    }
                    DayNoticeModel dayNoticeModel = dayNoticeModelMap.get(dayStatistics.getId());
                    dayNoticeModel.setGroupName(groupInfoMap.get(groupEntry.getKey()).getGroupName());
                    dayNoticeModelList.add(dayNoticeModel);
                }
            }
            SettlementNoticeModel settlementNoticeModel = new SettlementNoticeModel();
            settlementNoticeModel.setTitle(jnpf.util.DateUtil.dateToString(date, "yyyy年MM月dd日") + "考勤日报");
            settlementNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
            settlementNoticeModel.setUserId(userEntry.getKey());
            settlementNoticeModel.setDayNoticeModelList(dayNoticeModelList);
            settlementNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.INDIVIDUAL_DAILY_STATISTICS);
            noticeHandler.send(settlementNoticeModel);
        }
        return Boolean.TRUE;
    }

    /**
     * 获取用户通知配置信息
     *
     * @param code 配置编码
     * @return Integer
     */
    @Nullable
    private Integer getGlobalNoticeSetting(String code) {
        UserSettingVo userSettingVo = userSettingService.checkWebSetting(code);
        if (Objects.equals(userSettingVo.getStatus(), 0)) {
            return null;
        }
        return userSettingVo.getValue();
    }

    @Override
    @SneakyThrows
    public Boolean monthStatisticsNotice(String tenantId) {
        Integer globalNoticeSetting = getGlobalNoticeSetting(ATTENDANCE_GROUP_MONTHLY_REPORT_SEND);
        boolean isAbsent = Boolean.FALSE;
        DateTime now = DateUtil.beginOfMinute(new Date());
        // 获取当前日期
        LocalDate today = LocalDate.now();
        // 获取上个月的第一天
        LocalDate firstDayOfLastMonth = today.minusMonths(1).withDayOfMonth(1);
        // 获取上个月的最后一天
        LocalDate lastDayOfLastMonth = today.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
        Date date = DateUtil.date(lastDayOfLastMonth.atStartOfDay(ZoneId.systemDefault()).toInstant());
        if (Objects.equals(globalNoticeSetting, 1)) {
            DateTime dateTime = DateUtil.beginOfMonth(now);
            dateTime = dateTime.setField(DateField.HOUR_OF_DAY, 12);
            if (now.compareTo(dateTime) != 0) {
                return Boolean.TRUE;
            }
        } else if (Objects.equals(globalNoticeSetting, 2)) {
            DateTime dateTime = DateUtil.beginOfMonth(now);
            dateTime = dateTime.setField(DateField.HOUR_OF_DAY, 0);
            if (now.compareTo(dateTime) != 0) {
                return Boolean.TRUE;
            }
        } else if (Objects.equals(globalNoticeSetting, 3)) {
            isAbsent = Boolean.TRUE;
            String nowDay = DateDetail.getDate2Str(now, DateDetail.DF);
            DateTime dateTime = DateUtil.endOfMonth(now);
            String endDay = DateDetail.getDate2Str(dateTime, DateDetail.DF);
            dateTime = DateUtil.beginOfMonth(now);
            String beginDay = DateDetail.getDate2Str(dateTime, DateDetail.DF);
            if (!StringUtil.equals(nowDay, endDay) && !StringUtil.equals(beginDay, nowDay)) {
                return Boolean.TRUE;
            }
            if (StringUtil.equals(nowDay, endDay)) {
                firstDayOfLastMonth = today.withDayOfMonth(1);
                lastDayOfLastMonth = today.with(TemporalAdjusters.lastDayOfMonth());
            }
        }

        List<MonthStatisticsNoticeQueryVo> monthStatisticsList = this.baseMapper.getMonthStatisticsNotice(firstDayOfLastMonth, lastDayOfLastMonth);
        if (CollUtil.isEmpty(monthStatisticsList)) {
            log.info("当前时间：{} 个人月度统计提醒：未查询到统计数据", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        String month = lastDayOfLastMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
        Map<String, MonthNoticeModel> monthNoticeModelMap = getMonthNoticeModelMap(month, monthStatisticsList);
        //获取考勤组信息
        List<String> groupIdList = monthStatisticsList.stream().map(MonthStatisticsNoticeQueryVo::getGroupId).distinct().collect(Collectors.toList());
        List<AttendanceGroup> groupList = this.groupService.queryListByIds(groupIdList);
        if (CollUtil.isEmpty(groupList)) {
            log.info("当前时间：{} 个人月度统计提醒：未查询到考勤组信息", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        Map<String, AttendanceGroup> groupInfoMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        Map<String, List<MonthStatisticsNoticeQueryVo>> userMonthStatisticsMap = monthStatisticsList.stream().collect(Collectors.groupingBy(MonthStatisticsNoticeQueryVo::getUserId));
        for (Map.Entry<String, List<MonthStatisticsNoticeQueryVo>> userEntry : userMonthStatisticsMap.entrySet()) {
            Map<String, List<MonthStatisticsNoticeQueryVo>> groupDayStatisticsMap = userEntry.getValue().stream().collect(Collectors.groupingBy(MonthStatisticsNoticeQueryVo::getGroupId));
            List<MonthNoticeModel> monthNoticeModelList = Lists.newArrayList();
            for (Map.Entry<String, List<MonthStatisticsNoticeQueryVo>> groupEntry : groupDayStatisticsMap.entrySet()) {
                if (!groupInfoMap.containsKey(groupEntry.getKey())) {
                    continue;
                }
                List<MonthStatisticsNoticeQueryVo> value = groupEntry.getValue();
                for (MonthStatisticsNoticeQueryVo monthStatistics : value) {
                    if (!monthNoticeModelMap.containsKey(monthStatistics.getId())) {
                        continue;
                    }
                    boolean isEndOfDay = date.compareTo(DateUtil.endOfDay(monthStatistics.getRemindTime())) == 0;
                    if (isAbsent && (now.compareTo(monthStatistics.getRemindTime()) != 0 && !isEndOfDay && now.compareTo(DateUtil.beginOfDay(now)) != 0)) {
                        continue;
                    }
                    MonthNoticeModel monthNoticeModel = monthNoticeModelMap.get(monthStatistics.getId());
                    monthNoticeModel.setGroupName(groupInfoMap.get(groupEntry.getKey()).getGroupName());
                    monthNoticeModel.setEndDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
                    monthNoticeModelList.add(monthNoticeModel);
                }
            }
            if (CollUtil.isEmpty(monthNoticeModelList)) {
                continue;
            }
            SettlementNoticeModel settlementNoticeModel = new SettlementNoticeModel();
            settlementNoticeModel.setTitle(lastDayOfLastMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月")) + "个人统计月报");
            settlementNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
            settlementNoticeModel.setUserId(userEntry.getKey());
            settlementNoticeModel.setMonthNoticeModelList(monthNoticeModelList);
            settlementNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.INDIVIDUAL_MONTHLY_STATISTICS);
            noticeHandler.send(settlementNoticeModel);
        }
        return Boolean.TRUE;
    }

    @Override
    @SneakyThrows
    public void consentUnscheduledNotice(String tenantId) {
        Integer globalNoticeSetting = getGlobalNoticeSetting(UN_SCHEDULED_REMIND_ADMIN);
        DateTime now = DateUtil.beginOfDay(new Date());
        List<Date> collect = lambdaQuery().select(AttendanceDayStatistics::getDate).lt(AttendanceDayStatistics::getDate, now).eq(AttendanceDayStatistics::getIsScheduling, Boolean.FALSE).groupBy(AttendanceDayStatistics::getDate).orderByDesc(AttendanceDayStatistics::getDate).last("limit 30").list().stream().map(AttendanceDayStatistics::getDate).collect(Collectors.toList());
        if (CollUtil.isEmpty(collect)) {
            return;
        }
        List<AttendanceDayStatistics> list = lambdaQuery().select(AttendanceDayStatistics::getUserId, AttendanceDayStatistics::getGroupId, AttendanceDayStatistics::getDate).eq(AttendanceDayStatistics::getIsScheduling, Boolean.FALSE).in(AttendanceDayStatistics::getDate, collect).list();
        if (CollUtil.isEmpty(list)) {
            return;
        }
        int offsetDays = Optional.ofNullable(globalNoticeSetting).orElse(0);
        List<Date> datesByPeriod = DateDetail.getDatesByPeriod(DateUtil.offsetDay(new Date(), -offsetDays), new Date());
        List<String> collect3 = datesByPeriod.stream().map(DateDetail::getDateTime2Str).collect(Collectors.toList());
        Map<String, List<AttendanceDayStatistics>> collectGroup = list.stream().collect(Collectors.groupingBy(AttendanceDayStatistics::getGroupId));
        List<String> userIdList = list.stream().map(AttendanceDayStatistics::getUserId).distinct().collect(Collectors.toList());
        List<String> groupIdList = list.stream().map(AttendanceDayStatistics::getGroupId).distinct().collect(Collectors.toList());
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date startOfMinuteRange = calendar.getTime();
        calendar.set(Calendar.SECOND, 59);
        Date endOfMinuteRange = calendar.getTime();
        List<AttendanceGroupUser> attendanceGroupUserVos = attendanceUserService.getAttendanceGroupUsersOfSecondment(startOfMinuteRange, endOfMinuteRange, userIdList, groupIdList);
        if (CollUtil.isEmpty(attendanceGroupUserVos)) {
            return;
        }
        //判断当前推送时间节点用户是否离组或者离职(离职人员不推送)
        Map<String, Integer> userMap = dailyRuleService1.findUserIsExistsByUserList(attendanceGroupUserVos, startOfMinuteRange, endOfMinuteRange);
        List<String> removeUserList = Optional.ofNullable(userMap).orElse(Collections.emptyMap()).entrySet().stream().filter(entry -> entry.getValue().equals(0)).map(Map.Entry::getKey).collect(Collectors.toList());
        List<String> userIds = CollUtil.newArrayList();
        ConsecUnscheduledNoticeModel consecUnscheduledNoticeModel = new ConsecUnscheduledNoticeModel();
        collectGroup.forEach((groupId, userStatistics) -> {
            Map<String, List<AttendanceDayStatistics>> collect1 = userStatistics.stream().collect(Collectors.groupingBy(AttendanceDayStatistics::getUserId));
            userIds.clear();
            collect1.forEach((userId, sta) -> {
                List<Date> collect2 = sta.stream().sorted(Comparator.comparing(AttendanceDayStatistics::getDate).reversed()).map(AttendanceDayStatistics::getDate).filter(date -> collect3.contains(DateDetail.getDateTime2Str(date))).collect(Collectors.toList());
                if (collect2.size() < Optional.ofNullable(globalNoticeSetting).orElse(0)) {
                    return;
                }
                userIds.add(userId);
            });
            if (CollUtil.isEmpty(userIds)) {
                return;
            }
            //排除掉已离组的人员
            if (CollUtil.isNotEmpty(removeUserList) && CollUtil.containsAny(userIds, removeUserList)) {
                userIds.removeIf(removeUserList::contains);
            }
            consecUnscheduledNoticeModel.setUserIds(userIds);
            consecUnscheduledNoticeModel.setGroupId(groupId);
            consecUnscheduledNoticeModel.setTenantId(tenantId);
            consecUnscheduledNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.CONSEC_UNSCHEDULED);
            noticeHandler.send(consecUnscheduledNoticeModel);
        });
    }

    @Override
    @SneakyThrows
    public Boolean teamMonthStatisticsNotice(String tenantId) {
        Integer globalNoticeSetting = getGlobalNoticeSetting(ATTENDANCE_TEAM_MONTHLY_REPORT_SEND);
        boolean isAbsent = Boolean.FALSE;
        DateTime now = DateUtil.beginOfMinute(new Date());
        // 获取当前日期
        LocalDate today = LocalDate.now();
        LocalDateTime currentTime = LocalDateTime.now();
        // 获取上个月的第一天
        LocalDate firstDayOfLastMonth = today.minusMonths(1).withDayOfMonth(1);
        // 获取上个月的最后一天
        LocalDate lastDayOfLastMonth = today.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
        Date date = DateUtil.date(lastDayOfLastMonth.atStartOfDay(ZoneId.systemDefault()).toInstant());
        if (Objects.equals(globalNoticeSetting, 1)) {
            DateTime dateTime = DateUtil.beginOfMonth(now);
            dateTime = dateTime.setField(DateField.HOUR_OF_DAY, 12);
            if (now.compareTo(dateTime) != 0) {
                return Boolean.TRUE;
            }
        } else if (Objects.equals(globalNoticeSetting, 2)) {
            DateTime dateTime = DateUtil.beginOfMonth(now);
            dateTime = dateTime.setField(DateField.HOUR_OF_DAY, 0);
            if (now.compareTo(dateTime) != 0) {
                return Boolean.TRUE;
            }
        } else if (Objects.equals(globalNoticeSetting, 3)) {
            isAbsent = Boolean.TRUE;
            String nowDay = DateDetail.getDate2Str(now, DateDetail.DF);
            DateTime dateTime = DateUtil.endOfMonth(now);
            String endDay = DateDetail.getDate2Str(dateTime, DateDetail.DF);
            dateTime = DateUtil.beginOfMonth(now);
            String beginDay = DateDetail.getDate2Str(dateTime, DateDetail.DF);
            if (!StringUtil.equals(nowDay, endDay) && !StringUtil.equals(beginDay, nowDay)) {
                return Boolean.TRUE;
            }
            if (StringUtil.equals(nowDay, endDay)) {
                firstDayOfLastMonth = today.withDayOfMonth(1);
                lastDayOfLastMonth = today.with(TemporalAdjusters.lastDayOfMonth());
            }
        }
        List<TeamMonthStatisticsNoticeQueryVo> allTeamMonthStatisticsList = this.baseMapper.getStemMonthStatisticsNotice(firstDayOfLastMonth, lastDayOfLastMonth);
        if (CollUtil.isEmpty(allTeamMonthStatisticsList)) {
            log.info("当前时间：{} 团队考勤统计提醒：未查询到统计数据", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        Map<String, TeamMonthNoticeModel> teamMonthNoticeModelMap = DayStatisticsUtils.getTeamMonthNoticeModelMap(allTeamMonthStatisticsList);
        List<TeamMonthStatisticsNoticeQueryVo> teamMonthStatisticsList = getObtainPermissionGroupList(allTeamMonthStatisticsList);
        if (CollUtil.isEmpty(teamMonthStatisticsList)) {
            log.info("当前时间：{} 团队考勤统计提醒：所有考勤组都未开启团队考勤统计提醒", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        //获取考勤组信息
        List<String> groupIdList = teamMonthStatisticsList.stream().map(TeamMonthStatisticsNoticeQueryVo::getGroupId).distinct().collect(Collectors.toList());
        List<AttendanceGroup> groupList = this.groupService.queryListByIds(groupIdList);
        if (CollUtil.isEmpty(groupList)) {
            log.info("当前时间：{} 团队考勤统计提醒：未查询到考勤组信息", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
            return Boolean.TRUE;
        }
        Map<String, AttendanceGroup> groupInfoMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        //发送给超级管理员
        List<AttendanceUserVo> superAdminList = superAdminService.querySuperAdmin(null);
        List<String> superAdminUserList = CollUtil.isNotEmpty(superAdminList) ? superAdminList.stream().map(AttendanceUserVo::getId).collect(Collectors.toList()) : Lists.newArrayList();
        SettlementNoticeModel settlementNoticeModel = new SettlementNoticeModel();
        settlementNoticeModel.setTitle(lastDayOfLastMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月")) + "所管理考勤组团队统计月报");
        settlementNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
        settlementNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.TEAM_MONTHLY_STATISTICS);
        if (CollUtil.isNotEmpty(superAdminList)) {
            List<TeamMonthNoticeModel> teamMonthNoticeModelList = Lists.newArrayList();
            for (TeamMonthStatisticsNoticeQueryVo teamMonthStatistics : teamMonthStatisticsList) {
                if (!groupInfoMap.containsKey(teamMonthStatistics.getGroupId())) {
                    continue;
                }
                boolean isEndOfDay = date.compareTo(DateUtil.endOfDay(teamMonthStatistics.getRemindTime())) == 0;
                if (isAbsent && (now.compareTo(teamMonthStatistics.getRemindTime()) != 0 && !isEndOfDay && now.compareTo(DateUtil.beginOfDay(now)) != 0)) {
                    continue;
                }
                if (!teamMonthNoticeModelMap.containsKey(teamMonthStatistics.getGroupId()) || !groupInfoMap.containsKey(teamMonthStatistics.getGroupId())) {
                    continue;
                }
                TeamMonthNoticeModel teamMonthNoticeModel = teamMonthNoticeModelMap.get(teamMonthStatistics.getGroupId());
                teamMonthNoticeModel.setGroupName(groupInfoMap.get(teamMonthStatistics.getGroupId()).getGroupName());
                teamMonthNoticeModel.setEndDate(currentTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
                teamMonthNoticeModelList.add(teamMonthNoticeModel);
            }
            if (CollUtil.isEmpty(teamMonthNoticeModelList)) {
                return Boolean.TRUE;
            }
            settlementNoticeModel.setUserIds(superAdminUserList);
            settlementNoticeModel.setTeamMonthNoticeModelList(teamMonthNoticeModelList);
            noticeHandler.send(settlementNoticeModel);
        }
        //推送给管理员
        Map<String, List<String>> userGroupMap = this.groupService.userManagerGroupInfo();
        for (Map.Entry<String, List<String>> userEntry : userGroupMap.entrySet()) {
            List<TeamMonthNoticeModel> teamMonthNoticeModelList = Lists.newArrayList();
            List<TeamMonthStatisticsNoticeQueryVo> teamMonthFilter = teamMonthStatisticsList.stream().filter(item -> userEntry.getValue().contains(item.getGroupId())).collect(Collectors.toList());
            if (CollUtil.isEmpty(teamMonthFilter)) {
                continue;
            }
            for (TeamMonthStatisticsNoticeQueryVo teamMonthStatistics : teamMonthFilter) {
                if (!groupInfoMap.containsKey(teamMonthStatistics.getGroupId())) {
                    continue;
                }
                TeamMonthNoticeModel teamMonthNoticeModel = teamMonthNoticeModelMap.get(teamMonthStatistics.getGroupId());
                teamMonthNoticeModel.setGroupName(groupInfoMap.get(teamMonthStatistics.getGroupId()).getGroupName());
                teamMonthNoticeModel.setEndDate(currentTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")));
                teamMonthNoticeModelList.add(teamMonthNoticeModel);
            }
            settlementNoticeModel.setUserIds(List.of(userEntry.getKey()));
            settlementNoticeModel.setTeamMonthNoticeModelList(teamMonthNoticeModelList);
            noticeHandler.send(settlementNoticeModel);
        }
        return Boolean.TRUE;
    }

    @Override
    public List<AttendanceCountAvgHoursVo> countAttendanceAvgHours(AttendanceCountAvgHoursDto dto) {
        List<String> monthList = DayStatisticsUtils.generateMonthList(dto.getStartMonth(), dto.getEndMonth());
        //获取组织下所有考勤组
        List<AttendanceGroup> groupList = this.groupService.getGroupListByOrgIds(dto.getOrgIds().stream().distinct().collect(Collectors.toList()));
        List<AttendanceCountAvgHoursVo> countAvgHoursVoList = CollUtil.newArrayList();
        if (CollUtil.isEmpty(groupList) && CollUtil.isNotEmpty(monthList)) {
            countAvgHoursVoList.addAll(monthList.stream().map(item -> AttendanceCountAvgHoursVo.builder().month(item).avgHours(BigDecimal.ZERO).build()).collect(Collectors.toList()));
            return countAvgHoursVoList;
        }
        List<String> groupIds = groupList.stream().map(AttendanceGroup::getId).distinct().collect(Collectors.toList());
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM", Locale.ENGLISH);
        YearMonth yearStartMonth = YearMonth.parse(dto.getStartMonth(), monthFormatter);
        LocalDate startDate = yearStartMonth.atDay(1);
        YearMonth yearEndMonth = YearMonth.parse(dto.getEndMonth(), monthFormatter);
        LocalDate endDate = yearEndMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearEndMonth.atEndOfMonth();
        //获取考勤组每月的平均工时
        countAvgHoursVoList = this.baseMapper.getAvgHours(groupIds, startDate, endDate);
        Map<String, AttendanceCountAvgHoursVo> monthlyHoursVoMap = countAvgHoursVoList.stream().collect(Collectors.toMap(AttendanceCountAvgHoursVo::getMonth, a -> a, (k1, k2) -> k1));
        return monthList.stream().map(item -> AttendanceCountAvgHoursVo.builder().month(item).avgHours(CollUtil.isEmpty(monthlyHoursVoMap) || !monthlyHoursVoMap.containsKey(item) ? BigDecimal.ZERO : monthlyHoursVoMap.get(item).getAvgHours()).build()).collect(Collectors.toList());
    }

    @Override
    public MonthStatsDetailsVo getAttendanceAvgHoursDetails(MonthStatsDetailsDto dto) {
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        MonthStatsDetailsQueryVo monthStatsDetailsQueryVo = this.baseMapper.getAttendanceAvgHoursDetails(dto.getGroupIds(), startDate, endDate);
        if (Objects.isNull(monthStatsDetailsQueryVo)) {
            return null;
        }
        MonthStatsDetailsVo monthStatsDetailsVo = MonthStatsDetailsVo.builder().avgHours(monthStatsDetailsQueryVo.getAvgHours()).avgOverTimeHours(monthStatsDetailsQueryVo.getAvgOverTimeHours()).avgLeaveDays(monthStatsDetailsQueryVo.getAvgLeaveDays()).avgAbsentDays(monthStatsDetailsQueryVo.getAvgAbsentDays()).lateCount(monthStatsDetailsQueryVo.getLateCount()).earlyCount(monthStatsDetailsQueryVo.getEarlyCount()).build();
        // 获取同比数据
        YearMonth onYear = yearMonth.minusYears(1);
        LocalDate onYearStartDate = onYear.atDay(1);
        LocalDate onYearEndDate = onYear.atEndOfMonth();
        MonthStatsDetailsQueryVo onYearQueryVo = this.baseMapper.getAttendanceAvgHoursDetails(dto.getGroupIds(), onYearStartDate, onYearEndDate);
        monthStatsDetailsVo.setAvgHoursOnYear(onYearQueryVo.getAvgHours().compareTo(BigDecimal.ZERO) > 0 ? monthStatsDetailsVo.getAvgHours().subtract(onYearQueryVo.getAvgHours()).multiply(new BigDecimal(100)).divide(onYearQueryVo.getAvgHours(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        monthStatsDetailsVo.setAvgLeaveDaysOnYear(onYearQueryVo.getAvgLeaveDays().compareTo(BigDecimal.ZERO) > 0 ? monthStatsDetailsVo.getAvgLeaveDays().subtract(onYearQueryVo.getAvgLeaveDays()).multiply(new BigDecimal(100)).divide(onYearQueryVo.getAvgLeaveDays(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        monthStatsDetailsVo.setLateCountOnYear(onYearQueryVo.getLateCount().compareTo(BigDecimal.ZERO) > 0 ? monthStatsDetailsVo.getLateCount().subtract(onYearQueryVo.getLateCount()).multiply(new BigDecimal(100)).divide(onYearQueryVo.getLateCount(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        // 获取环比数据
        YearMonth onMonth = yearMonth.minusMonths(1);
        LocalDate onMonthStartDate = onMonth.atDay(1);
        LocalDate onMonthEndDate = onMonth.atEndOfMonth();
        MonthStatsDetailsQueryVo onMonthQueryVo = this.baseMapper.getAttendanceAvgHoursDetails(dto.getGroupIds(), onMonthStartDate, onMonthEndDate);
        monthStatsDetailsVo.setAvgHoursOnMonth(onMonthQueryVo.getAvgHours().compareTo(BigDecimal.ZERO) > 0 ? monthStatsDetailsVo.getAvgHours().subtract(onMonthQueryVo.getAvgHours()).multiply(new BigDecimal(100)).divide(onMonthQueryVo.getAvgHours(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        monthStatsDetailsVo.setAvgLeaveDaysMonth(onMonthQueryVo.getAvgLeaveDays().compareTo(BigDecimal.ZERO) > 0 ? monthStatsDetailsVo.getAvgLeaveDays().subtract(onMonthQueryVo.getAvgLeaveDays()).multiply(new BigDecimal(100)).divide(onMonthQueryVo.getAvgLeaveDays(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        monthStatsDetailsVo.setLateCountOnMonth(onMonthQueryVo.getLateCount().compareTo(BigDecimal.ZERO) > 0 ? monthStatsDetailsVo.getLateCount().subtract(onMonthQueryVo.getLateCount()).multiply(new BigDecimal(100)).divide(onMonthQueryVo.getLateCount(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        return monthStatsDetailsVo;
    }

    @Override
    public List<MonthStatsPerCapitaVo> getAttendanceMonthPerCapita(MonthStatsDetailsDto dto) {
        //获取月份日期列表
        List<LocalDate> dayList = DayStatisticsUtils.getDatesForMonth(dto.getMonth(), "yyyyMM");
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("MM.dd", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        List<MonthStatsPerCapitaQueryVo> perCapitaQueryVoList = this.baseMapper.getAttendanceMonthPerCapita(dto.getGroupIds(), startDate, endDate);
        Map<LocalDate, MonthStatsPerCapitaQueryVo> perCapitaMap = perCapitaQueryVoList.stream().collect(Collectors.toMap(MonthStatsPerCapitaQueryVo::getDay, item -> item));
        if (CollUtil.isEmpty(perCapitaQueryVoList)) {
            return dayList.stream().map(item -> MonthStatsPerCapitaVo.builder().day(item.format(dayFormatter)).avgHours(BigDecimal.ZERO).build()).collect(Collectors.toList());
        }
        return dayList.stream().map(item -> MonthStatsPerCapitaVo.builder().day(item.format(dayFormatter)).avgHours(perCapitaMap.containsKey(item) ? perCapitaMap.get(item).getAvgHours() : BigDecimal.ZERO).build()).collect(Collectors.toList());
    }

    @Override
    public List<MonthStatsDailySituationVo> getAttendanceDailySituation(MonthStatsDetailsDto dto) {
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        MonthStatsDailySituationQueryVo dailySituation = this.baseMapper.getAttendanceDailySituation(dto.getGroupIds(), startDate, endDate);
        return DayStatisticsUtils.createDailySituationDirectory(dailySituation);
    }

    @Override
    public List<MonthStatsHoursRankingVo> getAttendanceHoursRanking(MonthStatsDetailsDto dto) {
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        List<MonthStatsHoursRankingQueryVo> hoursRankingQueryVoList = this.baseMapper.getAttendanceHoursRanking(dto.getGroupIds(), startDate, endDate);
        if (CollUtil.isEmpty(hoursRankingQueryVoList)) {
            return List.of();
        }
        //查询用户信息
        List<String> userIdList = hoursRankingQueryVoList.stream().map(MonthStatsHoursRankingQueryVo::getUserId).collect(Collectors.toList());
        ActionResult<List<UserBoundVO>> getUserResult = v2UserApi.getAllUserInfoBatch(userIdList, UserProvider.getUser().getTenantId());
        if (Objects.isNull(getUserResult) || CollUtil.isEmpty(getUserResult.getData())) {
            log.info("未查询到用户花名册数据:{}", userIdList);
        }
        List<UserBoundVO> staffRosterDtoList = getUserResult.getData();
        Map<String, UserBoundVO> staffRosterMap = staffRosterDtoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        return hoursRankingQueryVoList.stream().map(item -> MonthStatsHoursRankingVo.builder().userName(staffRosterMap.containsKey(item.getUserId()) ? staffRosterMap.get(item.getUserId()).getName() : "未查到用户信息").hours(item.getHours()).build()).collect(Collectors.toList());
    }

    @Override
    public List<MonthStatsFullSituationVo> getAttendanceFullSituation(MonthStatsDetailsDto dto) {
        //获取月份日期列表
        List<LocalDate> dayList = DayStatisticsUtils.getDatesForMonth(dto.getMonth(), "yyyyMM");
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("MM.dd", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        List<MonthStatsFullSituationQueryVo> fullSituationQueryVoList = this.baseMapper.getAttendanceFullSituation(dto.getGroupIds(), startDate, endDate);
        Map<LocalDate, MonthStatsFullSituationQueryVo> perCapitaMap = fullSituationQueryVoList.stream().collect(Collectors.toMap(MonthStatsFullSituationQueryVo::getDay, item -> item));
        if (CollUtil.isEmpty(fullSituationQueryVoList)) {
            return dayList.stream().map(item -> MonthStatsFullSituationVo.builder().day(item.format(dayFormatter)).groupUserCount(0).fullCount(0).fullRatio(BigDecimal.ZERO).build()).collect(Collectors.toList());
        }
        return dayList.stream().map(item -> MonthStatsFullSituationVo.builder().day(item.format(dayFormatter)).groupUserCount(perCapitaMap.containsKey(item) ? perCapitaMap.get(item).getGroupUserCount() : 0).fullCount(perCapitaMap.containsKey(item) ? perCapitaMap.get(item).getFullCount() : 0).fullRatio(perCapitaMap.containsKey(item) ? new BigDecimal(perCapitaMap.get(item).getFullCount()).multiply(new BigDecimal(100)).divide(new BigDecimal(perCapitaMap.get(item).getGroupUserCount()), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO).build()).collect(Collectors.toList());
    }

    @Override
    public MonthStatsAbnormalConditionVo getAttendanceAbnormalCondition(MonthStatsDetailsDto dto) {
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        MonthStatsAbnormalConditionQueryVo abnormalConditionQueryVo = this.baseMapper.getAttendanceAbnormalCondition(dto.getGroupIds(), startDate, endDate);
        BigDecimal totalCount = new BigDecimal(abnormalConditionQueryVo.getNormalCount() + abnormalConditionQueryVo.getLateCount() + abnormalConditionQueryVo.getEarlyCount() + abnormalConditionQueryVo.getAbsenceCardCount() + abnormalConditionQueryVo.getAbsenceCount());
        return MonthStatsAbnormalConditionVo.builder().normalCount(abnormalConditionQueryVo.getNormalCount()).normalPercent(abnormalConditionQueryVo.getNormalCount() > 0 ? new BigDecimal(abnormalConditionQueryVo.getNormalCount()).multiply(new BigDecimal(100)).divide(totalCount, 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO).lateCount(abnormalConditionQueryVo.getLateCount()).latePercent(abnormalConditionQueryVo.getLateCount() > 0 ? new BigDecimal(abnormalConditionQueryVo.getLateCount()).multiply(new BigDecimal(100)).divide(totalCount, 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO).earlyCount(abnormalConditionQueryVo.getEarlyCount()).earlyPercent(abnormalConditionQueryVo.getEarlyCount() > 0 ? new BigDecimal(abnormalConditionQueryVo.getEarlyCount()).multiply(new BigDecimal(100)).divide(totalCount, 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO).absenceCardCount(abnormalConditionQueryVo.getAbsenceCardCount()).absenceCardPercent(abnormalConditionQueryVo.getAbsenceCardCount() > 0 ? new BigDecimal(abnormalConditionQueryVo.getAbsenceCardCount()).multiply(new BigDecimal(100)).divide(totalCount, 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO).absenceCount(abnormalConditionQueryVo.getAbsenceCount()).absencePercent(abnormalConditionQueryVo.getAbsenceCount() > 0 ? new BigDecimal(abnormalConditionQueryVo.getAbsenceCount()).multiply(new BigDecimal(100)).divide(totalCount, 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO).build();
    }

    @Override
    public List<MonthStatsOvertimeSituationVo> getAttendanceOvertimeSituation(MonthStatsDetailsDto dto) {
        //获取月份日期列表
        List<LocalDate> dayList = DayStatisticsUtils.getDatesForMonth(dto.getMonth(), "yyyyMM");
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM", Locale.ENGLISH);
        DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("MM.dd", Locale.ENGLISH);
        YearMonth yearMonth = YearMonth.parse(dto.getMonth(), monthFormatter);
        LocalDate startDate = yearMonth.atDay(1), endDate = yearMonth.atEndOfMonth().isAfter(LocalDate.now()) ? LocalDate.now() : yearMonth.atEndOfMonth();
        //获取当前月份加班情况
        List<MonthStatsOvertimeSituationQueryVo> overtimeSituationQueryVoList = this.baseMapper.getAttendanceOvertimeSituation(dto.getGroupIds(), startDate, endDate);
        if (CollUtil.isEmpty(overtimeSituationQueryVoList)) {
            return dayList.stream().map(item -> MonthStatsOvertimeSituationVo.builder().day(item.format(dayFormatter)).overtimeHours(BigDecimal.ZERO).overtimeHoursOnYear(BigDecimal.ZERO).overtimeHoursOnMonth(BigDecimal.ZERO).build()).collect(Collectors.toList());
        }
        Map<LocalDate, MonthStatsOvertimeSituationQueryVo> overtimeMap = overtimeSituationQueryVoList.stream().collect(Collectors.toMap(MonthStatsOvertimeSituationQueryVo::getDay, item -> item));
        //获取同比月份加班情况
        YearMonth onYear = yearMonth.minusYears(1);
        LocalDate onYearStartDate = onYear.atDay(1);
        LocalDate onYearEndDate = onYear.atEndOfMonth();
        List<MonthStatsOvertimeSituationQueryVo> overtimeOnYearList = this.baseMapper.getAttendanceOvertimeSituation(dto.getGroupIds(), onYearStartDate, onYearEndDate);
        Map<LocalDate, MonthStatsOvertimeSituationQueryVo> overtimeOnYearMap = CollUtil.isNotEmpty(overtimeOnYearList) ? overtimeOnYearList.stream().collect(Collectors.toMap(MonthStatsOvertimeSituationQueryVo::getDay, item -> item)) : new HashMap<>();
        //获取环比月份加班情况
        YearMonth onMonth = yearMonth.minusMonths(1);
        LocalDate onMonthStartDate = onMonth.atDay(1);
        LocalDate onMonthEndDate = onMonth.atEndOfMonth();
        List<MonthStatsOvertimeSituationQueryVo> overtimeOnMonthList = this.baseMapper.getAttendanceOvertimeSituation(dto.getGroupIds(), onMonthStartDate, onMonthEndDate);
        Map<LocalDate, MonthStatsOvertimeSituationQueryVo> overtimeOnMonthMap = CollUtil.isNotEmpty(overtimeOnMonthList) ? overtimeOnMonthList.stream().collect(Collectors.toMap(MonthStatsOvertimeSituationQueryVo::getDay, item -> item)) : new HashMap<>();
        return dayList.stream().map(item -> {
            MonthStatsOvertimeSituationVo statsOvertimeSituationVo = MonthStatsOvertimeSituationVo.builder().day(item.format(dayFormatter)).overtimeHours(overtimeMap.containsKey(item) ? overtimeMap.get(item).getOvertimeHours() : BigDecimal.ZERO).overtimeHoursOnYear(BigDecimal.ZERO).overtimeHoursOnMonth(BigDecimal.ZERO).build();
            if (overtimeMap.containsKey(item)) {
                LocalDate onYearItem = item.minusYears(1);
                if (CollUtil.isEmpty(overtimeOnYearMap) || !overtimeOnYearMap.containsKey(onYearItem)) {
                    statsOvertimeSituationVo.setOvertimeHoursOnYear(BigDecimal.ZERO);
                } else {
                    statsOvertimeSituationVo.setOvertimeHoursOnYear(overtimeOnYearMap.get(onYearItem).getOvertimeHours().compareTo(BigDecimal.ZERO) > 0 ? overtimeMap.get(item).getOvertimeHours().subtract(overtimeOnYearMap.get(onYearItem).getOvertimeHours()).multiply(new BigDecimal(100)).divide(overtimeOnYearMap.get(onYearItem).getOvertimeHours(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
                }
                LocalDate onMonthItem = item.minusMonths(1);
                if (CollUtil.isEmpty(overtimeOnMonthMap) || !overtimeOnMonthMap.containsKey(onMonthItem)) {
                    statsOvertimeSituationVo.setOvertimeHoursOnMonth(BigDecimal.ZERO);
                } else {
                    statsOvertimeSituationVo.setOvertimeHoursOnMonth(overtimeOnMonthMap.get(onMonthItem).getOvertimeHours().compareTo(BigDecimal.ZERO) > 0 ? overtimeMap.get(item).getOvertimeHours().subtract(overtimeOnMonthMap.get(onMonthItem).getOvertimeHours()).multiply(new BigDecimal(100)).divide(overtimeOnMonthMap.get(onMonthItem).getOvertimeHours(), 4, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
                }
            }
            return statsOvertimeSituationVo;
        }).collect(Collectors.toList());
    }

    @SneakyThrows
    @Override
    public List<MonthConfirmStatisticsListVo> getConfirmDetailsInfoByMonth(List<String> userIds, String month) {
        DateRangeDto dateRangeDto = new DateRangeDto(month, Boolean.TRUE);
        Date startDate = DateUtil.parseDate(dateRangeDto.getStartDate());
        Date endDate = DateUtil.parseDate(dateRangeDto.getEndDate());
        List<MonthStatisticsQueryVo> pageListVoList = this.baseMapper.getConfirmDetailsInfoByMonth(userIds, startDate, endDate);
        return monthConfirmStatisticsAssemble(month, dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), pageListVoList);
    }

    @Override
    @SneakyThrows
    public PageInfo<MonthSealPageListVo> sealPageList(MonthSealPageListDto dto) {
        DateRangeDto dateRangeDto = new DateRangeDto(dto.getMonth(), Boolean.TRUE);
        //校验是否选择当月
        Assert.isFalse(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM")).equals(dto.getMonth()), "不能选择当月！");
        StatisticsDataQueryDto queryDto = new StatisticsDataQueryDto();
        BeanUtils.copyProperties(dto, queryDto);
        List<String> groupIds = dto.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        queryDto.setGroupIds(groupIds);
        queryDto.setStartDate(dateRangeDto.getStartDate());
        queryDto.setEndDate(dateRangeDto.getEndDate());
        PageInfo<MonthSealPageListVo> pageInfo = new PageInfo<>();
        pageInfo.setPageSize((int) dto.getPageSize());
        pageInfo.setPageNum((int) dto.getCurrentPage());
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(dto.getFilterList(), dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), queryDto.getUserIds(), String.valueOf(0));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return pageInfo;
        }
        if (StringUtil.isNotEmpty(dto.getKeyword())) {
            userBoundVoList = userBoundVoList.stream().filter(item -> (StringUtil.isNotEmpty(item.getPhone()) && item.getPhone().contains(dto.getKeyword())) || (StringUtil.isNotEmpty(item.getName()) && item.getName().contains(dto.getKeyword())) || (StringUtil.isNotEmpty(item.getOrganizeName()) && item.getOrganizeName().contains(dto.getKeyword())) || (StringUtil.isNotEmpty(item.getPositionName()) && item.getPositionName().contains(dto.getKeyword())) || (StringUtil.isNotEmpty(item.getGradeName()) && item.getGradeName().contains(dto.getKeyword()))).collect(Collectors.toList());
        }
        List<String> userIdList = CollUtil.isNotEmpty(userBoundVoList) ? userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList()) : CollUtil.newArrayList();
        if (CollUtil.isEmpty(userIdList)) {
            log.error("未查询到考勤组用户数据");
            return pageInfo;
        }
        Map<String, UserBoundVO> userBoundVoMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        queryDto.setUserIds(userIdList);
        PageHelper.startPage(Math.toIntExact(dto.getCurrentPage()), Math.toIntExact(dto.getPageSize()));
        List<MonthSealPageListVo> pageListVoList = this.baseMapper.sealPageList(queryDto, dto.getSeal());
        if (CollUtil.isEmpty(pageListVoList) || CollUtil.isEmpty(userBoundVoMap)) {
            return pageInfo;
        }
        pageInfo = new PageInfo<>(monthSealPageAssemble(dateRangeDto, pageListVoList, userBoundVoMap));
        pageInfo.setTotal(new PageInfo<>(pageListVoList).getTotal());
        pageInfo.setPageSize((int) dto.getPageSize());
        return pageInfo;
    }

    /**
     * 获取 sealPageListVoList
     * @param dateRangeDto 周期范围
     * @param pageListVoList 用户封账数据
     * @param userBoundVoMap 用户信息
     * @return sealPageListVoList
     */
    private List<MonthSealPageListVo> monthSealPageAssemble(DateRangeDto dateRangeDto, List<MonthSealPageListVo> pageListVoList, Map<String, UserBoundVO> userBoundVoMap) {
        return pageListVoList.stream().peek(item -> {
            //设置用户基本信息
            if (userBoundVoMap.containsKey(item.getUserId())) {
                UserBoundVO staffRosterDto = userBoundVoMap.get(item.getUserId());
                item.setOrgName(StringUtil.isNotEmpty(staffRosterDto.getOrganizeName()) ? staffRosterDto.getOrganizeName() : "");
                item.setPostName(StringUtil.isNotEmpty(staffRosterDto.getPositionName()) ? staffRosterDto.getPositionName() : "");
                item.setUserName(staffRosterDto.getName());
                item.setWorkStatus(staffRosterDto.getWorkStatusEnums().getCode());
                item.setCycle(dateRangeDto.getStartDate() + "~" + dateRangeDto.getEndDate());
            }
        }).collect(Collectors.toList());
    }

    @Override
    @SneakyThrows
    @Transactional
    public Boolean sealSubmit(MonthSealSubmitDto dto) {
        DateRangeDto dateRangeDto = new DateRangeDto(dto.getMonth(), Boolean.TRUE);
        //校验是否选择当月
        Assert.isFalse(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM")).equals(dto.getMonth()), "不能选择当月！");
        //数据量非常大,分批执行数据修改
        List<String> idList = this.listObjs(Wrappers.lambdaQuery(AttendanceDayStatistics.class).in(AttendanceDayStatistics::getUserId, dto.getUserIdList()).between(AttendanceDayStatistics::getDate, dateRangeDto.getStartDate(), dateRangeDto.getEndDate()).select(AttendanceDayStatistics::getId), obj -> (String) obj);
        // 如果idList为空，则直接返回
        if (CollUtil.isEmpty(idList)) {
            return Boolean.TRUE;
        }
        List<List<String>> batches = DayStatisticsUtils.partition(idList, 1000);
        for (List<String> batch : batches) {
            LambdaUpdateWrapper<AttendanceDayStatistics> updateWrapper = Wrappers.lambdaUpdate();
            updateWrapper.in(AttendanceDayStatistics::getId, batch);
            updateWrapper.set(AttendanceDayStatistics::getSeal, 1);
            this.update(updateWrapper);
        }
        // 获取用户出勤换算比
        List<AttendanceUserGroupVo> attendanceUserGroupList = groupService.getAttendanceUserGroup(dto.getUserIdList());
        List<String> groupIds = CollUtil.isNotEmpty(attendanceUserGroupList) ? attendanceUserGroupList.stream().map(AttendanceUserGroupVo::getGroupId).distinct().collect(Collectors.toList()) : CollUtil.newArrayList();
        Map<String, AttendanceBaseSetting> groupBaseSettingMap = baseSettingService.getEnableBaseSettingAll(groupIds);
        Map<String, BigDecimal> ratioMap = new HashMap<>();
        if (CollUtil.isNotEmpty(attendanceUserGroupList)) {
            attendanceUserGroupList.forEach(item -> {
                ratioMap.put(item.getUserId(), groupBaseSettingMap.get(item.getGroupId()).getAttendanceRatio());
            });
        }
        holidayRulesService.processPublicHoliday(dto.getMonth(), dto.getUserIdList(), ratioMap);
        return Boolean.TRUE;
    }

    @Override
    @SneakyThrows
    @Transactional
    public Boolean unSealSubmit(MonthUnSealSubmitDto dto) {
        //校验是否选择当月
        Assert.isFalse(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM")).equals(dto.getMonth()), "不能选择当月！");
        LambdaUpdateWrapper<AttendanceDayStatistics> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(AttendanceDayStatistics::getUserId, dto.getUserId());
        updateWrapper.set(AttendanceDayStatistics::getSeal, dto.getSeal());
        this.update(updateWrapper);
        holidayRulesService.rollbackPublicHoliday(dto.getMonth(), List.of(dto.getUserId()));
        return Boolean.TRUE;
    }

    @Override
    public Map<String, Boolean> selectUserIsSeal(List<String> userIdList, String month) {
        DateRangeDto dateRangeDto = new DateRangeDto(month, Boolean.TRUE);
        List<SealUserInfoDataVo> sealUserInfoDataList = this.baseMapper.selectUserIsSeal(userIdList, dateRangeDto.getStartDate(), dateRangeDto.getEndDate());
        Map<String, SealUserInfoDataVo> sealUserInfoDataMap = CollUtil.isNotEmpty(sealUserInfoDataList) ? sealUserInfoDataList.stream().collect(Collectors.toMap(SealUserInfoDataVo::getUserId, item -> item)) : new HashMap<>();
        return userIdList.stream().collect(Collectors.toMap(item -> item, item -> sealUserInfoDataMap.containsKey(item) && sealUserInfoDataMap.get(item).getSeal() > 0));
    }

    @Override
    public Map<String, Boolean> selectUserIsSealByDateRange(List<String> userIdList, Date start, Date end) {
        if (CollUtil.isEmpty(userIdList)) {
            return new HashMap<>();
        }
        String startStr = jnpf.util.DateUtil.dateToString(start, "yyyy-MM-dd");
        String endStr = jnpf.util.DateUtil.dateToString(end, "yyyy-MM-dd");
        DateRangeDto dateRangeDto = new DateRangeDto(startStr, endStr);
        List<SealUserInfoDataVo> sealUserInfoDataList = this.baseMapper.selectUserIsSeal(userIdList, dateRangeDto.getStartDate(), dateRangeDto.getEndDate());
        Map<String, SealUserInfoDataVo> sealUserInfoDataMap = CollUtil.isNotEmpty(sealUserInfoDataList) ? sealUserInfoDataList.stream().collect(Collectors.toMap(SealUserInfoDataVo::getUserId, item -> item)) : new HashMap<>();
        return userIdList.stream().collect(Collectors.toMap(item -> item, item -> sealUserInfoDataMap.containsKey(item) && sealUserInfoDataMap.get(item).getSeal() > 0));
    }

    @Override
    public Map<String, Set<String>> selectUserDailySealByDateRange(List<String> userIdList, Date start, Date end) {
        if (CollUtil.isEmpty(userIdList) || start == null || end == null) {
            return new HashMap<>();
        }
        String startStr = jnpf.util.DateUtil.dateToString(start, "yyyy-MM-dd");
        String endStr = jnpf.util.DateUtil.dateToString(end, "yyyy-MM-dd");
        DateRangeDto dateRangeDto = new DateRangeDto(startStr, endStr);
        List<UserDailySealVo> list = this.baseMapper.selectUserDailySeal(userIdList, dateRangeDto.getStartDate(), dateRangeDto.getEndDate());
        if (CollUtil.isEmpty(list)) {
            return new HashMap<>();
        }
        return list.stream()
                .filter(vo -> vo != null && vo.getUserId() != null && vo.getDate() != null)
                .collect(Collectors.groupingBy(
                        UserDailySealVo::getUserId,
                        Collectors.mapping(UserDailySealVo::getDate, Collectors.toSet())
                ));
    }

    @Override
    public Map<String, SalaryAttendanceSupportVo> salaryAttendanceSupport(SalaryAttendanceSupportDto dto) {
        List<SalaryAttendanceSupportQuery> supportVoList = this.baseMapper.salaryAttendanceSupport(dto);
        Map<String, SalaryAttendanceSupportVo> salaryUserDataMap = initSalaryUserData(dto, new HashMap<>());
        DateRangeDto dateRangeDto = new DateRangeDto(dto.getStartDate(), dto.getEndDate());
        Date startDate = jnpf.util.DateUtil.stringToDate(dateRangeDto.getStartDate());
        Date endDate = jnpf.util.DateUtil.stringToDate(dateRangeDto.getEndDate());
        // 获取用户出勤换算比
        List<AttendanceGroupUser> attendanceUserGroupList = userService.getAttendanceGroupUsersOfSecondment(startDate, endDate, dto.getUserIds(), null);
        List<String> groupIds = CollUtil.isNotEmpty(supportVoList) ? supportVoList.stream().map(SalaryAttendanceSupportQuery::getGroupId).distinct().collect(Collectors.toList()) : CollUtil.newArrayList();
        if (CollUtil.isEmpty(attendanceUserGroupList) && CollUtil.isEmpty(groupIds)) {
            return salaryUserDataMap;
        }
        if (CollUtil.isNotEmpty(attendanceUserGroupList)) {
            groupIds.addAll(attendanceUserGroupList.stream().map(AttendanceGroupUser::getGroupId).distinct().collect(Collectors.toList()));
        }
        // 查询考勤组信息
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(groupIds);
        if (CollUtil.isEmpty(groupList)) {
            return salaryUserDataMap;
        }
        Map<String, AttendanceGroup> groupMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, item -> item));
        Map<String, List<SalaryAttendanceSupportQuery>> userStaMap = supportVoList.stream().collect(Collectors.groupingBy(SalaryAttendanceSupportQuery::getUserId));
        // 用户的所有节假日
        Map<String, List<AttendanceFestivalRules>> autoSchedulingDaysMap = festivalRulesService.batchGetFestivalRulesByUserIds(startDate, endDate, dto.getUserIds());
        // 自动排班天数
        Map<String, Integer> autoSchedulingDaysStaMap = DayStatisticsUtils.getUserAutoSchedulingDaysStaMap(startDate, endDate, autoSchedulingDaysMap);
        // 法定节假日天数
        Map<String, Map<String, HolidaysTypeJsonData1>> legalHolidaysStaMap = DayStatisticsUtils.getLegalHolidaysStaMap(startDate, endDate, autoSchedulingDaysMap);
        // 用户的节假日天数
        Map<String, Set<Date>> holidayDaysStaMapMap = DayStatisticsUtils.getUserHolidayDaysStaMapMap(startDate, endDate, autoSchedulingDaysMap);
        //获取公休转存休天数
        List<PublicHolidayTransferListVo> publicHolidayList = publicHolidayRulesMapper.getPublicHolidayTransferList(DayStatisticsUtils.getMonthFromDate(startDate), dto.getUserIds());
        Map<String, PublicHolidayTransferListVo> publicHolidayTransferMap = CollUtil.isNotEmpty(publicHolidayList) ? publicHolidayList.stream().collect(Collectors.toMap(PublicHolidayTransferListVo::getUserId, item -> item)) : new HashMap<>();
        // 获取加班算薪加班小时数(不计算存休-结算薪酬)
        Map<String, OvertimeSalaryHoursVo> overtimeSalaryMap = enableBalanceService.getOvertimeSalary(dto.getStartDate(), dto.getEndDate(), dto.getUserIds());
        // 加班存休天数以及剩余天数
        List<OvertimeBalanceModel> overtimeBalanceInfo = balanceRecordMapper.getOvertimeBalanceInfo(startDate, endDate, dto.getUserIds());
        Map<String, OvertimeBalanceModel> overtimeBalanceMap = CollUtil.isNotEmpty(overtimeBalanceInfo) ? overtimeBalanceInfo.stream().collect(Collectors.toMap(OvertimeBalanceModel::getUserId, item -> item)) : new HashMap<>();
        // 存休剩余天数
        List<SurplusDaysModel> surplusDaysInfo = balanceRecordMapper.getSurplusDaysInfo(dto.getUserIds());
        Map<String, BigDecimal> surplusDaysMap = CollUtil.isNotEmpty(surplusDaysInfo) ? surplusDaysInfo.stream().collect(Collectors.toMap(SurplusDaysModel::getUserId, SurplusDaysModel::getSurplusDays)) : new HashMap<>();
        Map<String, AttendanceBaseSetting> groupBaseSettingMap = baseSettingService.getEnableBaseSettingAll(groupIds);
        Map<String, BigDecimal> ratioMap = new HashMap<>();
        if (CollUtil.isNotEmpty(attendanceUserGroupList)) {
            attendanceUserGroupList.forEach(item -> {
                ratioMap.put(item.getUserId(), groupBaseSettingMap.containsKey(item.getGroupId()) && Objects.nonNull(groupBaseSettingMap.get(item.getGroupId())) ? groupBaseSettingMap.get(item.getGroupId()).getAttendanceRatio() : BigDecimal.ZERO);
            });
        }
        // 获取所有请假类型
        List<AttendanceLeaveType> leaveRulesLiat = leaveTypeService.list();
        Map<String, AttendanceLeaveType> leaveTypeMap = leaveRulesLiat.stream().collect(Collectors.toMap(AttendanceLeaveType::getId, item -> item));
        // 自定义假期余额列表
        List<LeaveBalanceInfoModel> holidayBalanceInfo = balanceRecordMapper.getLeaveBalanceInfo(startDate, endDate, dto.getUserIds());
        // 自定义请假审批记录
        List<String> leaveIds = supportVoList.stream().map(SalaryAttendanceSupportQuery::getLeaveBatchNumber).filter(Objects::nonNull).flatMap(batchNumber -> DayStatisticsUtils.parseBatchNumbers(batchNumber).stream()).distinct().collect(Collectors.toList());
        List<LeaveApprovalModel> leaveApprovalInfo = CollUtil.isNotEmpty(leaveIds) ? balanceRecordMapper.getLeaveApprovalInfo(leaveIds, dto.getUserIds()) : new ArrayList<>();
        // 请假天数数据
        Map<String, Map<String, LeaveTypeJsonData>> leaveTypeMapMap = DayStatisticsUtils.buildLeaveMap(leaveApprovalInfo, leaveTypeMap, supportVoList);
        // 请假余额数据
        Map<String, Map<String, LeaveTypeJsonData>> buildLeaveBalanceMap = DayStatisticsUtils.buildLeaveBalanceMap(holidayBalanceInfo, leaveTypeMap, ratioMap);
        // 请假抵扣天数数据
        Map<String, Map<String, LeaveTypeJsonData>> leaveDeductMap = DayStatisticsUtils.buildLeaveDeductMap(leaveApprovalInfo, leaveTypeMap, startDate, endDate);
        // 请假扣薪数据
        Map<String, Map<String, LeaveTypeJsonData>> leaveDeductWagesMap = DayStatisticsUtils.buildLeaveDeductWagesMap(leaveApprovalInfo, leaveTypeMap, startDate, endDate);
        // 节日类型天数
        Map<String, Map<String, HolidaysTypeJsonData1>> holidayLeaveTypeMap = DayStatisticsUtils.buildHolidayTypeMap(startDate, endDate, autoSchedulingDaysMap, ratioMap);
        // 节假日加班天数
        List<OvertimeHolidaysInfoModel> overtimeHolidaysInfoList = balanceRecordMapper.getOvertimeHolidaysInfo(startDate, endDate, dto.getUserIds());
        // 加班算薪json
        List<OvertimeSalaryHoursJsonVo> overtimeSalaryJson = enableBalanceService.getOvertimeSalaryJson(dto.getStartDate(), dto.getEndDate(), dto.getUserIds());
        Map<String, Map<String, HolidaysTypeJsonData1>> overtimeHolidaysMap = DayStatisticsUtils.buildOvertimeHolidaysMap(overtimeHolidaysInfoList, overtimeSalaryJson, ratioMap);
        for (Map.Entry<String, SalaryAttendanceSupportVo> entry : salaryUserDataMap.entrySet()) {
            SalaryAttendanceSupportVo supportVo = entry.getValue();
            List<SalaryAttendanceSupportQuery> dataList = userStaMap.containsKey(entry.getKey()) ? userStaMap.get(entry.getKey()) : new ArrayList<>();
            List<GroupInfoModel> groupInfoList = dataList.stream().map(item -> {
                GroupInfoModel groupInfoModel = new GroupInfoModel();
                groupInfoModel.setGroupName(groupMap.containsKey(item.getGroupId()) ? groupMap.get(item.getGroupId()).getGroupName() : "");
                groupInfoModel.setAttendanceRatio(item.getAttendanceRatio());
                return groupInfoModel;
            }).collect(Collectors.toList());
            supportVo.setGroupInfoList(groupInfoList);
            supportVo.setShouldAttendDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getShouldAttendDays));
            supportVo.setShouldAttendHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getShouldAttendPayrollHours));
            supportVo.setActualAttendDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getEffectiveAttendDays));
            supportVo.setActualAttendHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getEffectiveAttendPayrollHours));
            if (holidayDaysStaMapMap.containsKey(entry.getKey())) {
                List<SalarySupportDayStaModel> effectiveAttendDayList = DayStatisticsUtils.getCustomDataList(dataList.stream().map(SalaryAttendanceSupportQuery::getEffectiveAttendDayList).filter(CollUtil::isNotEmpty).collect(Collectors.toList()), SalarySupportDayStaModel.class);
                Set<Date> dates = holidayDaysStaMapMap.get(entry.getKey());
                supportVo.setHolidaysSchedulingDays(effectiveAttendDayList.stream().filter(item -> dates.contains(item.getDay())).map(SalarySupportDayStaModel::getDays).collect(Collectors.toList()).stream().reduce(BigDecimal.ZERO, BigDecimal::add));
                supportVo.setHolidaysSchedulingHours(effectiveAttendDayList.stream().filter(item -> dates.contains(item.getDay())).map(SalarySupportDayStaModel::getHours).collect(Collectors.toList()).stream().reduce(BigDecimal.ZERO, BigDecimal::add));
            }
            supportVo.setHolidaysAutoPaid(autoSchedulingDaysStaMap.getOrDefault(entry.getKey(), 0));
            supportVo.setHolidaysMap(legalHolidaysStaMap.getOrDefault(entry.getKey(), new HashMap<>()));
            supportVo.setPublicHolidays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getPublicHolidaysDays));
            supportVo.setPublicHolidaysHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getPublicHolidaysPayrollHours));
            supportVo.setPublicHolidaysOvertimeDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getPublicHolidaysOvertimeDays));
            supportVo.setPublicHolidaysOvertimeHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getPublicHolidaysOvertimeHours));
            // 实公休天数 = 公休排班天数 - 公休加班天数(小时数也是一样的)
            supportVo.setActualPublicHolidays(DayStatisticsUtils.calculateSubtract(supportVo.getPublicHolidays(), supportVo.getPublicHolidaysOvertimeDays()));
            supportVo.setActualPublicHolidaysHours(DayStatisticsUtils.calculateSubtract(supportVo.getPublicHolidaysHours(), supportVo.getPublicHolidaysOvertimeHours()));
            PublicHolidayTransferListVo transferListVo = publicHolidayTransferMap.getOrDefault(entry.getKey(), new PublicHolidayTransferListVo(entry.getKey(), BigDecimal.ZERO, BigDecimal.ZERO));
            supportVo.setPublicHolidaysTransfer(transferListVo.getSurplusDays().stripTrailingZeros());
            supportVo.setPublicHolidaysTransferHours(transferListVo.getSurplusDays().multiply(transferListVo.getAttendanceRatio()).stripTrailingZeros());
            OvertimeBalanceModel balanceModel = overtimeBalanceMap.getOrDefault(entry.getKey(), new OvertimeBalanceModel(entry.getKey(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
            supportVo.setPublicHolidaysSurplus(surplusDaysMap.getOrDefault(entry.getKey(), BigDecimal.ZERO).stripTrailingZeros());
            // 出勤换算比
            BigDecimal bigDecimal = ratioMap.getOrDefault(entry.getKey(), BigDecimal.ZERO);
            supportVo.setPublicHolidaysSurplusHours(surplusDaysMap.getOrDefault(entry.getKey(), BigDecimal.ZERO).multiply(bigDecimal).stripTrailingZeros());
            supportVo.setLateTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getLateTimes));
            supportVo.setLateMinutes(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getLateMinutes));
            List<SalarySupportDayModel> lateDayMinutesList = DayStatisticsUtils.getCustomDataList(dataList.stream().map(SalaryAttendanceSupportQuery::getLateDayMinutesList).filter(CollUtil::isNotEmpty).collect(Collectors.toList()), SalarySupportDayModel.class);
            Map<String, BigDecimal> dateLateDayMinutesMap = lateDayMinutesList.stream().collect(Collectors.groupingBy(SalarySupportDayModel::getDay, Collectors.mapping(SalarySupportDayModel::getMinutes, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
            Map<String, BigDecimal> lateDayMinutesMap = CollUtil.isNotEmpty(dateLateDayMinutesMap) ? dateLateDayMinutesMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) : new HashMap<>();
            supportVo.setLateDayMinutesMap(lateDayMinutesMap);
            supportVo.setEarlyLeaveTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getEarlyLeaveTimes));
            supportVo.setEarlyLeaveMinutes(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getEarlyLeaveMinutes));
            List<SalarySupportDayModel> earlyLeaveDayMinutesList = DayStatisticsUtils.getCustomDataList(dataList.stream().map(SalaryAttendanceSupportQuery::getEarlyLeaveDayMinutesList).filter(CollUtil::isNotEmpty).collect(Collectors.toList()), SalarySupportDayModel.class);
            Map<String, BigDecimal> dateEarlyLeaveDayMinutesMap = earlyLeaveDayMinutesList.stream().collect(Collectors.groupingBy(SalarySupportDayModel::getDay, Collectors.mapping(SalarySupportDayModel::getMinutes, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
            Map<String, BigDecimal> earlyLeaveDayMinutesMap = CollUtil.isNotEmpty(dateEarlyLeaveDayMinutesMap) ? dateEarlyLeaveDayMinutesMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) : new HashMap<>();
            supportVo.setEarlyLeaveDayMinutesMap(earlyLeaveDayMinutesMap);
            supportVo.setAbsenceCardTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getAbsenceCardTimes));
            supportVo.setAbsenceTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getAbsenceTimes));
            supportVo.setAbsenceDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getAbsenceDays));
            supportVo.setAbsenceHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getAbsencePayrollHours));
            supportVo.setMakeUpCardTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getMakeUpCardTimes));
            supportVo.setOutworkTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getOutworkTimes));
            supportVo.setOutworkDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getOutworkDays));
            supportVo.setOutworkHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getOutworkPayrollHours));
            supportVo.setOutDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getOutDays));
            supportVo.setOutHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getOutHours));
            supportVo.setBusDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getBusDays));
            supportVo.setBusHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getBusHours));
            supportVo.setOvertimeTimes(DayStatisticsUtils.calculateSumToInt(dataList, SalaryAttendanceSupportQuery::getOvertimeTimes));
            supportVo.setOvertimeDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getOvertimeDays));
            supportVo.setOvertimeHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getOvertimeHours));
            OvertimeSalaryHoursVo overtimeSalaryHoursVo = overtimeSalaryMap.getOrDefault(entry.getKey(), new OvertimeSalaryHoursVo(entry.getKey(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
            supportVo.setWeekdayOvertimeDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getWeekdayOvertimeDays));
            supportVo.setWeekdayOvertimeHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getWeekdayOvertimeHours));
            supportVo.setWeekdayOvertimeSalaryHours(overtimeSalaryHoursVo.getWeekdayOvertimeSalaryDays().multiply(bigDecimal).stripTrailingZeros());
            supportVo.setWeekdayOvertimeTransferHours(balanceModel.getWeekdayOvertimeDays().multiply(bigDecimal).stripTrailingZeros());
            supportVo.setHolidaysOvertimeDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getHolidaysOvertimeDays));
            supportVo.setHolidaysOvertimeHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getHolidaysOvertimeHours));
            supportVo.setHolidaysOvertimeSalaryHours(overtimeSalaryHoursVo.getHolidaysOvertimeSalaryDays().multiply(bigDecimal).stripTrailingZeros());
            supportVo.setHolidaysOvertimeTransferHours(balanceModel.getHolidaysOvertimeDays().multiply(bigDecimal).stripTrailingZeros());
            supportVo.setPublicHolidaysOvertimeSalaryHours(overtimeSalaryHoursVo.getPublicHolidaysOvertimeSalaryDays().multiply(bigDecimal).stripTrailingZeros());
            supportVo.setPublicHolidaysOvertimeTransferHours(balanceModel.getPublicHolidaysOvertimeDays().multiply(bigDecimal).stripTrailingZeros());
            supportVo.setLeaveDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getLeaveDays));
            supportVo.setLeaveHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getLeavePayrollHours));
            supportVo.setCompensationDays(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getCompensationDays));
            supportVo.setCompensationHours(DayStatisticsUtils.calculateSum(dataList, SalaryAttendanceSupportQuery::getCompensationPayrollHours));
            supportVo.setCustomLeaveMap(leaveTypeMapMap.getOrDefault(entry.getKey(), new HashMap<>()));
            supportVo.setCustomLeaveSurplusMap(buildLeaveBalanceMap.getOrDefault(entry.getKey(), new HashMap<>()));
            supportVo.setCustomLeaveDeductionMap(leaveDeductMap.getOrDefault(entry.getKey(), new HashMap<>()));
            supportVo.setCustomLeavePayMap(leaveDeductWagesMap.getOrDefault(entry.getKey(), new HashMap<>()));
            supportVo.setHolidaysTypeMap(holidayLeaveTypeMap.getOrDefault(entry.getKey(), new HashMap<>()));
            supportVo.setHolidaysTypeOvertimeMap(overtimeHolidaysMap.getOrDefault(entry.getKey(), new HashMap<>()));
        }
        return salaryUserDataMap;
    }

    @Override
    public List<DayStatisticsPageListVo> attendanceStaList(SalaryAttendanceSupportDto dto) {
        List<DayStatisticsPageListVo> staListVoList = new ArrayList<>();
        DayStatisticsDataPageListQueryDto queryDto = new DayStatisticsDataPageListQueryDto();
        queryDto.setUserIds(dto.getUserIds());
        queryDto.setQueryType(1);
        queryDto.setStartDate(dto.getStartDate());
        queryDto.setEndDate(dto.getEndDate());
        // 查询用户信息
        ActionResult<List<UserBoundVO>> getUserResult = v2UserApi.getAllUserInfoBatch(dto.getUserIds(), UserProvider.getUser().getTenantId());
        if (Objects.isNull(getUserResult) || CollUtil.isEmpty(getUserResult.getData())) {
            log.info("未查询到用户数据:{}", dto.getUserIds());
            return staListVoList;
        }
        List<UserBoundVO> staffRosterDtoList = getUserResult.getData();
        Map<String, UserBoundVO> userBoundVoMap = staffRosterDtoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        List<DayStatisticsQueryVo> pageListVoList = this.baseMapper.getDayPageList(queryDto);
        if (CollUtil.isEmpty(pageListVoList)) {
            return staListVoList;
        }
        List<String> groupIds = pageListVoList.stream().map(DayStatisticsQueryVo::getGroupId).distinct().collect(Collectors.toList());
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(groupIds);
        Map<String, AttendanceGroup> groupMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        return dayStatisticsAssemble(pageListVoList, groupMap, userBoundVoMap);
    }

    @Override
    public List<DayStatisticsVo> getAttendanceDayStaList(DayStatisticsDto dto) {
        LambdaQueryWrapper<AttendanceGroup> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.in(AttendanceGroup::getOrgId, dto.getStoreIds());
        queryWrapper.eq(AttendanceGroup::getDeleteMark, 0);
        List<AttendanceGroup> groupList = this.groupService.list(queryWrapper);
        if (CollUtil.isEmpty(groupList)) {
            return dto.getStoreIds().stream().map(storeId -> DayStatisticsVo.builder().storeId(storeId).attendCount(0).build()).collect(Collectors.toList());
        }
        Map<String, String> groupIdMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getOrgId, AttendanceGroup::getId));
        DayStatisticsDataPageListQueryDto queryDto = new DayStatisticsDataPageListQueryDto();
        queryDto.setGroupIds(groupList.stream().map(AttendanceGroup::getId).distinct().collect(Collectors.toList()));
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        queryDto.setStartDate(dto.getDay().format(dateTimeFormatter));
        queryDto.setEndDate(dto.getDay().format(dateTimeFormatter));
        queryDto.setQueryType(1);
        List<DayStatisticsQueryVo> pageListVoList = this.baseMapper.getDayPageList(queryDto);
        if (CollUtil.isNotEmpty(pageListVoList)) {
            pageListVoList.removeIf(item -> item.getEffectiveAttendHours().compareTo(BigDecimal.ZERO) <= 0);
        }
        Map<String, List<DayStatisticsQueryVo>> storeDayStaMap = CollUtil.isNotEmpty(pageListVoList) ? pageListVoList.stream().collect(Collectors.groupingBy(DayStatisticsQueryVo::getGroupId)) : new HashMap<>();
        return dto.getStoreIds().stream().map(item -> DayStatisticsVo.builder().storeId(item).attendCount(Math.toIntExact(storeDayStaMap.getOrDefault(groupIdMap.get(item), Lists.newArrayList()).stream().map(DayStatisticsQueryVo::getUserId).distinct().count())).build()).collect(Collectors.toList());
    }

    @Override
    public Map<String, List<DateDimensionsRangeVo>> getDimensionsAttendanceCountMap(DimensionsAttendanceCountDto dto) {
        List<DateDimensionsRangeVo> dimensionsRangeVoList = Stream.of(CompareTypeEnums.values()).map(typeEnums -> new DateDimensionsRangeVo(typeEnums, 0)).collect(Collectors.toList());
        LambdaQueryWrapper<AttendanceGroup> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.in(AttendanceGroup::getOrgId, dto.getStoreIds());
        queryWrapper.eq(AttendanceGroup::getDeleteMark, 0);
        List<AttendanceGroup> groupList = this.groupService.list(queryWrapper);
        if (CollUtil.isEmpty(groupList)) {
            return dto.getStoreIds().stream().collect(Collectors.toMap(a -> a, a -> dimensionsRangeVoList));
        }
        Map<String, String> groupIdMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getOrgId, AttendanceGroup::getId));
        List<AttendanceDimensionVo> dimensionVoList = this.baseMapper.getDimensionsAttendanceCountList(new ArrayList<>(groupIdMap.values()), dto.getDimensionsRangeList());
        Map<String, List<AttendanceDimensionVo>> groupMapList = dimensionVoList.stream().collect(Collectors.groupingBy(AttendanceDimensionVo::getGroupId));
        return dto.getStoreIds().stream().collect(Collectors.toMap(a -> a, id -> {
            List<DateDimensionsRangeVo> resultVoList = Stream.of(CompareTypeEnums.values()).map(typeEnums -> new DateDimensionsRangeVo(typeEnums, 0)).collect(Collectors.toList());
            if (!groupMapList.containsKey(groupIdMap.get(id))) {
                return resultVoList;
            }
            List<AttendanceDimensionVo> dimensionVos = groupMapList.get(groupIdMap.get(id));
            resultVoList.forEach(item -> {
                if (dimensionVos.stream().noneMatch(dimensionVo -> dimensionVo.getDimensionType().equals(item.getTypeEnums().getValue()))) {
                    return;
                }
                AttendanceDimensionVo dimensionVo1 = dimensionVos.stream().filter(dimensionVo -> dimensionVo.getDimensionType().equals(item.getTypeEnums().getValue())).findFirst().orElse(null);
                item.setCount(Optional.ofNullable(dimensionVo1).map(AttendanceDimensionVo::getPersonCount).orElse(0));
            });
            return resultVoList;
        }, (k1, k2) -> k1));
    }

    @Override
    public Map<String, Map<Date, Integer>> getDimensionsAttendanceDayCountMap(DimensionsAttendanceDayCountDto dto) {
        Map<Date, Integer> dimensionsRangeVoList = new HashMap<>();
        LambdaQueryWrapper<AttendanceGroup> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.in(AttendanceGroup::getOrgId, dto.getStoreIds());
        queryWrapper.eq(AttendanceGroup::getDeleteMark, 0);
        List<AttendanceGroup> groupList = this.groupService.list(queryWrapper);
        if (CollUtil.isEmpty(groupList)) {
            return dto.getStoreIds().stream().collect(Collectors.toMap(a -> a, a -> dimensionsRangeVoList));
        }
        Map<String, String> groupIdMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getOrgId, AttendanceGroup::getId));
        List<AttendanceDimensionDayVo> dimensionVoDayList = this.baseMapper.getDimensionsAttendanceDayCountList(new ArrayList<>(groupIdMap.values()), dto.getStartDate(), dto.getEndDate());
        Map<String, List<AttendanceDimensionDayVo>> groupMapList = dimensionVoDayList.stream().collect(Collectors.groupingBy(AttendanceDimensionDayVo::getGroupId));
        return dto.getStoreIds().stream().collect(Collectors.toMap(a -> a, id -> {
            Map<Date, Integer> resultVoList = new HashMap<>();
            if (!groupMapList.containsKey(groupIdMap.get(id))) {
                return resultVoList;
            }
            List<AttendanceDimensionDayVo> dimensionVos = groupMapList.get(groupIdMap.get(id));
            resultVoList = dimensionVos.stream().collect(Collectors.toMap(AttendanceDimensionDayVo::getDay, AttendanceDimensionDayVo::getPersonCount));
            return resultVoList;
        }, (k1, k2) -> k1));
    }

    @Override
    public PageInfo<DayPayrollStatisticsPageListVo> getDayPayrollPageList(DayPayrollStatisticsPageListDto req) {
        DayStatisticsDataPageListQueryDto queryDto = new DayStatisticsDataPageListQueryDto();
        BeanUtils.copyProperties(req, queryDto);
        PageInfo<DayPayrollStatisticsPageListVo> pageInfo = new PageInfo<>();
        pageInfo.setPageSize((int) req.getPageSize());
        pageInfo.setPageNum((int) req.getCurrentPage());
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), req.getStartDate(), req.getEndDate(), req.getUserIds(), req.getWorkStatus());
        Map<String, UserBoundVO> userBoundVoMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return pageInfo;
        }
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        //获取考勤组信息
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(groupIds);
        if (CollUtil.isEmpty(groupList)) {
            log.info("未查询到考勤组数据:{}", groupIds);
            return pageInfo;
        }
        Map<String, AttendanceGroup> groupMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        queryDto.setUserIds(userIds);
        queryDto.setGroupIds(groupIds);
        PageHelper.startPage(Math.toIntExact(req.getCurrentPage()), Math.toIntExact(req.getPageSize()));
        List<DayPayrollStatisticsQueryVo> pageListVoList = this.baseMapper.getDayPayrollPageList(queryDto);
        if (CollUtil.isEmpty(pageListVoList)) {
            return pageInfo;
        }
        pageInfo = new PageInfo<>(dayPayrollStatisticsAssemble(pageListVoList, groupMap, userBoundVoMap));
        pageInfo.setTotal(new PageInfo<>(pageListVoList).getTotal());
        pageInfo.setPageSize(Math.toIntExact(req.getPageSize()));
        pageInfo.setPageNum(Math.toIntExact(req.getCurrentPage()));
        return pageInfo;
    }

    /**
     * 计薪日统计数据封装
     *
     * @param statisticsQueryVoList 日统计数据
     * @param groupMap              考勤数据
     * @param staffRosterMap        用户花名册数据
     * @return List<DayStatisticsPageListVo>
     */
    private List<DayPayrollStatisticsPageListVo> dayPayrollStatisticsAssemble(List<DayPayrollStatisticsQueryVo> statisticsQueryVoList, Map<String, AttendanceGroup> groupMap, Map<String, UserBoundVO> staffRosterMap) {
        Map<String, List<DayStatisticsShiftsJsonVo>> shiftsJsonMap = new HashMap<>();
        List<String> shiftIdList = new ArrayList<>();
        for (DayPayrollStatisticsQueryVo item : statisticsQueryVoList) {
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = parseArray(item.getShiftsJson(), DayStatisticsShiftsJsonVo.class);
            if (CollUtil.isNotEmpty(shiftsJsonVoList)) {
                shiftIdList.addAll(shiftsJsonVoList.stream().map(DayStatisticsShiftsJsonVo::getShiftId).collect(Collectors.toList()));
            }
            shiftsJsonMap.put(item.getId(), CollUtil.isNotEmpty(shiftsJsonVoList) ? shiftsJsonVoList : new ArrayList<>());
        }
        if (CollUtil.isNotEmpty(shiftIdList)) {
            shiftIdList = shiftIdList.stream().distinct().collect(Collectors.toList());
        }
        Map<String, AttendanceShiftNameEntity> shiftNameEntityMap = CollUtil.isEmpty(shiftIdList) ? new HashMap<>() : dailyRuleService1.getShiftByShiftIds(shiftIdList);
        return statisticsQueryVoList.stream().map(item -> {
            DayPayrollStatisticsPageListVo dayStatistics = DayPayrollStatisticsPageListVo.builder().build();
            BeanUtils.copyProperties(item, dayStatistics);
            if (staffRosterMap.containsKey(item.getUserId())) {
                UserBoundVO staffRosterDto = staffRosterMap.get(item.getUserId());
                dayStatistics.setDeptName(StringUtil.isNotEmpty(staffRosterDto.getOrganizeName()) ? staffRosterDto.getOrganizeName() : "");
                dayStatistics.setPostName(StringUtil.isNotEmpty(staffRosterDto.getPositionName()) ? staffRosterDto.getPositionName() : "");
                dayStatistics.setUserName(StringUtil.isNotEmpty(staffRosterDto.getName()) ? staffRosterDto.getName() : "");
                dayStatistics.setWorkStatus(Objects.nonNull(staffRosterDto.getWorkStatusEnums()) ? staffRosterDto.getWorkStatusEnums().getCode() : null);
                StatisticsEnumUtil.WorkStatusEnum workStatusEnum = StatisticsEnumUtil.WorkStatusEnum.getWorkStatusEnum(dayStatistics.getWorkStatus());
                dayStatistics.setWorkStatusStr(Objects.nonNull(workStatusEnum) ? workStatusEnum.getMsg() : "");
            }
            // 计算各类请假数据
            dayStatistics.setCustomLeaveList(DayStatisticsUtils.getDayCustomLeaveList(item.getCustomLeaveList()));
            dayStatistics.setGroupName(groupMap.containsKey(item.getGroupId()) ? groupMap.get(item.getGroupId()).getGroupName() : null);
            dayStatistics.setDate(DateUtil.formatDate(item.getDate()));
            //处理班次、班次时间、打卡时间
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = shiftsJsonMap.containsKey(item.getId()) ? shiftsJsonMap.get(item.getId()) : Lists.newArrayList();
            List<SchedulesItemVo> collect = getSchedulesItemVos(shiftNameEntityMap, shiftsJsonVoList);
            dayStatistics.setShiftList(collect);
            dayStatistics.setShiftStr(CollUtil.isNotEmpty(collect) ? StringUtil.join(collect.stream().map(SchedulesItemVo::getShortName).filter(StringUtil::isNotBlank).distinct().collect(Collectors.toList()), "|") : "");
            //处理班次时间
            List<String> shiftTimeList = new ArrayList<>();
            boolean containsOrdinary = shiftsJsonVoList.stream().anyMatch(json -> Objects.equals(AttendanceTypeEnum.ORDINARY.getCode(), json.getAttendanceType()));
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoFilterList = shiftsJsonVoList.stream().filter(m -> m.getAttendanceType().equals(AttendanceTypeEnum.ORDINARY.getCode()) || m.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode()) || (!containsOrdinary && AttendanceTypeEnum.LEAVE.getCode().equals(m.getAttendanceType()))).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(shiftsJsonVoFilterList)) {
                shiftsJsonVoFilterList = shiftsJsonVoFilterList.stream().sorted(Comparator.comparing(DayStatisticsShiftsJsonVo::getInPoint)).collect(Collectors.toList());
            }
            StringBuilder shiftTimeStrList = new StringBuilder();
            shiftsJsonVoFilterList.forEach(shiftsJsonVo -> {
                StringBuilder shiftTimeStr = new StringBuilder(DateUtil.format(shiftsJsonVo.getInPoint(), "HH:mm")).append("~");
                shiftTimeStrList.append(DateUtil.format(shiftsJsonVo.getInPoint(), "HH:mm")).append("~");
                if (!DayStatisticsUtils.isSameDay(item.getDate(), shiftsJsonVo.getOutPoint())) {
                    shiftTimeStr.append("(次日)").append(DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm"));
                    shiftTimeStrList.append("(次日)").append(DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm")).append("|");
                } else {
                    shiftTimeStr.append(DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm"));
                    shiftTimeStrList.append(DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm")).append("|");
                }
                shiftTimeList.add(shiftTimeStr.toString());
            });
            dayStatistics.setShiftTimeList(shiftTimeList);
            dayStatistics.setShiftTimeStr(shiftTimeStrList.length() > 0 ? shiftTimeStrList.substring(0, shiftTimeStrList.length() - 1) : "");
            //处理打卡时间
            List<String> clockTimeList = new ArrayList<>();
            StringBuilder clockTimeStr = new StringBuilder();
            shiftsJsonVoFilterList.forEach(shiftsJsonVo -> {
                DayStatisticsUtils.enclosurePayrollData(item, shiftsJsonVo, clockTimeList, clockTimeStr);
            });
            dayStatistics.setActualAttendPayrollHours(dayStatistics.getActualAttendPayrollHours().compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : dayStatistics.getActualAttendPayrollHours());
            dayStatistics.setClockTimeList(clockTimeList);
            dayStatistics.setClockTimeStr(clockTimeStr.length() > 0 ? clockTimeStr.substring(0, clockTimeStr.length() - 1) : "");
            return dayStatistics;
        }).collect(Collectors.toList());
    }

    @Override
    public PageInfo<MonthPayrollStatisticsPageListVo> getMonthPayrollPageList(MonthPayrollStatisticsPageListDto req) throws Exception {
        MonthStatisticsDataQueryDto queryDto = new MonthStatisticsDataQueryDto();
        BeanUtils.copyProperties(req, queryDto);
        DateRangeDto dateRangeDto = new DateRangeDto(req.getMonth(), Boolean.TRUE);
        queryDto.setStartDate(dateRangeDto.getStartDate());
        queryDto.setEndDate(dateRangeDto.getEndDate());
        //获取考勤组信息
        PageInfo<MonthPayrollStatisticsPageListVo> pageInfo = new PageInfo<>();
        pageInfo.setPageSize((int) req.getPageSize());
        pageInfo.setPageNum((int) req.getCurrentPage());
        //获取有权限的用户列表
        List<UserBoundVO> userBoundVoList = getUserIdArr(req.getFilterList(), dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), req.getUserIds(), req.getWorkStatus());
        Map<String, UserBoundVO> userBoundVoMap = userBoundVoList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1));
        if (CollUtil.isEmpty(userBoundVoList)) {
            return pageInfo;
        }
        List<String> userIds = userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList());
        List<String> groupIds = req.getFilterList().stream().map(GroupFilterDto::getGroupId).distinct().collect(Collectors.toList());
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(groupIds);
        if (CollUtil.isEmpty(groupList)) {
            log.info("未查询到考勤组数据:{}", groupIds);
            return pageInfo;
        }
        Map<String, AttendanceGroup> groupMap = groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1));
        queryDto.setUserIds(userIds);
        req.setUserIds(userIds);
        queryDto.setGroupIds(groupIds);
        PageHelper.startPage(Math.toIntExact(req.getCurrentPage()), Math.toIntExact(req.getPageSize()));
        List<MonthPayrollStatisticsQueryVo> pageListVoList = this.baseMapper.getMonthPayrollPageList(queryDto);
        if (CollUtil.isEmpty(pageListVoList)) {
            return pageInfo;
        }
        pageInfo = new PageInfo<>(monthPayrollStatisticsAssemble(req.getMonth(), queryDto.getStartDate(), queryDto.getEndDate(), pageListVoList, groupMap, userBoundVoMap));
        pageInfo.setTotal(new PageInfo<>(pageListVoList).getTotal());
        pageInfo.setPageSize(Math.toIntExact(req.getPageSize()));
        pageInfo.setPageNum(Math.toIntExact(req.getCurrentPage()));
        return pageInfo;
    }

    /**
     * 获取考勤组ID集合
     *
     * @param ids 组织id/考勤组ID
     * @param aTrue  是否考勤组ID
     * @return 考勤组id
     */
    private Map<String, AttendanceGroup> getGroupIds(List<String> ids, Boolean aTrue) {
        List<AttendanceGroup> groupList;
        if (aTrue) {
            groupList = this.groupService.queryListIncludeDeleteByIds(ids);
        } else {
            groupList = this.groupService.getGroupListByOrgIds(ids.stream().distinct().collect(Collectors.toList()));
        }
        return CollUtil.isNotEmpty(groupList) ? groupList.stream().collect(Collectors.toMap(AttendanceGroup::getId, a -> a, (k1, k2) -> k1)) : Map.of();
    }

    @Override
    public PageListVO<AverageTrendPageListVo> averageWorkHoursTrendPageList(PersonnelApiInfoPageListDto pageDto) {
        //获取组织下所有考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getOrgIds(), Boolean.FALSE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getStartMonth(), pageDto.getEndMonth());
        Page<AverageTrendPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<AverageTrendPageListVo> listVoPage = this.baseMapper.averageWorkHoursTrendPageList(page, new ArrayList<>(groupMap.keySet()), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        listVoPage.getRecords().forEach(item -> {
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void averageWorkHoursTrendExport(HttpServletResponse response, PersonnelApiInfoPageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<AverageTrendPageListVo> pageListVoList = averageWorkHoursTrendPageList(pageDto);
        List<AverageTrendPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤平均工时趋势.xlsx", "考勤平均工时趋势", AverageTrendPageListVo.class, dataList);
    }

    @Override
    public PageListVO<PersonnelTrendPageListVo> personWorkHoursTrendPageList(PersonnelApiInfoSinglePageListDto pageDto) {
        //获取考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getGroupIds(), Boolean.TRUE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getMonth(), pageDto.getMonth());
        Page<PersonnelTrendPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<PersonnelTrendPageListVo> listVoPage = this.baseMapper.personWorkHoursTrendPageList(page, pageDto.getGroupIds(), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        listVoPage.getRecords().forEach(item -> {
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void personWorkHoursTrendExport(HttpServletResponse response, PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<PersonnelTrendPageListVo> pageListVoList = personWorkHoursTrendPageList(pageDto);
        List<PersonnelTrendPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤人均工时趋势.xlsx", "考勤人均工时趋势", PersonnelTrendPageListVo.class, dataList);

    }

    @Override
    public PageListVO<DailySituationPageListVo> dailySituationPageList(PersonnelApiInfoSinglePageListDto pageDto) {
        //获取考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getGroupIds(), Boolean.TRUE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getMonth(), pageDto.getMonth());
        Page<DailySituationPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<DailySituationPageListVo> listVoPage = this.baseMapper.dailySituationPageList(page, new ArrayList<>(groupMap.keySet()), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        List<UserBoundVO> userList = v2UserApi.userNameAndCopy(listVoPage.getRecords().stream().map(DailySituationPageListVo::getUserName).distinct().collect(Collectors.toList()));
        Map<String, UserBoundVO> userMap = CollUtil.isNotEmpty(userList) ? userList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1)) : Map.of();
        listVoPage.getRecords().forEach(item -> {
            UserBoundVO userBoundVO = userMap.getOrDefault(item.getUserName(), new UserBoundVO());
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
            item.setUserName(StrUtil.isBlank(userBoundVO.getUserName()) ? "---" : userBoundVO.getUserName());
            item.setPhoneNumber(StrUtil.isBlank(userBoundVO.getPhone()) ? "---" : userBoundVO.getPhone());
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void dailySituationExport(HttpServletResponse response, PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<DailySituationPageListVo> pageListVoList = dailySituationPageList(pageDto);
        List<DailySituationPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤日常情况.xlsx", "考勤日常情况", DailySituationPageListVo.class, dataList);
    }

    @Override
    public PageListVO<WorkHoursRankingPageListVo> workHoursRankingPageList(PersonnelApiInfoSinglePageListDto pageDto) {
        //获取考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getGroupIds(), Boolean.TRUE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getMonth(), pageDto.getMonth());
        Page<WorkHoursRankingPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<WorkHoursRankingPageListVo> listVoPage = this.baseMapper.workHoursRankingPageList(page, new ArrayList<>(groupMap.keySet()), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        List<UserBoundVO> userList = v2UserApi.userNameAndCopy(listVoPage.getRecords().stream().map(WorkHoursRankingPageListVo::getUserName).distinct().collect(Collectors.toList()));
        Map<String, UserBoundVO> userMap = CollUtil.isNotEmpty(userList) ? userList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1)) : Map.of();
        listVoPage.getRecords().forEach(item -> {
            UserBoundVO userBoundVO = userMap.getOrDefault(item.getUserName(), new UserBoundVO());
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
            item.setUserName(StrUtil.isBlank(userBoundVO.getUserName()) ? "---" : userBoundVO.getUserName());
            item.setPhoneNumber(StrUtil.isBlank(userBoundVO.getPhone()) ? "---" : userBoundVO.getPhone());
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void workHoursRankingExport(HttpServletResponse response, PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<WorkHoursRankingPageListVo> pageListVoList = workHoursRankingPageList(pageDto);
        List<WorkHoursRankingPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤工时排行.xlsx", "考勤工时排行", WorkHoursRankingPageListVo.class, dataList);
    }

    @Override
    public PageListVO<FullAttendanceStatusPageListVo> fullAttendanceStatusPageList(PersonnelApiInfoSinglePageListDto pageDto) {
        //获取考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getGroupIds(), Boolean.TRUE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getMonth(), pageDto.getMonth());
        Page<FullAttendanceStatusPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<FullAttendanceStatusPageListVo> listVoPage = this.baseMapper.fullAttendanceStatusPageList(page, new ArrayList<>(groupMap.keySet()), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        listVoPage.getRecords().forEach(item -> {
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
            // 设置全勤占比
            if (item.getFullAttendCount() > 0) {
                item.setFullAttendPercent(BigDecimal.valueOf(item.getFullAttendCount()).divide(BigDecimal.valueOf(item.getGroupCount()), 4,
                        RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
            } else {
                item.setFullAttendPercent(BigDecimal.ZERO);
            }
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void fullAttendanceStatusExport(HttpServletResponse response, PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<FullAttendanceStatusPageListVo> pageListVoList = fullAttendanceStatusPageList(pageDto);
        List<FullAttendanceStatusPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤全勤情况.xlsx", "考勤全勤情况", FullAttendanceStatusPageListVo.class, dataList);
    }

    @Override
    public PageListVO<ExceptionSituationPageListVo> exceptionSituationPageList(PersonnelApiInfoSinglePageListDto pageDto) {
        //获取考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getGroupIds(), Boolean.TRUE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getMonth(), pageDto.getMonth());
        Page<ExceptionSituationPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<ExceptionSituationPageListVo> listVoPage = this.baseMapper.exceptionSituationPageList(page, new ArrayList<>(groupMap.keySet()), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        List<UserBoundVO> userList = v2UserApi.userNameAndCopy(listVoPage.getRecords().stream().map(ExceptionSituationPageListVo::getUserName).distinct().collect(Collectors.toList()));
        Map<String, UserBoundVO> userMap = CollUtil.isNotEmpty(userList) ? userList.stream().collect(Collectors.toMap(UserBoundVO::getId, a -> a, (k1, k2) -> k1)) : Map.of();
        listVoPage.getRecords().forEach(item -> {
            UserBoundVO userBoundVO = userMap.getOrDefault(item.getUserName(), new UserBoundVO());
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
            item.setUserName(StrUtil.isBlank(userBoundVO.getUserName()) ? "---" : userBoundVO.getUserName());
            item.setPhoneNumber(StrUtil.isBlank(userBoundVO.getPhone()) ? "---" : userBoundVO.getPhone());
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void exceptionSituationExport(HttpServletResponse response, PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<ExceptionSituationPageListVo> pageListVoList = exceptionSituationPageList(pageDto);
        List<ExceptionSituationPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤异常情况.xlsx", "考勤异常情况", ExceptionSituationPageListVo.class, dataList);
    }

    @Override
    public PageListVO<OvertimeSituationPageListVo> overtimeSituationPageList(PersonnelApiInfoSinglePageListDto pageDto) {
        //获取考勤组
        Map<String, AttendanceGroup> groupMap = getGroupIds(pageDto.getGroupIds(), Boolean.TRUE);
        if (CollUtil.isEmpty(groupMap)) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        MutablePair<LocalDate, LocalDate> dateRange = DayStatisticsUtils.getDateBetweenByMonth(pageDto.getMonth(), pageDto.getMonth());
        Page<OvertimeSituationPageListVo> page = Page.of(Math.toIntExact(pageDto.getCurrentPage()), Math.toIntExact(pageDto.getPageSize()));
        Page<OvertimeSituationPageListVo> listVoPage = this.baseMapper.overtimeSituationPageList(page, new ArrayList<>(groupMap.keySet()), dateRange.getLeft(), dateRange.getRight());
        if (CollUtil.isEmpty(listVoPage.getRecords())) {
            return DayStatisticsUtils.returnEmptyList(pageDto);
        }
        listVoPage.getRecords().forEach(item -> {
            item.setGroupName(groupMap.getOrDefault(item.getGroupName(), new AttendanceGroup()).getGroupName());
        });
        return DayStatisticsUtils.returnList(listVoPage, pageDto);
    }

    @Override
    public void overtimeSituationExport(HttpServletResponse response, PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
        pageDto.setPageSize(100000L);
        PageListVO<OvertimeSituationPageListVo> pageListVoList = overtimeSituationPageList(pageDto);
        List<OvertimeSituationPageListVo> dataList = CollUtil.isNotEmpty(pageListVoList.getList()) ? pageListVoList.getList() : new ArrayList<>();
        EasyExcelUtil.export(response, "考勤加班情况.xlsx", "考勤加班情况", OvertimeSituationPageListVo.class, dataList);
    }

    @Override
    public QueryStatisticsInfoVo queryUserStatisticsInfo(QueryStatisticsInfoDto dto) {
        QueryStatisticsInfoVo infoVo = new QueryStatisticsInfoVo();
        DateRangeDto dateRangeDto = new DateRangeDto(dto.getMonth(), Boolean.TRUE);
        DayStatisticsQueryDbVo dbVo = this.baseMapper.queryUserStatisticsInfo(dto.getUserId(), dateRangeDto);
        if (Objects.isNull(dbVo)) {
            return infoVo;
        }
        BeanUtils.copyProperties(dbVo, infoVo);
        // 设置请假次数
        infoVo.setLeaveTimes(StrUtil.isNotBlank(dbVo.getLeaveTimes()) ? dbVo.getLeaveTimes().split(",").length : 0);
        if (infoVo.getLeaveTimes() > 0) {
            // 解析请假数据，得到各个请假类型的次数
            List<LeaveTypeJsonData> leaveList = DayStatisticsUtils.getMonthCustomLeaveList(dbVo.getCustomLeaveList());
            infoVo.setLeaveTypeList(leaveList.stream().map(item ->
                    LeaveTypeTimes.builder()
                            .typeName(item.getFieldName())
                            .times(item.getApplyList().size())
                            .build()
            ).collect(Collectors.toList()));
        }
        return infoVo;
    }

    @Override
    public QueryGroupStatisticsVo queryGroupStatistics(QueryGroupStatisticsDto dto) {
        QueryGroupStatisticsVo queryGroupStatisticsVo = new QueryGroupStatisticsVo();
        DateRangeDto dateRangeDto = new DateRangeDto(dto.getMonth(), Boolean.TRUE);
        List<AttendanceGroup> groupList = this.groupService.queryListIncludeDeleteByIds(List.of(dto.getGroupId()));
        if (CollUtil.isEmpty(groupList)) {
            log.info("未查询到考勤组数据:{}", dto.getGroupId());
            throw new RuntimeException("不存在该考勤组");
        }
        queryGroupStatisticsVo.setGroupName(groupList.get(0).getGroupName());
        List<GroupFilterDto> filterList = groupList.stream().map(item -> GroupFilterDto.builder().groupId(item.getId()).team(0).build()).collect(Collectors.toList());
        List<UserBoundVO> userBoundVoList = getUserIdArr(filterList, dateRangeDto.getStartDate(), dateRangeDto.getEndDate(), null, "0");
        if (CollUtil.isEmpty(userBoundVoList)) {
            log.info("未查询到用户数据:{}", dto.getGroupId());
            throw new RuntimeException("未查询到用户数据或者不具备权限");
        }
        StatisticsDataQueryDto queryDto = StatisticsDataQueryDto.builder()
                .groupIds(List.of(dto.getGroupId()))
                .startDate(dateRangeDto.getStartDate())
                .endDate(dateRangeDto.getEndDate())
                .userIds(userBoundVoList.stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList()))
                .build();
        StatisticsDataQueryVo dataQuery = this.baseMapper.getMonthStatisticsDataQuery(queryDto, Boolean.FALSE);
        if (Objects.isNull(dataQuery)) {
            return queryGroupStatisticsVo;
        }
        BeanUtils.copyProperties(dataQuery, queryGroupStatisticsVo);
        return queryGroupStatisticsVo;
    }

    @Override
    public QueryUserOvertimeVo queryUserOvertime(QueryStatisticsInfoDto dto) {
        QueryUserOvertimeVo overtimeVo = new QueryUserOvertimeVo();
        DateRangeDto dateRangeDto = new DateRangeDto(dto.getMonth(), Boolean.TRUE);
        List<PartUserInfoVo> userBoundVos = userApi.getStaffRosterListInfoByIds(List.of(dto.getUserId()));
        if (CollUtil.isEmpty(userBoundVos)) {
            log.info("未查询到用户数据:{}", dto.getUserId());
            throw new RuntimeException("不存在该用户");
        }
        overtimeVo.setUserName(userBoundVos.get(0).getRealName());
        List<QueryUserOvertimeDetailsDbVo> overtimeDetailsList = this.baseMapper.queryUserOvertime(dto.getUserId(), dateRangeDto);
        if (CollUtil.isEmpty(overtimeDetailsList)) {
            return overtimeVo;
        }
        // 加班小时数
        overtimeVo.setTotalHours(overtimeDetailsList.stream().map(QueryUserOvertimeDetailsDbVo::getHours).reduce(BigDecimal::add).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP));
        List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = new ArrayList<>();
        for (QueryUserOvertimeDetailsDbVo item : overtimeDetailsList) {
            List<DayStatisticsShiftsJsonVo> shiftsList = parseArray(item.getShiftsJson(), DayStatisticsShiftsJsonVo.class);
            if (CollUtil.isEmpty(shiftsJsonVoList)) {
                shiftsJsonVoList = new ArrayList<>();
            }
            QueryUserOvertimeDetailsVo detailsVo = new QueryUserOvertimeDetailsVo();
            detailsVo.setDay(item.getDay());
            detailsVo.setHours(item.getHours());
            // 获取加班时间
            detailsVo.setOvertimeTime(getOvertimeTime(item.getDay(), shiftsList));
            shiftsJsonVoList.addAll(shiftsList);
        }
        // 加班次数
        overtimeVo.setTotalCount(shiftsJsonVoList.size());
        return null;
    }


    /**
     * 获取加班时间
     *
     * @param day 加班日期
     * @param shiftsList 排班班次
     * @return 加班时间
     */
    private String getOvertimeTime(Date day, List<DayStatisticsShiftsJsonVo> shiftsList) {
        StringBuilder shiftTimeStrList = new StringBuilder();
        for (DayStatisticsShiftsJsonVo shiftsJsonVo : shiftsList) {
            String inTime = DateUtil.format(shiftsJsonVo.getInPoint(), "HH:mm");
            String outTime = DateUtil.format(shiftsJsonVo.getOutPoint(), "HH:mm");
            shiftTimeStrList.append(inTime).append("~");
            if (!DayStatisticsUtils.isSameDay(day, shiftsJsonVo.getOutPoint())) {
                String timeRange = "(次日)".concat(outTime);
                shiftTimeStrList.append(timeRange).append("|");
            } else {
                shiftTimeStrList.append(outTime).append("|");
            }
        }
        return shiftTimeStrList.length() > 0 ? shiftTimeStrList.substring(0, shiftTimeStrList.length() - 1) : "";
    }

    /**
     * 获取开启提醒的考勤组
     *
     * @param teamMonthStatisticsList 考勤组统计信息
     * @return List<TeamMonthStatisticsNoticeQueryVo>
     */
    private List<TeamMonthStatisticsNoticeQueryVo> getObtainPermissionGroupList(List<TeamMonthStatisticsNoticeQueryVo> teamMonthStatisticsList) {
        List<String> groupIdList = teamMonthStatisticsList.stream().map(TeamMonthStatisticsNoticeQueryVo::getGroupId).distinct().collect(Collectors.toList());
        List<UserSettingVo> groupSettingList = userSettingService.getSettingList(groupIdList, UserSettingTypeEnum.GROUP.getCode(), Lists.newArrayList());
        List<String> groupIds = groupSettingList.stream().map(UserSettingVo::getAssociationId).collect(Collectors.toList());
        return CollUtil.isEmpty(groupIds) ? Lists.newArrayList() : teamMonthStatisticsList.stream().filter(m -> groupIds.contains(m.getGroupId())).collect(Collectors.toList());
    }

    /**
     * 封装日统计消息提醒数据
     *
     * @param statisticsQueryVoList 用户日统计数据
     * @return 日统计消息提醒数据
     */
    public Map<String, DayNoticeModel> getDayNoticeModelMap(List<DayStatisticsNoticeQueryVo> statisticsQueryVoList) {
        Map<String, List<DayStatisticsShiftsJsonVo>> dayStatisticsShiftsJsonMap = new HashMap<>();
        List<String> shiftIdList = new ArrayList<>();
        for (DayStatisticsNoticeQueryVo item : statisticsQueryVoList) {
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = parseArray(item.getShiftsJson(), DayStatisticsShiftsJsonVo.class);
            if (CollUtil.isNotEmpty(shiftsJsonVoList)) {
                shiftIdList.addAll(shiftsJsonVoList.stream().map(DayStatisticsShiftsJsonVo::getShiftId).collect(Collectors.toList()));
            }
            dayStatisticsShiftsJsonMap.put(item.getUserId(), CollUtil.isNotEmpty(shiftsJsonVoList) ? shiftsJsonVoList : new ArrayList<>());
        }
        if (CollUtil.isNotEmpty(shiftIdList)) {
            shiftIdList = shiftIdList.stream().distinct().collect(Collectors.toList());
        }
        Map<String, AttendanceShiftNameEntity> shiftNameEntityMap = CollUtil.isEmpty(shiftIdList) ? new HashMap<>() : dailyRuleService1.getShiftByShiftIds(shiftIdList);
        Map<String, DayNoticeModel> shiftsJsonStrVoMap = new HashMap<>();
        statisticsQueryVoList.forEach(item -> {
            //处理班次、班次时间、打卡时间
            DayNoticeModel dayNoticeModel = new DayNoticeModel();
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoList = dayStatisticsShiftsJsonMap.containsKey(item.getUserId()) ? dayStatisticsShiftsJsonMap.get(item.getUserId()) : Lists.newArrayList();
            List<SchedulesItemVo> collect = getSchedulesItemVos(shiftNameEntityMap, shiftsJsonVoList);
            dayNoticeModel.setShiftList(CollUtil.isNotEmpty(collect) ? collect.stream().map(SchedulesItemVo::getShortName).distinct().collect(Collectors.toList()) : Lists.newArrayList());
            //处理班次时间
            List<String> shiftTimeList = new ArrayList<>();
            List<DayStatisticsShiftsJsonVo> shiftsJsonVoFilterList = getShiftsTimeList(item, shiftsJsonVoList, shiftTimeList);
            dayNoticeModel.setShiftTimeList(shiftTimeList);
            //处理打卡时间
            List<String> clockTimeList = new ArrayList<>();
            shiftsJsonVoFilterList.forEach(shiftsJsonVo -> {
                if (CollUtil.isNotEmpty(shiftsJsonVo.getClockInResultList())) {
                    if (DayStatisticsUtils.isShiftsJsonAbsence1(shiftsJsonVo.getClockInResultList())) {
                        clockTimeList.add("<span style='color:#F8593C'>旷工</span>");
                    } else {
                        StringBuilder sb = new StringBuilder();
                        List<ShiftsJsonClockInResultVo> onClockInResultList = shiftsJsonVo.getClockInResultList().stream().filter(m -> m.getClockInType().equals(ConstantUtil.ON_WORK)).collect(Collectors.toList());
                        ShiftsJsonClockInResultVo on = CollUtil.isNotEmpty(onClockInResultList) ? onClockInResultList.get(0) : null;
                        List<ShiftsJsonClockInResultVo> offClockInResultList = shiftsJsonVo.getClockInResultList().stream().filter(m -> m.getClockInType().equals(ConstantUtil.OFF_WORK)).collect(Collectors.toList());
                        ShiftsJsonClockInResultVo off = CollUtil.isNotEmpty(offClockInResultList) ? offClockInResultList.get(0) : null;
                        sb.append(DayStatisticsUtils.getClockInResultStr(shiftsJsonVo.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode()), item, on, off));
                        clockTimeList.add(sb.toString());
                    }
                }
            });
            dayNoticeModel.setClockTimeList(clockTimeList);
            DayClockRange dayClockRange = dailyRuleService1.getDayClockRange(item.getUserId(), item.getDate());
            dayNoticeModel.setClockTime(clockInService.getDailyClockInRecord(item.getUserId(), dayClockRange).size());
            dayNoticeModel.setShouldAttendHours(item.getShouldAttendHours());
            dayNoticeModel.setActualAttendHours(item.getActualAttendHours());
            dayNoticeModel.setWorkingHours(item.getEffectiveAttendHours());
            dayNoticeModel.setLateTimes(item.getLateTimes());
            dayNoticeModel.setLateMinutes(item.getLateMinutes());
            dayNoticeModel.setEarlyLeaveTimes(item.getEarlyLeaveTimes());
            dayNoticeModel.setEarlyLeaveMinutes(item.getEarlyLeaveMinutes());
            dayNoticeModel.setAbsenceCardTimes(item.getAbsenceCardTimes());
            dayNoticeModel.setAbsenceCardHours(item.getAbsenceCardHours());
            dayNoticeModel.setAbsenceTimes(item.getAbsenceTimes());
            dayNoticeModel.setAbsenceHours(item.getAbsenceHours());
            dayNoticeModel.setLeaveHours(item.getLeaveHours());
            dayNoticeModel.setLeaveDays(item.getLeaveDays());
            dayNoticeModel.setOvertimeHours(item.getOvertimeHours());
            dayNoticeModel.setBusDays(StringUtil.isNotEmpty(item.getBusBatchNumber()) ? 1 : 0);
            dayNoticeModel.setOutDays(item.getOutDays());
            dayNoticeModel.setOutHours(item.getOutHours());
            shiftsJsonStrVoMap.put(item.getId(), dayNoticeModel);
        });
        return shiftsJsonStrVoMap;
    }

    /**
     * 封装用户月统计提醒数据
     *
     * @param month               月份
     * @param monthStatisticsList 月统计数据
     * @return 月统计提醒数据
     * @throws Exception 异常问题
     */
    private Map<String, MonthNoticeModel> getMonthNoticeModelMap(String month, List<MonthStatisticsNoticeQueryVo> monthStatisticsList) throws Exception {
        List<String> userIds = monthStatisticsList.stream().map(MonthStatisticsNoticeQueryVo::getUserId).distinct().collect(Collectors.toList());
        List<String> groupIds = monthStatisticsList.stream().map(MonthStatisticsNoticeQueryVo::getGroupId).distinct().collect(Collectors.toList());
        //批量获取用户加入考勤组周期
        Map<String, Map<String, List<String>>> userCycleMap = CollUtil.isNotEmpty(monthStatisticsList) ? userService.batchGetUserCycleList(new DateRangeDto(month, Boolean.TRUE), groupIds, userIds) : new HashMap<>();
        Map<String, MonthNoticeModel> monthNoticeModelMap = new HashMap<>();
        for (MonthStatisticsNoticeQueryVo item : monthStatisticsList) {
            MonthNoticeModel monthNoticeModel = MonthNoticeModel.builder().build();
            BeanUtils.copyProperties(item, monthNoticeModel);
            //设置考勤组周期
            if (userCycleMap.containsKey(item.getGroupId()) && userCycleMap.get(item.getGroupId()).containsKey(item.getUserId())) {
                StringBuilder cycleStr = new StringBuilder();
                userCycleMap.get(item.getGroupId()).get(item.getUserId()).forEach(cycle -> cycleStr.append(cycle).append("|"));
                monthNoticeModel.setCycleDate(cycleStr.length() > 0 ? cycleStr.substring(0, cycleStr.length() - 1) : "");
                monthNoticeModel.setBusTimes(StrUtil.isNotBlank(item.getBusTimes()) ? item.getBusTimes().split(",").length : 0);
                //设置外出次数
                monthNoticeModel.setOutTimes(StrUtil.isNotBlank(item.getOutTimes()) ? item.getOutTimes().split(",").length : 0);
            }
            monthNoticeModelMap.put(item.getId(), monthNoticeModel);
        }
        return monthNoticeModelMap;
    }

    @Override
    public Map<String, UserWorkSituationVo> queryUserWorkSituation(UserWorkSituationDto dto) {
        // 初始化结果Map，为所有用户ID设置默认值
        Map<String, UserWorkSituationVo> resultMap = new HashMap<>();
        if (CollUtil.isEmpty(dto.getUserIds())) {
            return resultMap;
        }
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusDays(dto.getConfigDays() + 1);
        // 获取连续排班周信息
        MutablePair<LocalDate, LocalDate> weekRange = getWeekRangeByDate(LocalDate.now());
        List<UserWorkSituationDbVo> dataList = this.baseMapper.queryUserWorkSituation(dto.getUserIds(), weekRange.getLeft().isAfter(startDate) ? startDate : weekRange.getLeft(), weekRange.getRight());
        Map<String, List<UserWorkSituationDbVo>> userStaMap = CollUtil.isNotEmpty(dataList) ? dataList.stream().collect(Collectors.groupingBy(UserWorkSituationDbVo::getUserId)) : new HashMap<>();
        dto.getUserIds().forEach(item -> {
            List<UserWorkSituationDbVo> userStaList = userStaMap.getOrDefault(item, new ArrayList<>());
            Map<LocalDate, UserWorkSituationDbVo> userDateMap = userStaList.stream()
                    .collect(Collectors.toMap(vo -> vo.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
                            vo -> vo, (existing, replacement) -> existing));
            int continuousDays = calculateContinuousDays(endDate, userDateMap);
            // 排除掉比开始时间都还早的数据
            userDateMap = userDateMap.entrySet().stream()
                    .filter(entry -> !entry.getKey().isBefore(weekRange.getLeft()))
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
            MutablePair<Integer, BigDecimal> weekWorkSta = getWeekStaData(weekRange.getRight(), userDateMap);
            UserWorkSituationVo userWorkSituationVo = UserWorkSituationVo.builder()
                    .continuousDays(continuousDays)
                    .weekWorkDays(weekWorkSta.getLeft())
                    .weekWorkHours(weekWorkSta.getRight())
                    .build();
            resultMap.put(item, userWorkSituationVo);
        });
        return resultMap;
    }

    /**
     * 计算连续排班天数
     *
     * @param today      今天
     * @param userDateMap 用户排班数据
     * @return 连续排班天数
     */
    private int calculateContinuousDays(LocalDate today, Map<LocalDate, UserWorkSituationDbVo> userDateMap) {
        int continuousDays = 0;
        LocalDate checkDate = today;
        while (true) {
            UserWorkSituationDbVo dayData = userDateMap.get(checkDate);
            if (dayData != null && dayData.getIsScheduling() != null && dayData.getIsScheduling() == 1) {
                continuousDays++;
                checkDate = checkDate.minusDays(1);
            } else {
                break;
            }
        }

        return continuousDays;
    }

    /**
     * 获取本周内连续排班天数以及小时数
     *
     * @param today      今天
     * @param userDateMap 用户排班数据
     * @return 周数据
     */
    private MutablePair<Integer, BigDecimal> getWeekStaData(LocalDate today, Map<LocalDate, UserWorkSituationDbVo> userDateMap) {
        int continuousDays = 0;
        BigDecimal continuousHours = BigDecimal.ZERO;
        LocalDate checkDate = today;
        while (true) {
            UserWorkSituationDbVo dayData = userDateMap.get(checkDate);
            if (dayData != null && dayData.getIsScheduling() != null && dayData.getIsScheduling() == 1) {
                continuousDays++;
                if (dayData.getAttendanceHours() != null) {
                    continuousHours = continuousHours.add(dayData.getAttendanceHours());
                }
                checkDate = checkDate.minusDays(1);
            } else {
                break;
            }
        }
        return MutablePair.of(continuousDays, continuousHours);
    }

    private MutablePair<LocalDate, LocalDate> getWeekRangeByDate(LocalDate date) {
        LocalDate weekStart = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        LocalDate weekEnd = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
        return MutablePair.of(weekStart, weekEnd.isBefore(date) ? weekEnd : date);
    }
}
