package cn.iocoder.yudao.module.bpm.service.oa;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.common.pojo.BpmOALeaveDTO;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.bpm.api.oa.vo.leave.BpmOALeaveRpcVO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.leave.BpmOALeaveCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.leave.BpmOALeavePageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.leave.CalculateAndVerifyLeaveDTO;
import cn.iocoder.yudao.module.bpm.convert.oa.BpmOALeaveConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOALeaveMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmHistoryProcessInstanceService;
import cn.iocoder.yudao.module.system.api.attendance.AttendanceApi;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendanceTimeRangeInfoDTO;
import cn.iocoder.yudao.module.system.api.attendance.vo.AttendanceGroupShiftItemVO;
import cn.iocoder.yudao.module.system.api.attendance.vo.AttendanceTimeRangeInfoVO;
import cn.iocoder.yudao.module.system.api.holiday.HolidayApi;
import cn.iocoder.yudao.module.system.api.holiday.dto.CreateUserHolidayDTO;
import cn.iocoder.yudao.module.system.api.holiday.vo.HolidaySettingVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;

/**
 * OA 请假申请 Service 实现类
 *
 * @author jason
 */
@Service
@Validated
public class BpmOALeaveServiceImpl extends BpmOABaseService implements BpmOALeaveService {

    /**
     * OA 请假对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_leave";

    @Resource
    private BpmOALeaveMapper leaveMapper;

    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private AttendanceApi attendanceApi;
    @Resource
    private BpmHistoryProcessInstanceService historyProcessInstanceService;
    @Resource
    private HolidayApi holidayApi;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createLeave(Long userId, BpmOALeaveCreateReqVO createReqVO) {
        BpmOALeaveDO leave = BpmOALeaveConvert.INSTANCE.convert(createReqVO)
                .setUserId(userId)
                .setFileItems(createReqVO.getFileItems())
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());

        // 构建请假开始 or 结束时间
        LocalDateTime[] times = this.builderLeaveTime(leave);
        List<Integer> processingOrPassed = BpmProcessInstanceResultEnum.processingOrPassed();
        List<BpmOALeaveDO> bpmOALeaveDOS = leaveMapper.selectList(new LambdaQueryWrapper<BpmOALeaveDO>()
                .eq(BpmOALeaveDO::getUserId, userId)
                .le(BpmOALeaveDO::getStartTime, leave.getEndTime())
                .gt(BpmOALeaveDO::getEndTime, leave.getStartTime())
                .in(BpmOALeaveDO::getResult, processingOrPassed)
        );
        // -- 判断是否有重复时间
        if (CollectionUtil.isNotEmpty(bpmOALeaveDOS)) {
            for (BpmOALeaveDO bpmOALeaveDO : bpmOALeaveDOS) {
                LocalDateTime[] itemTimes = this.builderLeaveTime(bpmOALeaveDO);
                boolean intersects = LocalDateTimeUtils.intersects(times[0], times[1], itemTimes[0], itemTimes[1]);
                if (intersects) {
                    throw exception(THERE_IS_ALREADY_A_RECORD_OF_REQUESTING_LEAVE_DURING_THIS_TIME_PERIOD);
                }
            }
        }
        // -- 计算用户假期 -- 内含 判断假期是否充足
        BigDecimal duration = this.calculateAndVerifyTheNumberOfLeaveDays(leave);
        // 插入 OA 请假单
        leaveMapper.insert(leave);

        // 发起 BPM 流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
//        processInstanceVariables.put("duration", leave.getDuration().toString());
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
                        .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId()))).getCheckedData();

        // 将工作流的编号，更新到 OA 请假单中
        leaveMapper.updateById(new BpmOALeaveDO().setId(leave.getId()).setProcessInstanceId(processInstanceId));
        // 判断是否为重新发起的流程
        if (createReqVO.getProcessInstanceId() != null && createReqVO.getResult() == 3) {
            historyProcessInstanceService.createHistoryProcessInstance(processInstanceId, createReqVO.getProcessInstanceId());
        }
        List<UploadUserFile> fileItems = createReqVO.getFileItems();
        //这里的逻辑，如果fileItems不为空，且有数据，那么说明是上传了附件的，则需要更工作流文件表对应的实例Id
        if (fileItems != null && !fileItems.isEmpty()) {
            uploadBpmFileProcessInstanceId(processInstanceId, fileItems);
        }

        //   创建完流程后 - 减去对应用户的假期余额 - 记录请假原因
        String beginTimeStr = "";
        String endTimeStr = "";
        if (leave.getMinUnit() == 2) {
            beginTimeStr = "1".equals(leave.getStartTimeExtraFields()) ? "上午" : "下午";
            endTimeStr = "2".equals(leave.getStartTimeExtraFields()) ? "上午" : "下午";
        }
        String reason = createReqVO.getReason() +
                " 开始时间:" + leave.getStartTime().format(DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY)) + " " + beginTimeStr +
                " 结束时间:" + leave.getEndTime().format(DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY)) + " " + endTimeStr;
        holidayApi.createUserHoliday(new CreateUserHolidayDTO().setUserId(leave.getUserId()).setHolidaySettingId(leave.getHolidaySettingId())
                .setDirection(1).setHolidayBalance(duration).setReason(reason));
        return leave.getId();
    }

    /**
     * 计算校验用户假期余额是否充足
     *
     * @param leave
     */
    private BigDecimal calculateAndVerifyTheNumberOfLeaveDays(BpmOALeaveDO leave) {
        //-- 判断用户假期额度是否充足 - 根据假期配置来计算出请假时长先
        CommonResult<HolidaySettingVO> holidaySettings = holidayApi.getHolidaySettings(leave.getUserId(), leave.getHolidaySettingId());
        if (!holidaySettings.isSuccess() || holidaySettings.getData() == null) {
            throw exception(GET_USER_HOLIDAY_EXCEPTION);
        }
        HolidaySettingVO vo = holidaySettings.getCheckedData();
        leave.setLeaveName(vo.getName());
        // 计算请假多久 - ok 怎么计算？
        // 先判断是什么类型的请假，然后计算请假时长
        // --- 草 - 这里还要根据用户的考勤组计算 - 计算是否是工作时间 妈的
        Integer minUnit = leave.getMinUnit();
        BigDecimal duration = BigDecimal.ZERO;
        // -- 先拿到请假的这几天 - 完了后去获取当前用户所在考勤组这几天是否需要工作 - 是不是工作时间
        if (vo.getCalculationMethod() == 0) {
            if (minUnit == 1) {
                // 按天
                // -- 如果是同一天的话 +1
                duration = new BigDecimal(String.valueOf(LocalDateTimeUtil.between(leave.getStartTime(), leave.getEndTime(), ChronoUnit.DAYS) + 1));
            } else if (minUnit == 2) {
                // -- 按半天
                duration = new BigDecimal(String.valueOf(LocalDateTimeUtil.between(leave.getStartTime(), leave.getEndTime(), ChronoUnit.DAYS) + 1));
                if (leave.getStartTimeExtraFields().equals(leave.getEndTimeExtraFields())) {
                    duration = duration.subtract(new BigDecimal("0.5"));
                } else if (Integer.parseInt(leave.getStartTimeExtraFields()) > Integer.parseInt(leave.getEndTimeExtraFields())) {
                    duration = duration.subtract(BigDecimal.ONE);
                }
            } else {
                // 按小时
                // -- 计算出相差的分钟
                long betweenMinutes = LocalDateTimeUtil.between(leave.getStartTime(), leave.getEndTime(), ChronoUnit.MINUTES);
                duration = this.hourRounding(vo.getRoundFlag(), vo.getRoundUnit(), vo.getRoundDirection(), betweenMinutes);
            }
        } else {
            //获取当前登录用户所在考勤组 以及每天的班次
            Map<String, AttendanceTimeRangeInfoVO> dataList = this.getAttendanceInfoByTimeRange(new AttendanceTimeRangeInfoDTO()
                    .setUserId(leave.getUserId()).setStartTime(leave.getStartTime()).setEndTime(leave.getEndTime()));
            List<String> list = DateUtils.betweenDayList(leave.getStartTime(), leave.getEndTime());
            if (minUnit == 1) {
                for (String time : list) {
                    AttendanceTimeRangeInfoVO attendanceTimeRangeInfoVO = dataList.get(time);
                    if (attendanceTimeRangeInfoVO == null || attendanceTimeRangeInfoVO.getNeedAttendance().equals(0)
                            || CollectionUtil.isEmpty(attendanceTimeRangeInfoVO.getItems())) {
                        continue;
                    }
                    duration = duration.add(BigDecimal.ONE);
                }
            } else if (minUnit == 2) {
                // -- 按半天
                // -- 假设每天都需要上班 那么直接看第一天的开始时间和最后一天的结束时间即可 - 那么 也有可能他其中也有不上班的 - 不上班的话 - 去一天
                BigDecimal withoutWorkDayNum = BigDecimal.ZERO;
                duration = new BigDecimal(String.valueOf(LocalDateTimeUtil.between(leave.getStartTime(), leave.getEndTime(), ChronoUnit.DAYS) + 1));
                if (leave.getStartTimeExtraFields().equals(leave.getEndTimeExtraFields())) {
                    duration = duration.subtract(new BigDecimal("0.5"));
                } else if (Integer.parseInt(leave.getStartTimeExtraFields()) > Integer.parseInt(leave.getEndTimeExtraFields())) {
                    duration = duration.subtract(BigDecimal.ONE);
                }
                for (String time : list) {
                    AttendanceTimeRangeInfoVO attendanceTimeRangeInfoVO = dataList.get(time);
                    if (attendanceTimeRangeInfoVO == null || attendanceTimeRangeInfoVO.getNeedAttendance().equals(0)
                            || CollectionUtil.isEmpty(attendanceTimeRangeInfoVO.getItems())) {
                        LocalDateTime thisTime = LocalDateTimeUtil.parseDate(time, Constants.REPO_DATE_FORMAT).atStartOfDay();
                        if (LocalDateTimeUtil.isSameDay(thisTime, leave.getStartTime())) {
                            // -- 如果是开始时间
                            if (Objects.equals(leave.getStartTimeExtraFields(), "1")) {
                                withoutWorkDayNum = withoutWorkDayNum.add(BigDecimal.ONE);
                            } else {
                                withoutWorkDayNum = withoutWorkDayNum.add(new BigDecimal("0.5"));
                            }
                        } else if (LocalDateTimeUtil.isSameDay(thisTime, leave.getEndTime())) {
                            // -- 如果是结束时间
                            if (Objects.equals(leave.getStartTimeExtraFields(), "1")) {
                                withoutWorkDayNum = withoutWorkDayNum.add(new BigDecimal("0.5"));
                            } else {
                                withoutWorkDayNum = withoutWorkDayNum.add(BigDecimal.ONE);
                            }
                        } else {
                            withoutWorkDayNum = withoutWorkDayNum.add(BigDecimal.ONE);
                        }
                    }
                }
                duration = duration.subtract(withoutWorkDayNum);
            } else {
                long totalWorkMinutes = 0;
                for (String time : list) {
                    AttendanceTimeRangeInfoVO attendanceTimeRangeInfoVO = dataList.get(time);
                    if (attendanceTimeRangeInfoVO == null || attendanceTimeRangeInfoVO.getNeedAttendance().equals(0)
                            || CollectionUtil.isEmpty(attendanceTimeRangeInfoVO.getItems())) {
                        continue;
                    }
                    // 获取当天时间 - 转为LocalDateTime
                    LocalDateTime thisTime = LocalDateTimeUtil.parseDate(time, Constants.REPO_DATE_FORMAT).atStartOfDay();

                    // 升序从小到大
                    List<AttendanceGroupShiftItemVO> items = attendanceTimeRangeInfoVO.getItems().stream()
                            .sorted(Comparator.comparingInt(AttendanceGroupShiftItemVO::getLevel))
                            .collect(Collectors.toList());

                    AttendanceGroupShiftItemVO first = CollectionUtil.getFirst(items);
                    AttendanceGroupShiftItemVO last = CollectionUtil.getLast(items);
                    LocalDateTime beginTime = DateUtils.buildHHmmLocalDateTime(first.getBeginTime(), thisTime);

                    LocalDateTime lastTime = thisTime;
                    if (Arrays.asList(1, 2).contains(last.getNextDayFlag())) {
                        lastTime = lastTime.plusDays(1);
                    }

                    LocalDateTime endTime = DateUtils.buildHHmmLocalDateTime(last.getEndTime(), lastTime);
                    // -- 间隔的时间
                    long intervalTime = 0L;
                    int size = items.size();
                    if (size > 1) {
                        for (int i = 0; i < size - 1; i++) {
                            // -- 第一天的话不可能所有时间都跨天 只可能是结束时间跨天
                            LocalDateTime top = DateUtils.buildHHmmLocalDateTime(items.get(i).getBeginTime(), thisTime);
                            LocalDateTime next = DateUtils.buildHHmmLocalDateTime(items.get(i + 1).getEndTime(), thisTime);
                            // -- 只有这种情况需要设置下跨天
                            if (items.get(i).getNextDayFlag() == 0 && items.get(i + 1).getNextDayFlag() == 1) {
                                next = next.plusDays(1);
                            }
                            long between = LocalDateTimeUtil.between(top, next, ChronoUnit.MINUTES);
                            intervalTime = intervalTime + between;
                        }
                    }

                    // -- 一天的时间
                    long betweenMinutes = LocalDateTimeUtil.between(beginTime, endTime, ChronoUnit.MINUTES) - intervalTime;
                    // -- 一天中休息的时间 (请假开始和结束时间特殊处理过)
                    long allRestTime = 0L;

                    for (AttendanceGroupShiftItemVO item : items) {
                        LocalDateTime workBeginTime = DateUtils.buildHHmmLocalDateTime(item.getBeginTime(), thisTime);
                        LocalDateTime workEndTime = DateUtils.buildHHmmLocalDateTime(item.getEndTime(), thisTime);
                        if (Arrays.asList(1, 2).contains(item.getNextDayFlag())) {
                            workEndTime = workEndTime.plusDays(1);
                        }
                        boolean beginSameDayFlag = LocalDateTimeUtil.isSameDay(thisTime, leave.getStartTime());
                        if (beginSameDayFlag) {
                            // 如果请假开始时间 = 当前时间
                            // 判断开始时间是否是上班时间
                            boolean flag = LocalDateTimeUtil.isIn(leave.getStartTime(), workBeginTime, workEndTime);
                            if (flag) {
                                // 如果请假时间在当前时间段 - 再去判断 是否再休息时间段内 - 如果是再休息时间段内的话 拿休息结束时间当作请假开始时间
                                if (item.getRestFlag() == 1) {
                                    // -- 这里分三个情况 - 1.在工作开始时间 到 休息开始时间内 2.在休息时间内 3.在休息结束时间到工作结束时间
                                    LocalDateTime restBeginTime = DateUtils.buildHHmmLocalDateTime(item.getRestBeginTime(), lastTime);
                                    LocalDateTime restEndTime = DateUtils.buildHHmmLocalDateTime(item.getRestEndTime(), lastTime);
                                    // -- 如果休息的结束时间小于休息的开始时间 - 说明跨天了
                                    if (restBeginTime.isAfter(restEndTime)) {
                                        restEndTime = restEndTime.plusDays(1);
                                    }
                                    long restTime = LocalDateTimeUtil.between(restBeginTime, restEndTime, ChronoUnit.MINUTES);
                                    if (LocalDateTimeUtil.isIn(leave.getStartTime(), workBeginTime, restBeginTime)) {
                                        allRestTime = allRestTime + LocalDateTimeUtil.between(workBeginTime, leave.getStartTime(), ChronoUnit.MINUTES) + restTime;

                                    } else if (LocalDateTimeUtil.isIn(leave.getStartTime(), restBeginTime, restEndTime)) {
                                        allRestTime = allRestTime + LocalDateTimeUtil.between(workBeginTime, restBeginTime, ChronoUnit.MINUTES) + restTime;
                                    } else if (LocalDateTimeUtil.isIn(leave.getStartTime(), restEndTime, workEndTime)) {
                                        // -- 休息时间 = 工作开始时间 到 请假开始时间 - 期间的休息时间(因为总的已经减过了)
                                        allRestTime = allRestTime + LocalDateTimeUtil.between(workBeginTime, leave.getStartTime(), ChronoUnit.MINUTES);
                                    }
                                } else {
                                    allRestTime = allRestTime + LocalDateTimeUtil.between(workBeginTime, leave.getStartTime(), ChronoUnit.MINUTES);
                                }
                            } else {
                                // -- 如果请假时间不在当前工作时间段内段话则把当前工作时间当做休息时间 -
                                allRestTime = allRestTime + LocalDateTimeUtil.between(workBeginTime, workEndTime, ChronoUnit.MINUTES);
                            }
                        }

                        boolean endSameDayFlag = LocalDateTimeUtil.isSameDay(thisTime, leave.getEndTime());
                        if (endSameDayFlag) {
                            boolean flag = LocalDateTimeUtil.isIn(leave.getEndTime(), workBeginTime, workEndTime);
                            if (flag) {
                                // 如果请假时间在当前时间段 - 再去判断 是否再休息时间段内 - 如果是再休息时间段内的话 拿休息结束时间当作请假开始时间
                                if (item.getRestFlag() == 1) {
                                    // -- 这里分三个情况 - 1.在工作开始时间 到 休息开始时间内 2.在休息时间内 3.在休息结束时间到工作结束时间
                                    LocalDateTime restBeginTime = DateUtils.buildHHmmLocalDateTime(item.getRestBeginTime(), lastTime);
                                    LocalDateTime restEndTime = DateUtils.buildHHmmLocalDateTime(item.getRestEndTime(), lastTime);
                                    // -- 如果休息的结束时间小于休息的开始时间 - 说明跨天了
                                    if (restBeginTime.isAfter(restEndTime)) {
                                        restEndTime = restEndTime.plusDays(1);
                                    }
                                    long restTime = LocalDateTimeUtil.between(restBeginTime, restEndTime, ChronoUnit.MINUTES);
                                    if (LocalDateTimeUtil.isIn(leave.getEndTime(), workBeginTime, restBeginTime)) {
                                        allRestTime = allRestTime + LocalDateTimeUtil.between(leave.getEndTime(), workEndTime, ChronoUnit.MINUTES);
                                    } else if (LocalDateTimeUtil.isIn(leave.getEndTime(), restBeginTime, restEndTime)) {
                                        allRestTime = allRestTime + LocalDateTimeUtil.between(restBeginTime, workEndTime, ChronoUnit.MINUTES);
                                    } else if (LocalDateTimeUtil.isIn(leave.getEndTime(), restEndTime, workEndTime)) {
                                        // -- 这里需要判断 请假开始时间和结束时间是不是同一天  - 如果是同一天的话 就会出现重复扣除休息时间的情况 - 所以要避免这个情况
                                        if (beginSameDayFlag) {
                                            allRestTime = allRestTime + LocalDateTimeUtil.between(leave.getEndTime(), workEndTime, ChronoUnit.MINUTES);
                                        } else {
                                            allRestTime = allRestTime + LocalDateTimeUtil.between(leave.getEndTime(), workEndTime, ChronoUnit.MINUTES) + restTime;
                                        }
                                    }
                                } else {
                                    allRestTime = allRestTime + LocalDateTimeUtil.between(leave.getEndTime(), workEndTime, ChronoUnit.MINUTES);
                                }
                            }
                        }

                        if (!beginSameDayFlag && !endSameDayFlag) {
                            if (item.getRestFlag() == 1) {
                                LocalDateTime restBeginTime = DateUtils.buildHHmmLocalDateTime(item.getRestBeginTime(), lastTime);
                                LocalDateTime restEndTime = DateUtils.buildHHmmLocalDateTime(item.getRestEndTime(), lastTime);
                                // -- 如果休息的结束时间小于休息的开始时间 - 说明跨天了
                                if (restBeginTime.isAfter(restEndTime)) {
                                    restEndTime = restEndTime.plusDays(1);
                                }
                                long restTime = LocalDateTimeUtil.between(restBeginTime, restEndTime, ChronoUnit.MINUTES);
                                // -- 休息时间 = 工作开始时间 到 请假开始时间 - 期间的休息时间(因为总的已经减过了)
                                allRestTime = allRestTime + restTime;
                            }
                        }
                    }
                    // -- 总的时间 减去休息时间
                    betweenMinutes = betweenMinutes - allRestTime;
                    totalWorkMinutes = totalWorkMinutes + betweenMinutes;
                }
                // 按小时
                duration = this.hourRounding(vo.getRoundFlag(), vo.getRoundUnit(), vo.getRoundDirection(), totalWorkMinutes);
            }
        }
        if (vo.getStatus() == 1 && vo.getHolidayBalance() != null && vo.getHolidayBalance().compareTo(duration) < 0) {
            throw exception(INSUFFICIENT_LEAVE_BALANCE);
        }
        return duration;
    }


