package jnpf.attendance.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.attendance.mapper.AttendanceClockInMapper;
import jnpf.attendance.mapper.AttendanceClockInResultMapper;
import jnpf.attendance.mapper.AttendanceLeaveApproveMapper;
import jnpf.attendance.service.*;
import jnpf.database.model.TenantVO;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.*;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.exception.LoginException;
import jnpf.model.attendance.event.StatisticsSingleDto;
import jnpf.model.attendance.event.StatisticsSingleHistoryDto;
import jnpf.model.attendance.model.ClockInResult;
import jnpf.model.attendance.model.LeaveSituationData;
import jnpf.model.attendance.model.LeaveTypeStaModel;
import jnpf.model.attendance.model.UserDaySituationData;
import jnpf.model.attendance.vo.ClockInVo;
import jnpf.model.attendance.vo.attendance.DayStatisticsShiftsJsonVo;
import jnpf.model.attendance.vo.attendance.OutOrBusApproveVo;
import jnpf.model.attendance.vo.attendance.ShiftsJsonClockInResultVo;
import jnpf.util.RandomUtil;
import jnpf.util.StringUtil;
import jnpf.util.attendance.DayStatisticsUtils;
import jnpf.util.data.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

import static cn.hutool.core.collection.CollUtil.newArrayList;

@Slf4j
@Service
public class AttendanceDayStatisticsUtilImpl {
    @Resource
    private AttendanceUserService groupUsers;
    @Resource
    private AttendanceClockInMapper clockInMapper;
    @Resource
    private AttendanceDailyRuleService ruleService;
    @Resource
    private AttendanceBaseSettingService baseSettingService;
    @Resource
    private AttendanceLeaveApproveMapper leaveApproveMapper;
    @Resource
    private AttendanceDayStatisticsService statisticsService;
    @Resource
    private AttendanceClockInResultMapper clockInResultsMapper;
    @Resource
    private AttendanceLineSchedulingPayrollHoursService hoursService;

    /**
     * 获取考勤组出勤换算比
     *
     * @param groupId 考勤组ID
     * @return 出勤换算比
     */
    private AttendanceBaseSetting getAttendanceRatio(String groupId) {
        Map<String, AttendanceBaseSetting> groupBaseSetting = baseSettingService.getEnableBaseSettingAll(CollUtil.newArrayList(groupId));
        return groupBaseSetting.get(groupId);
    }

