package com.engine.jucailinkq.attendance.workflow.service.impl;

import com.engine.jucailinkq.attendance.component.persongroup.service.SchedulingResultsService;
import com.engine.jucailinkq.attendance.component.persongroup.service.impl.SchedulingResultsServiceImpl;
import com.engine.jucailinkq.attendance.enums.AccountingUnitEnum;
import com.engine.jucailinkq.attendance.enums.CheckBoxEnum;
import com.engine.jucailinkq.attendance.enums.ClassSegmentTypeEnum;
import com.engine.jucailinkq.attendance.enums.DateTypeEnum;
import com.engine.jucailinkq.attendance.workflow.cmd.GetAskForLeaveRecordListCmd;
import com.engine.jucailinkq.attendance.workflow.cmd.GetRestDayIntervalCmd;
import com.engine.jucailinkq.attendance.workflow.enums.AskAndEvctionWayEnum;
import com.engine.jucailinkq.attendance.workflow.service.AskForLeaveService;
import com.engine.jucailinkq.attendance.workflow.service.MakeUpClockInService;
import com.engine.jucailinkq.common.exception.AttendanceRunTimeException;
import com.engine.common.util.*;
import com.engine.core.impl.Service;
import com.engine.jucailinkq.common.util.CommonUtil;
import com.engine.jucailinkq.common.util.DateUtil;
import com.engine.jucailinkq.common.util.DbTools;
import com.engine.jucailinkq.common.util.Utils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import weaver.general.Util;