    /**
     * 小时计算取整
     *
     * @param roundFlag
     * @param roundUnit
     * @param roundDirection
     * @param totalWorkMinutes
     * @return
     */
    private BigDecimal hourRounding(Integer roundFlag, Integer roundUnit, Integer roundDirection, long totalWorkMinutes) {
        BigDecimal roundedDuration;
        if (roundFlag == 0) {
            return BigDecimal.valueOf(totalWorkMinutes).divide(BigDecimal.valueOf(60.0), 2, RoundingMode.HALF_UP);

        } else {
            if (roundUnit == 0) {
                // 按半小时取整
                if (roundDirection == 0) {
                    // 向下取整
                    roundedDuration = BigDecimal.valueOf(Math.floor(totalWorkMinutes / 30.0) * 30).divide(BigDecimal.valueOf(60.0), 2, RoundingMode.HALF_UP);
                } else {
                    // 向上取整
                    roundedDuration = BigDecimal.valueOf(Math.ceil(totalWorkMinutes / 30.0) * 30).divide(BigDecimal.valueOf(60.0), 2, RoundingMode.HALF_UP);
                }
            } else {
                // 按一个小时取整
                if (roundDirection == 0) {
                    // 向下取整
                    roundedDuration = BigDecimal.valueOf(Math.floor(totalWorkMinutes / 60.0) * 60).divide(BigDecimal.valueOf(60.0), 2, RoundingMode.HALF_UP);
                } else {
                    // 向上取整
                    roundedDuration = BigDecimal.valueOf(Math.ceil(totalWorkMinutes / 60.0) * 60).divide(BigDecimal.valueOf(60.0), 2, RoundingMode.HALF_UP);
                }
            }
        }
        return roundedDuration;
    }