    /**
     * 生成用户日统计数据
     *
     */
    @DSTransactional
    public void createDayStatisticsData(StatisticsSingleDto singleDto) throws LoginException {
        Date start = DateUtil.beginOfDay(singleDto.getDay());
        Date end = DateUtil.endOfDay(singleDto.getDay());
        AttendanceBaseSetting attendanceRatio1 = getAttendanceRatio(singleDto.getGroupId());
        BigDecimal attendanceRatio = Objects.isNull(attendanceRatio1) ? BigDecimal.valueOf(8) : attendanceRatio1.getAttendanceRatio();
        List<AttendanceGroupUser> groupUsers = this.groupUsers.getAttendanceGroupUsersOfSecondment(start, end, newArrayList(singleDto.getUserId()), CollUtil.newArrayList(singleDto.getGroupId()));
        if (ruleService.isExistStatus(groupUsers, singleDto.getDay()) <= 0) {
            LambdaQueryWrapper<AttendanceDayStatistics> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.eq(AttendanceDayStatistics::getGroupId, singleDto.getGroupId());
            queryWrapper.eq(AttendanceDayStatistics::getUserId, singleDto.getUserId());
            queryWrapper.eq(AttendanceDayStatistics::getDate, singleDto.getDay());
            this.statisticsService.remove(queryWrapper);
            return;
        }
        AttendanceGroupUser groupUser = groupUsers.stream().sorted(Comparator.comparing(AttendanceGroupUser::getCreatorTime)).reduce((r1, r2) -> r2).orElse(null);
        // 查询排班数据
        List<FtbAttendanceDailyRule> dailyRuleList = ruleService.getAttendanceDayRulesForStatistic(singleDto.getGroupId(), singleDto.getUserId(), singleDto.getDay());
        // 查询当天的请假申请数据
        List<LeaveSituationData> leaveForApplyIds = leaveApproveMapper.getUserLeaveList(singleDto.getGroupId(), singleDto.getUserId(), start, end);
        // 判断当天是否为划线排班
        boolean underlineScheduling = dailyRuleList.stream().anyMatch(item -> item.getFixedMark().equals(2));
        // 查询当天划线排班的计薪工时占比 MutablePair<计薪工时，占比>
        MutablePair<BigDecimal, BigDecimal> payrollProportion = MutablePair.of(BigDecimal.ZERO, BigDecimal.ZERO);
        if (underlineScheduling && CollUtil.isNotEmpty(dailyRuleList)) {
            int payrollMinute = dailyRuleList.stream().map(FtbAttendanceDailyRule::getValidDuration).mapToInt(Integer::intValue).sum();
            FtbAttendanceLineSchedulingPayrollHours dayPayrollHours = hoursService.listByUserIdsAndDays(singleDto.getUserId(), singleDto.getGroupId(), singleDto.getDay());
            BigDecimal payrollHours;
            if (Objects.isNull(dayPayrollHours) || Objects.isNull(dayPayrollHours.getPayrollHours())) {
                payrollHours = new BigDecimal(payrollMinute).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
            } else {
                payrollHours = dayPayrollHours.getPayrollHours();
            }
            if (payrollHours.compareTo(BigDecimal.ZERO) > 0 && payrollMinute > 0) {
                payrollProportion = MutablePair.of(payrollHours, payrollHours.divide(BigDecimal.valueOf(payrollMinute), 6, RoundingMode.HALF_UP));
            }
        }
        if (CollUtil.isEmpty(dailyRuleList) && CollUtil.isEmpty(leaveForApplyIds)) {
            handleNailData(singleDto.getGroupId(), CollUtil.newArrayList(singleDto.getUserId()), singleDto.getDay());
            return;
        }
        // 查询打卡结果
        List<String> ruleIds = dailyRuleList.stream().map(FtbAttendanceDailyRule::getId).collect(Collectors.toList());
        List<AttendanceClockInResult> clockInResults = CollUtil.isEmpty(ruleIds) ? CollUtil.newArrayList() : clockInResultsMapper.selectList(new LambdaQueryWrapper<AttendanceClockInResult>().in(AttendanceClockInResult::getRuleId, ruleIds).eq(AttendanceClockInResult::getDeleteMark, Boolean.FALSE));
        Map<String, List<AttendanceClockInResult>> clockInResultMap = clockInResults.stream().collect(Collectors.groupingBy(AttendanceClockInResult::getRuleId));
        // 查询有效打卡记录
        List<String> clockInIds = clockInResults.stream().map(AttendanceClockInResult::getClockInId).filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList());
        List<ClockInVo> clockInResultList = CollUtil.isEmpty(clockInIds) ? CollUtil.newArrayList() : clockInMapper.getClockInResultByRuleList(ruleIds);
        Map<String, ClockInVo> clockInMap = CollUtil.isNotEmpty(clockInResultList) ? clockInResultList.stream().filter(m -> Objects.nonNull(m.getClockInTime())).collect(Collectors.toMap(ClockInVo::getClockInId, a -> a, (k1, k2) -> k1)) : new HashMap<>();
        // 按照请假数据分组(申请通过就确定了，调整排班不影响结果)
        Map<String, List<LeaveSituationData>> leaveTypeMap = CollUtil.isNotEmpty(leaveForApplyIds) ? leaveForApplyIds.stream().collect(Collectors.groupingBy(LeaveSituationData::getLeaveId)) : new HashMap<>();
        Map<String, String> leaveTypeNameMap = CollUtil.isNotEmpty(leaveForApplyIds) ? leaveForApplyIds.stream().collect(Collectors.toMap(LeaveSituationData::getLeaveId, LeaveSituationData::getLeaveName, (k1, k2) -> k1)) : new HashMap<>();
        // 按照请假类型分组获取用户请假数据(申请通过就确定了，调整排班不影响结果)
        Map<String, LeaveTypeStaModel> leaveTypeStaMap = DayStatisticsUtils.getUserLeaveTypeSum(leaveTypeMap, start, end);
        // 获取外出、出差时长(申请通过就确定了，调整排班不影响结果)
        List<OutOrBusApproveVo> busOrOutApproveList = this.ruleService.getUserDayBusAndOutInfo(singleDto.getUserId(), singleDto.getGroupId(), singleDto.getDay(), List.of(AttendanceTypeEnum.BUSINESS_TRIP, AttendanceTypeEnum.STEP_OUT));
        Map<String, List<ShiftsJsonClockInResultVo>> shiftsMap = new HashMap<>();
        List<UserDaySituationData> ruleAndClockList = dailyRuleList.stream().map(rule -> {
            UserDaySituationData situationData = BeanUtil.toBean(rule, UserDaySituationData.class);
            situationData.setRuleId(rule.getId());
            situationData.setIsScheduling(1);
            List<AttendanceClockInResult> inResultList = clockInResultMap.containsKey(rule.getId()) ? clockInResultMap.get(rule.getId()) : CollUtil.newArrayList();
            if (Objects.nonNull(inResultList)) {
                List<ClockInResult> collect1 = inResultList.stream().map(clockInResult -> {
                    ClockInResult clockInResult1 = ClockInResult.builder().build();
                    BeanUtils.copyProperties(clockInResult, clockInResult1);
                    clockInResult1.setClockInTime(clockInMap.containsKey(clockInResult.getClockInId()) ? clockInMap.get(clockInResult.getClockInId()).getClockInTime() : null);
                    return clockInResult1;
                }).collect(Collectors.toList());
                situationData.setClockInResultList(collect1);
                shiftsMap.put(rule.getId(), collect1.stream().map(item -> {
                    ShiftsJsonClockInResultVo clockInResultVo = ShiftsJsonClockInResultVo.builder().build();
                    BeanUtils.copyProperties(item, clockInResultVo);
                    return clockInResultVo;
                }).collect(Collectors.toList()));
            }
            return situationData;
        }).collect(Collectors.toList());
        AttendanceDayStatistics dayStaVo = new AttendanceDayStatistics();
        dayStaVo.setAttendanceRatio(attendanceRatio);
        dayStaVo.setDate(singleDto.getDay());
        dayStaVo.setUserId(singleDto.getUserId());
        dayStaVo.setGroupId(singleDto.getGroupId());
        dayStaVo.setIsScheduling(0);
        dayStaVo.setCreatorTime(new Date());
        // 处理各项统计数据
        computeDayStatisticsData(dayStaVo, attendanceRatio, payrollProportion, ruleAndClockList, busOrOutApproveList, leaveTypeStaMap);
        dayStaVo.setJoinTime(Objects.nonNull(groupUser) ? groupUser.getCreatorTime() : null);
        dayStaVo.setSelfGroup(ruleService.isExistStatus(groupUsers, singleDto.getDay()) > 1 ? 1 : 0);
        List<DayStatisticsShiftsJsonVo> shiftsJsonVos = Lists.newArrayList();
        List<FtbAttendanceDailyRule> dailyRulesFilter = dailyRuleList.stream().filter(rule -> Objects.equals(AttendanceTypeEnum.BUSINESS_TRIP.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.STEP_OUT.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.ORDINARY.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.REST.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.LEAVE.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.WORKOVERTIME.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.HOLIDAYS.getCode(), rule.getAttendanceType())).collect(Collectors.toList());
        dailyRulesFilter.forEach(item -> {
            DayStatisticsShiftsJsonVo shiftsJsonVo = DayStatisticsShiftsJsonVo.builder().build();
            BeanUtils.copyProperties(item, shiftsJsonVo);
            shiftsJsonVo.setRuleId(item.getId());
            if (shiftsMap.containsKey(item.getId())) {
                shiftsJsonVo.setClockInResultList(shiftsMap.containsKey(item.getId()) ? shiftsMap.get(item.getId()) : CollUtil.newArrayList());
            }
            shiftsJsonVos.add(shiftsJsonVo);
        });
        dayStaVo.setShiftsJson(JSON.toJSONString(shiftsJsonVos));
        dayStaVo.setIsScheduling(CollUtil.isEmpty(dailyRulesFilter) ? 0 : 1);
        // 是否需要打卡
        boolean anyMatch = dailyRulesFilter.stream().anyMatch(item -> item.getAttendanceType().equals(AttendanceTypeEnum.ORDINARY.getCode()) || item.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode()));
        dayStaVo.setIsClocked(anyMatch ? 1 : 0);
        dayStaVo.setCustomLeaveJson(DayStatisticsUtils.getCustomLeaveJson(leaveTypeNameMap, leaveTypeStaMap));
        //获取提醒时间
        Date date = ruleService.getDayEndRuleDeletionDate(singleDto.getUserId(), singleDto.getDay());
        if (Objects.nonNull(date)) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            calendar.add(Calendar.MINUTE, 2);
            dayStaVo.setRemindTime(calendar.getTime());
        }
        // 明确指定更新条件，避免默认行为
        LambdaUpdateWrapper<AttendanceDayStatistics> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(AttendanceDayStatistics::getGroupId, singleDto.getGroupId())
                .eq(AttendanceDayStatistics::getUserId, singleDto.getUserId())
                .eq(AttendanceDayStatistics::getDate, singleDto.getDay());
        // 仅在插入时需要设置ID
        if (statisticsService.count(updateWrapper) == 0) {
            dayStaVo.setId(RandomUtil.uuId());
        }
        statisticsService.saveOrUpdate(dayStaVo, updateWrapper);
    }

    @DSTransactional
    public void createDayStatisticsDataHistory(StatisticsSingleHistoryDto singleDto) throws LoginException {
        Date start = DateUtil.beginOfDay(singleDto.getDay());
        Date end = DateUtil.endOfDay(singleDto.getDay());
        AttendanceBaseSetting attendanceRatio1 = getAttendanceRatio(singleDto.getGroupId());
        BigDecimal attendanceRatio = Objects.isNull(attendanceRatio1) ? BigDecimal.valueOf(8) : attendanceRatio1.getAttendanceRatio();
        List<AttendanceGroupUser> groupUsers = this.groupUsers.getAttendanceGroupUsersOfSecondment(start, end, newArrayList(singleDto.getUserId()), CollUtil.newArrayList(singleDto.getGroupId()));
        if (ruleService.isExistStatus(groupUsers, singleDto.getDay()) <= 0) {
            LambdaQueryWrapper<AttendanceDayStatistics> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.eq(AttendanceDayStatistics::getGroupId, singleDto.getGroupId());
            queryWrapper.eq(AttendanceDayStatistics::getUserId, singleDto.getUserId());
            queryWrapper.eq(AttendanceDayStatistics::getDate, singleDto.getDay());
            this.statisticsService.remove(queryWrapper);
            return;
        }
        AttendanceGroupUser groupUser = groupUsers.stream().sorted(Comparator.comparing(AttendanceGroupUser::getCreatorTime)).reduce((r1, r2) -> r2).orElse(null);
        // 查询排班数据
        List<FtbAttendanceDailyRule> dailyRuleList = ruleService.getAttendanceDayRulesForStatistic(singleDto.getGroupId(), singleDto.getUserId(), singleDto.getDay());
        // 查询当天的请假申请数据
        List<LeaveSituationData> leaveForApplyIds = leaveApproveMapper.getUserLeaveList(singleDto.getGroupId(), singleDto.getUserId(), start, end);
        // 判断当天是否为划线排班
        boolean underlineScheduling = dailyRuleList.stream().anyMatch(item -> item.getFixedMark().equals(2));
        // 查询当天划线排班的计薪工时占比 MutablePair<计薪工时，占比>
        MutablePair<BigDecimal, BigDecimal> payrollProportion = MutablePair.of(BigDecimal.ZERO, BigDecimal.ZERO);
        if (underlineScheduling && CollUtil.isNotEmpty(dailyRuleList)) {
            int payrollMinute = dailyRuleList.stream().map(FtbAttendanceDailyRule::getValidDuration).mapToInt(Integer::intValue).sum();
            FtbAttendanceLineSchedulingPayrollHours dayPayrollHours = hoursService.listByUserIdsAndDays(singleDto.getUserId(), singleDto.getGroupId(), singleDto.getDay());
            BigDecimal payrollHours;
            if (Objects.isNull(dayPayrollHours) || Objects.isNull(dayPayrollHours.getPayrollHours())) {
                payrollHours = new BigDecimal(payrollMinute).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
            } else {
                payrollHours = dayPayrollHours.getPayrollHours();
            }
            if (payrollHours.compareTo(BigDecimal.ZERO) > 0 && payrollMinute > 0) {
                payrollProportion = MutablePair.of(payrollHours, payrollHours.divide(BigDecimal.valueOf(payrollMinute), 6, RoundingMode.HALF_UP));
            }
        }
        if (CollUtil.isEmpty(dailyRuleList) && CollUtil.isEmpty(leaveForApplyIds)) {
            handleNailData(singleDto.getGroupId(), CollUtil.newArrayList(singleDto.getUserId()), singleDto.getDay());
            return;
        }
        // 查询打卡结果
        List<String> ruleIds = dailyRuleList.stream().map(FtbAttendanceDailyRule::getId).collect(Collectors.toList());
        List<AttendanceClockInResult> clockInResults = CollUtil.isEmpty(ruleIds) ? CollUtil.newArrayList() : clockInResultsMapper.selectList(new LambdaQueryWrapper<AttendanceClockInResult>().in(AttendanceClockInResult::getRuleId, ruleIds).eq(AttendanceClockInResult::getDeleteMark, Boolean.FALSE));
        Map<String, List<AttendanceClockInResult>> clockInResultMap = clockInResults.stream().collect(Collectors.groupingBy(AttendanceClockInResult::getRuleId));
        // 查询有效打卡记录
        List<String> clockInIds = clockInResults.stream().map(AttendanceClockInResult::getClockInId).filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList());
        List<ClockInVo> clockInResultList = CollUtil.isEmpty(clockInIds) ? CollUtil.newArrayList() : clockInMapper.getClockInResultByRuleList(ruleIds);
        Map<String, ClockInVo> clockInMap = CollUtil.isNotEmpty(clockInResultList) ? clockInResultList.stream().filter(m -> Objects.nonNull(m.getClockInTime())).collect(Collectors.toMap(ClockInVo::getClockInId, a -> a, (k1, k2) -> k1)) : new HashMap<>();
        Map<String, String> leaveTypeNameMap = CollUtil.isNotEmpty(leaveForApplyIds) ? leaveForApplyIds.stream().collect(Collectors.toMap(LeaveSituationData::getLeaveId, LeaveSituationData::getLeaveName, (k1, k2) -> k1)) : new HashMap<>();
        // 按照请假类型分组获取用户请假数据
        Map<String, LeaveTypeStaModel> leaveTypeMap = DayStatisticsUtils.getUserLeaveTypeSum(attendanceRatio, leaveForApplyIds, dailyRuleList);
        // 获取外出、出差时长(申请通过就确定了，调整排班不影响结果)
        List<OutOrBusApproveVo> busOrOutApproveList = this.ruleService.getUserDayBusAndOutInfo(singleDto.getUserId(), singleDto.getGroupId(), singleDto.getDay(), List.of(AttendanceTypeEnum.BUSINESS_TRIP, AttendanceTypeEnum.STEP_OUT));
        Map<String, List<ShiftsJsonClockInResultVo>> shiftsMap = new HashMap<>();
        List<UserDaySituationData> ruleAndClockList = dailyRuleList.stream().map(rule -> {
            UserDaySituationData situationData = BeanUtil.toBean(rule, UserDaySituationData.class);
            situationData.setRuleId(rule.getId());
            situationData.setIsScheduling(1);
            List<AttendanceClockInResult> inResultList = clockInResultMap.containsKey(rule.getId()) ? clockInResultMap.get(rule.getId()) : CollUtil.newArrayList();
            if (Objects.nonNull(inResultList)) {
                List<ClockInResult> collect1 = inResultList.stream().map(clockInResult -> {
                    ClockInResult clockInResult1 = ClockInResult.builder().build();
                    BeanUtils.copyProperties(clockInResult, clockInResult1);
                    clockInResult1.setClockInTime(clockInMap.containsKey(clockInResult.getClockInId()) ? clockInMap.get(clockInResult.getClockInId()).getClockInTime() : null);
                    return clockInResult1;
                }).collect(Collectors.toList());
                situationData.setClockInResultList(collect1);
                shiftsMap.put(rule.getId(), collect1.stream().map(item -> {
                    ShiftsJsonClockInResultVo clockInResultVo = ShiftsJsonClockInResultVo.builder().build();
                    BeanUtils.copyProperties(item, clockInResultVo);
                    return clockInResultVo;
                }).collect(Collectors.toList()));
            }
            return situationData;
        }).collect(Collectors.toList());
        AttendanceDayStatistics dayStaVo = new AttendanceDayStatistics();
        dayStaVo.setAttendanceRatio(attendanceRatio);
        dayStaVo.setDate(singleDto.getDay());
        dayStaVo.setUserId(singleDto.getUserId());
        dayStaVo.setGroupId(singleDto.getGroupId());
        dayStaVo.setIsScheduling(0);
        dayStaVo.setCreatorTime(new Date());
        // 处理各项统计数据
        computeDayStatisticsData(dayStaVo, attendanceRatio, payrollProportion, ruleAndClockList, busOrOutApproveList, leaveTypeMap);
        dayStaVo.setJoinTime(Objects.nonNull(groupUser) ? groupUser.getCreatorTime() : null);
        dayStaVo.setSelfGroup(ruleService.isExistStatus(groupUsers, singleDto.getDay()) > 1 ? 1 : 0);
        List<DayStatisticsShiftsJsonVo> shiftsJsonVos = Lists.newArrayList();
        List<FtbAttendanceDailyRule> dailyRulesFilter = dailyRuleList.stream().filter(rule -> Objects.equals(AttendanceTypeEnum.BUSINESS_TRIP.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.STEP_OUT.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.ORDINARY.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.REST.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.LEAVE.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.WORKOVERTIME.getCode(), rule.getAttendanceType()) || Objects.equals(AttendanceTypeEnum.HOLIDAYS.getCode(), rule.getAttendanceType())).collect(Collectors.toList());
        dailyRulesFilter.forEach(item -> {
            DayStatisticsShiftsJsonVo shiftsJsonVo = DayStatisticsShiftsJsonVo.builder().build();
            BeanUtils.copyProperties(item, shiftsJsonVo);
            shiftsJsonVo.setRuleId(item.getId());
            if (shiftsMap.containsKey(item.getId())) {
                shiftsJsonVo.setClockInResultList(shiftsMap.containsKey(item.getId()) ? shiftsMap.get(item.getId()) : CollUtil.newArrayList());
            }
            shiftsJsonVos.add(shiftsJsonVo);
        });
        dayStaVo.setShiftsJson(JSON.toJSONString(shiftsJsonVos));
        dayStaVo.setIsScheduling(CollUtil.isEmpty(dailyRulesFilter) ? 0 : 1);
        // 是否需要打卡
        boolean anyMatch = dailyRulesFilter.stream().anyMatch(item -> item.getAttendanceType().equals(AttendanceTypeEnum.ORDINARY.getCode()) || item.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode()));
        dayStaVo.setIsClocked(anyMatch ? 1 : 0);
        dayStaVo.setCustomLeaveJson(DayStatisticsUtils.getCustomLeaveJson(leaveTypeNameMap, leaveTypeMap));
        //获取提醒时间
        Date date = ruleService.getDayEndRuleDeletionDate(singleDto.getUserId(), singleDto.getDay());
        if (Objects.nonNull(date)) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            calendar.add(Calendar.MINUTE, 2);
            dayStaVo.setRemindTime(calendar.getTime());
        }
        // 明确指定更新条件，避免默认行为
        LambdaUpdateWrapper<AttendanceDayStatistics> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(AttendanceDayStatistics::getGroupId, singleDto.getGroupId())
                .eq(AttendanceDayStatistics::getUserId, singleDto.getUserId())
                .eq(AttendanceDayStatistics::getDate, singleDto.getDay());
        // 仅在插入时需要设置ID
        if (statisticsService.count(updateWrapper) == 0) {
            dayStaVo.setId(RandomUtil.uuId());
        }
        statisticsService.saveOrUpdate(dayStaVo, updateWrapper);
    }

    /**
     * 生成单个考勤组指定人员空白统计数据
     */
    @Transactional(rollbackFor = Exception.class)
    public void handleNailData(String groupId, List<String> userIds, Date day) {
        Date date = new Date();
        AttendanceBaseSetting baseSetting = getAttendanceRatio(groupId);
        BigDecimal attendanceRatio = Objects.isNull(baseSetting) ? BigDecimal.valueOf(8) : baseSetting.getAttendanceRatio();
        List<AttendanceDayStatistics> statisticList = new ArrayList<>();
        Date start = DateUtil.beginOfDay(date);
        Date end = DateUtil.endOfDay(date);
        List<AttendanceGroupUser> attendanceGroupUsers = groupUsers.getAttendanceGroupUsersOfSecondment(start, end, userIds, CollUtil.newArrayList(groupId));
        Map<String, List<AttendanceGroupUser>> userJoinDateMap = attendanceGroupUsers.stream().collect(Collectors.groupingBy(AttendanceGroupUser::getUserId));
        for (String userId : userIds) {
            if (!userJoinDateMap.containsKey(userId)) {
                continue;
            }
            List<AttendanceGroupUser> attendanceGroupUserList = userJoinDateMap.get(userId);
            AttendanceGroupUser attendanceGroupUser = attendanceGroupUserList.stream().sorted(Comparator.comparing(AttendanceGroupUser::getCreatorTime)).reduce((r1, r2) -> r2).orElse(null);
            AttendanceDayStatistics dayStatistics = AttendanceDayStatistics.builder().id(RandomUtil.uuId()).attendanceRatio(attendanceRatio).date(day).userId(userId).groupId(groupId).selfGroup(ruleService.isExistStatus(attendanceGroupUserList, day) > 1 ? 1 : 0).joinTime(Objects.nonNull(attendanceGroupUser) ? attendanceGroupUser.getCreatorTime() : null).remindTime(DateUtil.endOfDay(day)).creatorTime(date).build();
            // 获取外出出差记录
            List<OutOrBusApproveVo> busOrOutApproveVoList = this.ruleService.getUserDayBusAndOutInfo(userId, groupId, day, List.of(AttendanceTypeEnum.BUSINESS_TRIP, AttendanceTypeEnum.STEP_OUT));
            // 获取外出出差时长
            DayStatisticsUtils.processBusOrOutData(busOrOutApproveVoList, dayStatistics, attendanceRatio);
            statisticList.add(dayStatistics);
        }
        //按照用户+考勤组+日期进行去重
        if (CollUtil.isNotEmpty(statisticList)) {
            // 先按关键字段分组
            Map<String, List<AttendanceDayStatistics>> grouped = statisticList.stream().collect(Collectors.groupingBy(stat -> String.format("%s_%s_%s", stat.getUserId(), stat.getGroupId(), stat.getDate())));
            // 从每个组中取第一个（或根据业务逻辑合并）
            statisticList = grouped.values().stream().map(list1 -> list1.get(0)).collect(Collectors.toList());
        }
        //按照用户+考勤组+日期进行去重
        if (CollUtil.isNotEmpty(statisticList)) {
            // 先按关键字段分组
            Map<String, List<AttendanceDayStatistics>> grouped = statisticList.stream().collect(Collectors.groupingBy(stat -> String.format("%s_%s_%s", stat.getUserId(), stat.getGroupId(), stat.getDate())));
            // 从每个组中取第一个（或根据业务逻辑合并）
            statisticList = grouped.values().stream().map(list1 -> list1.get(0)).collect(Collectors.toList());
        }
        // 逐条处理数据，利用唯一索引避免重复
        for (AttendanceDayStatistics dayStatistics : statisticList) {
            // 明确指定更新条件，避免默认行为
            LambdaUpdateWrapper<AttendanceDayStatistics> updateWrapper = Wrappers.lambdaUpdate();
            updateWrapper.eq(AttendanceDayStatistics::getGroupId, dayStatistics.getGroupId())
                    .eq(AttendanceDayStatistics::getUserId, dayStatistics.getUserId())
                    .eq(AttendanceDayStatistics::getDate, dayStatistics.getDate());
            // 仅在插入时需要设置ID
            if (statisticsService.count(updateWrapper) == 0) {
                dayStatistics.setId(RandomUtil.uuId());
            }
            statisticsService.saveOrUpdate(dayStatistics, updateWrapper);
        }
    }

    /**
     * 计算当日统计数据
     *
     * @param statisticsListVo    日统计数据
     * @param attendanceRatio     出勤换算比
     * @param payrollProportion   当天划线排班计薪工时占比MutablePair<计薪工时小时数, 计薪工时小时数/分钟>
     * @param dataList            用户排班打卡数据集合
     * @param busOrOutApproveList 外出出差数据集合
     * @param leaveTypeMap        请假数据
     */
    private void computeDayStatisticsData(AttendanceDayStatistics statisticsListVo, BigDecimal attendanceRatio, MutablePair<BigDecimal, BigDecimal> payrollProportion, List<UserDaySituationData> dataList, List<OutOrBusApproveVo> busOrOutApproveList, Map<String, LeaveTypeStaModel> leaveTypeMap) {
        // 获取全天班、半天班、划线排班的班次比以及计薪工时Map<班次类型, MutablePair<班次比, 计薪工时占比>>
        Map<Integer, MutablePair<BigDecimal, MutablePair<BigDecimal, BigDecimal>>> schedulesTypeMap = DayStatisticsUtils.processClassTypeData(dataList, attendanceRatio, payrollProportion);
        // 处理请假、调休数据
        DayStatisticsUtils.processLeaveOrCompensationData(statisticsListVo, leaveTypeMap);
        // 处理外出、出差时长
        DayStatisticsUtils.processBusOrOutData(busOrOutApproveList, statisticsListVo, attendanceRatio);
        // 处理应出勤、实际出勤
        DayStatisticsUtils.processShouldAttendData(dataList, schedulesTypeMap, statisticsListVo);
        // 处理有效出勤处理
        DayStatisticsUtils.processEffectiveAttendData(dataList, schedulesTypeMap, statisticsListVo);
        // 处理各种打卡类型的统计项
        DayStatisticsUtils.processStatisticalItems(attendanceRatio, dataList, schedulesTypeMap, statisticsListVo);
    }
}