import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class AskForLeaveServiceImpl extends Service implements AskForLeaveService {
    private SchedulingResultsService schedulingResultsService = ServiceUtil.getService(SchedulingResultsServiceImpl.class);

    private MakeUpClockInService makeUpClockInService = ServiceUtil.getService(MakeUpClockInServiceImpl.class);

    @Override
    public Map<String, Object> getUesdHolidayItem(Map<String, Object> params) {
        Map<String,Object> resultMap = Maps.newHashMap();
        // 流程表单主表数据
        Map<String,String> mainTableData = (Map<String,String>)params.get("mainTableData");
        // 流程表单明细表1数据
        List<Map<String, String>> detailTableData = (List<Map<String, String>>)params.get("detailTableData");


        if (detailTableData == null || detailTableData.size() == 0){
            return Maps.newHashMap();
        }
        detailTableData = detailTableData.stream().sorted(Comparator.comparing(e-> DateUtil.getTime(e.get("ksrq")).toInstant(ZoneOffset.of("+8")).toEpochMilli())).collect(Collectors.toList());


        //第一笔开始时间
        String firstStartDate = detailTableData.get(0).get("ksrq");

        //请假人员
        String qjry = mainTableData.get("qjry");
        //假期类型
        String jqlx = mainTableData.get("jqlx");

        String sql = "select id,mc,hsdw,hsl,jcbyxsyqjb,yxsydjb,qzsyyxjb from uf_jcl_kq_kqxm where id=?";

        Map<String,Object> holidayItem = DbTools.getSqlToMap(sql,jqlx);


        if (CheckBoxEnum.CHECKED.getKey().equals(holidayItem.get("qzsyyxjb")) &&
                CheckBoxEnum.CHECKED.getKey().equals(holidayItem.get("jcbyxsyqjb"))){
            Map<String,Object> holidayPriorityItem = DbTools.getSqlToMap(sql,holidayItem.get("yxsydjb"));

            sql = "select id,jqid,sxrq,ktsc,yxsc,wxsc,yqsxrq,ztsc from uf_jcl_kq_jqye where ygid=? and jqid=? and sxrq<=? and yqsxrq>=? order by sxrq";

            List<Map<String,Object>> holidayBalanceList = DbTools.getSqlToList(sql,qjry,holidayPriorityItem.get("id"),firstStartDate,firstStartDate);

            Map<String,Object> useItemMap = Maps.newHashMap();
            for (Map<String, String> detail:detailTableData){
                String ksrq = detail.get("ksrq");
                List<Map<String,Object>> list = holidayBalanceList.stream().filter(e-> DateUtil.getTime(e.get("yqsxrq").toString()).compareTo(DateUtil.getTime(ksrq))>=0).collect(Collectors.toList());
                log.debug("ksrq : [{}], list : [{}]",ksrq,list);
                double totalUnearnedLeave = list.stream().mapToDouble(e->Double.valueOf(e.get("wxsc").toString())).sum();
                if (totalUnearnedLeave == 0){
                    List<Map<String,String>> details = (List<Map<String,String>>)useItemMap.get(holidayItem.get("id"));
                    if (details == null){
                        details = Lists.newArrayList();
                    }
                    details.add(detail);
                    useItemMap.put(holidayItem.get("id").toString(),details);
                }else {
                    List<Map<String,String>> details = (List<Map<String,String>>)useItemMap.get(holidayPriorityItem.get("id"));
                    if (details == null){
                        details = Lists.newArrayList();
                    }
                    details.add(detail);
                    useItemMap.put(holidayPriorityItem.get("id").toString(),details);
                }
            }
            //需要使用到2种以上假期
            if (useItemMap.size() > 1){
                throw new AttendanceRunTimeException("优先使用的"+holidayPriorityItem.get("mc")+"余额不足");
            }else {
                if (useItemMap.get(holidayPriorityItem.get("id")) != null){
                    resultMap.put("jqlx",holidayPriorityItem.get("id"));
                    resultMap.put("hsdw",holidayPriorityItem.get("hsdw"));
                    resultMap.put("hsl",holidayPriorityItem.get("hsl"));
                    resultMap.put("mc",holidayPriorityItem.get("mc"));
                    resultMap.put("jcbyxsyqjb",CheckBoxEnum.CHECKED.getKey());
                }else if (useItemMap.get(holidayItem.get("id")) != null){
                    resultMap.put("jqlx",jqlx);
                    resultMap.put("hsdw",holidayItem.get("hsdw"));
                    resultMap.put("hsl",holidayItem.get("hsl"));
                    resultMap.put("mc",holidayItem.get("mc"));
                    resultMap.put("jcbyxsyqjb",CheckBoxEnum.UNCHECKED.getKey());
                }
            }
        }else {
            resultMap.put("jqlx",jqlx);
            resultMap.put("hsdw",holidayItem.get("hsdw"));
            resultMap.put("hsl",holidayItem.get("hsl"));
            resultMap.put("mc",holidayItem.get("mc"));
            resultMap.put("jcbyxsyqjb",CheckBoxEnum.UNCHECKED.getKey());
        }

        return resultMap;
    }

    @Override
    public Map<String, Object> getAskForLeaveRecordList(Map<String, Object> params) {
        return commandExecutor.execute(new GetAskForLeaveRecordListCmd(params,user));
    }

    @Override
    public Map<String, Object> getRestDayInterval(Map<String, Object> params) {
        Map<String, Object> resultMap = Maps.newHashMap();
        String itemId = Util.null2String(params.get("itemId"));
        String sql = "select zdycrqqjndxxb from uf_jcl_kq_kqxm where id=?";
        List<Map<String,Object>> itemList = DbTools.getSqlToList(sql,itemId);
        String zdycrqqjndxxb = Util.null2String(itemList.get(0).get("zdycrqqjndxxb"));
        if (CheckBoxEnum.UNCHECKED.getKey().equals(zdycrqqjndxxb)){
            return resultMap;
        }
        return commandExecutor.execute(new GetRestDayIntervalCmd(params,user));
    }

    /**
     * 批量请假-生成请假信息明细表数据
     */
    @Override
    public Map<String, Object> generateLeaveInfoList(Map<String, Object> params){
        Map<String,Object> resultMap = new HashMap<>();
        try {
            List<String> errorMessage = new ArrayList<>();
            // 流程表单主表数据
            Map<String,String> mainTableData = (Map<String,String>)params.get("mainTableData");
            log.info("mainTableData : {}", mainTableData);
            // 流程表单明细表1数据
            List<Map<String, String>> detailTableData = (List<Map<String, String>>)params.get("detailTableData");
            //需要校验假期额度的假期类型
            List<String> checkAmountJqIdList = CommonUtil.getJqInfoWithAmount();
            //开始日期、结束日期、请假方式、开始时间、结束时间、假期类型、请假时长
            String startDate = Util.null2String(mainTableData.get("ksrq"));
            String endDate = Util.null2String(mainTableData.get("jsrq"));
            String startTime = Util.null2String(mainTableData.get("kssj"));
            String endTime = Util.null2String(mainTableData.get("jssj"));
            String leaveMode = Util.null2String(mainTableData.get("cxjqj"));
            String leaveType = Util.null2String(mainTableData.get("jqlx"));
            String leaveDuration = Util.null2String(mainTableData.get("qjsc"));
            String dailyRepeat = Util.null2String(mainTableData.get("mtcfsdjq"));
            //获取请假日期集合
            List<String> leaveDateList = DateUtil.getDatesBetween(startDate, endDate);
            //已编辑内容中的假期额度使用信息
            Map<String, Double> editedUseJqed = new HashMap<>();
            //记录本次触发按钮时，目标日期区间内已编辑内容中的<假期类型_人员,使用时长>信息
            Map<String, Double> editedUseJqlxWithEmp = new HashMap<>();
            //假期余额时长已用完的假期余额id
            List<String> unableUseJqyeIdList = new ArrayList<>();
            //记录已编辑的请假记录，<人员id-请假类型-日期, 请假时长>
            Map<String, String> editedLeaveInfo = new HashMap<>();
            //处理明细表1数据，生成触发接口之前编辑内容中已使用的假期额度信息
            editedLeaveInfo = dealDetailTableData(detailTableData, leaveDateList, editedUseJqlxWithEmp, editedUseJqed, unableUseJqyeIdList, editedLeaveInfo);
            //处理主表数据
            String qjr = Util.null2String(mainTableData.get("qjr"));
            //请假人姓名映射
            Map<String, String> empIdToName = new HashMap<>();
            //请假人员列表
            List<String> leaveEmpIdList = new ArrayList<>();
            if (!"".equals(qjr)) {
                leaveEmpIdList = Arrays.asList(qjr.split(","));
                String sql = "select id, lastname from hrmresource where id in (" + qjr + ")";
                List<Map<String, Object>> data = DbTools.getSqlToList(sql);
                empIdToName = data.stream().collect(Collectors.toMap(e->Util.null2String(e.get("id")),e->Util.null2String(e.get("lastname"))));
            }
            //获取填写的请假类型关联的考勤项目
            String sql = "select id,mc,hsdw,hsl,jcbyxsyqjb,yxsydjb,qzsyyxjb,zdycbcndfgzsd,zdycrqqjndxxb,yxyz,zdyzsl,zysd from uf_jcl_kq_kqxm where id=?";
            Map<String,Object> holidayItem = DbTools.getSqlToMap(sql,leaveType);
            //勾选假期类型名称
            String checkItemName = Util.null2String(holidayItem.get("mc"));
            //核算单位
            String hsdw = Util.null2String(holidayItem.get("hsdw"));
            //核算量
            double hsl = Double.valueOf(Util.null2String(holidayItem.get("hsl")));
            //核算单位为天时，只允许请全天假、半天假
            if (hsdw.equals(AccountingUnitEnum.DAY.getKey()) && !leaveMode.equals(AskAndEvctionWayEnum.ALLDAY.getKey()) && !leaveMode.equals(AskAndEvctionWayEnum.HALFDAY.getKey())) {
                resultMap.put("status", false);
                resultMap.put("errorInfo", "假期类型的核算单位为天时，只允许请全天假、半天假！");
                resultMap.put("data", null);
                return resultMap;
            }
            //判断是否需要自动移除时间区间内的非工作时长、自动移除日期区间内的休息日
            boolean removeNonWorkTimeRange = "1".equals(Util.null2String(holidayItem.get("zdycbcndfgzsd")));
            boolean removeNonWorkDayRange = "1".equals(Util.null2String(holidayItem.get("zdycrqqjndxxb")));
            //获取作用时段
            String zysd = Util.null2String(holidayItem.get("zysd"));
            //获取需要统计时长的班段类型集合（仅作用在“指定时间区间”请假方式）
            List<String> countBdlxList = new ArrayList<>();
            List<String> zysdList = new ArrayList<>();
            if (!"".equals(zysd)) {
                zysdList = Arrays.asList(zysd.split(","));
                for(String zysdKey : zysdList) {
                    countBdlxList.add(Utils.getClassSegmenByWorkFor(zysdKey));
                }
                if (!removeNonWorkTimeRange) {
                    countBdlxList.add(ClassSegmentTypeEnum.REST_AND_DINE.getKey());
                    countBdlxList.add(ClassSegmentTypeEnum.REST_PERIOD.getKey());
                    countBdlxList.add(ClassSegmentTypeEnum.DINING_PERIOD.getKey());
                }
            }
            //判断考勤项目是否需要强制使用“优先使用项目”
            boolean useFirstItemSign = CheckBoxEnum.CHECKED.getKey().equals(holidayItem.get("qzsyyxjb")) && CheckBoxEnum.CHECKED.getKey().equals(holidayItem.get("jcbyxsyqjb"));
            Map<String,Object> holidayPriorityItem = useFirstItemSign ? DbTools.getSqlToMap(sql,holidayItem.get("yxsydjb")) : null;
            //遍历人员、日期，生成人员+日期+请假时长的请假信息
            //请假人的请假区间内每一天的日期类型信息
            Map<String, List<String>> restDayInfo = removeNonWorkDayRange ? getRestDayWithEmpId(leaveEmpIdList, leaveType, startDate, endDate) : new HashMap<>();
            //请假人的请假区间内的排班结果
            Map<String, List<Map<String, Object>>> scheduleInfoMap = getScheduleInfoWithEmpId(leaveEmpIdList, DateUtil.beforeDay(startDate,1), DateUtil.AfterDay(endDate,1));

            //收集未关联假期余额的请假明细数据
            List<Map<String, String>> simpleLeaveDetailList;
            if (leaveMode.equals(AskAndEvctionWayEnum.TIME_INTERVAL.getKey()) && !"1".equals(dailyRepeat)) {
                simpleLeaveDetailList = createSimpleLeaveDetailListWithNoDaily(leaveEmpIdList, leaveDateList, removeNonWorkDayRange, scheduleInfoMap,
                        restDayInfo, startDate, endDate, startTime, endTime, countBdlxList, hsdw, hsl);
            } else {
                simpleLeaveDetailList = createSimpleLeaveDetailList(leaveEmpIdList, leaveDateList, removeNonWorkDayRange, scheduleInfoMap,
                        restDayInfo, leaveMode, startTime, endTime, leaveDuration, countBdlxList, hsdw, hsl);
            }
            //收集已关联假期余额的请假明细数据
            List<Map<String, String>> completeLeaveDetailList = new ArrayList<>();
            //按照人员id分组处理请假明细信息，关联假期余额数据
            Map<String, List<Map<String, String>>> leaveDetailGroupByEmp = simpleLeaveDetailList.stream().collect(Collectors.groupingBy(e -> Util.null2String(e.get("qjr"))));

            for(Map.Entry<String,List<Map<String,String>>> entry : leaveDetailGroupByEmp.entrySet()) {
                //校验该人员的考勤周期是否正常
                boolean kqCycleAllow = kqCycleCheck(entry.getKey(), entry.getValue(), errorMessage, empIdToName.get(entry.getKey()));
                if (!kqCycleAllow) {
                    continue;
                }
                //1-收集使用勾选的关联的可使用的余额，才能够生成请假明细，可使用的条件为，a-满足最小使用时长，b-满足使用次数上限，c-存在未休时长
                List<Map<String, Object>> canUseCheckJqyeInfo = collectUsableHolidayBalance(unableUseJqyeIdList, editedUseJqed, holidayItem, startDate, entry.getKey());
                List<Map<String,String>> detailListItem;
                //判断考勤项目是否需要强制使用“优先使用项目”
                if (useFirstItemSign) {
                    //勾选假期类型名称
                    String firstItemName = Util.null2String(holidayPriorityItem.get("mc"));
                    //2-收集优先使用的项目关联的可使用的余额，才能够生成请假明细，可使用的条件为，a-满足最小使用时长，b-满足使用次数上限，c-存在未休时长
                    List<Map<String, Object>> canUseFirstJqyeInfo = collectUsableHolidayBalance(unableUseJqyeIdList, editedUseJqed, holidayPriorityItem, startDate, entry.getKey());
                    detailListItem = matchHolidayBalance(editedLeaveInfo, checkAmountJqIdList, errorMessage, entry.getKey(), empIdToName.get(entry.getKey()), canUseCheckJqyeInfo, canUseFirstJqyeInfo, editedUseJqed,
                            editedUseJqlxWithEmp, entry.getValue(), leaveType, holidayPriorityItem.get("id").toString(), checkItemName, firstItemName);
                } else {
                    detailListItem = matchHolidayBalance(editedLeaveInfo, checkAmountJqIdList, errorMessage, entry.getKey(), empIdToName.get(entry.getKey()), canUseCheckJqyeInfo, null, editedUseJqed,
                            editedUseJqlxWithEmp, entry.getValue(), leaveType, null, checkItemName, null);
                }
                if (detailListItem.size() > 0) {
                    completeLeaveDetailList.addAll(detailListItem);
                }
            }

            if (errorMessage.size() == 0) {
                resultMap.put("status", true);
                resultMap.put("data", completeLeaveDetailList);
            } else {
                resultMap.put("status", false);
                resultMap.put("errorInfo", errorMessage);
                resultMap.put("data", null);
            }

        } catch (Exception e) {
            log.info(e.getMessage());
            resultMap.put("status", false);
            resultMap.put("errorInfo", e.getMessage());
            resultMap.put("data", null);
        }
        return resultMap;
    }

    /**
     * @param detailTableData 明细表1数据
     * @param leaveDateList 请假日期集合
     * @param editedUseJqlxWithEmp 记录本次触发按钮时，目标日期区间内已编辑内容中的<假期类型_人员,使用时长>信息
     * @param editedUseJqed 记录已编辑内容中的假期额度使用信息
     * @param unableUseJqyeIdList 记录假期余额时长已用完的假期余额id
     * @param editedLeaveInfo 记录记录已编辑的请假记录，<人员id-请假类型-日期, 请假时长>
     * @return 处理明细表1数据，生成触发接口之前编辑内容中已使用的假期额度信息
     */
    private Map<String, String> dealDetailTableData(List<Map<String, String>> detailTableData, List<String> leaveDateList, Map<String, Double> editedUseJqlxWithEmp, Map<String, Double> editedUseJqed, List<String> unableUseJqyeIdList, Map<String, String> editedLeaveInfo) {
        if (detailTableData != null && detailTableData.size() > 0){
            for (Map<String, String> detailItem : detailTableData) {
                if (leaveDateList.contains(Util.null2String(detailItem.get("ksrq")))) {
                    double detailSc = "".equals(Util.null2String(detailItem.get("qjsc"))) ? 0 : Double.parseDouble(Util.null2String(detailItem.get("qjsc")));
                    String qjlx = Util.null2String(detailItem.get("qjlx"));
                    String empId = Util.null2String(detailItem.get("qjr"));
                    editedUseJqlxWithEmp.merge(qjlx + "_" + empId, detailSc, Double::sum);
                }
                String jqye = Util.null2String(detailItem.get("jqye"));
                if ("".equals(jqye)) {
                    continue;
                } else if (jqye.contains(",")) {
                    List<String> jqyeIds = Arrays.asList(jqye.split(","));
                    for (int i = 0; i < jqyeIds.size(); i++ ) {
                        //假期余额id会用下划线“_”拼接上该假期余额记录被使用的时长
                        String jqyeIdStr = jqyeIds.get(i);
                        String[] jqyeInfo = jqyeIdStr.split("_");
                        String jqyeId = jqyeInfo[0];
                        double qjsc = Double.parseDouble(jqyeInfo[1]);
                        editedUseJqed.merge(jqyeId, qjsc, Double::sum);
                        //最后一个id前的jqye的id都属于额度已使用完的，记录到已用完假期余额id集合中
                        if (i < jqyeIds.size() - 1) {
                            unableUseJqyeIdList.add(jqyeId);
                        }
                    }
                } else {
                    String[] jqyeInfo = jqye.split("_");
                    String jqyeId = jqyeInfo[0];
                    double qjsc = Double.parseDouble(jqyeInfo[1]);
                    editedUseJqed.merge(jqyeId, qjsc, Double::sum);
                }
            }
            editedLeaveInfo = detailTableData.stream()
                    .collect(Collectors.toMap(e-> Util.null2String(e.get("qjr")) + "_" + Util.null2String(e.get("qjlx")) + "_" + Util.null2String(e.get("ksrq")),
                            e->Util.null2String(e.get("qjsc"))));
        }
        return editedLeaveInfo;
    }

    /**
     * @param leaveEmpIdList 请假人员id集合
     * @param leaveDateList 请假日期集合
     * @param removeNonWorkDayRange 是否移除非工作日时长
     * @param scheduleInfoMap 排班信息
     * @param restDayInfo 休息日信息
     * @param leaveMode 请假方式
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param leaveDuration 请假时长
     * @param countBdlxList 收集需要统计时长的班段类型
     * @return 组装初步的请假明细
     */
    private List<Map<String, String>> createSimpleLeaveDetailList(List<String> leaveEmpIdList, List<String> leaveDateList, boolean removeNonWorkDayRange,
                                                                  Map<String, List<Map<String, Object>>> scheduleInfoMap, Map<String, List<String>> restDayInfo,
                                                                  String leaveMode, String startTime, String endTime, String leaveDuration, List<String> countBdlxList,
                                                                  String hsdw, double hsl) {
        List<Map<String, String>> simpleLeaveDetailList = new ArrayList<>();
        Map<String, String> simpleLeaveDetailItem;

        for (String leaveEmpId : leaveEmpIdList) {
            //需要自动移除日期区间内的休息日时，去除请假日期区间中的休息日
            List<Map<String, Object>> scheduleInfoList = scheduleInfoMap.getOrDefault(leaveEmpId, new ArrayList<>());
            Map<String, String> dateToBcxxMap = scheduleInfoList.stream().collect(Collectors.toMap(e->Util.null2String(e.get("bcrq")),e->Util.null2String(e.get("bcxx"))));

            List<String> restDateList = new ArrayList<>();
            if (removeNonWorkDayRange) {
                //排班结果中休息的日期
                List<String> restDateListFromSchedule = scheduleInfoList.stream()
                        .filter(f -> Util.null2String(f.get("sfxx")).equals(CheckBoxEnum.CHECKED.getKey()))
                        .map(e -> Util.null2String(e.get("bcrq")))
                        .collect(Collectors.toList());
                List<String> allDateListFromSchedule = scheduleInfoList.stream().map(e -> Util.null2String(e.get("bcrq"))).collect(Collectors.toList());
                //排班结果中未出现的日期
                List<String> nonSetDateListFromSchedule = leaveDateList.stream().filter(f -> !allDateListFromSchedule.contains(f)).collect(Collectors.toList());
                //企业日历中人员对应日期的休息日
                restDateList = restDayInfo.get(leaveEmpId);
                //筛选排班信息无法辨别的日期，依靠企业日历的日期信息
                restDateList = restDateList.stream().filter(nonSetDateListFromSchedule::contains).collect(Collectors.toList());
                restDateList.addAll(restDateListFromSchedule);
            }
            for (String leaveDate : leaveDateList) {
                //请假方式为“指定时间区间”时
                if (leaveMode.equals(AskAndEvctionWayEnum.TIME_INTERVAL.getKey())) {
                    simpleLeaveDetailItem = new HashMap<>();
                    //组装初步的请假明细数据
                    simpleLeaveDetailItem.put("qjr", leaveEmpId);
                    simpleLeaveDetailItem.put("ksrq", leaveDate);
                    simpleLeaveDetailItem.put("jsrq", leaveDate);
                    simpleLeaveDetailItem.put("kssj", startTime);
                    simpleLeaveDetailItem.put("jssj", endTime);
                    //获取当天班次id
                    String currentDayBcId = restDateList.contains(leaveDate) ? "" : Util.null2String(dateToBcxxMap.get(leaveDate)).split("-")[0];
                    //获取前一天班次id
                    String yesterday = DateUtil.beforeDay(leaveDate,1);
                    String yesterdayBcId = restDateList.contains(yesterday) ? "" : Util.null2String(dateToBcxxMap.get(yesterday)).split("-")[0];
                    //获取次日班次id
                    String nextDay = DateUtil.AfterDay(leaveDate,1);
                    String nextDayBcId = restDateList.contains(nextDay) ? "" : Util.null2String(dateToBcxxMap.get(nextDay)).split("-")[0];

                    String sql = "";
                    List<Map<String, Object>> bcDetailData;
                    String endDate = leaveDate;
                    int scMinutes = 0;
                    //开始时时和结束时间存在跨天情况时,即开始时间大于等于结束时间
                    if (startTime.compareTo(endTime) >= 0) {
                        endDate = nextDay;
                        simpleLeaveDetailItem.put("jsrq", nextDay);
                    }

                    if (!"".equals(yesterdayBcId)) {
                        sql = "select id, bdlx, gsrq, kssj as dtkssj, jssj as dtjssj from uf_jcl_kq_bcxx_dt1 where mainid  = " + yesterdayBcId;
                        bcDetailData = DbTools.getSqlToList(sql);
                        bcDetailData = bcDetailData.stream().filter(e -> countBdlxList.contains(Util.null2String(e.get("bdlx")))).collect(Collectors.toList());
                        //获取需要累计的班段时长区间和请假区间存在交集的分钟数
                        scMinutes = Utils.removeTime(leaveDate + " " + startTime, endDate + " " + endTime, bcDetailData, yesterday);
                    }
                    if (!"".equals(currentDayBcId)) {
                        sql = "select id, bdlx, gsrq, kssj as dtkssj, jssj as dtjssj from uf_jcl_kq_bcxx_dt1 where mainid  = " + currentDayBcId;
                        bcDetailData = DbTools.getSqlToList(sql);
                        bcDetailData = bcDetailData.stream().filter(e -> countBdlxList.contains(Util.null2String(e.get("bdlx")))).collect(Collectors.toList());
                        //获取需要累计的班段时长区间和请假区间存在交集的分钟数
                        scMinutes = scMinutes + Utils.removeTime(leaveDate + " " + startTime, endDate + " " + endTime, bcDetailData, leaveDate);
                    }
                    if (!"".equals(nextDayBcId)) {
                        sql = "select id, bdlx, gsrq, kssj as dtkssj, jssj as dtjssj from uf_jcl_kq_bcxx_dt1 where mainid  = " + nextDayBcId;
                        bcDetailData = DbTools.getSqlToList(sql);
                        bcDetailData = bcDetailData.stream().filter(e -> countBdlxList.contains(Util.null2String(e.get("bdlx")))).collect(Collectors.toList());
                        //获取需要累计的班段时长区间和请假区间存在交集的分钟数
                        scMinutes = scMinutes + Utils.removeTime(leaveDate + " " + startTime, endDate + " " + endTime, bcDetailData, nextDay);
                    }
                    //增加加班计划中的数据，加班计划明细中的数据作为“加班计划”班段类型数据的补充
                    if (countBdlxList.contains(ClassSegmentTypeEnum.OVERTIME_PLAN.getKey())) {
                        sql = "select b.jbry,b.ksrq,b.kssj,b.jblx,b.jsrq,b.jssj,b.jbsc,b.gsrq from uf_jcl_kq_jbjh a left join uf_jcl_kq_jbjh_dt1 b on a.id=b.mainid where b.jbry =? and b.ksrq>=? and b.jsrq<=? and (b.jbcx=0 or b.jbcx is null) and a.jlzt=1";
                        List<Map<String,Object>> overtimePlanList = DbTools.getSqlToList(sql, leaveEmpId, DateUtil.beforeDay(leaveDate,1), DateUtil.AfterDay(endDate,1));
                        int scMinutesInOvertimePlan = Utils.removeTimeWithOvertimePlan(leaveDate + " " + startTime, endDate + " " + endTime, overtimePlanList);
                        scMinutes = scMinutes + scMinutesInOvertimePlan;
                    }
                    if (scMinutes > 0) {
                        double qjscByAccount = Utils.getItemduration(hsl, hsdw, scMinutes, AccountingUnitEnum.MINUTES, 8.0);
                        //组装初步的请假时长
                        simpleLeaveDetailItem.put("qjsc", hsdw.equals(AccountingUnitEnum.MINUTES.getKey())
                                ? String.format("%.2f", qjscByAccount / 60.0)
                                : String.valueOf(qjscByAccount));
                        simpleLeaveDetailList.add(simpleLeaveDetailItem);
                    }
                } else if (leaveMode.equals(AskAndEvctionWayEnum.HOUR.getKey()) && !restDateList.contains(leaveDate)) {
                    simpleLeaveDetailItem = new HashMap<>();
                    //组装初步的请假明细数据
                    simpleLeaveDetailItem.put("qjr", leaveEmpId);
                    simpleLeaveDetailItem.put("ksrq", leaveDate);
                    simpleLeaveDetailItem.put("jsrq", leaveDate);
                    simpleLeaveDetailItem.put("qjsc", leaveDuration);
                    if (Double.parseDouble(leaveDuration) > 0) {
                        double qjscByAccount = Utils.getItemduration(hsl, hsdw, Double.parseDouble(leaveDuration), AccountingUnitEnum.HOUR, 8.0);
                        //组装初步的请假时长
                        simpleLeaveDetailItem.put("qjsc", hsdw.equals(AccountingUnitEnum.MINUTES.getKey())
                                ? String.format("%.2f", qjscByAccount / 60.0)
                                : String.valueOf(qjscByAccount));
                        simpleLeaveDetailList.add(simpleLeaveDetailItem);
                    }
                } else if (leaveMode.equals(AskAndEvctionWayEnum.ALLDAY.getKey()) && !restDateList.contains(leaveDate)) {
                    simpleLeaveDetailItem = new HashMap<>();
                    //组装初步的请假明细数据
                    simpleLeaveDetailItem.put("qjr", leaveEmpId);
                    simpleLeaveDetailItem.put("ksrq", leaveDate);
                    simpleLeaveDetailItem.put("jsrq", leaveDate);
                    simpleLeaveDetailItem.put("qtj", "1");
                    simpleLeaveDetailList.add(simpleLeaveDetailItem);
                } else if (leaveMode.equals(AskAndEvctionWayEnum.HALFDAY.getKey()) && !restDateList.contains(leaveDate)) {
                    simpleLeaveDetailItem = new HashMap<>();
                    //组装初步的请假明细数据
                    simpleLeaveDetailItem.put("qjr", leaveEmpId);
                    simpleLeaveDetailItem.put("ksrq", leaveDate);
                    simpleLeaveDetailItem.put("jsrq", leaveDate);
                    simpleLeaveDetailItem.put("btj", "1");
                    simpleLeaveDetailList.add(simpleLeaveDetailItem);
                }
            }
        }
        return simpleLeaveDetailList;
    }

    /**
     * @param leaveEmpIdList 请假人员id集合
     * @param leaveDateList 请假日期集合
     * @param removeNonWorkDayRange 是否移除非工作日时长
     * @param scheduleInfoMap 排班信息
     * @param restDayInfo 休息日信息
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param countBdlxList 收集需要统计时长的班段类型
     * @return 组装初步的请假明细，这边只处理“每天重复时段请假”未勾选，且请假方式为“指定时间区间”
     */
    private List<Map<String, String>> createSimpleLeaveDetailListWithNoDaily(List<String> leaveEmpIdList, List<String> leaveDateList, boolean removeNonWorkDayRange,
                                                                             Map<String, List<Map<String, Object>>> scheduleInfoMap, Map<String, List<String>> restDayInfo,
                                                                             String startDate, String endDate, String startTime, String endTime, List<String> countBdlxList,
                                                                             String hsdw, double hsl) {
        List<Map<String, String>> simpleLeaveDetailList = new ArrayList<>();
        Map<String, String> simpleLeaveDetailItem;
        String sql = "";
        List<Map<String, Object>> bcDetailData;
        String targetDateBcId = "";
        leaveDateList.add(0, DateUtil.beforeDay(startDate,1));
        leaveDateList.add(DateUtil.AfterDay(endDate,1));
        for (String leaveEmpId : leaveEmpIdList) {
            int scMinutes = 0;
            //需要自动移除日期区间内的休息日时，去除请假日期区间中的休息日
            List<Map<String, Object>> scheduleInfoList = scheduleInfoMap.getOrDefault(leaveEmpId, new ArrayList<>());
            Map<String, String> dateToBcxxMap = scheduleInfoList.stream().collect(Collectors.toMap(e->Util.null2String(e.get("bcrq")),e->Util.null2String(e.get("bcxx"))));

            List<String> restDateList = new ArrayList<>();
            if (removeNonWorkDayRange) {
                //排班结果中休息的日期
                List<String> restDateListFromSchedule = scheduleInfoList.stream()
                        .filter(f -> Util.null2String(f.get("sfxx")).equals(CheckBoxEnum.CHECKED.getKey()))
                        .map(e -> Util.null2String(e.get("bcrq")))
                        .collect(Collectors.toList());
                List<String> allDateListFromSchedule = scheduleInfoList.stream().map(e -> Util.null2String(e.get("bcrq"))).collect(Collectors.toList());
                //排班结果中未出现的日期
                List<String> nonSetDateListFromSchedule = leaveDateList.stream().filter(f -> !allDateListFromSchedule.contains(f)).collect(Collectors.toList());
                //企业日历中人员对应日期的休息日
                restDateList = restDayInfo.get(leaveEmpId);
                //筛选排班信息无法辨别的日期，依靠企业日历的日期信息
                restDateList = restDateList.stream().filter(nonSetDateListFromSchedule::contains).collect(Collectors.toList());
                restDateList.addAll(restDateListFromSchedule);
            }
            simpleLeaveDetailItem = new HashMap<>();
            //组装初步的请假明细数据
            simpleLeaveDetailItem.put("qjr", leaveEmpId);
            simpleLeaveDetailItem.put("ksrq", startDate);
            simpleLeaveDetailItem.put("jsrq", endDate);
            simpleLeaveDetailItem.put("kssj", startTime);
            simpleLeaveDetailItem.put("jssj", endTime);
            for (String date : leaveDateList) {
                targetDateBcId = restDateList.contains(date) ? "" : Util.null2String(dateToBcxxMap.get(date)).split("-")[0];
                if (!"".equals(targetDateBcId)) {
                    sql = "select id, bdlx, gsrq, kssj as dtkssj, jssj as dtjssj from uf_jcl_kq_bcxx_dt1 where mainid  = " + targetDateBcId;
                    bcDetailData = DbTools.getSqlToList(sql);
                    bcDetailData = bcDetailData.stream().filter(e -> countBdlxList.contains(Util.null2String(e.get("bdlx")))).collect(Collectors.toList());
                    //获取需要累计的班段时长区间和请假区间存在交集的分钟数
                    scMinutes = scMinutes + Utils.removeTime(startDate + " " + startTime, endDate + " " + endTime, bcDetailData, date);
                }
            }
            //增加加班计划中的数据，加班计划明细中的数据作为“加班计划”班段类型数据的补充
            if (countBdlxList.contains(ClassSegmentTypeEnum.OVERTIME_PLAN.getKey())) {
                sql = "select b.jbry,b.ksrq,b.kssj,b.jblx,b.jsrq,b.jssj,b.jbsc,b.gsrq from uf_jcl_kq_jbjh a left join uf_jcl_kq_jbjh_dt1 b on a.id=b.mainid where b.jbry =? and b.ksrq>=? and b.jsrq<=? and (b.jbcx=0 or b.jbcx is null) and a.jlzt=1";
                List<Map<String,Object>> overtimePlanList = DbTools.getSqlToList(sql, leaveEmpId, DateUtil.beforeDay(startDate,1), DateUtil.AfterDay(endDate,1));
                int scMinutesInOvertimePlan = Utils.removeTimeWithOvertimePlan(startDate + " " + startTime, endDate + " " + endTime, overtimePlanList);
                scMinutes = scMinutes + scMinutesInOvertimePlan;
            }
            if (scMinutes > 0) {
                double qjscByAccount = Utils.getItemduration(hsl, hsdw, scMinutes, AccountingUnitEnum.MINUTES, 8.0);
                //组装初步的请假时长
                simpleLeaveDetailItem.put("qjsc", hsdw.equals(AccountingUnitEnum.MINUTES.getKey())
                        ? String.format("%.2f", qjscByAccount / 60.0)
                        : String.valueOf(qjscByAccount));
                simpleLeaveDetailList.add(simpleLeaveDetailItem);
            }
        }
        return simpleLeaveDetailList;
    }
    /**
     * @param qjry 请假人员id
     * @param detailTableData 请假明细
     * @param errorMessage 错误信息
     * @param empName 请假人姓名
     * @return 校验该人员的考勤周期是否正常
     */
    private boolean kqCycleCheck(String qjry, List<Map<String, String>> detailTableData, List<String> errorMessage, String empName) {

        Map<String, Object> params = new HashMap<>();
        params.put("userId",qjry);
        params.put("submitDate",DateUtil.getCurrentDate());
        params.put("submitStr","ksrq");
        params.put("submitDataList",detailTableData);
        if (detailTableData == null || detailTableData.size() == 0){
            errorMessage.add(empName + "没有请假明细数据！");
            return false;
        }
        Map<String,Object> dataMap = makeUpClockInService.getKqCycleTimeIntervalCmd(params);
        List<Map<String,Object>> closeList = (List<Map<String,Object>>)dataMap.get("closeList");
        List<String> nocycleList = (List<String>)dataMap.get("nocycleList");
//        List<Map<String,Object>> dateList = (List<Map<String,Object>>)dataMap.get("dataList");

        boolean status = (boolean)dataMap.get("status");
        if (!status){
            errorMessage.add(empName + "没有考勤周期！");
            return false;
        }
        if (closeList.size() > 0 || nocycleList.size() > 0){
            String message = empName + "：";
            if (nocycleList.size() > 0){
                message = message +String.join(",",nocycleList)+"未找对对应的考勤周期；";
            }
            if (closeList.size() > 0){
                List<String> list = closeList.stream().map(e->e.get("rq").toString()).collect(Collectors.toList());
                message = message +String.join(",",list)+"对应的考勤周期的考勤周期已关账";
            }
            errorMessage.add(message);
            return false;
        }
        return true;
    }

    /**
     * @param editedLeaveInfo 触发接口前已编辑的请假明细信息
     * @param checkAmountJqIdList 需要校验假期额度的假期类型id集合
     * @param errorMessage 错误信息记录集合
     * @param empId 请假人员id
     * @param empName 请假人员姓名
     * @param canUseCheckJqyeInfo 勾选的假期类型可使用的假期余额信息
     * @param canUseFirstJqyeInfo 优先使用的假期类型可使用的假期余额信息
     * @param editedUseJqed 触发接口前已编辑的假期额度使用信息
     * @param editedUseJqlxWithEmp 触发接口前,本次提交的日期集合中已编辑的假期类型id_人员id条件下已使用的时长
     * @param leaveDetailList 请假明细集合
     * @param checkItemId 勾选的假期类型id
     * @param firstItemId 优先使用的假期类型id
     * @return 给请假明细中请假时长分配应扣的假期余额id及时长
     */
    private List<Map<String, String>> matchHolidayBalance(Map<String, String> editedLeaveInfo, List<String> checkAmountJqIdList, List<String> errorMessage, String empId, String empName,
                                                          List<Map<String, Object>> canUseCheckJqyeInfo, List<Map<String, Object>> canUseFirstJqyeInfo, Map<String, Double> editedUseJqed,
                                                          Map<String, Double> editedUseJqlxWithEmp,List<Map<String, String>> leaveDetailList, String checkItemId, String firstItemId,
                                                          String checkItemName, String firstItemName) {
        List<Map<String, String>> result = new ArrayList<>();
        //记录分配假期余额的结果
        boolean matchResultSign = true;
        //此次遍历请假总时长
        double totalLeaveDuration = 0;
        for (Map<String, String> detailData : leaveDetailList) {
            String qtj = Util.null2String(detailData.get("qtj"));
            String btj = Util.null2String(detailData.get("btj"));
            //请假时间
            double leaveDuration;
            if (qtj.equals(CheckBoxEnum.CHECKED.getKey())) {
                //全天默认8小时
                leaveDuration = 8;
            } else if (btj.equals(CheckBoxEnum.CHECKED.getKey())) {
                //半天默认4小时
                leaveDuration = 4;
            } else{
                leaveDuration = Double.parseDouble(Util.null2String(detailData.get("qjsc")));
            }
            totalLeaveDuration = totalLeaveDuration + leaveDuration;
        }
        //已编辑明细中已使用时长
        Double checkItemEditedUseSc = editedUseJqlxWithEmp.getOrDefault(checkItemId + "_" + empId, (double) 0);
        Double firstItemEditedUseSc = editedUseJqlxWithEmp.getOrDefault(firstItemId + "_" + empId, (double) 0);
//        totalLeaveDuration = Utils.subtract(totalLeaveDuration, checkItemEditedUseSc == null ? 0 : checkItemEditedUseSc);
//        totalLeaveDuration = Utils.subtract(totalLeaveDuration, firstItemEditedUseSc == null ? 0 : firstItemEditedUseSc);
//        //如果总时长为0，则退出
        if (Utils.add(checkItemEditedUseSc, firstItemEditedUseSc) > 0) {
            String message = empName + "在该请假类型的本次请假日期范围内，已存在开始日期相同的请假明细，请重新选择日期范围！";
            errorMessage.add(message);
            return result;
        }
        Map<String, Double> leaveDurationMap = new HashMap<>();
        leaveDurationMap.put("totalLeaveDuration", totalLeaveDuration);
        //默认存在优先使用项目时长
        boolean firstItemHaveDuration = true;
        //优先使用的假期类型项目是否不需要校验假期余额的额度
        boolean firstItemNonCheck = firstItemId != null && !checkAmountJqIdList.contains(firstItemId);
        //勾选的假期类型项目是否不需要校验假期余额的额度
        boolean checkItemNonCheck = !checkAmountJqIdList.contains(checkItemId);
        //遍历关联请假余额id和假期类型id
        for (Map<String, String> detailData : leaveDetailList) {
            String ksrq = Util.null2String(detailData.get("ksrq"));
            String qtj = Util.null2String(detailData.get("qtj"));
            String btj = Util.null2String(detailData.get("btj"));
            detailData.put("qjrName", empName);
            //请假时间
            double leaveDuration;
            if (qtj.equals(CheckBoxEnum.CHECKED.getKey())) {
                //全天默认8小时
                leaveDuration = 8;
            } else if (btj.equals(CheckBoxEnum.CHECKED.getKey())) {
                //半天默认4小时
                leaveDuration = 4;
            } else{
                leaveDuration = Double.parseDouble(Util.null2String(detailData.get("qjsc")));
            }
            log.info("leaveDuration : [{}]", leaveDuration);
            //优先使用的的请假项目是否已被编辑
            double firstItemEditedDetailSc = editedLeaveInfo.get(empId + "_" + firstItemId + "_" + ksrq) == null ? 0 : Double.parseDouble(editedLeaveInfo.get(empId + "_" + firstItemId + "_" + ksrq));
            boolean firstItemEditedDetail = editedLeaveInfo.get(empId + "_" + firstItemId + "_" + ksrq) != null;
            //勾选的请假项目是否已被编辑，此处判定包含了优先使用的请假项目编辑记录中的时长大于等于提交请假需求者的时长
            double checkItemEditedDetailSc = editedLeaveInfo.get(empId + "_" + checkItemId + "_" + ksrq) == null ? 0 : Double.parseDouble(editedLeaveInfo.get(empId + "_" + checkItemId + "_" + ksrq));
            boolean checkItemEditedDetail = editedLeaveInfo.get(empId + "_" + checkItemId + "_" + ksrq) != null || (firstItemEditedDetail && firstItemEditedDetailSc - leaveDuration >= 0);

            leaveDuration = Utils.subtract(leaveDuration, firstItemEditedDetailSc);
            leaveDuration = Utils.subtract(leaveDuration, checkItemEditedDetailSc);
            if (leaveDuration <= 0) {
                continue;
            }
            //存在优先使用假期类型时,且该假期类型不需要进行额度校验
            if (firstItemNonCheck && !firstItemEditedDetail) {
                detailData.put("qjlx", firstItemId);
                detailData.put("qjlxName", firstItemName);
                detailData.put("qjsc", String.valueOf(leaveDuration));
                result.add(detailData);
                continue;
            }
            //不存在优先使用假期类型时，勾选的假期类型不需要进行额度校验
            if (firstItemId == null && checkItemNonCheck && !checkItemEditedDetail) {
                detailData.put("qjlx", checkItemId);
                detailData.put("qjlxName", checkItemName);
                detailData.put("qjsc", String.valueOf(leaveDuration));
                result.add(detailData);
                continue;
            }

            leaveDurationMap.put("leaveDuration", leaveDuration);
            //存在优先使用假期类型时,但是需要进行额度校验
            if (canUseFirstJqyeInfo != null && canUseFirstJqyeInfo.size() > 0 && firstItemHaveDuration && !firstItemEditedDetail) {
                dealLeaveDetailWithJqyeInfo(detailData, canUseFirstJqyeInfo, editedUseJqed, leaveDurationMap, ksrq, firstItemId, firstItemName, result);
                if (leaveDurationMap.get("leaveDuration") == 0) {
                    //如果同假期类型出现多假期余额拼接配置一条请假明细时，请假明细的时长需要重新设置下
                    //请假明细初始要求时长已配置完成则赋值为初始请假时长，否则为初始时长-剩余未配置时长
                    if (Util.null2String(detailData.get("jqye")).contains(",")) {
                        detailData.put("qjsc", String.valueOf(leaveDuration));
                    }
                    continue;
                } else {
                    firstItemHaveDuration = false;
                    if (Util.null2String(detailData.get("jqye")).contains(",")) {
                        detailData.put("qjsc", String.valueOf(leaveDuration - leaveDurationMap.get("leaveDuration")));
                    }
                }
            }
            if (!"".equals(Util.null2String(detailData.get("qjlx")))) {
                result.add(new HashMap<>(detailData));
                detailData.remove("qjlx");
                detailData.remove("qjlxName");
                detailData.remove("qjsc");
                detailData.remove("jqye");
            }
            //不存在优先使用假期类型，或者优先使用假期类型关联的假期余额用完时
            //勾选的假期类型不需要进行额度校验
            if (checkItemNonCheck && !checkItemEditedDetail) {
                detailData.put("qjlx", checkItemId);
                detailData.put("qjlxName", checkItemName);
                detailData.put("qjsc", String.valueOf(leaveDurationMap.get("leaveDuration")));
                leaveDurationMap.put("totalLeaveDuration", Utils.subtract(leaveDurationMap.get("totalLeaveDuration"), leaveDurationMap.get("leaveDuration")));
                result.add(detailData);
                continue;
            }
            //勾选的假期类型需要进行额度校验
            boolean checkItemDealSign = true;
            if (!checkItemEditedDetail) {
                checkItemDealSign = dealLeaveDetailWithJqyeInfo(detailData, canUseCheckJqyeInfo, editedUseJqed, leaveDurationMap, ksrq, checkItemId, checkItemName, result);
            }
            if (leaveDurationMap.get("leaveDuration") > 0) {
                String message = empName;
                if (checkItemEditedDetail) {
                    //人员id+日期+假期类型三种条件约束唯一性
                    message = message + "_" + ksrq + "已存在一笔该请假类型的请假明细！";
                } else if(!checkItemDealSign) {
                    message = message + "_" + ksrq  + "不存在可使用的假期余额，请检查假期余额使用次数和最小使用时长设置！";
                } else {
                    //假期余额不足
                    message = message + "_" + ksrq  + "假期余额不足！";
                }
                errorMessage.add(message);
                matchResultSign = false;
            }
        }
        return matchResultSign ? result : new ArrayList<>();
    }

    private boolean dealLeaveDetailWithJqyeInfo(Map<String, String> detailData, List<Map<String, Object>> canUseJqyeInfo,
                                             Map<String, Double> editedUseJqed, Map<String, Double> leaveDurationMap,
                                             String ksrq, String itemId, String itemName, List<Map<String, String>> result) {
        double totalLeaveDuration = leaveDurationMap.get("totalLeaveDuration");
        double leaveDuration = leaveDurationMap.get("leaveDuration");
        //假期类型相关的假期余额记录
        List<Map<String, Object>> holidayBalancefilterList = new ArrayList<>();
        for (Map<String, Object> map : canUseJqyeInfo){
            if (DateUtil.getTime(map.get("yqsxrq").toString()).compareTo(DateUtil.getTime(ksrq)) >= 0){
                holidayBalancefilterList.add(map);
            }
        }
        if (holidayBalancefilterList.size() == 0) {
            return false;
        }
        //使用的假期类型的额度单位
        String eddw = Util.null2String(holidayBalancefilterList.get(0).get("eddw"));
        int multipleNum = eddw.equals(AccountingUnitEnum.DAY.getKey()) ? 8 : 1;
        for (Map<String, Object> holidayBalance : holidayBalancefilterList) {
            //1-如果已编辑的请假明细中已有该假期余额id的记录，则满足最小使用时长;如果result的请假明细中已有该假期余额id的记录，则满足最小使用时长
            boolean minLeaveDurationSign = editedUseJqed.get(holidayBalance.get("id").toString()) != null || result.stream().anyMatch(map -> itemId.equals(map.get("qjlx")));
            //2-如果编辑记录中不存在，则需要判断是否满足最小使用时长
            if (!minLeaveDurationSign) {
                String minLeaveDuration = Util.null2String(holidayBalance.get("minLeaveDuration"));
                minLeaveDurationSign = "".equals(minLeaveDuration) || totalLeaveDuration >= Double.parseDouble(minLeaveDuration) * multipleNum;
            }
            //不满足最小使用时长，直接执行下一条
            if (!minLeaveDurationSign) {
                return false;
            }
            //额定未休时长
            double wxsc = "".equals(Util.null2String(holidayBalance.get("wxsc"))) ? 0 : Double.parseDouble(holidayBalance.get("wxsc").toString());
            wxsc = multipleNum * wxsc;
            if (leaveDuration > 0  && wxsc > 0) {
                detailData.put("qjlx", itemId);
                detailData.put("qjlxName", itemName);
                String jqyeId = Util.null2String(detailData.get("jqye"));
                //已分配的请假时长
                String matchedQjsc = Util.null2String(detailData.get("qjsc"));
                if (leaveDuration > wxsc) {
                    detailData.put("qjsc", String.valueOf(wxsc));
                    leaveDuration = Utils.subtract(leaveDuration, wxsc);
                    totalLeaveDuration = Utils.subtract(totalLeaveDuration, wxsc);
                    if ("".equals(jqyeId)) {
                        detailData.put("jqye", holidayBalance.get("id").toString() + "_" + wxsc);
                    } else {
                        //同一假期类型，当一条假期余额记录不足请假时长需求时，需要拼接上另一条假期余额记录的id
                        detailData.put("jqye", jqyeId + "," + holidayBalance.get("id").toString() + "_" + wxsc);
                        detailData.put("qjsc", String.valueOf(Utils.add(wxsc, Double.parseDouble(matchedQjsc))));
                    }
                    wxsc = 0;
                } else {
                    detailData.put("qjsc", String.valueOf(leaveDuration));
                    wxsc = Utils.subtract(wxsc, leaveDuration);
                    //可能存在倍数转换
                    wxsc = Utils.divide(wxsc, multipleNum);
                    totalLeaveDuration = Utils.subtract(totalLeaveDuration, leaveDuration);
                    if ("".equals(jqyeId)) {
                        detailData.put("jqye", holidayBalance.get("id").toString() + "_" + leaveDuration);
                    } else {
                        //同一假期类型，当一条假期余额记录不足请假时长需求时，需要拼接上另一条假期余额记录的id
                        detailData.put("jqye", jqyeId + "," + holidayBalance.get("id").toString() + "_" + leaveDuration);
                        detailData.put("qjsc", String.valueOf(Utils.add(leaveDuration, Double.parseDouble(matchedQjsc))));
                    }
                    leaveDuration = 0;
                    result.add(detailData);
                }

                holidayBalance.put("wxsc", wxsc);
            }
        }
        leaveDurationMap.put("totalLeaveDuration", totalLeaveDuration);
        leaveDurationMap.put("leaveDuration", leaveDuration);
        return true;
    }


    /**
     * 收集假期类型关联的可使用的假期余额
     * @param unableUseJqyeIdList 不可使用的假期余额id集合
     * @param editedUseJqed 已编辑过的假期余额id和具体使用的时长映射
     * @param holidayItem 假期类型对应的考勤项目信息
     * @param startDate 开始日期
     * @param leaveEmpId 请假人id
     */
    private List<Map<String, Object>> collectUsableHolidayBalance(List<String> unableUseJqyeIdList, Map<String, Double> editedUseJqed, Map<String,Object> holidayItem, String startDate, String leaveEmpId) {

        String itemId = Util.null2String(holidayItem.get("id"));
        String sql = "select id,jqid,sxrq,ktsc,yxsc,wxsc,yqsxrq,ztsc from uf_jcl_kq_jqye where ygid=? and jqid=? and sxrq<=? and yqsxrq>=? order by yqsxrq, modedatacreatedate, modedatacreatetime";
        List<Map<String, Object>> holidayBalanceList = DbTools.getSqlToList(sql, leaveEmpId, itemId, startDate, startDate);
        //获取延期失效日期最晚的一条
        Map<String, Object> maxYqsxrqMap = holidayBalanceList.stream().reduce((m1, m2) -> m2).orElse(null);
        //获取假期额度规则中额度可修次数、单次最小休时长、额度单位
        String jqedSql = "select eddw, dczskxsc, edbxdcxw from uf_jcl_kq_jqed where jb = ?";
        Map<String, Object> jqedInfo = DbTools.getSqlToMap(jqedSql, itemId);
        //假期额度的额度单位，0-天、1-小时
        String eddw = Util.null2String(jqedInfo.get("eddw"));
        int multiple = 1;
        if (eddw.equals(AccountingUnitEnum.DAY.getKey())) {
            multiple = 8;
        }
        //单次最小休时长
        String minDuration = Util.null2String(jqedInfo.get("dczskxsc"));
        Double minLeaveDuration = null;
        if (!"".equals(eddw) && !"".equals(minDuration)) {
            minLeaveDuration = Double.parseDouble(minDuration) * multiple;
        }
        //额度可修次数
        String allowLeaveNumStr = Util.null2String(jqedInfo.get("edbxdcxw"));
        if (!"".equals(allowLeaveNumStr) && holidayBalanceList.size() > 0) {
            //查询请假记录中，人员id+假期类别id情况下的结果，遍历每条主表数据下的明细数据中使用了哪些假期余额id
            sql = "select a.id,dt2.jqye from uf_jcl_kq_qjjl a left join uf_jcl_kq_qjjl_dt1 dt1 on dt1.mainid = a.id left join uf_jcl_kq_qjjl_dt2 dt2 on dt2.glmxid = dt1.glmxid " +
                    "where a.jqlx = " + itemId + " and qjry = " + leaveEmpId + " and a.cxqj = 0 and dt1.cxqj = 0 and a.jlzt in (0, 1) and dt1.glmxid is not null";
            List<Map<String, Object>> leaveList = DbTools.getSqlToList(sql);
            Map<String, Integer> jqyeUseNumInfo = new HashMap<>();
            List<String> leaveUseList = new ArrayList<>();
            Integer useNum;
            for (Map<String, Object> leaveItem : leaveList) {
                String jqyeId = Util.null2String(leaveItem.get("jqye"));
                if (!"".equals(jqyeId)) {
                    String leaveInfo = leaveItem.get("id").toString() + "_" + jqyeId;
                    if (leaveUseList.size() == 0) {
                        leaveUseList.add(leaveInfo);
                        jqyeUseNumInfo.put(jqyeId, 1);
                    } else if (!leaveUseList.contains(leaveInfo)) {
                        leaveUseList.add(leaveInfo);
                        useNum = jqyeUseNumInfo.get(jqyeId);
                        jqyeUseNumInfo.put(jqyeId, useNum == null ? 1 : ++useNum);
                    }
                }
            }
            //筛选次数未使用完的假期余额
            holidayBalanceList = holidayBalanceList.stream().filter(f -> {
                int beforeUseNum = jqyeUseNumInfo.get(f.get("id").toString()) == null ? 0 : jqyeUseNumInfo.get(f.get("id").toString());
                return Integer.parseInt(allowLeaveNumStr) > beforeUseNum;
            }).collect(Collectors.toList());
        }
        //判断是否可预支
        String allowAdvance = Util.null2String(holidayItem.get("yxyz"));
        String advanceSc = Util.null2String(holidayItem.get("zdyzsl"));
        //遍历可使用的假期余额，去除已编辑请假明细记录中使用的时长
        List<Map<String, Object>> canUseHolidayBalanceList = new ArrayList<>();
        for (Map<String, Object> holidayBalance : holidayBalanceList) {
            String jqyeId = Util.null2String(holidayBalance.get("id"));
            if (!unableUseJqyeIdList.contains(jqyeId)) {
                double wxsc = Utils.convertDouble(holidayBalance.get("wxsc")) * multiple;
                //如果该条假期余额可以预支，则在原有未休时长基础上增加预支时长
                if ("1".equals(allowAdvance) && maxYqsxrqMap != null && Util.null2String(maxYqsxrqMap.get("id")).equals(jqyeId)) {
                    wxsc = wxsc + Utils.convertDouble(advanceSc) * multiple;
                }
                double editedUseSc = Utils.convertDouble(editedUseJqed.get(jqyeId));
                double realWxsc = wxsc - editedUseSc;
                if ((minLeaveDuration == null || wxsc >= minLeaveDuration) && realWxsc > 0) {
                    holidayBalance.put("wxsc", Utils.divide(realWxsc, multiple));
                    holidayBalance.put("minLeaveDuration", minLeaveDuration == null ? null : Utils.divide(minLeaveDuration, multiple));
                    holidayBalance.put("eddw", eddw);
                    canUseHolidayBalanceList.add(holidayBalance);
                }
            }
        }
        return canUseHolidayBalanceList;
    }

    /**
     * 请假人的请假区间内的排班结果
     * @param leaveEmpIdList 请假人id集合
     * @param startDate 开始日期
     * @param endDate 结束日期
     */
    private Map<String, List<Map<String, Object>>> getScheduleInfoWithEmpId(List<String> leaveEmpIdList, String startDate, String endDate) {
        Map<String, List<Map<String, Object>>> scheduleInfo = new HashMap<>();
        Map<String,Object> params = new HashMap<>();
        params.put("tableName", "uf_pbjg");
        params.put("startDate", startDate);
        params.put("endDate", endDate);
        params.put("pblx", "0");
        params.put("current", "1");
        params.put("pageSize", "999");
        params.put("recurrence", "1");
        for (String empId : leaveEmpIdList) {
            params.put("pbdx", empId);
            Map<String,Object> schedulingResultsMap = schedulingResultsService.queryDataTableActualUse(params);
            scheduleInfo.put(empId, (List<Map<String, Object>>) schedulingResultsMap.get("data"));
        }
        return scheduleInfo;
    }

    /**
     * 请假人的请假区间内属于休息日的日期集合
     * @param leaveEmpIdList 请假人id集合
     * @param leaveType 假期类型id，即考勤项目id
     * @param startDate 开始日期
     * @param endDate 结束日期
     */
    private Map<String, List<String>> getRestDayWithEmpId(List<String> leaveEmpIdList, String leaveType, String startDate, String endDate) {
        Map<String, List<String>> restDayInfoWithEmpId = new HashMap<>();

        Map<String, Object> restDayParam = new HashMap<>();
        restDayParam.put("itemId", leaveType);
        restDayParam.put("startDate", startDate);
        restDayParam.put("endDate", endDate);

        List<String> dateTypeList = new ArrayList<>();
        dateTypeList.add(DateTypeEnum.HOLIDAY.getKey());
        dateTypeList.add(DateTypeEnum.PUBLIC_RESTDAY.getKey());
        dateTypeList.add(DateTypeEnum.EXCHANGE_LEAVEDAY.getKey());
        Map<String, Object> restDayInfo;
        List<Map<String,Object>> dataList;
        List<String> restDateList = new ArrayList<>();
        for (String empId : leaveEmpIdList) {
            restDayParam.put("userId", empId);
            restDayInfo = getRestDayInterval(restDayParam);
            dataList = (List<Map<String, Object>>) restDayInfo.get("data");
            if (dataList != null && dataList.size() > 0) {
                restDateList = dataList.stream()
                        .filter(f -> dateTypeList.contains(Util.null2String(f.get("rqlx"))))
                        .map(e -> Util.null2String(e.get("rq")))
                        .collect(Collectors.toList());
            }
            restDayInfoWithEmpId.put(empId, restDateList);
        }
        return restDayInfoWithEmpId;
    }
}