    /**
     * 按时间范围获取出勤信息
     *
     * @param attendanceTimeRangeInfoDTO
     * @return
     */
    @Override
    public Map<String, AttendanceTimeRangeInfoVO> getAttendanceInfoByTimeRange(AttendanceTimeRangeInfoDTO attendanceTimeRangeInfoDTO) {
        CommonResult<Map<String, AttendanceTimeRangeInfoVO>> attendanceInfoByTimeRange = attendanceApi.getAttendanceInfoByTimeRange(attendanceTimeRangeInfoDTO);
        if (!attendanceInfoByTimeRange.isSuccess()) {
            throw exception(THE_USER_ATTENDANCE_INFORMATION_IS_ABNORMAL);
        }
        return attendanceInfoByTimeRange.getData();
    }

    /**
     * 构建请假开始结束时间 -
     *
     * @return
     */
    private LocalDateTime[] builderLeaveTime(BpmOALeaveDO leave) {
        LocalDateTime startTime = leave.getStartTime();
        LocalDateTime endTime = leave.getEndTime();
        if (leave.getMinUnit() == 1) {
            startTime = LocalDateTimeUtil.beginOfDay(startTime);
            endTime = LocalDateTimeUtil.endOfDay(endTime);
        } else if (leave.getMinUnit() == 2) {
            if ("1".equals(leave.getStartTimeExtraFields())) {
                // -- 如果开始时间是1 - 则设为0点开始
                startTime = LocalDateTimeUtil.beginOfDay(startTime);
            } else {
                startTime = startTime.withHour(12).withMinute(0).withSecond(0);
            }
            if ("1".equals(leave.getEndTimeExtraFields())) {
                endTime = endTime.withHour(12).withMinute(0).withSecond(0);
            } else {
                endTime = LocalDateTimeUtil.endOfDay(endTime);
            }
        } else if (leave.getMinUnit() == 3) {
            String[] split = leave.getStartTimeExtraFields().split(":");
            startTime = startTime.withHour(Integer.parseInt(split[0])).withMinute(Integer.parseInt(split[1])).withSecond(0);
            split = leave.getEndTimeExtraFields().split(":");
            endTime = endTime.withHour(Integer.parseInt(split[0])).withMinute(Integer.parseInt(split[1])).withSecond(0);
        } else {
            throw exception(WRONG_TIME_FORMAT);
        }
        leave.setStartTime(startTime);
        leave.setEndTime(endTime);
        return new LocalDateTime[]{startTime, endTime};
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLeaveResult(Long id, Integer result) {
        BpmOALeaveDO leave = validateLeaveExists(id);
        leaveMapper.updateById(new BpmOALeaveDO().setId(id).setResult(result));
        // -- 如果是通过 - 则判断当前时间是否是请假开始时间前
        // -- 如果是的话 先插入到redis中 - (事前请假)
        // -- 如果不是 则查询相应的考勤 修改考勤状态 （事后请假 - 还需要判断结束时间是否）
        //
        LocalDateTime now = LocalDateTimeUtil.now();
        if (result.equals(BpmProcessInstanceResultEnum.APPROVE.getResult())) {
            // 事后请假修改考勤 = 考勤的预设已经生成过了 - 并且已经在表里面存在了 - 所以要找到表中的数据 - 修改考勤状态
            CommonResult commonResult = attendanceApi.askingForLeaveAfterwardsToModifyAttendance(new AttendancePunchRecordDTO()
                    .setUserId(leave.getUserId())
                    .setStartTime(leave.getStartTime())
                    .setEndTime(leave.getEndTime())
                    .setLeaveId(id)
                    .setLeaveName(leave.getLeaveName())
            );
            if (!commonResult.isSuccess()) {
                throw exception(FAILED_TO_APPLY_FOR_LEAVE);
            }
            if (now.isBefore(leave.getEndTime())) {
                // 事前请假 = 考勤预设可能还没有生成 - 因为可能请假好几天 - 所以这里处理就比较麻烦点 - 先看下考勤表里面有没有在这个区间的考勤 - 如果有的话先修改考勤状态 -
                // 然后将数据先存入redis - 在设置考勤预设的时候 就去redis 中查询是否有请假 - 有的话预设的时候就预设进去
                BpmOALeaveDTO dto = new BpmOALeaveDTO();
                BeanUtil.copyProperties(leave, dto);
                String key = "leave" + "_" + leave.getUserId().toString();
                stringRedisTemplate.opsForHash().put(key, id.toString(), JSONUtil.toJsonStr(dto));
                // -- 将请假put到redis的map中 -
            }
        } else if (Arrays.asList(BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult()).contains(result)) {
            holidayApi.createUserHoliday(new CreateUserHolidayDTO().setUserId(leave.getUserId()).setHolidaySettingId(leave.getHolidaySettingId())
                    .setDirection(0).setHolidayBalance(leave.getDuration()).setReason("请假审核不通过返回假期余额"));
        }
    }

    private BpmOALeaveDO validateLeaveExists(Long id) {
        BpmOALeaveDO bpmOALeaveDO = leaveMapper.selectById(id);
        if (bpmOALeaveDO == null) {
            throw exception(OA_LEAVE_NOT_EXISTS);
        }
        return bpmOALeaveDO;
    }

    @Override
    public BpmOALeaveDO getLeave(Long id) {
        return leaveMapper.selectById(id);
    }

    @Override
    public PageResult<BpmOALeaveDO> getLeavePage(Long userId, BpmOALeavePageReqVO pageReqVO) {
        return leaveMapper.selectPage(userId, pageReqVO);
    }


    @Override
    public BpmOALeaveDO getByProcessInstanceId(String processInstanceId) {

        List<BpmOALeaveDO> list = leaveMapper.selectList(BpmOALeaveDO::getProcessInstanceId, processInstanceId);
        if (CollectionUtil.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    @Override
    public BigDecimal calculateAndVerifyTheNumberOfLeaveDays(Long loginUserId, CalculateAndVerifyLeaveDTO dto) {
        BpmOALeaveDO leave = BpmOALeaveConvert.INSTANCE.convertCalculateLeaveDTO(dto).setUserId(loginUserId);
        this.builderLeaveTime(leave);
        return this.calculateAndVerifyTheNumberOfLeaveDays(leave);
    }

    @Override
    public List<BpmOALeaveRpcVO> getLeaveListByIds(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<BpmOALeaveDO> bpmOALeaveDOS = leaveMapper.selectBatchIds(ids);
        return BeanUtil.copyToList(bpmOALeaveDOS, BpmOALeaveRpcVO.class);
    }
}
