package com.yonyou.hrcloud.attentrpt.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yonyou.hrcloud.adder.context.AppContext;
import com.yonyou.hrcloud.adder.exception.AdderBusinessException;
import com.yonyou.hrcloud.adder.model.param.Sorter;
import com.yonyou.hrcloud.adder.model.query.ChainLogicExpression;
import com.yonyou.hrcloud.adder.model.query.Condition;
import com.yonyou.hrcloud.adder.model.query.LogicExpression;
import com.yonyou.hrcloud.adder.model.query.ValueExpression;
import com.yonyou.hrcloud.adder.service.IGenericService;
import com.yonyou.hrcloud.attend.helper.IAttendConst;
import com.yonyou.hrcloud.attend.model.*;
import com.yonyou.hrcloud.attend.service.IAttendRuleService;
import com.yonyou.hrcloud.attend.service.IAttendStaffService;
import com.yonyou.hrcloud.attend.service.IShiftService;
import com.yonyou.hrcloud.attentrpt.dao.IAttendDataDao;
import com.yonyou.hrcloud.attentrpt.helper.utils.ElasticShiftaAttendUtils;
import com.yonyou.hrcloud.attentrpt.model.AttendParm;
import com.yonyou.hrcloud.attentrpt.model.AttendRptParams;
import com.yonyou.hrcloud.attentrpt.service.IAttendDataService;
import com.yonyou.hrcloud.berry.helper.DateStyle;
import com.yonyou.hrcloud.berry.helper.DateUtil;
import com.yonyou.hrcloud.berry.helper.HRCloudConst;
import com.yonyou.hrcloud.berry.model.TenantEntity;
import com.yonyou.hrcloud.businesstrip.model.BusinessTripApplyDetail;
import com.yonyou.hrcloud.businesstrip.model.BusinessTripRule;
import com.yonyou.hrcloud.businesstrip.service.IBusinessTripApplyService;
import com.yonyou.hrcloud.calendar.service.IAttendCalendarHistoryService;
import com.yonyou.hrcloud.calendar.service.IAttendCalendarService;
import com.yonyou.hrcloud.commons.staff.model.CommonStaff;
import com.yonyou.hrcloud.commons.staff.model.CommonStaffJob;
import com.yonyou.hrcloud.holiday.model.Holiday;
import com.yonyou.hrcloud.holiday.model.WorkingCalendar;
import com.yonyou.hrcloud.holiday.service.IHolidayService;
import com.yonyou.hrcloud.leave.model.LeaveApply;
import com.yonyou.hrcloud.leave.model.LeaveApplyDetail;
import com.yonyou.hrcloud.leave.model.LeaveTypeNew;
import com.yonyou.hrcloud.leave.service.ILeaveApplyService;
import com.yonyou.hrcloud.leave.service.ILeaveTypeNewService;
import com.yonyou.hrcloud.overtime.helper.OvertimeTypeEnum;
import com.yonyou.hrcloud.overtime.model.*;
import com.yonyou.hrcloud.overtime.service.IOvertimeBillService;
import com.yonyou.hrcloud.overtime.service.IOvertimeCoreBusinessServive;
import com.yonyou.hrcloud.sign.model.*;
import com.yonyou.hrcloud.sign.service.IAttendRecordService;
import com.yonyou.hrcloud.sign.service.IFillAttendHeadService;
import com.yonyou.hrcloud.support.MqMsgSupport;
import com.yonyou.hrcloud.tcommon.helper.AttendStaffHelper;
import com.yonyou.hrcloud.tcommon.helper.HolidayHelper;
import com.yonyou.hrcloud.tcommon.helper.staffAndOrg.StaffCommonHepler;
import com.yonyou.hrcloud.tcommon.model.AttendData;
import com.yonyou.hrcloud.tcommon.model.DefaultTimeScope;
import com.yonyou.hrcloud.tcommon.model.ITimeScope;
import com.yonyou.hrcloud.tcommon.model.TimeConst;
import com.yonyou.hrcloud.tcommon.service.ItimeCommonService;
import com.yonyou.hrcloud.tcommon.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 补考勤
 *
 * @author zhouyuh
 */
@Slf4j
@Service
@Transactional
@Scope("prototype")
public class AttendDataServiceImpl implements IAttendDataService {
    @Autowired
    private IAttendDataDao dao;
    @Autowired
    private IAttendRecordService recordservice;
    @Autowired
    private ILeaveApplyService leaveservice;
    @Autowired
    private IShiftService shiftservice;
    @Autowired
    private IHolidayService holidayservice;
    @Autowired
    private IAttendStaffService attendStaffService;
    @Autowired
    private IAttendCalendarService calendarService;
    @Autowired
    private ILeaveTypeNewService leaveTypeNewService;
    @Autowired
    private IAttendRuleService ruleService;
    @Autowired
    private IFillAttendHeadService fillAttendHeadService;
    @Autowired
    private IGenericService genericService;
    @Autowired
    private ItimeCommonService itimeCommonService;
    @Autowired
    private IBusinessTripApplyService businessTripApplyService;
    @Lazy
    @Autowired
    private IOvertimeCoreBusinessServive otCoreBusinessServive;
    @Lazy
    @Autowired
    private IOvertimeBillService otBillService;
    @Autowired
    private StaffCommonHepler staffCommonHepler;
    @Autowired
    private IAttendCalendarHistoryService calendarHistoryService;
    @Autowired
    private HolidayHelper holidayHelper;
    @Autowired
    private IOvertimeBillService iOvertimeBillService;
    private int rawOffSet = Calendar.getInstance().getTimeZone().getRawOffset();
    private static final int ONEMINUTE_MILLISECOND = 59 * 1000;

    @Override
    public Page<AttendData> queryPageByConditon(Condition cond, Pageable pageable) {
        return dao.queryPageByConditon(cond, pageable);
    }

    @Override
    public void deleteByCond(Condition cond) {
        dao.deleteByCond(cond);
    }

    @Override
    public List<AttendData> getAttendData(Date beginDate, Date endDate, String[] staff_ids, AttendSchemeVO attendSchemeVO, Map<String, Shift> shiftMap, boolean isTask,List<AttendStaff> attendStaffList) throws Exception {
        //是否是Diwork版本
        StopWatch stopWatch = new StopWatch();
        String swID = UUID.randomUUID().toString();
        stopWatch.start(String.format("------ getAttendData(%s)-begin-------",swID));
        boolean isSenior = itimeCommonService.isSeniorVersion();
        try {
            //将begindate往前推一天，enddate往后推一天
            beginDate = DateUtil.StringToDate(DateUtil.DateToString(beginDate, DateStyle.YYYY_MM_DD), DateStyle.YYYY_MM_DD);
            endDate = DateUtil.getDayEnd(endDate);
            Date yesterday = TimePubUtils.getYesterDay(beginDate);
            Date nextday = TimePubUtils.getNextDay(endDate);

            //需要计算的人
            List<String> stafffids = new ArrayList<>();
            if (ArrayUtils.isNotEmpty(staff_ids)) {
                stafffids = Arrays.asList(staff_ids);
            }

            AttendParm parms = new AttendParm();
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-leaveTypeNews-------",swID));
            //休假规则ID
            String leaveRuleId = attendSchemeVO.getLeaveRuleId();
            //根据休假规则查询对应的休假类别 初级班和高级版都同样处理
            List<LeaveTypeNew> leaveTypeNews = leaveTypeNewService.getAllLeaveTyeNews(leaveRuleId);
            if (!CollectionUtils.isEmpty(leaveTypeNews)) {
                Map<String, LeaveTypeNew> leaveTypeNewMap = TimePubUtils.group2VOById(leaveTypeNews);
                parms.setLeaveTypeNewMap(leaveTypeNewMap);
            }
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-calendarMap-------",swID));
            //每人每天的排班Map
            //班次分为两种1 ：排班 2：考勤规则，这里先把所有排班(包含规则)查出来
            //日期范围：前后2天
            Date beforeYestoday = TimePubUtils.getYesterDay(yesterday);
            Date afterTomorrow = TimePubUtils.getNextDay(nextday);
            Map<String,Map<Date,String>> calendarMap= calendarService.queryCalendarRuleByDate(Arrays.asList(staff_ids), beforeYestoday,afterTomorrow);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-calendarMapHis-------",swID));
            //合并固定班和日弹历史记录
            Map<String,Map<Date,String>> calendarMapHis= calendarHistoryService.queryCalendarRuleByDate(Arrays.asList(staff_ids), beforeYestoday,afterTomorrow);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-calendarMap-combine-------",swID));
            processCalendarRuleMap(parms,calendarMap,calendarMapHis);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-ruleVOS-------",swID));
            //考勤规则MAP
            Condition ruleCond = new Condition();
            ruleCond.addExpression("tenantid", AppContext.getContext().getScope());
            List<TsAttendRuleVO> ruleVOS = genericService.find(TsAttendRuleVO.class,ruleCond,null);
            if(CollectionUtils.isNotEmpty(ruleVOS)) {
                parms.setRuleMap(ruleVOS.stream().collect(Collectors.toMap(TsAttendRuleVO::getId, a->a,(a1, a2)->a1)));
            }
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-queryByStaffIdAndOrgId-------",swID));
            //查询考勤规则map
            Map<String, TsAttendRuleVO> staffRuleMap = ruleService.queryByStaffIdAndOrgId(Arrays.asList(staff_ids),false,false);
            parms.setStaffRuleMap(staffRuleMap);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-overtimeRule-------",swID));
            //加班规则
            String overtimeRuleId = attendSchemeVO.getOvertimeRuleId();
            //加班规则
            OvertimeRule overtimeRule = genericService.load(OvertimeRule.class, overtimeRuleId);
            parms.setOvertimeRule(overtimeRule);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-businessTripRule-------",swID));
            //出差规则
            if(!StringUtils.isEmpty(attendSchemeVO.getAwayRuleId())) {
                BusinessTripRule businessTripRule = genericService.load(BusinessTripRule.class, attendSchemeVO.getAwayRuleId());
                parms.setBusinessTripRule(businessTripRule);
            }
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-holidayMap-------",swID));
            //日报生成范围可能存在跨年的情况
            Condition hCond = new Condition();
            SimpleDateFormat yearSdf = new SimpleDateFormat("yyyy");
            ChainLogicExpression cle = new ChainLogicExpression(true, new ValueExpression("year", yearSdf.format(beginDate), "="), new ValueExpression("year", yearSdf.format(endDate), "="));
            hCond.addExpressions(cle);
            Map<String, List<Holiday>> holidayMap = holidayservice.queryHolidayMapByConditon(hCond);
            parms.setHolidayMap(holidayMap);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-queryAttendRecord-------",swID));
            //打卡记录
            Map<String, List<AttendRecord>> recordMap = queryAttendRecord(yesterday, nextday, isTask?null:staff_ids);
            parms.setRecordMap(recordMap);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-queryLeaveApply-------",swID));
            //请假记录
            Map<String, List<LeaveApplyDetail>> leaveMap = queryLeaveApply(yesterday, nextday, isTask?null:staff_ids);
            parms.setLeaveMap(leaveMap);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-queryBusinessTripApply-------",swID));
            Map<String, List<BusinessTripApplyDetail>> tripMap = queryBusinessTripApply(yesterday, nextday,isTask ? null : staff_ids);
            parms.setBusinessTripMap(tripMap);
            stopWatch.stop();
            try {
                stopWatch.start(String.format("------ getAttendData(%s)-overtimebill-------",swID));
                //如果加班规则中，根据打卡生成加班单:把之前根据打卡数据签到的都删除掉 再重新生成
                if("0".equals(overtimeRule.getWorkcaltype())||"0".equals(overtimeRule.getRestcaltype())||"0".equals(overtimeRule.getHolidaycaltype())) {
                    otBillService.deleteOTVOsByCondition(beginDate, endDate, isTask ? null : Arrays.asList(staff_ids));
                }
                //然后再查出来源为单据提交的加班记录重新计算其时长
                Map<String, List<OvertimeSplitBillVO>> otSplitBillMap = queryOvertimeSplitBills(0, yesterday, nextday, isTask?null:staff_ids, HRCloudConst.BILL_STATUS_APPROVED_PASS);
                parms.setOvertimeSplitMap(otSplitBillMap);
                stopWatch.stop();
            } catch (Exception ex) {
                if(stopWatch.isRunning())
                    stopWatch.stop();
                log.error(ex.getMessage(), ex);
            }
            stopWatch.start(String.format("------ getAttendData(%s)-fillattend-------",swID));
            //diwork查补考勤
            Condition fillCond = new Condition();
            fillCond.addExpression("main.fill_date", yesterday, ">=");
            fillCond.addExpression("main.fill_date", nextday, "<=");
            if (!isTask){
                fillCond.addExpression("main.staff_id", staff_ids, "in");
            }
            fillCond.addExpression("main.approve_status", "4", "=");
            Sorter sorter = new Sorter(Direction.ASC, "main.fill_date");
            List<FillAttendHead> fillAttendHeadList = fillAttendHeadService.queryHeadDetail(fillCond, sorter);
            Map<String, List<FillAttendHead>> fillAttendMap = group2ListByField("staff_id", fillAttendHeadList);
            parms.setFillAttendMap(fillAttendMap);
            parms.setShiftMap(shiftMap);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-workCalendarList-------",swID));
            //工作日设置查询
            List<WorkingCalendar> workCalendarList = genericService.find(WorkingCalendar.class, ruleCond, new Sorter("creationtime"));
            Map<String, WorkingCalendar> workCalendarIdMap = TimePubUtils.group2VOById(workCalendarList);
            parms.setWorkCalendarIdMap(workCalendarIdMap);
            stopWatch.stop();
            stopWatch.start(String.format("------ getAttendData(%s)-generateSenior-------",swID));
            List<AttendData> attendDataList = null;
            if (isSenior) {
                attendDataList = generateSenior(parms, beginDate, endDate, stafffids, staff_ids,isTask,attendSchemeVO,attendStaffList);
            } else {
                 List<CommonStaff> staffList = staffCommonHepler.queryStaffsByIds(stafffids);
                 attendDataList = generate(parms, beginDate, endDate, staffList, staff_ids);
            }
            return attendDataList;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e.getMessage(),e);
        }finally {
            if(stopWatch.isRunning()){
                stopWatch.stop();
            }
            log.debug(stopWatch.prettyPrint());
        }
    }

    /**
     * 初级班生成数据
     *
     * @param parms
     * @param beginDate
     * @param endDate
     * @param staffExtList
     * @param staff_ids
     * @return
     */
    private List<AttendData> generate(AttendParm parms, Date beginDate, Date endDate,
                                      List<CommonStaff> staffExtList, String[] staff_ids) {// 某天会有多个签到记录 按天只取出某人最早和最晚的签到记录 分正常签到和外勤
        // 按天合并 统计出每人每天最早的签到时间 和最晚的签到时间 作为上下班时间
        List<AttendRecord> recordAll = null;// 所有的签到记录
        List<LeaveApplyDetail> leaveListAll = null;// 某人日期范围内所有的休假记录
        List<FillAttendHead> fillAttendList = null;//某人日期范围内所有的补考勤记录-diwork
        // 构建开始结束日期数组
        Date[] dates = TimePubUtils.createDateArray(beginDate, endDate);
        List<AttendData> dataList = new ArrayList<>();
        // 将请假出差记录与shiftScope合并取当天的单据时间
        //日报生成范围可能存在跨年的情况
        Condition hCond = new Condition();
        SimpleDateFormat yearSdf = new SimpleDateFormat("yyyy");
        ChainLogicExpression cle = new ChainLogicExpression(true,
                new ValueExpression("year", yearSdf.format(beginDate), "="),
                new ValueExpression("year", yearSdf.format(endDate), "=")/* , new ValueExpression("e.deptname", qryStr, "like") */);
        hCond.addExpressions(cle);
        List<Holiday> holidayList = holidayservice.queryHolidayByConditon(hCond);
        Map<String, WorkingCalendar> workCalendar = parms.getWorkCalendarIdMap();//工作日日历
        for (int i = 0; i < staffExtList.size(); i++) {
            CommonStaff staffExt = staffExtList.get(i);
            if (null != parms.getRecordMap()) {
                recordAll = parms.getRecordMap().get(staffExt.getId());
            }
            //diwork补考勤数据
            if (null != parms.getFillAttendMap()) {
                fillAttendList = parms.getFillAttendMap().get(staffExt.getId());
            }
            if (null != parms.getLeaveMap()) {
                leaveListAll = parms.getLeaveMap().get(staffExt.getId());
            }
            TsAttendRuleVO rulevo = parms.getStaffRuleMap().get(staffExt.getId());
            if (rulevo == null) {
                continue;
            }
            for (int j = 0; j < dates.length; j++) {
                Date date = dates[j];
                //diwork419版本是没有离职业务的  只有一条staffjob记录 存的只有开始日期，判断该日期是否在日报计算的范围内，不在就过滤掉
                CommonStaffJob job = staffExt.getLastJob();
                if (DateUtil.StringToDate(DateUtil.DateToString(job.getBegindate(), DateStyle.YYYY_MM_DD)).after(date)) {
                    continue;
                }
                AttendData data = new AttendData();
                //初始化datavo
                createAttendData(data, staffExt.getId(), date);
                data.setOrgId(job.getOrgid());
                data.setDeptId(job.getDeptid());
                //判断是否是工作日，不是工作日的也直接continue;
                if (null != workCalendar) {
                    if (holidayHelper.isHolidayDiWork(date, holidayList, workCalendar.get(rulevo.getWorkCalendarId()))) {
                        //还要判断当天不是补班
                        data.setIsholiday(true);
                        data.setIsnormal(true);
                        continue;
                    }
                } else {
                    if (holidayHelper.isHolidayNew(date, holidayList)) {
                        //还要判断当天不是补班
                        data.setIsholiday(true);
                        data.setIsnormal(true);
                        continue;
                    }
                }
                if (null == rulevo) {
                    continue;
                }
                data.setShiftId(rulevo.getShiftId());
                Shift shiftvo = parms.getShiftMap().get(rulevo.getShiftId());
                //赋值应出勤时长，应出勤天数
                data.setWorkhours(new BigDecimal(shiftvo.getWorkHours()));
                data.setWorkdays(new BigDecimal(1));
                Double unnormallen = 0d;

                ITimeScope kqScope = ElasticShiftUtils.getKqscope(shiftvo, date);// 班次的开始结束时间
                ITimeScope[] leaveApplys = null;

                AttendRecord[] recordsByDate = null;//当天的打卡
                AttendRecord[] recordsContainFillByDate = null;//当天包含补考勤的的考勤记录
                FillAttendBody[] fillBodysByDate = null;//当天的补考勤记录子表
                if (!CollectionUtils.isEmpty(recordAll)) {// 某人当天的签到记录
                    recordsByDate = getDateRecordList(recordAll, date, shiftvo,null).toArray(new AttendRecord[0]);
                }
                if (!CollectionUtils.isEmpty(leaveListAll)) {// 某人当天的请假记录
                    leaveApplys = DataFilterUtils.filterRegVOs(kqScope, leaveListAll);
                }
                if (!CollectionUtils.isEmpty(fillAttendList)) {//某人当天的补考勤记录
                    fillBodysByDate = getDateFillRecordList(fillAttendList, date, shiftvo).toArray(new FillAttendBody[0]);
                }
                if (ArrayUtils.isNotEmpty(fillBodysByDate)) {//讲补考勤的时间点跟正常打卡的时间点放到一起
                    recordsContainFillByDate = getRecordContainFill(recordsByDate, fillBodysByDate);
                } else {
                    recordsContainFillByDate = recordsByDate;
                }

                //单据合并放在一个数组里便于计算迟到早退
                ITimeScope[] mergeLASScopes = TimeScopeUtils.mergeTimeScopes(leaveApplys);

                /**处理班次 弹性班的 要先固化**/
                ITimeScope[] workTimeScopes = processShift(date, recordsContainFillByDate, shiftvo, mergeLASScopes, kqScope);

                /**处理请假时长-请假时长是为了算实际出勤时长才计算的，此时一定是工作日的，公休不需要处理实际出勤时长，
                 * 针对日报上需要展示公休日的请假时长在TsAttendRptCoreBusinessServiceImpl 里处理，**/
                BigDecimal leavedaySum = processLeaveSum(parms, leaveListAll, shiftvo, date, workTimeScopes, leaveApplys, kqScope,1,data,BigDecimal.ZERO);
                if (ArrayUtils.isNotEmpty(leaveApplys)) {
                    data.setIsleave(true);
                    data.setLeavecnt(leaveApplys.length);
                }

                /**先处理当天的单据状态，APP端展示用 **/
                //diwork暂时注释掉
                /**将签到数据与请假补考勤单据交切计算迟到早退时间**/
                // 无签卡或者只有一张卡 如果其他单据不能完全覆盖工作时间则就是旷工
                if (ArrayUtils.isEmpty(recordsContainFillByDate) || recordsContainFillByDate.length == 1) {// 无签卡记录或者只有一张卡
                    processOneSignOrNoSign(data, leaveApplys, mergeLASScopes, workTimeScopes);
                } else {// 有两张以上签卡-签卡按顺序排列的
                    //先算下实际出勤时长,再在处理迟到早退时 根据允许迟到早退时长 补实际出勤时长
                    //按月弹的实出勤要减去与之相交集的请假时间段
                    //实出勤时长计算 也加上补考勤的时间
                    ElasticShiftaAttendUtils.calActworklen(date, recordsContainFillByDate, kqScope, shiftvo, data, workTimeScopes, leaveApplys);
                    processTwoSign(data, recordsContainFillByDate, shiftvo, mergeLASScopes, workTimeScopes, leaveApplys);
                }

                /**处理打卡时间点 异常时长,迟到早退旷工时长**/
                /** 打卡时间点不带补考勤的，取原始的打卡时间-diwork2018-03-20 **/
                processData(recordsContainFillByDate, data, unnormallen, shiftvo, workTimeScopes, rulevo);
                /**处理实际出勤时长 实际出勤天数 包括固定班和弹性班**/
                processActualHours(shiftvo, data, leavedaySum, workTimeScopes,BigDecimal.ZERO,BigDecimal.ZERO);
                /**容错处理**/
                processLast(data);
                dataList.add(data);
            }
        }
        return dataList;
    }

    private void processCalendarRuleMap(AttendParm param,Map<String,Map<Date,String>> calendarMap,Map<String,Map<Date,String>> calendarMapHis){
        Map<String,Map<Date,String>> calendarDatas = new HashMap<>();
        Map<String,Map<Date,String>> ruleDatas = new HashMap<>();
        processCalendarRuleMap_Single(calendarDatas,ruleDatas,calendarMapHis);
        processCalendarRuleMap_Single(calendarDatas,ruleDatas,calendarMap);
        param.setStaffCalMap(calendarDatas);
        param.setAllStaffRuleDateMap(ruleDatas);
    }

    private void processCalendarRuleMap_Single(Map<String,Map<Date,String>> calendarDatas,Map<String,Map<Date,String>> ruleDatas,Map<String,Map<Date,String>> Datas) {
        if (MapUtils.isEmpty(Datas)) return;
        for (Map.Entry<String,Map<Date,String>> entry : Datas.entrySet()) {
            Map<Date,String> mapCalendarDate = calendarDatas.containsKey(entry.getKey()) ?
                    calendarDatas.get(entry.getKey()) : new HashMap<Date, String>();
            Map<Date,String> mapRuleDate = ruleDatas.containsKey(entry.getKey()) ?
                    ruleDatas.get(entry.getKey()) : new HashMap<Date, String>();
            for (Map.Entry<Date,String> entryDate : entry.getValue().entrySet()) {
                String[] infos = entryDate.getValue().split("_");
                mapCalendarDate.put(entryDate.getKey(), infos[0]);
                mapRuleDate.put(entryDate.getKey(), infos.length > 1 ? infos[1] : null);
            }
            calendarDatas.put(entry.getKey(),mapCalendarDate);
            ruleDatas.put(entry.getKey(),mapRuleDate);
        }
    }

    private TsAttendRuleVO getRuleVo(TsAttendRuleVO ruleVO,Date date,Map<String,TsAttendRuleVO> mapRuleInfo,Map<Date,String> staffRuleMap){
        boolean isToday = DateUtils.isSameDay(new Date(),date);
        if(isToday) return ruleVO;
        if(ruleVO != null && "3".equals(ruleVO.getShiftModeFix())){
            return ruleVO;
        }else if(MapUtils.isNotEmpty(staffRuleMap) && MapUtils.isNotEmpty(mapRuleInfo) && staffRuleMap.get(date)!=null){
            return (staffRuleMap.containsKey(date) ? mapRuleInfo.get(staffRuleMap.get(date)) : null);
        }else{
            return null;
        }
    }

    private Shift getShiftVo(TsAttendRuleVO ruleVO,Date date,Map<Date, String> mapCal,Map<String,Shift> mapShift){
        try {
            String fixMode = ruleVO.getShiftModeFix();
            Shift shift;
            if ("3".equals(fixMode) || (DateUtils.isSameDay(new Date(), date) && ("0".equals(fixMode) || "2".equals(fixMode)))) {
                shift = mapShift.get(ruleVO.getShiftId());
            } else {
                shift = mapCal.containsKey(date) ? mapShift.get(mapCal.get(date)) : null;
            }
            if(shift == null){
                log.debug("####attend-getShiftVo is null,ruleVO--{},date--{},mapCal--{},mapShift.size-{}",JSONObject.toJSONString(ruleVO),
                        date,JSONObject.toJSONString(mapCal),mapShift.size());
            }
            shift.setElasticRule(ElasticShiftUtils.getElasticRule(fixMode));
            return shift;
        }catch (Exception ex){
            log.error("####attend-getShiftVo:error-{},ruleVO--{},date--{},mapCal--{},mapShift.size-{}",ex.getMessage(),JSONObject.toJSONString(ruleVO),
                    date,JSONObject.toJSONString(mapCal),mapShift.size());
            return null;
        }
    }

    /**
     * 高级版 考勤数据生成
     *
     * @param parms
     * @param beginDate
     * @param endDate
     * @param staffExtList
     * @param staff_ids
     */
    private List<AttendData> generateSenior(AttendParm parms, Date beginDate, Date endDate, List<String> staffExtList, String[] staff_ids, boolean isTask,AttendSchemeVO attendSchemeVO,List<AttendStaff> attendStaffList) {// 某天会有多个签到记录 按天只取出某人最早和最晚的签到记录 分正常签到和外勤
        // 按天合并 统计出每人每天最早的签到时间 和最晚的签到时间 作为上下班时间
        List<AttendRecord> recordAll = null;// 所有的签到记录
        List<LeaveApplyDetail> leaveListAll = null;// 某人日期范围内所有的休假记录
        List<FillAttendHead> fillAttendList = null;//某人日期范围内所有的补考勤记录-diwork
        List<OvertimeSplitBillVO> splitOTBillListAll=null;// 某人日期范围内所有的加班记录-来源为加班单子的
        List<BusinessTripApplyDetail> tripListAll = null;//出差记录
        // 构建开始结束日期数组
        Date[] dates = TimePubUtils.createDateArray(beginDate, endDate);
        List<AttendData> dataList = new ArrayList<>();
        // 将请假出差记录与shiftScope合并取当天的单据时间
        Map<String, WorkingCalendar> workCalendar = parms.getWorkCalendarIdMap();//工作日日历

        Map<String, AttendStaff> attendStaffMap;
        if(CollectionUtils.isEmpty(attendStaffList)) {
            attendStaffMap = attendStaffService.queryAttendStaffForRpt(isTask ? null : staff_ids, beginDate, endDate);
        }else{
            attendStaffMap = AttendStaffHelper.getAttendStaffMapByDate(attendStaffList,beginDate,endDate);
        }
        if (MapUtils.isEmpty(attendStaffMap)) {
            throw new AdderBusinessException("没有可计算人员!","attend.report.i18n41");
        }
        List<OvertimeMainBillVO> mainBillVoList4Insert=new ArrayList<>();//生成新单据批量插入使用
        List<OvertimeSplitBillVO> splitBillVoList4Update=new ArrayList<>();
        Map<String, Map<Date, String>> mapCal = parms.getStaffCalMap();
        Map<String,TsAttendRuleVO> mapRuleInfo = parms.getRuleMap();//所有考勤规则MAP
        Map<String,Double> mapElasticShiftWorkHour = new HashMap<>();//弹性班实际工作时长
        //工作日历对应的日期类型
        Map<String,Map<Date,Integer>> mapCalendarDateType = new HashMap<>();
        Map<String,List<ShiftDate>> mapShiftDates = new HashMap<>();//班次时段数据
        for (String staffId : staffExtList) {
            if(!mapCal.containsKey(staffId) && !parms.getStaffRuleMap().containsKey(staffId)){
                log.error("#####TM generateSenior-{},没有排班数据和规则,跳过",staffId);
                continue;
            }
            Map<Date, String> staffCalMap = mapCal.get(staffId);
            if (null != parms.getRecordMap()) {
                recordAll = parms.getRecordMap().get(staffId);
            }
            //diwork补考勤数据
            if (null != parms.getFillAttendMap()) {
                fillAttendList = parms.getFillAttendMap().get(staffId);
            }

            if (null != parms.getLeaveMap()) {
                leaveListAll = parms.getLeaveMap().get(staffId);
            }

            if (null != parms.getOvertimeSplitMap()) {
                splitOTBillListAll = parms.getOvertimeSplitMap().get(staffId);
            }
            //出差记录
            if(null != parms.getBusinessTripMap()){
                tripListAll = parms.getBusinessTripMap().get(staffId);
            }
            BigDecimal curDeptLeaveSum;//部分放休时长，会影响应出勤
            for (int j = 0; j < dates.length; j++) {//会影响应出勤
                Date date = dates[j];
                try {
                    List<LeaveApplyDetail> leaveListAllNew = new ArrayList<>();
                    if(CollectionUtils.isNotEmpty(leaveListAll)){
                        leaveListAllNew = JSON.parseArray(JSON.toJSONString(leaveListAll), LeaveApplyDetail.class);
                    }
                    curDeptLeaveSum = BigDecimal.ZERO;//部门放休时长，
                    // 考勤档案要根据日报日期及人员确定考勤档案
                    String dateStr = DateUtil.DateToString(date, DateStyle.YYYY_MM_DD);
                    //根据员工ID + date
                    AttendStaff attendstaff = attendStaffMap.get(String.format("%s@%s",staffId ,dateStr));
                    //考勤方式不考勤的也不用算日报
                    if (null == attendstaff || TimeConst.ATTEND_TYPE_NO.equals(attendstaff.getAttendType())) {
                        log.error(String.format("#####TM generateSenior-人员%s在%s没有对应假勤档案信息,跳过", staffId, dateStr));
                        continue;
                    }
                    //如果当前日期的部门不在假勤档案的范围中，跳过
                    if(CollectionUtils.isNotEmpty(attendSchemeVO.getOrgs()) && !attendSchemeVO.getOrgs().contains(attendstaff.getDeptId())){
                        log.error(String.format("#####TM generateSenior-人员%s在%s的假勤档案部门{}不属于改方案,跳过", staffId, dateStr,attendstaff.getDeptId()));
                        continue;
                    }
                    //获取规则和班次
                    TsAttendRuleVO rulevo = getRuleVo(parms.getStaffRuleMap().get(staffId),date,mapRuleInfo,parms.getAllStaffRuleDateMap().get(staffId));
                    if (rulevo == null) {
                        log.error("#####TM generateSenior-人员{}-{}获取考勤规则失败,跳过",staffId,dateStr);
                        continue;
                    }
                    Shift shiftvo = getShiftVo(rulevo,date,staffCalMap,parms.getShiftMap());
                    if (shiftvo == null) {
                        log.debug(String.format("*********attend generateSenior shiftvo is null,人员%s在%s没有排班",attendstaff.getStaffName(), dateStr));
                        continue;
                    }
                    if(shiftvo.getOriWorkHours() == null){
                        shiftvo.setOriWorkHours(shiftvo.getWorkHours());
                    }
                    parms.setCurAttendStaff(attendstaff);
                    //处理工作日历相关的日期类型
                    if(!mapCalendarDateType.containsKey(rulevo.getWorkCalendarId())){
                        List<Date> dateList = new ArrayList<>(Arrays.asList(dates));
                        dateList.add(TimePubUtils.getYesterDay(beginDate));
                        Map<Date,Integer> map = holidayHelper.getDaysTypeDiWork(dateList,parms.getHolidayMap().get(rulevo.getWorkCalendarId()),workCalendar.get(rulevo.getWorkCalendarId()));
                        mapCalendarDateType.put(rulevo.getWorkCalendarId(),map);
                    }
                    log.debug("#####attend generateSenior: getDaysTypeDiWork-map:{}",mapCalendarDateType);
                    String attendType = rulevo.getShiftModeFix();
                    //处理弹性班次的工作时长
                    if (("2".equals(attendType) || "3".equals(attendType)) && shiftvo.isIsdeductrest()) {
                        processElasticShiftWorkHour(mapElasticShiftWorkHour, shiftvo, date);
                    }
                    AttendData data = new AttendData();//初始化datavo
                    createAttendData(data, staffId, date);
                    data.setCalendar(date);//日期
                    data.setDeptId(attendstaff.getDeptId());
                    data.setOrgId(attendstaff.getOrgId());
                    data.setRuleid(rulevo.getId());
                    //判断是否是工作日，不是工作日的也直接continue;
                    //是否假日判断 先确定排班方式 如果不是排班制的话，严格根据工作日历定义
                    WorkingCalendar workingCalendar = workCalendar.get(rulevo.getWorkCalendarId());
                    data.setCountrycode(workingCalendar == null ? "" : workingCalendar.getCountrycode());//国家编码
                    int dataType = mapCalendarDateType.get(workingCalendar.getId()).get(date);
                    int workType = getWorkType(rulevo, shiftvo, dataType);//日期类型
                    data.setDateType(dataType);//日期类型
                    data.setIsholiday(workType == 3);
                    log.debug("#####attend generateSenior: dateType:{},workType:{},date:{},shift:{},rule:{}",dataType, workType,dateStr,shiftvo.getShiftname(),rulevo.getShiftName());
                    /*//如果是免签到的，则记为正常，且打卡开始结束时间为班次的开始结束时间
                    if (workType == 1 && TimeConst.ATTEND_TYPE_NOSIGN.equals(attendstaff.getAttendType())) {//免签到算正常
                        processNoSign(data, shiftvo, dataList, workType);
                        continue;
                    }*/

                    boolean isNeedCalOverTime = isNeedCalOverTime(parms.getOvertimeRule(), workType);
                    /**预先构造加班计算参数,节假日时候也需要用**/
                    OvertimeProcessParmVO otParm = null;
                    if (isNeedCalOverTime) {
                        otParm = getOvertimeProcessParmVO(parms, recordAll, staffId, rulevo, staffCalMap, date, attendstaff, workingCalendar, workType);
                    }
                    ITimeScope[] leaveApplys = null;
                    ITimeScope[] tripApplys = null;//出差记录
                    AttendRecord[] recordsByDate = null;//当天的打卡
                    AttendRecord[] recordsContainFillByDate = null;//当天包含补考勤的的考勤记录
                    FillAttendBody[] fillBodysByDate = null;//当天的补考勤记录子表

                    AttendRecord[] recordsOfThreeDays = null; //三天的打卡记录

                    ITimeScope kqScope = null;
                    if (workType == 1) {
                        kqScope = ElasticShiftUtils.getKqscope(shiftvo, date);
                    } else {
                        kqScope = getRestScope4Ot(date, staffCalMap, parms.getShiftMap(),rulevo,mapCalendarDateType)[0];// 班次的开始结束时间
                        log.error(String.format("*********attend generateSenior getRestScope_GX:%s", JSONObject.toJSONString(kqScope)));
                    }
                    if (!CollectionUtils.isEmpty(recordAll)) {// 某人当天的签到记录
                        if (workType == 1) {
                            recordsByDate = getDateRecordList(recordAll, date, shiftvo, null).toArray(new AttendRecord[0]);
                        } else {
                            recordsByDate = getDateRecordList(recordAll, date, shiftvo, kqScope).toArray(new AttendRecord[0]);
                        }
                    }

                    if (!CollectionUtils.isEmpty(leaveListAllNew)) {// 某人当天的请假记录
                        //哺乳假特殊处理
                        processBreastFeed(leaveListAllNew,kqScope,date,recordsByDate,shiftvo,staffId,workType);
                        leaveApplys = DataFilterUtils.filterRegVOs(kqScope, leaveListAllNew);
                    }
                    //当天的出差记录
                    if (!CollectionUtils.isEmpty(tripListAll)) {
                        tripApplys = DataFilterUtils.filterRegVOs(kqScope, tripListAll);
                    }

                    if (!CollectionUtils.isEmpty(fillAttendList)) {//某人当天的补考勤记录
                        fillBodysByDate = getDateFillRecordList(fillAttendList, date, shiftvo).toArray(new FillAttendBody[0]);
                        data.setResigncnt(ArrayUtils.isEmpty(fillBodysByDate) ? 0 : fillBodysByDate.length);
                    }
                    recordsContainFillByDate = ArrayUtils.isNotEmpty(fillBodysByDate) ? getRecordContainFill(recordsByDate, fillBodysByDate) : recordsByDate;

                    recordsOfThreeDays =  getSignCardOfThreeDays(recordAll,fillAttendList,date);
                    if (isNeedCalOverTime){
                        otParm.setRecordAll(Arrays.asList(recordsOfThreeDays));
                    }
                    data.setShiftId(shiftvo.getId());
                    //非工作日情况
                    if (StringUtils.isEmpty(shiftvo.getId()) || shiftvo.getId().endsWith(IAttendConst.SHIFT_GX) || workType != 1){
                        data.setIsnormal(true);
                        data.setShiftId(AppContext.getContext().getScope() + IAttendConst.SHIFT_GX);
                        //处理公休日请假和出差
                        processLeaveGX(parms, leaveListAllNew, shiftvo, date, null, leaveApplys, kqScope, workType, data,curDeptLeaveSum);
                        processBusinessTripGX(parms, shiftvo, rulevo, workingCalendar, parms.getHolidayMap().get(rulevo.getWorkCalendarId()), tripApplys, date, data, recordsOfThreeDays);
                        setSignCardData(recordsContainFillByDate,data);//设置签卡时间
                        if (isNeedCalOverTime) {
                            processOVertimeGX(splitBillVoList4Update, mainBillVoList4Insert, staffCalMap, data.getShiftId(), parms, date, otParm, recordAll, splitOTBillListAll, data, dataList, shiftvo, kqScope);
                        } else {
                            dataList.add(data);
                        }
                        continue;
                    }

                    //赋值应出勤时长，应出勤天数
                    data.setWorkhours(new BigDecimal(shiftvo.getWorkHours()));
                    data.setWorkdays(new BigDecimal(1));
                    Double unnormallen = 0d;
                    //单据合并放在一个数组里便于计算迟到早退
                    ITimeScope[] mergeLASScopes = TimeScopeUtils.mergeTimeScopes(leaveApplys);
                    //如果出差规则不需要外勤卡，合并时间段
                    boolean notTripCard = parms.getBusinessTripRule() != null && (parms.getBusinessTripRule().getNeedoutsidesign() == null || parms.getBusinessTripRule().getNeedoutsidesign() == 0);
                    if(notTripCard) {
                        mergeLASScopes = TimeScopeUtils.mergeTimeScopes(mergeLASScopes, tripApplys);
                    }

                    /**处理班次 弹性班的 要先固化**/
                    ITimeScope[] workTimeScopes = processShift(date, recordsContainFillByDate, shiftvo, mergeLASScopes, kqScope);

                    /**处理请假时长-请假时长是为了算实际出勤时长才计算的，此时一定是工作日的，公休不需要处理实际出勤时长，
                     * 针对日报上需要展示公休日的请假时长在TsAttendRptCoreBusinessServiceImpl 里处理，**/
                    BigDecimal leavedaySum = processLeaveSum(parms, leaveListAllNew, shiftvo, date, workTimeScopes, leaveApplys, kqScope, workType, data,curDeptLeaveSum);

                    //出差
                    BigDecimal tripDaySum = processTripDaySum(parms, shiftvo, rulevo, workingCalendar, parms.getHolidayMap().get(rulevo.getWorkCalendarId()), tripApplys, date, data, recordsOfThreeDays);

                    if (isNeedCalOverTime) {
                        try {
                            /**处理加班业务**/
                            otParm.setRecords(recordsContainFillByDate);
                            otParm.setShift_id(shiftvo.getId());
                            otParm.setShiftvo(shiftvo);
                            otParm.setWorkTimeScopes(getWorkTimeScope4Ot(kqScope, shiftvo, workTimeScopes));
                            //1,按打卡时间生成加班单据
                            List<OvertimeSplitBillVO> splitBillVOS = new ArrayList<>();
                            OvertimeMainBillVO mainbillvos = generateOtBillsBySign(otParm);
                            if (null != mainbillvos) {
                                mainBillVoList4Insert.add(mainbillvos);
                                splitBillVOS.addAll(mainbillvos.getSplitvolist());
                            }
				            //2,重新计算加班时长
                            List<OvertimeSplitBillVO> updateSplitVos = calSplitBillsLenth(otParm, splitOTBillListAll);
                            if (CollectionUtils.isNotEmpty(updateSplitVos)) {
                                splitBillVoList4Update.addAll(updateSplitVos);
                                splitBillVOS.addAll(updateSplitVos);
                            }
                            //处理加班类别数据
                            if (!CollectionUtils.isEmpty(splitBillVOS)) {
                                Map<String, String> mapJSType = new HashMap<>();
                                for (OvertimeSplitBillVO vo : splitBillVOS) {
                                    String jsTypeId = getJSTypeId(mapJSType, otParm.getOtRule(), vo.getOvertimetypeid());
                                    processBillTypeData(data, vo.getOvertimetypeid(), jsTypeId, vo.getActuallen().doubleValue());
                                }
                            }
                        } catch (Exception ex) {
                            log.error(String.format("###生成加班数据出错-%s",ex.getMessage()), ex);
                        }
                    }
                    if (TimeConst.ATTEND_TYPE_NOSIGN.equals(attendstaff.getAttendType())) {
                        data.setActualworkhours(new BigDecimal(shiftvo.getWorkHours()));
                        data.setActualworkdays(new BigDecimal(1));
                        data.setWorkhours(new BigDecimal(shiftvo.getWorkHours()));
                        data.setWorkdays(new BigDecimal(1));
                        data.setIsnormal(true);
                        data.setShiftId(shiftvo.getId());//班次i
                        if (workType == 3) {
                            data.setIsholiday(true);
                        }
                        /**处理实际出勤时长 实际出勤天数 包括固定班和弹性班**/
                        processActualHours(shiftvo, data, leavedaySum, workTimeScopes, curDeptLeaveSum, tripDaySum);
                    }else {
                        /**将签到数据与请假补考勤单据交切计算迟到早退时间**/
                        // 无签卡或者只有一张卡 如果其他单据不能完全覆盖工作时间则就是旷工
                        //如果出差 + 请假时长  > 班次总时长 则 isnormal=true ,容错 弹性工时不正确的问题
                        Double workHours = shiftvo.getWorkHours();
                        if (workHours <= leavedaySum.doubleValue() + tripDaySum.doubleValue() || workHours <= data.getActualworkhours().doubleValue()) {
                            data.setIsnormal(true);
                        }else if (ArrayUtils.isEmpty(recordsContainFillByDate) || recordsContainFillByDate.length == 1) {// 无签卡记录或者只有一张卡
                            if(data.getActualworkhours().compareTo(BigDecimal.valueOf(0)) == 0 || notTripCard) {
                                processOneSignOrNoSign(data, leaveApplys, mergeLASScopes, workTimeScopes);
                            }else{
                                data.setAbsenthour(data.getWorkhours().subtract(data.getActualworkhours()).subtract(leavedaySum));
                            }
                            //缺勤小时判断
                            if(data.getAbsenthour().doubleValue() > shiftvo.getWorkHours()){
                                data.setAbsenthour(BigDecimal.valueOf(shiftvo.getWorkHours()).setScale(2, BigDecimal.ROUND_HALF_UP));
                            }
                            //如果只有请假时，需要处理下
                            if(leavedaySum.doubleValue() > 0 && leavedaySum.doubleValue() + data.getAbsenthour().doubleValue() > shiftvo.getWorkHours()){
                                Double absenthour = shiftvo.getWorkHours() > leavedaySum.doubleValue() ? shiftvo.getWorkHours() - leavedaySum.doubleValue() : 0d;
                                data.setAbsenthour(BigDecimal.valueOf(absenthour).setScale(2, BigDecimal.ROUND_HALF_UP));
                            }
                        }else {// 有两张以上签卡-签卡按顺序排列的
                            //先算下实际出勤时长,再在处理迟到早退时 根据允许迟到早退时长 补实际出勤时长
                            //按月弹的实出勤要减去与之相交集的请假时间段
                            //实出勤时长计算 也加上补考勤的时间
                            ElasticShiftaAttendUtils.calActworklen(date, recordsContainFillByDate, kqScope, shiftvo, data, workTimeScopes, leaveApplys);
                            boolean isdeductrestsign = shiftvo.isIsdeductrestsign();//中午是否打卡
                            if (isdeductrestsign) {
                                //中午打卡
                                processFourSign(data, recordsContainFillByDate, shiftvo, mergeLASScopes, workTimeScopes, leaveApplys, date);
                            } else {
                                //中午不打卡
                                processTwoSign(data, recordsContainFillByDate, shiftvo, mergeLASScopes, workTimeScopes, leaveApplys);
                            }
                        }

                        /**处理打卡时间点 异常时长,迟到早退旷工时长**/
                        /** 打卡时间点不带补考勤的，取原始的打卡时间-diwork2018-03-20 **/
                        processData(recordsContainFillByDate, data, unnormallen, shiftvo, workTimeScopes, rulevo);

                        /**处理实际出勤时长 实际出勤天数 包括固定班和弹性班**/
                        processActualHours(shiftvo, data, leavedaySum, workTimeScopes, curDeptLeaveSum, tripDaySum);

                        //四次打卡处理打卡时间
                        if (shiftvo.isIsdeductrestsign()) {
                            processDataFour(recordsContainFillByDate, data, shiftvo, date);
                        }

                        //增加缺卡次数处理 2019-05-22
                        try {
                            processShiftDates(shiftvo, mapShiftDates);
                            List<AttendList> attendLists = getShiftCardStatus(date, shiftvo, recordsContainFillByDate, leaveApplys, tripApplys, parms.getBusinessTripRule(),workTimeScopes);
                            log.debug("####缺卡次数数据1:{}-{},list:{}", attendstaff.getStaffName(), date, JSONObject.toJSONString(attendLists));
                            Integer lackCardCnt = getLackCardCnt(attendLists);
                            log.debug("####缺卡次数数据2,result-{}", lackCardCnt);
                            data.setNobrushcount(lackCardCnt);
                            processSignTimeData(data, attendLists);
                        } catch (Exception ex) {
                            log.error("###缺卡次数error,{}", ex.getMessage(), ex);
                        }
                    }
                    /**容错处理**/
                    processLast(data);
                    log.error(String.format("####***attend-生成的考勤数据(%s@%s)---%s", staffId,dateStr,JSONObject.toJSONString(data)));
                    dataList.add(data);
                }catch (Exception ex){
                    log.error(String.format("####attend 生成的考勤数据失败,跳过：staffid-%s,date-%s",staffId,date),ex);
                    continue;
                }
            }
        }

        //生成的加班数据也一次性插入下
        if (!CollectionUtils.isEmpty(mainBillVoList4Insert)) {
            mainBillVoList4Insert.forEach(vo -> {
                if(vo != null) {
                    vo.getSplitvolist().forEach(splitVo -> splitVo.setIscalculate(true));
                }
            });
            log.debug(String.format("#####attend generateSenior-batchInserOTVOs begin,数量:%s,first:%s",mainBillVoList4Insert.size(),JSONObject.toJSONString(mainBillVoList4Insert.get(0))));
            otBillService.batchInserOTVOs(mainBillVoList4Insert);
            log.debug("#####attend generateSenior-batchInserOTVOs end");
        }

        //重新计算过的加班单 往数据库里更新下
//        log.error("###splitBillVoList4Update::{0}####", JSON.toJSONString(splitBillVoList4Update));
        log.error("###splitBillVoList4Update::####"+JSON.toJSONString(splitBillVoList4Update));
        if (!CollectionUtils.isEmpty(splitBillVoList4Update)) {

            List<String> splitIdListForTranslateLeave = new ArrayList();
            List<OvertimeSplitBillVO> transLeave = new ArrayList<>();
            splitBillVoList4Update.forEach(vo -> {
                vo.setIscalculate(true);
                // 获取实际加班时长，实际加班时长大于零时才需要转调休
                BigDecimal actualLen = vo.getActuallen();
                if(actualLen.compareTo(BigDecimal.ZERO) > 0){
                    splitIdListForTranslateLeave.add(vo.getId());
                    transLeave.add(vo);
                }
            });
            log.debug(String.format("#####attend generateSenior-batchUpdateOvertimeSplitVos begin,数量:%s,first:%s",splitBillVoList4Update.size(),JSONObject.toJSONString(splitBillVoList4Update.get(0))));
            otBillService.batchUpdateOvertimeSplitVos(splitBillVoList4Update);
            log.debug("#####attend generateSenior-batchUpdateOvertimeSplitVos end");
            // 如果加班数据有更新，异步调用转调休方法，将结算类型为转调休的按转调休规则进行转调休处理
            log.error("日报计算后，待转调休单据id:{}", JSONObject.toJSONString(transLeave));
            if(CollectionUtils.isNotEmpty(transLeave)) {
                otBillService.transferRest(attendSchemeVO,transLeave);
            }
        }
        if(dataList.size() == 1){
            log.debug("####***attend-生成的考勤数据RESULT-{}",JSONObject.toJSONString(dataList.get(0)));
        }
        return dataList;
    }

    void processBreastFeed(List<LeaveApplyDetail> leaveListAllNew,ITimeScope kqScope,Date date,AttendRecord[] recordsByDate,Shift shiftVo,String staffId,int workType){
        LeaveApplyDetail addLeaveApplyDetail = null;
        boolean hasBR = false;
        for(LeaveApplyDetail leaveApplyDetail : leaveListAllNew){
            if(leaveApplyDetail.getLeaveway() == null){
                continue;
            }
            Date[] leaveApplyDates = TimePubUtils.createDateArray(leaveApplyDetail.getScopeBeginTime(), leaveApplyDetail.getScopeEndTime());
            List<Date> leaveApplyDateList = Arrays.asList(leaveApplyDates);
            if(!leaveApplyDateList.contains(date)){
                continue;
            }
            if(workType != 1){
                leaveApplyDetail.setLeaveday(BigDecimal.ZERO);
                continue;
            }
            hasBR = true;
            Date shiftBeginDate = kqScope.getScopeBeginTime();
            //考虑允许迟到时长
            if (shiftVo.getAllowLateLen() != null && shiftVo.getAllowLateLen() > 0) {
                shiftBeginDate = com.yonyou.hrcloud.berry.helper.DateUtil.addMinute(shiftBeginDate, shiftVo.getAllowLateLen());
            }
            Date shiftEndDate = kqScope.getScopeEndTime();
            if(shiftVo.getElasticRule() != null && shiftVo.getElasticRule() == 1){
                ITimeScope[] workTimeScopes = (ITimeScope[]) ElasticShiftaAttendUtils.getBegEndTime4Tanxing(kqScope, recordsByDate, null, shiftVo, date);
                if(ArrayUtils.isNotEmpty(workTimeScopes)){
                    shiftBeginDate = workTimeScopes[0].getScopeBeginTime();
                    shiftEndDate = workTimeScopes[workTimeScopes.length - 1].getScopeEndTime();
                }
            }
            if(leaveApplyDetail.getLeaveway() == 1){
                //1、系统推荐
                if(recordsByDate != null && recordsByDate.length >= 2){
                    Date firstAttendRecord = recordsByDate[0].getSigntime();
                    Date lastAttendRecord = recordsByDate[recordsByDate.length-1].getSigntime();
                    if(DateUtil4Sign.compare(firstAttendRecord, shiftBeginDate)>0 && DateUtil4Sign.compare(shiftEndDate, lastAttendRecord)>0){
                        //处理既迟到又早退
                        long lateMin = DateUtil4Sign.diffMinutes(firstAttendRecord,shiftBeginDate);
                        //long earlyMin = DateUtil4Sign.diffMinutes(shiftEndDate,lastAttendRecord);
                        long halfLeaveDay = leaveApplyDetail.getLeaveday().longValue() * 60 / 2;
                        if(lateMin > halfLeaveDay){
                            setLeaveApplyDetailTime(leaveApplyDetail,shiftBeginDate,DateUtil4Sign.addMinDate(shiftBeginDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)));
                        }else{
                            setLeaveApplyDetailTime(leaveApplyDetail,shiftBeginDate,DateUtil4Sign.addMinDate(shiftBeginDate, (int) (halfLeaveDay)));
                            leaveApplyDetail.setLeaveday(BigDecimal.valueOf(halfLeaveDay / 60));
                            addLeaveApplyDetail = new LeaveApplyDetail();
                            addLeaveApplyDetail.setLeavetypeid(leaveApplyDetail.getLeavetypeid());
                            addLeaveApplyDetail.setLeaveday(BigDecimal.valueOf(halfLeaveDay / 60));
                            addLeaveApplyDetail.setLeaveway(3);//下班后
                            setLeaveApplyDetailTime(addLeaveApplyDetail,DateUtil4Sign.subMinDate(shiftEndDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60/2)),shiftEndDate);
                            addLeaveApplyDetail.setManualAddLeaveApply(1);
                        }
                    }else if(DateUtil4Sign.compare(firstAttendRecord, shiftBeginDate)>0){
                        //处理迟到
                        setLeaveApplyDetailTime(leaveApplyDetail,shiftBeginDate,DateUtil4Sign.addMinDate(shiftBeginDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)));
                    }else if(DateUtil4Sign.compare(shiftEndDate, lastAttendRecord)>0){
                        //处理早退
                        setLeaveApplyDetailTime(leaveApplyDetail,DateUtil4Sign.subMinDate(shiftEndDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)),shiftEndDate);
                    }else{
                        setLeaveApplyDetailTime(leaveApplyDetail,shiftBeginDate,DateUtil4Sign.addMinDate(shiftBeginDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)));
                    }
                }else{
                    setLeaveApplyDetailTime(leaveApplyDetail,shiftBeginDate,DateUtil4Sign.addMinDate(shiftBeginDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)));
                }
            }else if(leaveApplyDetail.getLeaveway() == 2){
                //2、上班前
                setLeaveApplyDetailTime(leaveApplyDetail,shiftBeginDate,DateUtil4Sign.addMinDate(shiftBeginDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)));
            }else if(leaveApplyDetail.getLeaveway() == 3){
                //3、下班后
                setLeaveApplyDetailTime(leaveApplyDetail,DateUtil4Sign.subMinDate(shiftEndDate, (int) (leaveApplyDetail.getLeaveday().longValue()*60)),shiftEndDate);
            }
        }
        if(addLeaveApplyDetail != null){
            leaveListAllNew.add(addLeaveApplyDetail);
        }
        if(hasBR) {
            log.error("哺乳假匹配结果({}_{}):{}", staffId, date, JSONObject.toJSONString(leaveListAllNew));
        }
    }
    void setLeaveApplyDetailTime(LeaveApplyDetail detail,Date beginTime,Date endTime){
        detail.setLeavebegintime(beginTime);
        detail.setLeaveendtime(endTime);
    }

    /***
     * 判断是否需要计算加班单
     * @param otRule
     * @param workType
     * @return
     */
    private boolean isNeedCalOverTime(OvertimeRule otRule,int workType){
        log.debug(String.format("####attend isNeedCalOverTime-%s,workType-%s",JSONObject.toJSONString(otRule),workType));
        boolean isCal = false;
        if(otRule != null) {
            if (1 == workType && "1".equals(otRule.getWorkovertimetype())) {
                isCal = true;//工作日是否启用加班
            } else if (2 == workType && "1".equals(otRule.getRestovertimetype())) {
                isCal =  true;//公休日日是否启用加班
            } else if (3 == workType && "1".equals(otRule.getHolidayovertimetype())) {
                isCal =  true;//节假日日是否启用加班
            }
        }
        log.debug(String.format("####attend isNeedCalOverTime,result-%s",isCal));
        return isCal;
    }

    /***
     * 获取日期类型
     * @param ruleVO
     * @param shiftVo
     * @param dataType
     * @return
     */
    private int getWorkType(TsAttendRuleVO ruleVO,Shift shiftVo,int dataType){
        //如果是排班制
        if (shiftVo != null && "1".equals(ruleVO.getShiftModeFix())){
            if(shiftVo.getId().endsWith(IAttendConst.SHIFT_GX)){
                return dataType == 3 ? 3 : 2;//排了休息班次，如果日期属性为节假日，返回3，否则返回公休日
            }else {
                return 1;
            }
        }
        return dataType;
    }

    /***
     * 获取日期类型
     * @param ruleVO
     * @param date
     * @param shiftVo
     * @param workingCalendar
     * @return
     */
    private int getWorkType(TsAttendRuleVO ruleVO,Date date,Shift shiftVo,WorkingCalendar workingCalendar,Map<String,Map<Date,Integer>> mapCalendarDateType){
        int dataType = mapCalendarDateType.get(workingCalendar.getId()).get(date);
        //如果是排班制
        if (shiftVo != null && "1".equals(ruleVO.getShiftModeFix())){
            if(shiftVo.getId().endsWith(IAttendConst.SHIFT_GX)){
                return dataType == 3 ? 3 : 2;//排了休息班次，如果日期属性为节假日，返回3，否则返回公休日
            }else {
                return 1;
            }
        }
        return dataType;
    }

    private OvertimeProcessParmVO getOvertimeProcessParmVO(AttendParm parms, List<AttendRecord> recordAll, String staffId, TsAttendRuleVO rulevo, Map<Date, String> staffCalMap, Date date, AttendStaff attendstaff, WorkingCalendar workingCalendar, int workType) {
        OvertimeProcessParmVO otParm =new OvertimeProcessParmVO();
        Map<String,AttendStaff> attendStaffMap4OT=new HashMap<>();
        attendStaffMap4OT.put(staffId,attendstaff);
        otParm.setAttendStaffMap(attendStaffMap4OT);
        otParm.setDate(date);
        otParm.setDateShiftMap(staffCalMap);//排班
        otParm.setIsInsert(false);
        otParm.setDateType(workType);
        log.debug(String.format("#####attend generateSenior workType=%s(staffId-%s,date-%s)",workType,staffId,date));
        otParm.setAttendStaff(attendstaff);
        otParm.setOtRule(parms.getOvertimeRule());
        if(parms.getOvertimeRule() == null){
            log.error(String.format("#####attend generateSenior:人员(%s)对应假勤方案没有指定加班规则!!",attendstaff.getStaffName()));
        }
        otParm.setRecordAll(recordAll);//针对加班 打卡数据不能只取当天的 还要根据规则可能还要取前一天或者后一天的，所以把所有的打卡数据以及班次map做为参数传递
        otParm.setShiftMap(parms.getShiftMap());
        otParm.setWorkingCalendar(workingCalendar);

        AttendStaffExt attendStaffExt = new AttendStaffExt();
        attendStaffExt.setAttendStaff(attendstaff);
        attendStaffExt.setAttendRuleVO(rulevo);
        otParm.setAttendStaffExt(attendStaffExt);
        otParm.setCalType(0);
        return otParm;
    }

    /**
     * 工作时间段，弹性和非弹性的工作时间段算法不同
     * 弹性的需要 把所有的单据和他的打卡时间放在一起固化出一个最合适的上下班时间点用于计算
     *
     * @param date
     * @param records
     * @param shiftvo
     * @param mergeLASScopes
     * @param kqScope
     * @return
     */
    private ITimeScope[] processShift(Date date, AttendRecord[] records, Shift shiftvo, ITimeScope[] mergeLASScopes, ITimeScope kqScope) {
        //工作时间段，弹性和非弹性的工作时间段算法不同
        //弹性的需要 把所有的单据和他的打卡时间放在一起固化出一个最合适的上下班时间点用于计算
        ITimeScope[] workTimeScopes = null;
        if (1 == shiftvo.getShiftType()) {//非弹性
            workTimeScopes = TimePubUtils.getWorkTimeScopes(date, shiftvo);
        } else {//弹性
            if (null == shiftvo.getElasticRule() || 1 == shiftvo.getElasticRule()) {//按日弹
                workTimeScopes = (ITimeScope[]) ElasticShiftaAttendUtils.getBegEndTime4Tanxing(kqScope, records, mergeLASScopes, shiftvo, date);
            } else {//按月弹
                workTimeScopes = (ITimeScope[]) ElasticShiftUtils.getBegEndTime4MonthTanxing(shiftvo, date);
                //按月弹的应出勤时长就是核心时间段时长--按需求定义 应需求时长是班次定义的时长
            }
        }
        log.error(String.format("#####attend-processShift,获取工作时段：%s",JSONObject.toJSONString(workTimeScopes)));
        return workTimeScopes;
    }

    /**
     * 只有一张签卡记录时，与当天的请假记录合并计算,只有休假，待改进
     * 是否缺勤
     * 缺勤次数
     * 缺勤时长
     * 是否正常
     *
     * @param data
     * @param leaveApplys
     * @param mergeLASScopes
     * @param workTimeScopes
     */
    private void processOneSignOrNoSign(AttendData data, ITimeScope[] leaveApplys, ITimeScope[] mergeLASScopes,
                                        ITimeScope[] workTimeScopes) {
        // 如果没刷卡时间完全被请假等单据覆盖则当天没有迟到早退旷工
        // 减去了请假等单据之后的旷工时间段
        ITimeScope[] exceptionScopesMinusLASBills = TimeScopeUtils.minusTimeScopes(workTimeScopes, mergeLASScopes);
        // 为空表示旷工时段被其他单据覆盖
        if (ArrayUtils.isEmpty(exceptionScopesMinusLASBills)) {
            data.setIsnormal(true);
        } else {// 其他单据覆盖后还有异常时间段
            double absentSeconds = new Double(TimeScopeUtils.getLength(exceptionScopesMinusLASBills));
            data.setIsabsent(true);
            data.setAbsentcount(1);
            data.setAbsenthour((BigDecimal.valueOf(absentSeconds / 3600)).setScale(2, BigDecimal.ROUND_HALF_UP));
        }
    }

    // 有多张卡时候处理迟到早退数据
    private void processTwoSign(AttendData data, AttendRecord[] records, Shift shiftvo, ITimeScope[] mergeLASScopes,
                               ITimeScope[] workTimeScopes, ITimeScope[] leaveApplys) {
        Date firstsigntime;
        Date lastsigntime;
        if (ArrayUtils.isEmpty(records) && ArrayUtils.isEmpty(mergeLASScopes)) {
            return;
        } else if (ArrayUtils.isNotEmpty(records) && ArrayUtils.isEmpty(mergeLASScopes)) {
            firstsigntime = records[0].getSigntime();
            lastsigntime = records[records.length - 1].getSigntime();
        } else if (ArrayUtils.isEmpty(records) && ArrayUtils.isNotEmpty(mergeLASScopes)) {
            firstsigntime = mergeLASScopes[0].getScopeBeginTime();
            lastsigntime = mergeLASScopes[mergeLASScopes.length - 1].getScopeEndTime();
        } else {
            firstsigntime = DateUtil4Sign.compare(records[0].getSigntime(), mergeLASScopes[0].getScopeBeginTime()) == 1 ? mergeLASScopes[0].getScopeBeginTime() : records[0].getSigntime();
            lastsigntime = DateUtil4Sign.compare(records[records.length - 1].getSigntime(), mergeLASScopes[mergeLASScopes.length - 1].getScopeEndTime()) == 1 ? records[records.length - 1].getSigntime() : mergeLASScopes[mergeLASScopes.length - 1].getScopeEndTime();
        }
        // 当天班的开始上班时间与结束上班时间
        //liushm 多时段处理
        Date shiftBegin = workTimeScopes[0].getScopeBeginTime();
        Date shiftEnd = workTimeScopes[workTimeScopes.length - 1].getScopeEndTime();
        /*if (workTimeScopes.length == 2) {
            shiftBegin = workTimeScopes[0].getScopeBeginTime();
            shiftEnd = workTimeScopes[1].getScopeEndTime();
        } else {
            shiftBegin = workTimeScopes[0].getScopeBeginTime();
            shiftEnd = workTimeScopes[0].getScopeEndTime();
        }*/
        // 虽然多张卡如果下班卡晚于班次结束时间或者上班卡早于班次开始时间则相当于只刷一张，还是旷工
        if (lastsigntime.getTime() < shiftBegin.getTime() || firstsigntime.getTime() > shiftEnd.getTime()) {
            processOneSignOrNoSign(data, leaveApplys, mergeLASScopes, workTimeScopes);
        } else {
            if (firstsigntime.getTime() <= shiftBegin.getTime() && lastsigntime.getTime() >= shiftEnd.getTime()) {// 正常
                data.setIsnormal(true);
//				day.setOriginal_isnormal(true);
            } else {
                // 减去了请假等单据之后的异常时间段
                ITimeScope[] exceptionScopesMinusLASBills = TimeScopeUtils.minusTimeScopes(workTimeScopes,
                        mergeLASScopes);
                if (ArrayUtils.isEmpty(exceptionScopesMinusLASBills)) {// 异常被请假出差补考勤等单据覆盖
                    data.setIsnormal(true);
                } else {
                    ITimeScope[] lateScope = null;// 迟到时间段
                    ITimeScope[] earlyScope = null;// 早退时间段
                    if (firstsigntime.getTime() > shiftBegin.getTime()) {// 迟到
                        //day.setIslater(true);
                        // 取出迟到时间段
                        lateScope = getLaterScopes(data.getCalendar(), firstsigntime, shiftvo, workTimeScopes);
                        // 将其他单据与迟到时间段交切,得出还未被覆盖的异常时间段
                        ITimeScope[] minusScopes = TimeScopeUtils.minusTimeScopes(lateScope, mergeLASScopes);// 将迟到段减去出休假等单据
                        // 如果minusScopes为空则说明迟到时间被其他单据覆盖，不再有迟到了
                        if (ArrayUtils.isEmpty(minusScopes)) {// 如果有迟到或者早退
                            // 则肯定不会再有旷工了，旷工只存在没打卡或者一张卡的情况
                            //day.setIslater(false);
                        } else {// 计算被单据覆盖后的迟到时长
                            processLateWithShift(shiftvo, minusScopes,
                                    new Double(TimeScopeUtils.getLength(minusScopes)), data);
                        }
                    }
                    if (lastsigntime.getTime() < shiftEnd.getTime()) {// 早退
                        //day.setIsearly(true);
                        // 取出早退时间段
                        earlyScope = getEarlyScopes(data.getCalendar(), lastsigntime, shiftvo, workTimeScopes);
                        // 被其他单据覆盖后的早退时间段
                        ITimeScope[] minusScopes = TimeScopeUtils.minusTimeScopes(earlyScope, mergeLASScopes);
                        // 如果被覆盖后异常为空则不再早退了
                        if (ArrayUtils.isEmpty(minusScopes)) {
                            //day.setIsearly(false);
                            data.setEarlycount(null);
                            data.setEarlylong(null);
                        } else {// 计算被单据覆盖后的早退时长
                            processEarlyWithShift(shiftvo, minusScopes,
                                    new Double(TimeScopeUtils.getLength(minusScopes)), data);
                        }
                    }
                }
            }
        }
    }

    /**
     * 有多张卡时候处理迟到早退、旷工数据等
     *
     * @param data
     * @param records
     * @param shiftvo
     * @param mergeLASScopes
     * @param workTimeScopes
     * @param leaveApplys
     */
    private void processFourSign(AttendData data, AttendRecord[] records, Shift shiftvo, ITimeScope[] mergeLASScopes,
                                 ITimeScope[] workTimeScopes, ITimeScope[] leaveApplys, Date date) {
        ITimeScope[] timeScopes = TimePubUtils.getSignTimeScopeByType(date, shiftvo);
        //第一段时间内的打卡记录
        AttendRecord[] firstScopeRecords = getDateRecordListInScope(Arrays.asList(records), timeScopes[0]).toArray(new AttendRecord[0]);
        //第二段时间内的打卡记录
        AttendRecord[] secondScopeRecords = getDateRecordListInScope(Arrays.asList(records), timeScopes[1]).toArray(new AttendRecord[0]);

        AttendData attendData1 = new AttendData();
        attendData1.setCalendar(data.getCalendar());
        AttendData attendData2 = new AttendData();
        attendData2.setCalendar(data.getCalendar());
        Shift shiftTime1Vo = new Shift();//第一段班次信息
        shiftTime1Vo.setIsdeductrest(false);//中午不休息
        shiftTime1Vo.setBeginday(shiftvo.getBeginday());
        shiftTime1Vo.setBegintime(shiftvo.getBegintime());
        shiftTime1Vo.setShiftType(1);//两段是固定班次
        shiftTime1Vo.setAllowEarlyLen(shiftvo.getAllowEarlyLen());
        shiftTime1Vo.setEarlylenIsabsent(shiftvo.getEarlylenIsabsent());
        shiftTime1Vo.setAllowLateLen(shiftvo.getAllowLateLen());
        shiftTime1Vo.setLatelenIsabsent(shiftvo.getLatelenIsabsent());
        shiftTime1Vo.setEndday(shiftvo.getRestbeginday());
        shiftTime1Vo.setEndtime(shiftvo.getRestBegtime());
        Shift shiftTime2Vo = new Shift();

        shiftTime2Vo.setIsdeductrest(false);//中午不休息
        shiftTime2Vo.setBeginday(shiftvo.getRestendday());
        shiftTime2Vo.setBegintime(shiftvo.getRestEndtime());
        shiftTime2Vo.setShiftType(1);//两段是固定班次
        shiftTime2Vo.setAllowEarlyLen(shiftvo.getAllowEarlyLen());
        shiftTime2Vo.setEarlylenIsabsent(shiftvo.getEarlylenIsabsent());
        shiftTime2Vo.setAllowLateLen(shiftvo.getAllowLateLen());
        shiftTime2Vo.setLatelenIsabsent(shiftvo.getLatelenIsabsent());
        shiftTime2Vo.setEndday(shiftvo.getEndday());
        shiftTime2Vo.setEndtime(shiftvo.getEndtime());
        ITimeScope[] timeScopes1 = new ITimeScope[1];
        timeScopes1[0] = workTimeScopes[0];
        ITimeScope[] timeScopes2 = new ITimeScope[1];
        timeScopes2[0] = workTimeScopes[1];

        if (firstScopeRecords == null || firstScopeRecords.length == 0 || firstScopeRecords.length == 1) {
            processOneSignOrNoSign(attendData1, leaveApplys, mergeLASScopes, timeScopes1);
        } else {
            processTwoSign(attendData1, firstScopeRecords, shiftTime1Vo, mergeLASScopes, timeScopes1, leaveApplys);
        }

        if (secondScopeRecords == null || secondScopeRecords.length == 0 || secondScopeRecords.length == 1) {
            processOneSignOrNoSign(attendData2, leaveApplys, mergeLASScopes, timeScopes2);
        } else {
            processTwoSign(attendData2, secondScopeRecords, shiftTime2Vo, mergeLASScopes, timeScopes2, leaveApplys);
        }

        //第一段班相关统计数据 + 第二段班统计数据
        Integer firstLateCount = attendData1.getLatecount() == null ? 0 : attendData1.getLatecount();// 迟到次数
        BigDecimal firstLateLong = attendData1.getLatelong() == null ? new BigDecimal("0") : attendData1.getLatelong();//迟到时长
        Integer firstEarlyCount = attendData1.getEarlycount() == null ? 0 : 1;//早退次数
        BigDecimal firstEarlyHour = attendData1.getEarlylong() == null ? new BigDecimal("0") : attendData1.getEarlylong();//早退时长
        BigDecimal firstAbsentHour = attendData1.getAbsenthour() == null ? new BigDecimal("0") : attendData1.getAbsenthour();//矿工时长
        //第二段班相关统计数据
        Integer secondLateCount = attendData2.getLatecount() == null ? 0 : attendData2.getLatecount();//迟到次数
        BigDecimal secondLateLong = attendData2.getLatelong() == null ? new BigDecimal("0") : attendData2.getLatelong();//迟到时长
        Integer secondEarlyCount = attendData2.getEarlycount() == null ? 0 : 1;//早退次数
        BigDecimal secondEarlyHour = attendData2.getEarlylong() == null ? new BigDecimal("0") : attendData2.getEarlylong();//早退时长
        BigDecimal secondAbsentHour = attendData2.getAbsenthour() == null ? new BigDecimal("0") : attendData2.getAbsenthour();//矿工时长

        //第一段班相关统计数据  第二段班统计数据
        Integer lateCount = firstLateCount + secondLateCount;//迟到次数
        BigDecimal lateLong = firstLateLong.add(secondLateLong);//迟到时长
        Integer earlyCount = firstEarlyCount + secondEarlyCount;//早退次数
        BigDecimal earlyLong = firstEarlyHour.add(secondEarlyHour);//早退时长
        boolean isabsent = false;//是否旷工
        BigDecimal absentHour = new BigDecimal("0");//旷工时长
        Integer absentCount = 0;//旷工次数

        //一整天的状态，如果有迟到时长
        //处理迟到是否算为旷工
        Integer lateIsAbsent = shiftvo.getLatelenIsabsent();
        if (null != lateIsAbsent && lateLong.doubleValue() > lateIsAbsent.intValue()) {//迟到时长 超过记为旷工时长
            isabsent = true;
            absentHour = lateLong.divide(new BigDecimal(60), 4, BigDecimal.ROUND_HALF_UP);//迟到分钟数记为旷工小时
            absentCount = 1;//旷工次数
            //记为旷工则则迟到数据就清除掉
            lateCount = 0;
            lateLong = null;
        }

        Integer earlylen_isabsent = shiftvo.getEarlylenIsabsent();
        //中午要求打卡暂时先不计算旷工相关内容
        if (null != earlylen_isabsent && earlyLong.doubleValue() > earlylen_isabsent.intValue()) {//早退时长 超过 记为旷工时长
            isabsent = true;
            absentHour = absentHour.add((earlyLong.divide(new BigDecimal(60), 4, BigDecimal.ROUND_HALF_UP)));//早退分钟数记为旷工小时
            absentCount = 1;//旷工次数
            //记为旷工则早退数据就清除掉
            earlyLong = null;
            earlyCount = 0;
        }

        //处理本来的旷工
        absentHour = firstAbsentHour.add(secondAbsentHour).add(absentHour);
        data.setLatecount(lateCount);
        data.setLatelong(lateLong);
        data.setEarlycount(earlyCount);
        data.setEarlylong(earlyLong);
        data.setAbsentcount(absentCount);
        data.setAbsenthour(absentHour);
        if (absentHour.doubleValue() > 0) {
            data.setIsabsent(true);
        } else {
            data.setIsabsent(isabsent);
        }

        if ((null == data.getLatecount() || data.getLatecount() == 0) && (null == data.getEarlycount() || data.getEarlycount() == 0) && !data.isIsabsent()) {//如果既没迟到又没早退，则当天应该算是正常的
            data.setIsnormal(true);
        }
    }

    // 判断第一次打卡的时间是否正常，需要跟班次设置中的计算异常考勤的开始时间，结束时间，允许迟到时长，早退时长做比较 2017-4-15号
    private void processLateWithShift(Shift shift, ITimeScope[] lateScope, Double lateLength, AttendData data) {
        Integer allow_late_len = shift.getAllowLateLen();// 允许迟到时长-整数单位是分钟
        if (null != allow_late_len && lateLength.intValue() / 60 <= allow_late_len) {
            data.setLatecount(0);
            data.setLatelong(BigDecimal.ZERO);
        } else {

            Integer lateIsAbsent = shift.getLatelenIsabsent();
            data.setLatelong(BigDecimal.valueOf(lateLength / 60));
            data.setLatecount(1);
            //四段班的先不计算计算旷工相关数据，后面统一计算
            if (!shift.isIsdeductrestsign() && null != lateIsAbsent && data.getLatelong().doubleValue() > lateIsAbsent.intValue()) {//迟到时长 超过记为旷工时长
                data.setIsabsent(true);
                data.setAbsentcount(1);
                data.setAbsenthour(data.getLatelong().divide(new BigDecimal(60), 4, BigDecimal.ROUND_HALF_UP));//迟到分钟数记为旷工小时
                //记为旷工则则迟到数据就清除掉
                data.setLatelong(BigDecimal.ZERO);
                data.setLatecount(0);
            }
        }
    }

    /**
     * 处理早退
     */
    private void processEarlyWithShift(Shift shift, ITimeScope[] earlyScope, Double earlyLength, AttendData data) {
        // 时区偏移量,不加这个取的时间不带时区getTime毫秒数不正确
        Integer allow_early_len = shift.getAllowEarlyLen();// 允许早退时长
        if (null != allow_early_len && earlyLength.intValue() / 60 <= allow_early_len) {
            data.setEarlycount(null);
            data.setEarlylong(null);
            if ((null == data.getLatecount() || data.getLatecount() == 0) && !data.isIsabsent()) {//如果既没迟到又没早退，则当天应该算是正常的
                data.setIsnormal(true);
            }
            /**如果有早退 但是在允许范围内 则针对弹性班 他的实际出勤时长是要加上这几分钟的**/
            if (2 == shift.getShiftType() && data.getActualworklong() > 0) {
                double result = data.getActualworklong() + earlyLength.doubleValue();
                data.setActualworklong(result);
            }
        } else {
            data.setEarlycount(1);
            data.setEarlylong(BigDecimal.valueOf(earlyLength / 60));
            //之后处理早退时长超过班次设定时长记为旷工的逻辑
            //Integer earlylen_isabsent=shift.getEarlylen_isabsent();
            Integer earlylen_isabsent = shift.getEarlylenIsabsent();
            //中午要求打卡暂时先不计算旷工相关内容
            if (!shift.isIsdeductrestsign() && null != earlylen_isabsent && data.getEarlylong().doubleValue() > earlylen_isabsent.intValue()) {//早退时长 超过 记为旷工时长
                data.setIsabsent(true);
                data.setAbsentcount(1);
                if (data.getAbsenthour() != null && data.getAbsenthour().doubleValue() > 0) {
                    data.setAbsenthour(data.getAbsenthour().add(data.getEarlylong().divide(new BigDecimal(60), 4, BigDecimal.ROUND_HALF_UP)));//早退分钟数记为旷工小时
                } else if(data.getEarlylong() != null) {
                    data.setAbsenthour(data.getEarlylong().divide(new BigDecimal(60), 4, BigDecimal.ROUND_HALF_UP));
                }
                //记为旷工则早退数据就清除掉
                data.setEarlycount(null);
                data.setEarlylong(null);
            }
        }
    }

    /**
     * 拼出迟到时间scope,要分弹性班和非弹性班次
     *
     * @param date
     * @param firstSigntime
     * @param shiftvo
     * @param workTimeScopes
     * @return
     */
    private ITimeScope[] getLaterScopes(Date date, Date firstSigntime, Shift shiftvo, ITimeScope[] workTimeScopes) {
        //班次结束的毫秒数
        Date worktime1_begin = TimePubUtils.getRealTime(date, shiftvo.getBeginday(), shiftvo.getBegintime(),true);
        if (shiftvo.getShiftType() == 2) {
            worktime1_begin = workTimeScopes[0].getScopeBeginTime();
        }
        ITimeScope[] scopes = new ITimeScope[]{new DefaultTimeScope(worktime1_begin, firstSigntime)};
        if (shiftvo.isIsdeductrest() && null != shiftvo.getRestBegtime() && null != shiftvo.getRestEndtime()) {
            Date worktime1_end = TimePubUtils.getRealTime(date, shiftvo.getRestbeginday(), shiftvo.getRestBegtime());
            Date worktime2_begin = TimePubUtils.getRealTime(date, shiftvo.getRestendday(), shiftvo.getRestEndtime());
            if (firstSigntime.before(worktime1_end)) {// 第一次签到在休息时间之前
                scopes = new ITimeScope[]{new DefaultTimeScope(worktime1_begin, firstSigntime)};
            } else if (!worktime1_end.after(firstSigntime) && !worktime2_begin.before(firstSigntime)) {// 签到时间落在休息时间中间
                scopes = new ITimeScope[]{new DefaultTimeScope(worktime1_begin, worktime1_end)};
            } else {// 签到时间在下午段
                scopes = new ITimeScope[]{new DefaultTimeScope(worktime1_begin, worktime1_end),
                        new DefaultTimeScope(worktime2_begin, firstSigntime)};
            }
        }
        return scopes;
    }

    /**
     * 拼出早退时间scope 要分弹性班和非弹性班次
     *
     * @param date
     * @param lastSigntime
     * @param shiftvo
     * @param workTimeScopes
     * @return
     */
    public ITimeScope[] getEarlyScopes(Date date, Date lastSigntime, Shift shiftvo, ITimeScope[] workTimeScopes) {
        //班次结束的毫秒数
        //long shiftEnd=shiftvo.getEndtime().getTime();
        Date worktime2_end = TimePubUtils.getRealTime(date, shiftvo.getEndday(), shiftvo.getEndtime());
        if (shiftvo.getShiftType() == 2) {
            worktime2_end = workTimeScopes[workTimeScopes.length - 1].getScopeEndTime();
        }
        ITimeScope[] scopes = new ITimeScope[]{new DefaultTimeScope(lastSigntime, worktime2_end)};
        if (shiftvo.isIsdeductrest() && null != shiftvo.getRestBegtime() && null != shiftvo.getRestEndtime()) {
            Date worktime1_end = TimePubUtils.getRealTime(date, shiftvo.getRestbeginday(), shiftvo.getRestBegtime());
            Date worktime2_begin = TimePubUtils.getRealTime(date, shiftvo.getRestendday(), shiftvo.getRestEndtime());
            if (lastSigntime.before(worktime1_end)) {// 最后一次签到在休息时间之前
                scopes = new ITimeScope[]{new DefaultTimeScope(lastSigntime, worktime1_end),
                        new DefaultTimeScope(worktime2_begin, worktime2_end)};
            } else if (!worktime1_end.after(lastSigntime) && !worktime2_begin.before(lastSigntime)) {// 最后一次签到落在休息日中间
                scopes = new ITimeScope[]{new DefaultTimeScope(worktime2_begin, worktime2_end)};
            } else {// 最后一次签到在下午段
                scopes = new ITimeScope[]{new DefaultTimeScope(lastSigntime, worktime2_end)};
            }
        }
        return scopes;
    }

    /**
     * 处理免签到
     * 免签到按照全勤处理
     */
    private void processNoSign(AttendData data, Shift shiftvo, List<AttendData> dataList, int worktype) {
        data.setActualworkhours(new BigDecimal(shiftvo.getWorkHours()));
        data.setActualworkdays(new BigDecimal(1));
        data.setWorkhours(new BigDecimal(shiftvo.getWorkHours()));
        data.setWorkdays(new BigDecimal(1));
        data.setIsnormal(true);
        data.setShiftId(shiftvo.getId());//班次id
        if (worktype == 3) {
            data.setIsholiday(true);
        }
        dataList.add(data);
    }

    /**
     * 处理请假时长
     */
    private BigDecimal processLeaveSum(AttendParm parms, List<LeaveApplyDetail> leaveListAll, Shift shiftvo, Date date, ITimeScope[] workTimeScopes,
                                       ITimeScope[] leaveApplys, ITimeScope kqScope,int dateType,AttendData data,BigDecimal curDeptLeaveSum) {
        BigDecimal leavedaySum = new BigDecimal(0);
        if (CollectionUtils.isNotEmpty(leaveListAll)) {/** 与请假单合并计算 **/
            log.debug("#####attend processLeaveSum-leaveListAll-{}",leaveListAll.size());
            leaveApplys = DataFilterUtils.filterRegVOs(kqScope, leaveListAll);
            log.debug("#####attend processLeaveSum-leaveApplys-{}",JSONObject.toJSONString(leaveApplys));
            // 新算法请假时长
            if (!ArrayUtils.isEmpty(leaveApplys)) {
                Map<Date,Shift> mapShiftData = null;
                //有实际请假时长的次数
                int actualCnt = 0;
                for (ITimeScope bill : leaveApplys) {
                    LeaveApplyDetail apply = (LeaveApplyDetail) bill;
                    if (null == parms.getLeaveTypeNewMap()) {
                        continue;
                    }
                    LeaveTypeNew leavetype = parms.getLeaveTypeNewMap().get(apply.getLeavetypeid());
                    if(leavetype == null){
                        log.error(String.format("#####attend processLeaveSum-leavetype(id=%s)为null",apply.getLeavetypeid()));
                        continue;
                    }
                    //存休假别设置
                    if("12".equals(leavetype.getCode())){
                        leavetype.setMinUnit(apply.getMinunit());
                    }
                    BigDecimal curBillLen = BigDecimal.ZERO;
                    boolean halfDay = false;
                    BigDecimal halfDay_workHour = BigDecimal.ZERO;
                    try {
                        if(mapShiftData == null){
                            mapShiftData = getDateShiftMap(parms,apply.getStaffid());
                        }
                        //半天假
                        if("1".equals(leavetype.getMinTime()) && "2".equals(leavetype.getMinUnit())){
                            Map<String,BigDecimal> mapData = leaveservice.getHalfDayDurationReport(apply.getLeavebegintime(),
                                    apply.getLeaveendtime(), leavetype,getCopyShift(shiftvo), date,dateType,mapShiftData,apply.getStaffid());
                            log.error(String.format("#####attend processLeaveSum-mapData---%s",mapData));
                            if(MapUtils.isNotEmpty(mapData)) {
                                if(mapData.containsKey("duration")) {
                                    curBillLen = mapData.get("duration");
                                }
                                if(curBillLen.doubleValue() == 1) {
                                    halfDay_workHour = new BigDecimal(shiftvo.getWorkHours());
                                } else if(mapData.containsKey("actualWorkHours")) {
                                    halfDay_workHour = mapData.get("actualWorkHours");
                                }
                                halfDay = true;
                            }
                        }else{
                            //判断哺乳假不走微服务
                            if(((LeaveApplyDetail) bill).getLeaveway()!=null){
                                curBillLen = ((LeaveApplyDetail) bill).getLeaveday();
                            }else{
                                curBillLen = leaveservice.getLength4NormalShift(apply.getLeavebegintime(),
                                        apply.getLeaveendtime(), getCopyShift(shiftvo), date, leavetype, workTimeScopes,null,mapShiftData, dateType);
                            }
                            MqMsgSupport.logMsg(parms.getLogId(),String.format("#####attend processLeaveSum-curBillLen---%s",curBillLen));
                        }
                    } catch (Exception ex) {
                        log.error(ex.getMessage(), ex);
                    }
                    processBillTypeData(data,leavetype.getId(),leavetype.getRefId(),curBillLen.doubleValue());//处理请假类别数据
                    //此处的休假时长是为了计算实出勤时长用的，实出勤时长单位都是小时，这里的休假时长 如果单位是天的 需要转换成小时
                    //多条记录时，数据会有偏差
                    if(halfDay){
                        leavedaySum = leavedaySum.add(halfDay_workHour);
                    }else if (null != leavedaySum && "2".equals(leavetype.getMinUnit())) {//按天计算
                        leavedaySum = leavedaySum.add(new BigDecimal(curBillLen.doubleValue() * shiftvo.getWorkHours()).setScale(2, RoundingMode.HALF_UP));
                    }else{
                        leavedaySum = leavedaySum.add(BigDecimal.valueOf(curBillLen.doubleValue()));
                    }
                    //如果是放休，计入放休时间
                    if("11".equals(leavetype.getCode())){
                        curDeptLeaveSum = curDeptLeaveSum.add(leavedaySum);
                    }
                    if(curBillLen.doubleValue() > 0 && null == apply.getManualAddLeaveApply()){
                        actualCnt ++;
                    }
                }
                data.setIsleave(actualCnt > 0);//是否请假
                data.setLeavecnt(actualCnt);//请假次数
            }
        }
        return leavedaySum;
    }

    private Map<Date,Shift> getDateShiftMap(AttendParm parms,String staffId){
        Map<Date,Shift> map = new HashMap<>();
        if(MapUtils.isEmpty(parms.getShiftMap()) || MapUtils.isEmpty(parms.getStaffCalMap()) || !parms.getStaffCalMap().containsKey(staffId)) {
            return map;
        }

        for (Map.Entry<Date,String> entry : parms.getStaffCalMap().get(staffId).entrySet()) {
            Shift shift = parms.getShiftMap().get(entry.getValue());
            if(shift != null){
                map.put(entry.getKey(),shift);
            }
        }
        return map;
    }

    /**
     * 处理出差时长(小时)
     */
    private BigDecimal processTripDaySum(AttendParm parms, Shift shiftvo,TsAttendRuleVO attendRuleVO,WorkingCalendar workingCalendar,List<Holiday> holidays,ITimeScope[] tripApplys,Date calDate,AttendData data,AttendRecord[] records) {
        BigDecimal tripDaySum = BigDecimal.ZERO;
        if(ArrayUtils.isEmpty(tripApplys) || parms.getBusinessTripRule() == null) {
            return tripDaySum;
        }
        try {
            log.error("####attend-queryBusinessDurationByAll-出差计算结果-ready:{}",JSONObject.toJSONString(shiftvo));
            Map<String,Object> mapData = businessTripApplyService.queryBusinessDurationByAll(tripApplys, null, parms.getBusinessTripRule(),
                    attendRuleVO, parms.getStaffCalMap(), new Shift[]{getCopyShift(shiftvo)}, workingCalendar, holidays, calDate, records, parms.getCurAttendStaff());
            MqMsgSupport.logMsg(parms.getLogId(),String.format("####attend-queryBusinessDurationByAll-出差计算结果:%s",mapData));
            if(MapUtils.isEmpty(mapData)) {
                return tripDaySum;
            }
            for (Map.Entry<String,Object> entry : mapData.entrySet()) {
                if("tripcnt".equals(entry.getKey())) {
                    data.setTripcnt(Integer.valueOf(String.valueOf(entry.getValue())));//出差次数
                } else if("tripHourSum".equals(entry.getKey())) {
                    data.setActualworkhours(new BigDecimal(String.valueOf(entry.getValue())));
                }else if("tripDaySum".equals(entry.getKey())) {
                    data.setActualworkdays(new BigDecimal(String.valueOf(entry.getValue())));
                }else if(entry.getKey().contains("_")){
                    String[] billType = entry.getKey().split("_",2);
                    Double len = Double.parseDouble(String.valueOf(entry.getValue()));
                    processBillTypeData(data,billType[0],billType[1],len);
                }
            }
        }catch (Exception ex){
            log.error(String.format("####计算出差时间出错-%s",ex.getMessage()), ex);
        }
        return tripDaySum;
    }

    /***
     * 获取班次copy对象
     * @param shift
     * @return
     */
    private Shift getCopyShift(Shift shift){
        if(shift.getElasticRule() != null && shift.getElasticRule() == 1){
            Shift newShift = new Shift();
            BeanUtils.copyProperties(shift,newShift);
            return newShift;
        }
        return shift;
    }

    /***
     * 出差时长-从天转小时
     * 正常班次---> 天数 * 班次时长
     * 非工作日---> 天数 * 出差规则中的折算小时数
     * @param tripRule
     * @param shift
     * @param len
     * @return
     *//*
    private BigDecimal getTripHour(BusinessTripRule tripRule,Shift shift,BigDecimal len,Date date){
        if(shift == null) return BigDecimal.ZERO;
        if(shift.getShiftType() == 1){
            if(shift.getId().endsWith(IAttendConst.SHIFT_GX)){
                if(tripRule == null) return BigDecimal.ZERO;
                return new BigDecimal(len.doubleValue() * tripRule.getCalconvert());
            }
            return new BigDecimal(len.doubleValue() * shift.getWorkHours());
        }else if(shift.getElasticRule() == null || shift.getElasticRule() == 1 || shift.getElasticRule() == 2)
        {
            double workHour = shift.getWorkHours();
            if(shift.isIsdeductrest()){
                long decuctRest = ElasticShiftUtils.getShiftDeductrest(date, shift);
                if (decuctRest > 0){
                    workHour = BigDecimal.valueOf((workHour * 3600 - decuctRest) / 3600).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                }
            }
            return BigDecimal.valueOf(len.doubleValue() * workHour);
        }
        return BigDecimal.ZERO;
    }*/

    /***
     * 处理业务类别数据
     * @param attendData
     * @param billType1
     * @param billType2
     * @param len
     */
    private void processBillTypeData(AttendData attendData,String billType1,String billType2,Double len){
        if(len <= 0) return;
        Map<String,Double> mapBTData = attendData.getMapBillTypeData();
        if(mapBTData == null) mapBTData = new HashMap<>();
        if(StringUtils.isNotEmpty(billType1)) addMap(mapBTData,billType1,len);
        if(StringUtils.isNotEmpty(billType2)) addMap(mapBTData,billType2,len);
        attendData.setMapBillTypeData(mapBTData);
    }
    private void addMap(Map<String,Double> mapBTData,String key,Double len){
        if(StringUtils.isEmpty(key)) return;
        if(mapBTData.containsKey(key)){
            len = len + mapBTData.get(key);
        }
        mapBTData.put(key,len);
    }

    private void setSignCardData(AttendRecord[] records,AttendData data) {
        //设置 首次发卡时间 末次刷卡时间
        if (ArrayUtils.isEmpty(records)) return;
        AttendRecord firstRecord;
        if (records.length == 1) {
            firstRecord = records[0];
            data.setFirstBeginsign(firstRecord.getSigntime());
            data.setSignbegintime(data.getFirstBeginsign());
        } else {
            firstRecord = records[0];
            data.setFirstBeginsign(firstRecord.getSigntime());
            data.setFirstEndsign(records[records.length - 1].getSigntime());
            //暂时冗余字段
            data.setSignbegintime(data.getFirstBeginsign());
            data.setSignendtime(data.getFirstEndsign());
        }
    }

    private void setSignCardData(AttendRecord[] records,AttendData data,Date shiftBegin,Date shiftEnd) {
        if(shiftBegin == null || shiftEnd == null){
            setSignCardData(records,data);
        }
        if (ArrayUtils.isEmpty(records)) {
            return;
        }
        //上班卡，早于加班卡的第一个卡
        long firstCard = 0;
        //下班卡，上班之后的最后一个卡
        long endCard = 0;
        for (int i = 0; i < records.length; i++) {
            if(records[i].getSigntime().getTime() > shiftBegin.getTime()){
                endCard = records[i].getSigntime().getTime();
            }
            if(records[records.length - 1 -i].getSigntime().getTime() < shiftEnd.getTime()){
                firstCard = records[records.length - 1 -i].getSigntime().getTime();
            }
        }
        if(firstCard > 0){
            data.setFirstBeginsign(new Date(firstCard));
            data.setSignbegintime(new Date(firstCard));
            if(endCard > 0 && endCard != firstCard){
                data.setFirstEndsign(new Date(endCard));
                data.setSignendtime(new Date(endCard));
            }
        }else if(endCard > 0){
            data.setFirstEndsign(new Date(endCard));
            data.setSignendtime(new Date(endCard));
        }
    }

    /**
     * 处理附加信息
     *
     * @param records
     * @param data
     * @param unnormallen
     * @param shiftvo
     * @param workTimeScopes
     */
    private void processData(AttendRecord[] records,
                             AttendData data,
                             Double unnormallen,
                             Shift shiftvo,
                             ITimeScope[] workTimeScopes,
                             TsAttendRuleVO attendRuleVO) {
        data.setIsfullduty(0);
        data.setNobrushcount(1);//未刷卡次数，当天打卡少于两次的 此处记录就为1 超过两次的就是0
        // 当天班的开始上班时间与结束上班时间
        Date shiftBegin = null;
        Date shiftEnd = null;
        AttendRecord firstRecord = null;
        AttendRecord lastRecord = null;
        if(ArrayUtils.isNotEmpty(workTimeScopes)){
            shiftBegin = workTimeScopes[0].getScopeBeginTime();
            shiftEnd = workTimeScopes[workTimeScopes.length - 1].getScopeEndTime();
        }

        //外勤是否计算迟到早退 0:计算 1：不计算 2：允许一只条外勤
        String outsideCalculateType = attendRuleVO.getIsCalculate();
        //设置 首次发卡时间 末次刷卡时间
        setSignCardData(records,data,shiftBegin,shiftEnd);
        if (!ArrayUtils.isEmpty(records) && records.length > 1) {
            if (data.getFirstBeginsign() != null && data.getFirstEndsign() != null) {
                if (!data.getFirstBeginsign().after(shiftBegin) && !data.getFirstEndsign().before(shiftEnd)) {
                    data.setIsfullduty(1);
                }
            }
            //如果是中午要求打卡 则至少要求四次卡 少于四次的设置1 多余四次设置为0
            boolean isdeductrestsign = shiftvo.isIsdeductrestsign();
            if (isdeductrestsign) {
                if (records.length >= 4) {
                    data.setNobrushcount(0);
                }
            } else {
                // 中午不要求打卡 至少两次打卡 少于两次的设置为1 多余两次设置为0
                data.setNobrushcount(0);
            }
        }
        if (data.isIsabsent()) {
            data.setAbsenthour(data.getAbsenthour());
            data.setAbsentcount(1);
        }

        //缺勤工时 （迟到+早退）/60 +旷工小时数
        if (null != data.getLatelong()) {
            unnormallen += data.getLatelong().doubleValue();
        }
        if (null != data.getEarlylong()) {
            unnormallen += data.getEarlylong().doubleValue();
        }
        if (unnormallen > 0) {
            unnormallen = unnormallen / 60;
        }
        if (null != data.getAbsenthour()) {
            unnormallen += data.getAbsenthour().doubleValue();
        }
        data.setUnnormallen(new BigDecimal(unnormallen));
        if (unnormallen.doubleValue() > 0) {
            data.setUnnormalcount(1);//勤次数，unnormallen》0缺勤工时大于0此处就记为1 ，否则记为0
        }

        //统计外勤次数
        int outsideCount = 0;
        if (!ArrayUtils.isEmpty(records)) {
            for (AttendRecord record : records) {
                if (record.isIsoutside()) {
                    //liushm 2018-08-22 外勤次数变更为外勤天数
                    //outsideCount++;
                    outsideCount = 1;
                    break;
                }
            }
        }
        data.setOutsidecount(outsideCount);
        if (outsideCount > 0) {
            data.setIsoutside(true);
            //一条外勤记录算正常
            if ("1".equals(outsideCalculateType)) {
                data.setFirstBeginsignStatus(0);//签到状态 0正常 1 迟到 2 早退 3 旷工 如果
                data.setFirstEndsignStatus(0);//签到状态 0正常 1 迟到 2 早退 3 旷工 如果
                data.setIsnormal(true);
                data.setAbsentcount(0);
                data.setIsabsent(false);
                data.setUnnormallen(BigDecimal.ZERO);
                data.setLatecount(0);
                data.setLatelong(BigDecimal.ZERO);
                data.setAbsenthour(BigDecimal.ZERO);
                data.setActualworkdays(BigDecimal.ONE);
                data.setEarlylong(BigDecimal.ZERO);
                data.setEarlycount(0);
            }
        }
    }

    /**
     * 处理附加信息
     * 四次签卡时间
     *
     * @param records
     * @param data
     * @param date
     */
    private void processDataFour(AttendRecord[] records,
                                 AttendData data,
                                 Shift shiftvo,
                                  Date date) {
        //签到开始时间
        Date signBeginTime = TimePubUtils.getRealTime(date, shiftvo.getSignBeginday(), shiftvo.getSignBegintime(),true);
        //上班开始时间
        Date workBeginTime = TimePubUtils.getRealTime(date, shiftvo.getBeginday(), shiftvo.getBegintime(),true);
        //休息开始时间
        Date restBeginTime = TimePubUtils.getRealTime(date, shiftvo.getRestbeginday(), shiftvo.getRestBegtime());
        //休息结束时间
        Date restEndTime = TimePubUtils.getRealTime(date, shiftvo.getRestendday(), shiftvo.getRestEndtime());
        //上班结束时间
        Date workEndTime = TimePubUtils.getRealTime(date, shiftvo.getEndday(), shiftvo.getEndtime());
        //签到结束时间
        Date signEndTime = TimePubUtils.getRealTime(date, shiftvo.getSignEndday(), shiftvo.getSignEndtime());
        long restTimeLong = restEndTime.getTime() - restBeginTime.getTime();
        //中间休息时间点
        Date firstEndScope = new Date(restBeginTime.getTime() + restTimeLong / 2);
        ITimeScope iTimeScopeFirst = new DefaultTimeScope(signBeginTime, firstEndScope);
        ITimeScope iTimeScopeSecond = new DefaultTimeScope(firstEndScope, signEndTime);
        //休息中间点 -- > 班次结束
        ITimeScope iTimeScopeSecondRestHalfWorkEnd = new DefaultTimeScope(firstEndScope, workEndTime);
        //休息结束 ---> 签到结束
        ITimeScope iTimeScopeRestEndToSignEnd = new DefaultTimeScope(restEndTime, signEndTime);
        //休息中间点 ---> 签到结束
        ITimeScope iTimeFirstEndScopeToSignEnd = new DefaultTimeScope(firstEndScope, signEndTime);
        Date firstSign1 = null;
        Date firstSign2 = null;
        Date secondSign1 = null;
        Date secondSign2 = null;
        //第一段时间内的打卡记录 签到开始 到 休息中间点
        if (records == null || records.length == 0) {
            return;
        }
        AttendRecord[] firstScopeRecords = getDateRecordListInScope(Arrays.asList(records), iTimeScopeFirst).toArray(new AttendRecord[0]);
        if (firstScopeRecords != null && firstScopeRecords.length > 0) {
            //时段1上班卡=[签到开始时间,休息时段中间点]之间最早打卡，前闭后开区间
            if (firstScopeRecords[0].getSigntime().compareTo(restBeginTime) <= 0) {
                firstSign1 = firstScopeRecords[0].getSigntime();//上班卡
            }
            //时段1如果有上班卡，下班卡=[班次开始时间,休息时段中间点]之间最晚打卡，前开后闭；
            if (firstSign1 != null) {
                if (firstScopeRecords.length >= 2) {
                    //多张卡
                    //下班卡
                    firstSign2 = firstScopeRecords[firstScopeRecords.length - 1].getSigntime();//第一段最后一次打卡
                }
            } else {
                //如果时段1没有上班卡，下班卡=[休息时段开始时间,休息时段中间点]最晚打卡，前闭后开区间；
                Date firstLastSign = firstScopeRecords[firstScopeRecords.length - 1].getSigntime();//第一段最后一次打卡
                //一段最后打卡落在班次开始时间之后
                if (firstLastSign.compareTo(workBeginTime) >= 0) {
                    firstSign2 = firstLastSign;
                }
            }
        }

        //第二段时间内的打卡记录
//        AttendRecord[] secondScopeRecords4End = getDateRecordListInScope(Arrays.asList(records), iTimeScopeRestEndToSignEnd).toArray(new AttendRecord[0]);
//        AttendRecord[] secondScopeRecordsBegin = getDateRecordListInScope(Arrays.asList(records), iTimeScopeSecondRestHalfWorkEnd).toArray(new AttendRecord[0]);
        AttendRecord[] secondScopeRecordsBegin = getDateRecordListInScope(Arrays.asList(records), iTimeFirstEndScopeToSignEnd).toArray(new AttendRecord[0]);

        if (secondScopeRecordsBegin != null && secondScopeRecordsBegin.length > 0) {
            //时段2上班卡=[休息时段中间点,班次结束时间]之间最早打卡，前闭后开区间
            if (secondScopeRecordsBegin[0].getSigntime().compareTo(workEndTime) < 0) {
                secondSign1 = secondScopeRecordsBegin[0].getSigntime();
            }
            //时段2如果有上班卡，下班卡=[休息时段结束时间,签到结束时间]之间最晚打卡，前开后闭；
            if (secondSign1 != null && secondScopeRecordsBegin.length >= 2) {
                if (secondScopeRecordsBegin[secondScopeRecordsBegin.length - 1].getSigntime().compareTo(restEndTime) > 0) {
                    secondSign2 = secondScopeRecordsBegin[secondScopeRecordsBegin.length - 1].getSigntime();
                }
            }
        }
        //如果时段2没有上班卡，下班卡=[班次结束时间,签到结束时间]之间最晚打卡，闭区间
        if (secondSign1 == null && secondScopeRecordsBegin.length > 0) {
            if (secondScopeRecordsBegin[secondScopeRecordsBegin.length - 1].getSigntime().compareTo(workEndTime) >= 0) {
                secondSign2 = secondScopeRecordsBegin[secondScopeRecordsBegin.length - 1].getSigntime();
            }
        }

        data.setFirstBeginsign(firstSign1);
        data.setFirstEndsign(firstSign2);
        data.setSecondBeginsign(secondSign1);
        data.setSecondEndsign(secondSign2);
    }

    /***
     * 处理弹性班次的工作时长
     * @param mapElasticShift
     * @param shiftVo
     * @param date
     */
    private void processElasticShiftWorkHour(Map<String,Double> mapElasticShift,Shift shiftVo,Date date){
        if(!shiftVo.isIsdeductrest()) return;
        if(shiftVo.getOriWorkHours() != null && shiftVo.getOriWorkHours().doubleValue() != shiftVo.getWorkHours().doubleValue()){
            log.debug("跳过处理弹性班次的工作时长原:shift--{}",JSONObject.toJSONString(shiftVo));
            return;
        }
        if(mapElasticShift.containsKey(shiftVo.getId())){
            shiftVo.setWorkHours(mapElasticShift.get(shiftVo.getId()));
        }else{
            long decuctRest = ElasticShiftUtils.getShiftDeductrest(date,shiftVo);
            if (decuctRest > 0){
                BigDecimal workHours = new BigDecimal((shiftVo.getWorkHours() * 3600 - decuctRest) / 3600).setScale(2, BigDecimal.ROUND_HALF_UP);
                shiftVo.setWorkHours(workHours.doubleValue());
            }
            mapElasticShift.put(shiftVo.getId(),shiftVo.getWorkHours());
        }
    }

    private void processActualHours(Shift shiftvo, AttendData data, BigDecimal leavedaySum, ITimeScope[] workTimeScopes,BigDecimal curDeptLeaveSum,BigDecimal tripDaySum) {
        //应出勤时长要扣掉放休时长
        data.setWorkhours(data.getWorkhours().subtract(curDeptLeaveSum));
        /*//如果是外勤并且正常
        if(data.isIsoutside() && data.isIsnormal()){
            data.setActualworkhours(data.getWorkhours());
            data.setActualworkdays(new BigDecimal(1));
            return;
        }*/
        if (1 == shiftvo.getShiftType()) {//正常班
            data.setActualworkhours(data.getWorkhours().subtract(data.getUnnormallen()).subtract(leavedaySum));//实际出勤时长
            data.setActualworkdays(data.getActualworkhours().divide(new BigDecimal(shiftvo.getWorkHours()), 4, BigDecimal.ROUND_HALF_UP));//实际工作天数
        } else {//弹性班--弹性班的实际出勤时长不能直接工作时长减去异常 ，是算出来的
            //按日弹
            BigDecimal actualDay = new BigDecimal(0);
            if (null == shiftvo.getElasticRule() || 1 == shiftvo.getElasticRule()) {
                data.setActualworkhours(data.getWorkhours().subtract(data.getUnnormallen()).subtract(leavedaySum));//实际出勤时长
                actualDay = data.getActualworkhours().divide(new BigDecimal(shiftvo.getWorkHours()), 4, BigDecimal.ROUND_HALF_UP);
            } else {//按月弹,按月弹再算当天实际出勤时长时已经减过了
                double actualLong = data.getActualworklong() + tripDaySum.doubleValue() * 3600;//加上出差时长
                if (actualLong > 0) {
                    double actualHours = actualLong / (3600);
                    data.setActualworkhours(BigDecimal.valueOf(actualHours).setScale(2, RoundingMode.HALF_UP));
                }
                //月弹缺勤时长=平均时长-实出勤
                if(data.getWorkdays().doubleValue() > 0) {
                    data.setUnnormallen(new BigDecimal(shiftvo.getWorkHours()).subtract(data.getActualworkhours()));
                }
                //核心时段时长
                double hexinLen = new Long(TimeScopeUtils.getLength(workTimeScopes)).doubleValue() / 3600;
                actualDay = data.getActualworkhours().divide(BigDecimal.valueOf(hexinLen), 2, BigDecimal.ROUND_HALF_UP);
            }
            //弹性的，每天的实出勤天数不能大于1
            if (actualDay.intValue() >= 1) {
                data.setActualworkdays(new BigDecimal(1));
            } else {
                data.setActualworkdays(actualDay.setScale(2, RoundingMode.HALF_UP));
            }
        }
    }

    // 在某人所有刷卡记录里找出当天的记录
    public List<AttendRecord> getDateRecordList(List<AttendRecord> list, Date date, Shift shiftvo,ITimeScope kqScope) {
        //班次的允许打卡时间点
        if(kqScope == null) kqScope = TimePubUtils.getSignTimeScope(date, shiftvo);
        List<AttendRecord> result = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            AttendRecord vo = list.get(i);
            long signtime = vo.getSigntime().getTime();
            if (signtime >= kqScope.getScopeBeginTime().getTime() &&
                    signtime <= kqScope.getScopeEndTime().getTime()) {
                result.add(vo);
            }
        }
        return result;
    }

    /**
     * 在某人所有刷卡记录里找出某个范围的记录
     */
    private List<AttendRecord> getDateRecordListInScope(List<AttendRecord> list, ITimeScope timeScope) {
        //班次的允许打卡时间点
        List<AttendRecord> result = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            AttendRecord vo = list.get(i);
            long signtime = vo.getSigntime().getTime();
            if (signtime >= timeScope.getScopeBeginTime().getTime() &&
                    signtime <= timeScope.getScopeEndTime().getTime()) {
                result.add(vo);
            }
        }
        return result;
    }

    // 在某人所有补考勤记录里找出当天的记录
    private List<FillAttendBody> getDateFillRecordList(List<FillAttendHead> fillList, Date date, Shift shiftvo) {
        //班次的允许打卡时间点
        ITimeScope signScope = TimePubUtils.getSignTimeScope(date, shiftvo);
        List<FillAttendBody> result = new ArrayList<>();
        for (int i = 0; i < fillList.size(); i++) {
            FillAttendHead headvo = fillList.get(i);
            List<FillAttendBody> bodyList = headvo.getBodyList();
            for (FillAttendBody body : bodyList) {
                long filltime = body.getFillTime().getTime();
                if (filltime >= signScope.getScopeBeginTime().getTime() && filltime <= signScope.getScopeEndTime().getTime()) {
                    result.add(body);
                }
            }
        }
        return result;
    }

    /***
     * 获取3天(昨天今天明天)的打卡(含补考勤)记录
     * @return
     */
    private AttendRecord[] getSignCardOfThreeDays(List<AttendRecord> recordList,List<FillAttendHead> fillAttendList,Date calDate){
        Date[] dateRange = TimePubUtils.getDateStartEnd(TimePubUtils.getYesterDay(calDate),TimePubUtils.getNextDay(calDate));
        log.debug(String.format("####getSignCardOfThreeDays,dateRange:%s",JSONObject.toJSONString(dateRange)));
        List<AttendRecord> recordData = new ArrayList<>();
        List<FillAttendBody> fillAttendData = new ArrayList<>();
        if(CollectionUtils.isNotEmpty(recordList)){
            for (AttendRecord record :recordList) {
                if(matchedRange(record.getSigntime(),dateRange)){
                    recordData.add(record);
                }
            }
        }
        if(CollectionUtils.isNotEmpty(fillAttendList)){
            for (FillAttendHead headvo : fillAttendList) {
                List<FillAttendBody> bodyList = headvo.getBodyList();
                for (FillAttendBody body : bodyList) {
                    if(matchedRange(body.getFillTime(),dateRange)){
                        fillAttendData.add(body);
                    }
                }
            }
        }
        return getRecordContainFill(recordData.toArray(new AttendRecord[recordData.size()]),fillAttendData.toArray(new FillAttendBody[fillAttendData.size()]));
    }

    /***
     * 匹配日期
     * @param date
     * @param dateRange
     * @return
     */
    boolean matchedRange(Date date,Date[] dateRange){
        boolean isMatched = date.getTime() >= dateRange[0].getTime() && date.getTime() <= dateRange[1].getTime();
        log.debug(String.format("####getSignCardOfThreeDays,isMatched:%s,%s",isMatched,date));
        return isMatched;
    }

    /**
     * 整合补考勤数据和打卡数据
     *
     * @param records
     * @param fillBodys
     * @return
     */
    private AttendRecord[] getRecordContainFill(AttendRecord[] records, FillAttendBody[] fillBodys) {
        if (records == null) {
            records = new AttendRecord[0];
        }
        for (FillAttendBody body : fillBodys) {
            AttendRecord ar = new AttendRecord();
            ar.setIsoutside(false);
            ar.setSigntime(body.getFillTime());
            ar.setSigndate(body.getFillDate());
            records = (AttendRecord[]) ArrayUtils.add(records, ar);
        }
        AttendRecord temp = null;
        //再将结果排序
        for (int i = 0; i < records.length - 1; i++) {
            for (int j = i + 1; j < records.length; j++) {
                if (records[i].getSigntime().after(records[j].getSigntime())) {
                    temp = records[i];
                    records[i] = records[j];
                    records[j] = temp;
                }
            }
        }
        return records;
    }

    //如果没排班或者班次为公休 查找其当日打卡数据
    private List<AttendRecord> getDateRecordList4GX(List<AttendRecord> list, ITimeScope kqScope) {
        long beginTime = kqScope.getScopeBeginTime().getTime();
        long endTime = kqScope.getScopeEndTime().getTime();
        return list.stream().filter(a -> a.getSignTime().getTime() >= beginTime && a.getSignTime().getTime() <= endTime).collect(Collectors.toList());
    }

    /**
     * 获取非工作日的时间范围
     * 结合前一天的班次信息，在其结束签到时间之后
     * @param date
     * @param dateShiftMap
     * @param shiftMap
     * @return
     */
    private ITimeScope[] getRestScope4Ot(Date date, Map<Date, String> dateShiftMap, Map<String, Shift> shiftMap,TsAttendRuleVO ruleVO,Map<String,Map<Date,Integer>> mapCalendarDateType) {
        //当前日期前一天的排班
        Date yesterDay = TimePubUtils.getYesterDay(date);
        //查找前一天的班次
        String previous_shift_id = null;
        if(dateShiftMap != null) {
            previous_shift_id = dateShiftMap.get(yesterDay);
        }
        //固定班或者日弹班次，判断前一天的是否休息班
        if("0".equals(ruleVO.getShiftModeFix()) || "2".equals(ruleVO.getShiftModeFix())){
            Map<Date,Integer> mapDateType = mapCalendarDateType.get(ruleVO.getWorkCalendarId());
            if(mapDateType != null && mapDateType.containsKey(yesterDay)){
                if(mapDateType.get(yesterDay) != 1){
                    previous_shift_id = IAttendConst.SHIFT_GX;
                }
            }
        }
        Date[] dates = TimePubUtils.getDateStartEnd(date);
        long beginTime = dates[0].getTime();
        long endTime = dates[1].getTime();
        if (null != previous_shift_id && !previous_shift_id.endsWith(IAttendConst.SHIFT_GX)) {//前一天是正常班次例如周五 则周六的签卡开始时间是班次上设置的前天的结束时间
            Shift shiftvo = shiftMap.get(previous_shift_id);
            if (2 == shiftvo.getSignEndday()) {
                //跨天时，时间取值错误
                //begin = date.getTime() + shiftvo.getSignEndtime().getTime() + rawOffSet;
                beginTime = TimePubUtils.getRealTime(yesterDay,shiftvo.getSignEndday(),shiftvo.getSignEndtime(),false).getTime();
            }
        }
        return new ITimeScope[]{new DefaultTimeScope(new Date(beginTime), new Date(endTime))};
    }

    /**
     * 构造某天00:00:00 以及23:59:59 两个时间Date
     *
     * @param date
     * @return
     */
    private Date[] getDateStartEnd(Date date) {
        Date[] result = new Date[2];
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String s1 = sdf.format(date) + " " + "00:00:00";
        String s2 = sdf.format(date) + " " + "23:59:59";
        try {
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            Date dateStart = sdf2.parse(s1);
            Date dateEnd = sdf2.parse(s2);
            result[0] = dateStart;
            result[1] = dateEnd;
        } catch (ParseException e) {
        }
        return result;
    }

    // 公共类还有点问题 暂时写在这,将一个list按其中某个字段为KEY拼成MAP-fieldname暂时用不到待公共类完善
    private static <E, T extends TenantEntity> Map<E, List<T>> group2ListByField(String fieldname, List<T> vos) {
        if (CollectionUtils.isEmpty(vos)) {
            return null;
        }
        Map<E, List<T>> retMap = new HashMap<>();
        for (T vo : vos) {
            E fieldVal = null;
            if (vo instanceof AttendRecord) {
                fieldVal = ((E) ((AttendRecord) vo).getStaffId());
            }
            if (vo instanceof LeaveApply) {
                fieldVal = (E) ((LeaveApply) vo).getStaffid();
            }
            if (vo instanceof LeaveApplyDetail) {
                fieldVal = (E) ((LeaveApplyDetail) vo).getStaffid();
            }
            if (vo instanceof AttendApply) {
                fieldVal = (E) ((AttendApply) vo).getStaffId();
            }
            if (vo instanceof AttendStaff) {
                fieldVal = (E) ((AttendStaff) vo).getStaffId();
            }
            if (vo instanceof FillAttendHead) {
                fieldVal = (E) ((FillAttendHead) vo).getStaffId();
            }
			if (vo instanceof OvertimeSplitBillVO) {
                fieldVal = (E) ((OvertimeSplitBillVO) vo).getStaffid();
            }
            if (vo instanceof BusinessTripApplyDetail) {
                fieldVal = (E) ((BusinessTripApplyDetail) vo).getStaffid();
            }

            if (vo instanceof Shift) {
                fieldVal = (E) ((Shift) vo).getId();
            }
            List<T> list = retMap.get(fieldVal);
            if (list == null) {
                list = new ArrayList<>();
                retMap.put(fieldVal, list);
            }
            list.add(vo);
        }
        return retMap;
    }

    // 换算两个时间点之间相差的时长
    private BigDecimal getHours(Date time1, Date time2) {
        long milliSecond = time2.getTime() - time1.getTime();
        if (milliSecond <= 0) {
            return new BigDecimal(0);
        }
        long nh = 1000 * 60 * (long)60;// 一小时的毫秒数
        long result = milliSecond / nh;
        return new BigDecimal(result);
    }

    private Integer getBillStatePass(AttendRecord[] records, ITimeScope[] leaveApplys, ITimeScope[] attendApplys) {
        Integer billStatePass = 0;
        if (ArrayUtils.isNotEmpty(records)) {
            for (AttendRecord record : records) {
                if (record.isIsoutside() && record.getApproveStatus() != HRCloudConst.BILL_STATUS_APPROVED_PASS) {
                    billStatePass = 1;
                    break;
                } else {
                    billStatePass = 2;
                }
            }
        }
        if ((billStatePass != 1) && (ArrayUtils.isNotEmpty(leaveApplys))) {
            for (ITimeScope leaveApply : leaveApplys) {
                if (((LeaveApply) leaveApply).getApprovestatus() != HRCloudConst.BILL_STATUS_APPROVED_PASS) {
                    billStatePass = 1;
                    break;
                } else {
                    billStatePass = 2;
                }
            }
        }
        if ((billStatePass != 1) && (ArrayUtils.isNotEmpty(attendApplys))) {
            for (ITimeScope attendApply : attendApplys) {
                if (((AttendApply) attendApply).getApproveStatus() != HRCloudConst.BILL_STATUS_APPROVED_PASS) {
                    billStatePass = 1;
                    break;
                } else {
                    billStatePass = 2;
                }
            }
        }
        return billStatePass;
    }

    @Override
    public List<AttendData> queryByConditon(Condition cond) {
        return dao.queryByConditon(cond);
    }

    @Override
    public List<Map> queryTimeitemBills4Month(Condition cond) {
        return dao.queryTimeitemBills4Month(cond);
    }

    public static Date getMidTime(Date time1, Date time2) {
        long diff = (time2.getTime() - time1.getTime()) / 2;
        return new Date(time1.getTime() + diff);
    }

    /**
     * 初始化attenddata
     *
     * @param data
     * @param date
     */
    private void createAttendData(AttendData data, String staffid, Date date) {
        data.setStaffId(staffid);// 员工ID
        data.setCalendar(date);
        data.setActualworkhours(new BigDecimal(0));
        data.setActualworkdays(new BigDecimal(0));
        data.setWorkhours(new BigDecimal(0));
        data.setWorkdays(new BigDecimal(0));
        data.setUnnormallen(new BigDecimal(0));
        data.setLatelong(new BigDecimal(0));
        data.setEarlylong(new BigDecimal(0));
        data.setAbsenthour(new BigDecimal(0));
        data.setLatecount(0);
        data.setEarlycount(0);
        data.setResigncnt(0);
        data.setIsabsent(false);
        data.setAbsentcount(0);
        data.setIsnormal(false);
        data.setIsholiday(false);
        data.setFirstBeginsign(null);
        data.setFirstBeginsignStatus(null);
        data.setFirstEndsign(null);
        data.setFirstEndsignStatus(null);
        data.setIsleave(false);
        data.setLatecount(0);
        data.setNobrushcount(0);
        data.setUnnormalcount(0);
        data.setIsfullduty(0);
        data.setOutsidecount(0);
        data.setIsoutside(false);
    }

    // 查日期范围内的所有请假记录
    @Override
    public Map<String, List<LeaveApplyDetail>> queryLeaveApply(Date beginDate, Date endDate, String[] staff_ids) {
        Condition leaveCond = new Condition();
        Date[] startdateTime = getDateStartEnd(beginDate);
        Date[] enddateTime = getDateStartEnd(endDate);
//        leaveCond.addExpression("leavebegintime", enddateTime[1], "<=");// 开始结束日期内所有的休假单
//        leaveCond.addExpression("leaveendtime", startdateTime[0], ">=");
        leaveCond.addExpression("main.approvestatus", HRCloudConst.BILL_STATUS_APPROVED_PASS, "=");// 已审批通过的,按时间顺序排
        if (ArrayUtils.isNotEmpty(staff_ids)) {
            leaveCond.addExpression("main.staffid", staff_ids, "in");
        }
//        leaveCond.addExpression("dr", 0);
        Map<String, List<LeaveApplyDetail>> leaveMap = null;
        try {
            List<LeaveApplyDetail> leaveApplyList = leaveservice.queryApplyDetailByStaffAndDate(leaveCond, startdateTime[0], enddateTime[1]);
//            List<LeaveApplyDetail> leaveApplyList = genericService.find(LeaveApplyDetail.class, leaveCond, new Sorter("leavebegintime"));
            leaveMap = group2ListByField("staff_id", leaveApplyList);
        } catch ( Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        return leaveMap;
    }

    @Override
    public Map<String, List<AttendRecord>> queryAttendRecord(Date beginDate, Date endDate, String[] staff_ids) {
        // 如果非节假日计算其考勤数据
        Condition condRecord = new Condition();
        condRecord.addExpression("signdate", beginDate, ">=");
        condRecord.addExpression("signdate", endDate, "<=");
        if (ArrayUtils.isNotEmpty(staff_ids)) {
            condRecord.addExpression("staff_id", staff_ids, "in");
        }

        // 查询正常考勤的和已经审批通过的外勤数据(isoutside=0 or(isoutside=1 and approve_status=2))
        LogicExpression le = new LogicExpression(new ValueExpression("isoutside", 0),
                new LogicExpression(new ValueExpression("isoutside", 1),
                        new ValueExpression("approve_status", HRCloudConst.BILL_STATUS_APPROVED_PASS), false),
                true);
        condRecord.addExpressions(le);
        // 排序字段条件--按签到时间顺序
        Sorter sort = new Sorter(Direction.ASC, "sign_time");
        // 时间范围内所有人的考勤数据,按签到时间先后顺序排列
        List<AttendRecord> recordList = recordservice.queryByConditon(condRecord, sort);
        // 将考勤数据按staff_id分组
        return group2ListByField("staff_id", recordList);
    }

    /**
     * 最后数处理
     * 是否旷工
     * 旷工次数
     * 是否正常
     * 实际出勤时长的修正
     * @param data
     */
    private void processLast(AttendData data) {
        //处理下有旷工 但是旷工次数是零的问题，暂时容错处理实际不需要这样
        if (null != data.getAbsenthour() && data.getAbsenthour().doubleValue() > 0) {
            data.setIsabsent(true);
            data.setAbsentcount(1);
        }

        //处理实际出勤时长是零的问题，因为请假时长可以向上取整 这样实出勤可能会出现负数
        if (null != data.getActualworkhours() && data.getActualworkhours().doubleValue() < 0) {
            data.setActualworkhours(new BigDecimal(0));
            data.setActualworkdays(new BigDecimal(0));
        }

        //处理下没异常时间 但当天isnormal为false问题
        //月弹的缺勤可以为负，当天考勤状态改为根据实出勤天数判断
        //if (null == data.getUnnormallen() || data.getUnnormallen().doubleValue() == 0) {
        if (null == data.getActualworkdays() || data.getActualworkdays().doubleValue() == 1) {
            data.setIsnormal(true);
        }
        if((data.getLatelong() != null && data.getLatelong().doubleValue() > 0) || (data.getEarlylong() != null && data.getEarlylong().doubleValue() > 0) ||
                (data.getAbsenthour() != null && data.getAbsenthour().doubleValue() > 0)){
            data.setIsnormal(false);
        }else{
            data.setIsnormal(true);
        }
        //如果正常，缺卡次数=0
        if(data.isIsnormal()) {
            data.setNobrushcount(0);
        }

        //如果应出勤小时小于旷工小时，则赋值旷工小时为应出勤小时
        if(data.getAbsenthour().compareTo(data.getWorkhours())==1){
            data.setAbsenthour(data.getWorkhours());
        }

        //如果应出勤小时小于旷工小时，则赋值旷工小时为应出勤小时
        if(data.getUnnormallen().compareTo(data.getWorkhours())==1){
            data.setUnnormallen(data.getWorkhours());
        }
    }
    /**
     *  查日期范围内的所有加班记录
     */
    @Override
    public Map<String, List<OvertimeSplitBillVO>> queryOvertimeSplitBills(Integer bill_source,Date beginDate,Date endDate,String[] staff_ids,Integer approve_status){
        OvertimeQueryCondition otCond=new OvertimeQueryCondition();
        Date[] startdateTime = getDateStartEnd(beginDate);
        Date[] enddateTime = getDateStartEnd(endDate);
        otCond.setBegindatestr(DateUtil.DateToString(startdateTime[0], DateStyle.YYYY_MM_DD));
        otCond.setEnddatestr(DateUtil.DateToString(enddateTime[1], DateStyle.YYYY_MM_DD));
        otCond.setBegindate(startdateTime[0]);
        otCond.setEnddate(enddateTime[1]);
        if(null!=bill_source){
            otCond.setBillsource(0);//0是申请单 1是系统自动生成的
            otCond.setBillsource_dept(2);
        }
        if(null!=approve_status){
            otCond.setApprovestatus(approve_status);// 已审批通过的,按时间顺序排
        }
        otCond.setMinlen(0.0);
        otCond.setMaxlen(0.0);
        if (ArrayUtils.isNotEmpty(staff_ids)) {
            otCond.setStaffids(Arrays.asList(staff_ids));
        }
//        List<OvertimeSplitBillVO> otSplitBillVosList = null;
                List<OvertimeSplitBillVO> otSplitBillVosList = otBillService.querySplitVOsByCondition(otCond);

        return group2ListByField("staffid", otSplitBillVosList);
    }

    @Override
    public Map<String, List<BusinessTripApplyDetail>> queryBusinessTripApply(Date beginDate, Date endDate, String[] staff_ids) {
        Date[] startdateTime = getDateStartEnd(beginDate);
        Date[] enddateTime = getDateStartEnd(endDate);
        Condition cond = new Condition();
        cond.addExpression("main.approvestatus", HRCloudConst.BILL_STATUS_APPROVED_PASS, "=");// 已审批通过的,按时间顺序排
        if (ArrayUtils.isNotEmpty(staff_ids)) {
            cond.addExpression("main.staffid", staff_ids, "in");
        }
        Map<String, List<BusinessTripApplyDetail>> businessTripMap = null;
        try {
            List<BusinessTripApplyDetail> businessTripList = businessTripApplyService.queryApplyDetailByStaffAndDate(cond,startdateTime[0],enddateTime[1]);
            businessTripMap = group2ListByField("staffid", businessTripList);
        } catch ( Exception ex) {
            log.error("###attend_queryBusinessTripApply error:"+ex.getMessage(), ex);
        }
        return businessTripMap;
    }

    private ITimeScope[] getWorkTimeScope4Ot(ITimeScope kqScope, Shift shiftvo, ITimeScope[] workTimeScopes) {
        ITimeScope[] result = workTimeScopes;
        //如果是按月弹的,比如每天弹一个小时则算加班时长时 工作时间取核心时间段往前推一个小时往后推一个小时
        if (2 == shiftvo.getShiftType() && shiftvo.getElasticRule() != null && 2 == shiftvo.getElasticRule()) {//按月弹
            result = new ITimeScope[]{kqScope};
        }
        //加班业务 中间的休息段不能算加班，要抛去
        result = new ITimeScope[]{new DefaultTimeScope(result[0].getScopeBeginTime(), result[result.length - 1].getScopeEndTime())};
        return result;
    }

    /**
     * 生成的数据 实际时长也都是计算好的
     */
    private OvertimeMainBillVO generateOtBillsBySign(OvertimeProcessParmVO otParm) {
        if (!isNeedGenerateBill(otParm)){
            return null;
        }
        OvertimeMainBillVO otMainBillVo=otCoreBusinessServive.generateCommonOvertimeBills(otParm);
        return otMainBillVo;
    }

    /**
     * @描述   :  是否需要生成加班单
     * @参数   : 【otParm】            :otParm
     * @返回值 : boolean
     * @日期   : 2019/11/2 14:53
     * @作者   : sunshh
     */
    private boolean isNeedGenerateBill(OvertimeProcessParmVO otParm) {
        boolean needGenerateBill = false;
        // 只处理根据打卡计算加班单的
        if (otParm.getOtRule() == null ) {
            log.error("打卡自动生成加班单加班规则为空,OvertimeProcessParmVO={}",JSON.toJSONString(otParm));
            return needGenerateBill;
        }
        if(otParm.getDateType() == null){
            log.error("打卡自动生成加班单日期类型错误[1：工作日和调班日;2.公休日;3.节假日],OvertimeProcessParmVO={}",JSON.toJSONString(otParm));
            return needGenerateBill;
        }
        if(otParm.getDateType() == 1){
            // 勾选工作日加班&&打卡自动计算
            if( "1".equals(otParm.getOtRule().getWorkovertimetype()) && "0".equals(otParm.getOtRule().getWorkcaltype())){
                needGenerateBill = true;
            }
        }else if(otParm.getDateType() == 2){
            // 勾选公休日加班&&打卡自动计算
            if( "1".equals(otParm.getOtRule().getRestovertimetype()) && "0".equals(otParm.getOtRule().getRestcaltype())){
                needGenerateBill = true;
            }
        }else if(otParm.getDateType() == 3){
            // 勾选公休日加班&&打卡自动计算
            if( "1".equals(otParm.getOtRule().getHolidayovertimetype()) && "0".equals(otParm.getOtRule().getHolidaycaltype())){
                needGenerateBill = true;
            }
        }else{
            log.error("打卡自动生成加班单日期类型错误[1：工作日和调班日;2.公休日;3.节假日],OvertimeProcessParmVO={}",JSON.toJSONString(otParm));
        }
        return needGenerateBill;
    }

    /**
     * 计算加班单时长
     * @param otParm
     * @param splitOTBillListAll
     * @return
     */
    private List<OvertimeSplitBillVO> calSplitBillsLenth(OvertimeProcessParmVO otParm,List<OvertimeSplitBillVO> splitOTBillListAll){
        List<OvertimeSplitBillVO> otSplitVos = null;
        if (!CollectionUtils.isEmpty(splitOTBillListAll)) {// 某人当天的加班记录记录
            otSplitVos = getOtSplitNowDate(splitOTBillListAll, otParm.getDate());
        }
        List<OvertimeSplitBillVO> newsplitvos=otCoreBusinessServive.calOvertimeLen(otParm,otSplitVos);
        return newsplitvos;
    }

    /**
     * 在某人所有加班里找出当天的记录
      */
    private List<OvertimeSplitBillVO> getOtSplitNowDate(List<OvertimeSplitBillVO> list,Date date) {
        List<OvertimeSplitBillVO> result = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            OvertimeSplitBillVO vo = list.get(i);
            if(vo.getBillsource() == 1) {
                continue;
            }
            String d = DateUtil.DateToString(vo.getOvertimebegintime(),DateStyle.YYYY_MM_DD);
            if (d.equals(DateUtil.DateToString(date, DateStyle.YYYY_MM_DD))) {
                result.add(vo);
            }
        }
        return result;
    }

    /**
     * 处理公休加班
     * @param splitBillVoList4Update
     * @param mainBillVoList4Insert
     * @param staffCalMap
     * @param shiftid
     * @param parms
     * @param date
     * @param otParm
     * @param recordAll
     * @param splitOTBillListAll
     * @param data
     * @param dataList
     */
    private void processOVertimeGX(List<OvertimeSplitBillVO> splitBillVoList4Update,
                                   List<OvertimeMainBillVO> mainBillVoList4Insert,
                                   Map<Date, String> staffCalMap,String shiftid,
                                   AttendParm parms, Date date, OvertimeProcessParmVO otParm,
                                   List<AttendRecord> recordAll,List<OvertimeSplitBillVO> splitOTBillListAll,
                                   AttendData data, List<AttendData> dataList, Shift shiftVo, ITimeScope kqScope) {
        try {
            //根据加班规则区分取卡范围
            boolean byCard = (data.getDateType() == 2 && "0".equals(otParm.getOtRule().getRestcaltype())) || (data.getDateType() == 3 && "0".equals(otParm.getOtRule().getHolidaycaltype()));
            if(byCard) {
                if (!CollectionUtils.isEmpty(recordAll)) {// 某人当天的签到记录-针对周六日的处理
                    AttendRecord[] records = getDateRecordList4GX(recordAll, kqScope).toArray(new AttendRecord[0]);
                    otParm.setRecords(records);
                } else {
                    otParm.setRecords(null);
                }
            }else{
                otParm.setRecords(CollectionUtils.isNotEmpty(recordAll) ? recordAll.toArray(new AttendRecord[0]) : null);
            }
            otParm.setShift_id(shiftid);
            otParm.setShiftvo(null == shiftid ? null : parms.getShiftMap().get(shiftid));
            otParm.setWorkTimeScopes(new ITimeScope[]{ kqScope });
            OvertimeMainBillVO mainbillvos = generateOtBillsBySign(otParm);
            List<OvertimeSplitBillVO> splitBillVOS = new ArrayList<>();
            if (null != mainbillvos) {
                mainbillvos.setApplyerid(mainbillvos.getStaffid());
                splitBillVOS.addAll(mainbillvos.getSplitvolist());
                mainBillVoList4Insert.add(mainbillvos);
            }
            List<OvertimeSplitBillVO> updateSplitVos = calSplitBillsLenth(otParm, splitOTBillListAll);
            if (CollectionUtils.isNotEmpty(updateSplitVos)) {
                splitBillVoList4Update.addAll(updateSplitVos);
                splitBillVOS.addAll(updateSplitVos);
            }
            //处理加班类别数据
            if(!CollectionUtils.isEmpty(splitBillVOS)){
                Map<String,String> mapJSType = new HashMap<>();//加班类别是否结算加班费
                for (OvertimeSplitBillVO vo : splitBillVOS) {
                    if(vo == null) continue;
                    String jsTypeId =  getJSTypeId(mapJSType,otParm.getOtRule(),vo.getOvertimetypeid());
                    processBillTypeData(data,vo.getOvertimetypeid(),jsTypeId,vo.getActuallen().doubleValue());
                }
            }
            //如果有加班数据，则即使当天公休也需要生成一条attenddata数据 只包含上下班时间和staff_id,org_id
            data.setIsnormal(true);
            //月弹班次的加班统计到实出勤
            if(null != shiftVo.getElasticRule() && shiftVo.getElasticRule() == 2){
                BigDecimal otLen = getOTLen(mainbillvos,updateSplitVos);
                if(otLen.doubleValue() > 0) {
                    data.setActualworkhours(otLen);
                    BigDecimal actualWorkDay = otLen.divide(new BigDecimal(shiftVo.getWorkHours()), 4, BigDecimal.ROUND_HALF_UP);
                    data.setActualworkdays(actualWorkDay.doubleValue() > 1 ? BigDecimal.valueOf(1) : actualWorkDay);//实际工作天数
                }
            }
            dataList.add(data);
        }catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
    }

    /***
     * 判断加班类别是否结算加班费
     * @param mapJSType
     * @param otRule
     * @param ovType
     * @return
     */
    private String getJSTypeId(Map<String,String> mapJSType,OvertimeRule otRule,String ovType){
        if(mapJSType.containsKey(ovType)) return mapJSType.get(ovType);
        if(otRule == null) return "";
        String jsTypdId = "";
        if((OvertimeTypeEnum.WORK_TYPE.getIndex().equals(ovType) && "0".equals(otRule.getWorkbuchangtype())) ||
                (OvertimeTypeEnum.REST_TYPE.getIndex().equals(ovType) && "0".equals(otRule.getRestbuchangtype())) ||
                (OvertimeTypeEnum.HOLIDAY_TYPE.getIndex().equals(ovType) && "0".equals(otRule.getHolidaybctype()))){
            jsTypdId = ovType.replace("0000000","00000js");
            mapJSType.put(ovType,jsTypdId);
        }
        return jsTypdId;
    }

    private BigDecimal getOTLen(OvertimeMainBillVO mainBillVO,List<OvertimeSplitBillVO> updateSplitVos){
        BigDecimal len = BigDecimal.ZERO;
        if(mainBillVO != null && CollectionUtils.isNotEmpty(mainBillVO.getSplitvolist())) {
            for (OvertimeSplitBillVO vo : mainBillVO.getSplitvolist()) {
                len = len.add(vo == null ? BigDecimal.ZERO : vo.getActuallen());
            }
        }
        if(CollectionUtils.isNotEmpty(updateSplitVos)) {
            for (OvertimeSplitBillVO vo : updateSplitVos) {
                len = len.add(vo == null ? BigDecimal.ZERO : vo.getActuallen());
            }
        }
        return len;
    }

    /***
     * 处理公休请假
     */
    private void processLeaveGX(AttendParm parms, List<LeaveApplyDetail> leaveListAll, Shift shiftvo, Date date, ITimeScope[] workTimeScopes,
                                ITimeScope[] leaveApplys, ITimeScope kqScope,int dateType,AttendData data,BigDecimal curDeptLeaveSum){
        processLeaveSum(parms, leaveListAll, shiftvo, date, workTimeScopes,leaveApplys, kqScope,dateType,data,curDeptLeaveSum);

    }

    /***
     * 处理公休出差
     */
    private void processBusinessTripGX(AttendParm parms, Shift shiftvo,TsAttendRuleVO attendRuleVO,WorkingCalendar workingCalendar,List<Holiday> holidays,ITimeScope[] tripApplys,Date calDate,AttendData data,AttendRecord[] records){
        BigDecimal len = processTripDaySum(parms, shiftvo,attendRuleVO,workingCalendar, holidays, tripApplys, calDate, data, records);
    }

    @Override
    public List<AttendData> getAttendDataToday(AttendRptParams params) {
        try {
            Map<String, AttendStaff> attendStaffMap = attendStaffService.queryAttendStaffForTodayRpt(params.getListStaffIds().toArray(new String[params.getListStaffIds().size()]), params.getBeginDate(), params.getEndDate());
            if (MapUtils.isEmpty(attendStaffMap)) {
                log.debug("####attend getAttendDataToday 没有可计算人员,staffIds:"+ JSONObject.toJSONString(params.getListStaffIds()));
                return new ArrayList<>();
            }
            //将begindate往前推一天，enddate往后推一天
            Date beginDate = DateUtil.StringToDate(DateUtil.DateToString(params.getBeginDate(), DateStyle.YYYY_MM_DD), DateStyle.YYYY_MM_DD);
            Date endDate = DateUtil.getDayEnd(params.getEndDate());
            Date yesterday = TimePubUtils.getYesterDay(beginDate);
            Date nextday = TimePubUtils.getNextDay(endDate);

            //每人每天的排班Map
            //班次分为两种1 ：排班 2：考勤规则，这里先把所有排班查出来
            Map<String,Map<Date,String>> calendarMap= calendarService.queryCalendarByDate(params.getListStaffIds().toArray(new String[params.getListStaffIds().size()]), yesterday,nextday);
            params.setStaffCalMap(calendarMap);
            //查询考勤规则map
            Map<String, TsAttendRuleVO> staffRuleMap = ruleService.queryByStaffIdAndOrgId(params.getListStaffIds(), false, false);
            params.setStaffRuleMap(staffRuleMap);

            //日报生成范围可能存在跨年的情况
            if(MapUtils.isEmpty(params.getHolidayMap())) {
                Condition hCond = new Condition();
                SimpleDateFormat yearSdf = new SimpleDateFormat("yyyy");
                ChainLogicExpression cle = new ChainLogicExpression(true,
                        new ValueExpression("year", yearSdf.format(beginDate), "="),
                        new ValueExpression("year", yearSdf.format(endDate), "="));
                hCond.addExpressions(cle);
                Map<String, List<Holiday>> holidayMap = holidayservice.queryHolidayMapByConditon(hCond);
                params.setHolidayMap(holidayMap);
            }
            if(MapUtils.isEmpty(params.getShiftMap())) {
                params.setShiftMap(shiftservice.queryShiftMapByConditon(null));
            }
            //工作日设置查询
            Condition condition = new Condition();
            List<WorkingCalendar> workCalendarList = genericService.find(WorkingCalendar.class, condition, new Sorter("creationtime"));
            Map<String, WorkingCalendar> workCalendarIdMap = TimePubUtils.group2VOById(workCalendarList);
            params.setWorkCalendarIdMap(workCalendarIdMap);
            return generateToday(attendStaffMap,params);
        } catch (Exception e) {
            log.error("###attend-getAttendDataToday error:",e);
            throw new AdderBusinessException("生成当日数据出错!","attend.report.i18n41");
        }
    }

    /**
     * 高级版 考勤数据生成
     *
     * @param attendStaffMap
     * @param params
     */
    private List<AttendData> generateToday(Map<String, AttendStaff> attendStaffMap,AttendRptParams params) {// 某天会有多个签到记录 按天只取出某人最早和最晚的签到记录 分正常签到和外勤
        List<AttendData> dataList = new ArrayList<>();
        // 将请假出差记录与shiftScope合并取当天的单据时间
        Map<String, WorkingCalendar> workCalendar = params.getWorkCalendarIdMap();//工作日日历

        Map<String, Map<Date, String>> mapCal = params.getStaffCalMap();
        Map<String,Double> mapElasticShiftWorkHour = new HashMap<>();//弹性班实际工作时长
        //工作日历对应的日期类型
        Map<String,Map<Date,Integer>> mapCalendarDateType = new HashMap<>();
        for(AttendStaff attendStaff : attendStaffMap.values()){
            String staffId = attendStaff.getStaffId();
            //获取考勤规则
            TsAttendRuleVO rulevo = params.getStaffRuleMap().get(staffId);
            if (rulevo == null) {
                log.error(String.format("#####TM generateSenior-人员%s获取考勤规则失败,跳过",staffId));
                continue;
            }
            //获取考勤方式
            String attendType = rulevo.getShiftModeFix();
            Shift shiftvo = null; // 当天的班次
            String shiftid = rulevo.getShiftId();
            Map<Date, String> staffCalMap = null;//员工排班
            //如果排班方式为排班制，根据排班表获取
            if ("1".equals(attendType)) {
                if (MapUtils.isNotEmpty(mapCal))
                    staffCalMap = mapCal.get(staffId);
                if(staffCalMap == null){
                    log.error(String.format("#####TM generateSenior-排班制人员%s,根据考勤规则获取班次(%s)失败,跳过",staffId,rulevo.getShiftId()));
                    continue;
                }
            } else {//非排班制,固定班次 弹性班次 （按日弹性，按月弹性），直接获取班次信息
                /**处理班次 弹性班的 要先固化**/
                shiftvo = params.getShiftMap().get(shiftid);
                String shiftModeFix = rulevo.getShiftModeFix();
                shiftvo.setElasticRule(ElasticShiftUtils.getElasticRule(shiftModeFix));//将按日月弹性放到班次信息上
            }
            //处理工作日历相关的日期类型
            if(!mapCalendarDateType.containsKey(rulevo.getWorkCalendarId())){
                Map<Date,Integer> map = holidayHelper.getDaysTypeDiWork(Arrays.asList(params.getBeginDate()),params.getHolidayMap().get(rulevo.getWorkCalendarId()),workCalendar.get(rulevo.getWorkCalendarId()));
                mapCalendarDateType.put(rulevo.getWorkCalendarId(),map);
            }

            try {
                // 考勤档案要根据日报日期及人员确定考勤档案
                String dateStr = DateUtil.DateToString(params.getBeginDate(), DateStyle.YYYY_MM_DD);
                //考勤方式不考勤的也不用算日报
                if (null == attendStaff || TimeConst.ATTEND_TYPE_NO.equals(attendStaff.getAttendType())) {
                    log.error(String.format("#####TM generateSenior-人员%s在%s没有对应假勤档案信息,跳过", staffId, dateStr));
                    continue;
                }
                //如果是排班制的时候取排班表上的数据
                if ("1".equals(attendType)) {
                    shiftvo = params.getShiftMap().get(staffCalMap.get(params.getBeginDate()));
                }
                if (shiftvo == null) {
                    log.debug(String.format("*********attend generateSenior shiftvo is null,人员%s在%s没有排班",
                            attendStaff.getStaffName(), dateStr));
                    continue;
                }
                if(shiftvo.getOriWorkHours() == null){
                    shiftvo.setOriWorkHours(shiftvo.getWorkHours());
                }
                //处理弹性班次的工作时长
                if (("2".equals(attendType) || "3".equals(attendType)) && shiftvo.isIsdeductrest()) {
                    processElasticShiftWorkHour(mapElasticShiftWorkHour, shiftvo, params.getBeginDate());
                }
                AttendData data = new AttendData();//初始化datavo
                createAttendData(data, staffId, params.getBeginDate());
                data.setCalendar(params.getBeginDate());//日期
                data.setDeptId(attendStaff.getDeptId());
                data.setOrgId(attendStaff.getOrgId());
                data.setRuleid(rulevo.getId());
                //判断是否是工作日，不是工作日的也直接continue;
                //是否假日判断 先确定排班方式 如果不是排班制的话，严格根据工作日历定义
                WorkingCalendar workingCalendar = workCalendar.get(rulevo.getWorkCalendarId());
                data.setCountrycode(workingCalendar == null ? "" : workingCalendar.getCountrycode());//国家编码
                int workType = getWorkType(rulevo, params.getBeginDate(), shiftvo, workingCalendar,mapCalendarDateType);//日期类型
                log.debug(String.format("#####attend generateSenior: workType:%s,date:%s",workType,dateStr));
                //如果是免签到的，则记为正常，且打卡开始结束时间为班次的开始结束时间
                if (workType == 1 && TimeConst.ATTEND_TYPE_NOSIGN.equals(attendStaff.getAttendType())) {//免签到算正常
                    processNoSign(data, shiftvo, dataList, workType);
                    continue;
                }
                data.setIsholiday(workType == 3);

                //排班制的情况
                if ("1".equals(attendType)) {
                    if(StringUtils.isEmpty(shiftvo.getId())) continue;
                    data.setShiftId(shiftvo.getId());
                    if (shiftvo.getId().endsWith(IAttendConst.SHIFT_GX)) {//没有排班或者周末假日需要计算加班'
                        dataList.add(data);
                        continue;
                    }
                } else {
                    //非排班制,固定班次 弹性班次 （按日弹性，按月弹性），直接获取班次信息
                    data.setShiftId(shiftid);
                    if (workType != 1) {
                        data.setIsnormal(true);
                        data.setShiftId(AppContext.getContext().getScope() + IAttendConst.SHIFT_GX);
                        dataList.add(data);
                        continue;
                    }
                }
                //赋值应出勤时长，应出勤天数
                data.setWorkhours(new BigDecimal(shiftvo.getWorkHours()));
                data.setWorkdays(new BigDecimal(1));
                log.error(String.format("####***attend-生成的考勤数据---%s", JSONObject.toJSONString(data)));
                dataList.add(data);
            }catch (Exception ex){
                log.error(String.format("####attend 生成的考勤数据失败,跳过：staffid-%s,date-%s",staffId,params.getBeginDate()),ex);
                continue;
            }
        }
        return dataList;
    }

    /***
     * 统计缺卡次数
     * @param attendLists
     * @return
     */
    private Integer getLackCardCnt(List<AttendList> attendLists){
        if(CollectionUtils.isEmpty(attendLists)) return 0;
        Integer lackCardCnt = 0;
        for (AttendList attend : attendLists) {
            if(attend.getSignState() != null && attend.getSignState() == 3) lackCardCnt ++;
        }
        return lackCardCnt;
    }

    private void processSignTimeData(AttendData data,List<AttendList> attendLists){
        if(CollectionUtils.isEmpty(attendLists)) return;
        if(attendLists.size() > 3){
            data.setSecondBeginsign(attendLists.get(2).getSignTime());
            data.setSecondEndsign(attendLists.get(3).getSignTime());
        }
        data.setFirstBeginsign(attendLists.get(0).getSignTime());
        data.setFirstEndsign(attendLists.get(1).getSignTime());
        data.setSignbegintime(attendLists.get(0).getSignTime());
        data.setSignendtime(attendLists.get(attendLists.size() - 1).getSignTime());
    }

    /***
     * 处理班次的shiftdate，先不考虑替班数据
     * @param shift
     * @param mapShiftDates
     */
    private void processShiftDates(Shift shift, Map<String,List<ShiftDate>> mapShiftDates){
        if(mapShiftDates.containsKey(shift.getId())) shift.setShiftDates(mapShiftDates.get(shift.getId()));
        List<ShiftDate> listShiftDates = shiftservice.getShiftDates(shift);
        /*if(attendCalendar != null && StringUtils.isNotBlank(attendCalendar.getAttendshiftId())){
            if(mapShiftDates.containsKey(attendCalendar.getAttendshiftId())){
                if(listShiftDates.addAll(mapShiftDates.get(attendCalendar.getAttendshiftId())));
            }else{
                Shift attendShift = mapShift.get(attendCalendar.getAttendshiftId());
                if(shift != null) listShiftDates.addAll(shiftservice.getShiftDates(attendShift));
            }
        }*/
        shift.setShiftDates(listShiftDates);
        mapShiftDates.put(shift.getId(),listShiftDates);
    }

    /***
     * 获取打卡状态数据
     * @param date
     * @param shift
     * @param allAttendRecords
     * @return
     */
    public List<AttendList> getShiftCardStatus(Date date, Shift shift, AttendRecord[] allAttendRecords,ITimeScope[] leaveApplys,ITimeScope[] tripApplys,BusinessTripRule tripRule,ITimeScope[] workTimeScopes) {
        //签卡有效时间范围开始时间
        Date countStartTime = DateUtil4Sign.equiptDate(date, shift.getSignBegintime());
        if(shift.getSignBeginday() == 2) countStartTime = DateUtil4Sign.addDate(-1, countStartTime);
        //签卡有效时间范围结束时间
        Date countEndTime = DateUtil4Sign.equiptDate(date, shift.getSignEndtime());
        if(shift.getSignEndday() == 2) countEndTime = DateUtil4Sign.addDate(1, countEndTime);
        /*//上班
        Date workTimeBeginMarkT = DateUtil4Sign.equiptDate(date,shift.getBegintime());
        //下班,可能跨夜
        Date workTimeEndMarkT = DateUtil4Sign.equiptDate(date,shift.getEndtime());
        if(shift.getEndday() == 2) workTimeEndMarkT = DateUtil4Sign.addDate(1, workTimeEndMarkT);*/

        //考虑弹性班次时间范围
        //上班
        Date workTimeBeginMarkT = workTimeScopes[0].getScopeBeginTime();
        //下班
        Date workTimeEndMarkT = workTimeScopes[workTimeScopes.length - 1].getScopeEndTime();

        //班次迟到、早退弹性处理
        //规定上班时间5分钟后不算迟到,在0分钟后到6分钟前都不算迟到
        //规定下班时间前5分钟内不算早退,超过5分钟算作早退
        Integer lateFlexMilliSecondTime = shift.getAllowLateLen() * 60 * 1000 + ONEMINUTE_MILLISECOND;
        Integer LeaveFlexMilliSecondTime = shift.getAllowEarlyLen() * 60 * 1000;
        //弹性时间后上班
        Date lateFlexBeginTime = new Date(workTimeBeginMarkT.getTime() + lateFlexMilliSecondTime);
        //弹性时间后下班
        Date LeaveFlexEndTime = new Date(workTimeEndMarkT.getTime() - LeaveFlexMilliSecondTime);
        List<AttendList> attendList = new ArrayList<>();
        List<AttendRecord> attendRecord = new ArrayList<>();
        //替班
        if(shift.getMoveShift()){
            for(ShiftDate shiftDate : shift.getShiftDates()){
                Date workTimeBeginMarkTT = DateUtil4Sign.equiptDate(date,shiftDate.getBeginTime());
                shiftDate.setBeginDate(workTimeBeginMarkTT);
                Date workTimeEndMarkTT = DateUtil4Sign.equiptDate(date,shiftDate.getEndTime());
                if(shiftDate.getBeginDay() == 2) workTimeEndMarkTT = DateUtil4Sign.addDate(1, workTimeEndMarkTT);
                shiftDate.setEndDate(workTimeEndMarkT);
                Date lateFlexBeginTimeT = new Date(workTimeBeginMarkTT.getTime() + lateFlexMilliSecondTime);
                Date leaveFlexEndTimeT = new Date(workTimeEndMarkTT.getTime() - LeaveFlexMilliSecondTime);
                shiftDate.setLateFlexBeginTime(lateFlexBeginTimeT);
                shiftDate.setLeaveFlexEndTime(leaveFlexEndTimeT);
            }
            //保存各休息时段的中间时间点 3
            List<Date> middleTimes = new ArrayList<>();
            for(int i=0;i<shift.getShiftDates().size()-1;i++){
                Date middleTime = new Date((shift.getShiftDates().get(i).getEndDate().getTime() + shift.getShiftDates().get(i+1).getBeginDate().getTime())/2);
                middleTimes.add(middleTime);
            }
            for(int i=0; i<=shift.getShiftDates().size()-1; i++){
                List<AttendRecord> attendRecordT = new ArrayList<>();
                if(i == 0){
                    attendRecordT = matchAttendRecords(countStartTime, middleTimes.get(i),allAttendRecords);
                }else if(i == shift.getShiftDates().size()-1){
                    attendRecordT = matchAttendRecords(middleTimes.get(i-1), countEndTime,allAttendRecords);
                }else{
                    attendRecordT = matchAttendRecords(middleTimes.get(i-1), middleTimes.get(i),allAttendRecords);
                }
                List<AttendList> attendListT = equiptAttendList(shift.getShiftDates().get(i).getLeaveFlexEndTime(), lateFlexBeginTime, shift.getShiftDates().get(i).getBeginDate(), shift.getShiftDates().get(i).getEndDate(), attendRecordT);
                attendList.addAll(getAttendList(attendListT));
            }
        }
        //八次打卡,展示八条记录
        if(shift.isIsdeductrestsign() && shift.getRestBegtime3()!=null){
            //下班1,可能跨夜
            Date workTimeEnd1MarkT = DateUtil4Sign.equiptDate(date,shift.getRestBegtime());
            if(shift.getRestbeginday() == 2) workTimeEnd1MarkT = DateUtil4Sign.addDate(1, workTimeEnd1MarkT);
            //上班2,可能跨夜
            Date workTimeBegin2MarkT = DateUtil4Sign.equiptDate(date,shift.getRestEndtime());
            if(shift.getRestendday() == 2) workTimeBegin2MarkT = DateUtil4Sign.addDate(1, workTimeBegin2MarkT);
            //下班2,可能跨夜
            Date workTimeEnd2MarkT = DateUtil4Sign.equiptDate(date,shift.getRestBegtime2());
            if(shift.getRestbeginday2() == 2) {
                workTimeEnd2MarkT = DateUtil4Sign.addDate(1, workTimeEnd2MarkT);
            }
            //上班3,可能跨夜
            Date workTimeBegin3MarkT = DateUtil4Sign.equiptDate(date,shift.getRestEndtime2());
            if(shift.getRestendday2() == 2) {
                workTimeBegin3MarkT = DateUtil4Sign.addDate(1, workTimeBegin3MarkT);
            }
            //下班3,可能跨夜
            Date workTimeEnd3MarkT = DateUtil4Sign.equiptDate(date,shift.getRestBegtime3());
            if(shift.getRestbeginday3() == 2) {
                workTimeEnd3MarkT = DateUtil4Sign.addDate(1, workTimeEnd3MarkT);
            }
            //上班4,可能跨夜
            Date workTimeBegin4MarkT = DateUtil4Sign.equiptDate(date,shift.getRestEndtime3());
            if(shift.getRestendday3() == 2) {
                workTimeBegin4MarkT = DateUtil4Sign.addDate(1, workTimeBegin4MarkT);
            }
            //下班1和上班2中间时间
            Date middleTime = new Date((workTimeEnd1MarkT.getTime() + workTimeBegin2MarkT.getTime())/2);
            //下班2和上班3中间时间
            Date middleTime2 = new Date((workTimeEnd2MarkT.getTime() + workTimeBegin3MarkT.getTime())/2);
            //下班3和上班4中间时间
            Date middleTime3 = new Date((workTimeEnd3MarkT.getTime() + workTimeBegin4MarkT.getTime())/2);
            //弹性时间后下班1
            Date LeaveFlexEndTime1 = new Date(workTimeEnd1MarkT.getTime() - LeaveFlexMilliSecondTime);
            //弹性时间后上班2
            Date lateFlexBeginTime2 = new Date(workTimeBegin2MarkT.getTime() + lateFlexMilliSecondTime);
            //弹性时间后下班2
            Date LeaveFlexEndTime2 = new Date(workTimeEnd2MarkT.getTime() - LeaveFlexMilliSecondTime);
            //弹性时间后上班3
            Date lateFlexBeginTime3 = new Date(workTimeBegin3MarkT.getTime() + lateFlexMilliSecondTime);
            //弹性时间后下班3
            Date LeaveFlexEndTime3 = new Date(workTimeEnd3MarkT.getTime() - LeaveFlexMilliSecondTime);
            //弹性时间后上班4
            Date lateFlexBeginTime4 = new Date(workTimeBegin4MarkT.getTime() + lateFlexMilliSecondTime);
            //获取签到范围开始时间到中间时间点1内的考勤
            attendRecord = matchAttendRecords(countStartTime, middleTime,allAttendRecords);
            //获取中间时间点1到中间时间点2内的考勤
            List<AttendRecord> attendRecord2 = matchAttendRecords(middleTime, middleTime2,allAttendRecords);
            //获取中间时间点2到中间时间点3内的考勤
            List<AttendRecord> attendRecord3 = matchAttendRecords(middleTime2, middleTime3,allAttendRecords);
            //获取中间时间点3到签到范围结束时间内的考勤
            List<AttendRecord> attendRecord4 = matchAttendRecords(middleTime3, countEndTime,allAttendRecords);
            List<AttendList> attendList1 = equiptAttendList(LeaveFlexEndTime1, lateFlexBeginTime, workTimeBeginMarkT, workTimeEnd1MarkT, attendRecord);
            List<AttendList> attendList2 = equiptAttendList(LeaveFlexEndTime2, lateFlexBeginTime2, workTimeBegin2MarkT, workTimeEnd2MarkT, attendRecord2);
            List<AttendList> attendList3 = equiptAttendList(LeaveFlexEndTime3, lateFlexBeginTime3, workTimeBegin3MarkT, workTimeEnd3MarkT, attendRecord3);
            List<AttendList> attendList4 = equiptAttendList(LeaveFlexEndTime, lateFlexBeginTime4, workTimeBegin4MarkT, workTimeEndMarkT, attendRecord4);
            attendList.addAll(getAttendList(attendList1,attendList2,attendList3,attendList4));
        }
        //六次打卡,展示六条记录
        else if(shift.isIsdeductrestsign() && shift.getRestBegtime2()!=null){
            //下班1,可能跨夜
            Date workTimeEnd1MarkT = DateUtil4Sign.equiptDate(date,shift.getRestBegtime());
            if(shift.getRestbeginday() == 2) {
                workTimeEnd1MarkT = DateUtil4Sign.addDate(1, workTimeEnd1MarkT);
            }
            //上班2,可能跨夜
            Date workTimeBegin2MarkT = DateUtil4Sign.equiptDate(date,shift.getRestEndtime());
            if(shift.getRestendday() == 2) {
                workTimeBegin2MarkT = DateUtil4Sign.addDate(1, workTimeBegin2MarkT);
            }
            //下班2,可能跨夜
            Date workTimeEnd2MarkT = DateUtil4Sign.equiptDate(date,shift.getRestBegtime2());
            if(shift.getRestbeginday2() == 2) {
                workTimeEnd2MarkT = DateUtil4Sign.addDate(1, workTimeEnd2MarkT);
            }
            //上班3,可能跨夜
            Date workTimeBegin3MarkT = DateUtil4Sign.equiptDate(date,shift.getRestEndtime2());
            if(shift.getRestendday2() == 2) {
                workTimeBegin3MarkT = DateUtil4Sign.addDate(1, workTimeBegin3MarkT);
            }
            //下班1和上班2中间时间
            Date middleTime = new Date((workTimeEnd1MarkT.getTime() + workTimeBegin2MarkT.getTime())/2);
            //下班2和上班3中间时间
            Date middleTime2 = new Date((workTimeEnd2MarkT.getTime() + workTimeBegin3MarkT.getTime())/2);
            //弹性时间后下班1
            Date LeaveFlexEndTime1 = new Date(workTimeEnd1MarkT.getTime() - LeaveFlexMilliSecondTime);
            //弹性时间后上班2
            Date lateFlexBeginTime2 = new Date(workTimeBegin2MarkT.getTime() + lateFlexMilliSecondTime);
            //弹性时间后下班2
            Date LeaveFlexEndTime2 = new Date(workTimeEnd2MarkT.getTime() - LeaveFlexMilliSecondTime);
            //弹性时间后上班3
            Date lateFlexBeginTime3 = new Date(workTimeBegin3MarkT.getTime() + lateFlexMilliSecondTime);
            //获取签到范围开始时间到中间时间点1内的考勤
            attendRecord = matchAttendRecords(countStartTime, middleTime,allAttendRecords);
            //获取中间时间点1到中间时间点2内的考勤
            List<AttendRecord> attendRecord2 = matchAttendRecords(middleTime, middleTime2,allAttendRecords);
            //获取中间时间点2到签到范围结束时间内的考勤
            List<AttendRecord> attendRecord3 = matchAttendRecords(middleTime2, countEndTime,allAttendRecords);
            List<AttendList> attendList1 = equiptAttendList(LeaveFlexEndTime1, lateFlexBeginTime, workTimeBeginMarkT, workTimeEnd1MarkT, attendRecord);
            List<AttendList> attendList2 = equiptAttendList(LeaveFlexEndTime2, lateFlexBeginTime2, workTimeBegin2MarkT, workTimeEnd2MarkT, attendRecord2);
            List<AttendList> attendList3 = equiptAttendList(LeaveFlexEndTime, lateFlexBeginTime3, workTimeBegin3MarkT, workTimeEndMarkT, attendRecord3);
            attendList.addAll(getAttendList(attendList1,attendList2,attendList3));
        }
        //午休需要打卡,展示四条记录
        else if(shift.isIsdeductrestsign() && shift.getRestBegtime()!=null) {
            //下班1,可能跨夜
            Date workTimeEnd1MarkT = DateUtil4Sign.equiptDate(date,shift.getRestBegtime());
            if(shift.getRestbeginday() == 2) {
                workTimeEnd1MarkT = DateUtil4Sign.addDate(1, workTimeEnd1MarkT);
            }
            //上班2,可能跨夜
            Date workTimeBegin2MarkT = DateUtil4Sign.equiptDate(date,shift.getRestEndtime());
            if(shift.getRestendday() == 2) {
                workTimeBegin2MarkT = DateUtil4Sign.addDate(1, workTimeBegin2MarkT);
            }
            //下班1和上班2中间时间
            Date middleTime = new Date((workTimeEnd1MarkT.getTime() + workTimeBegin2MarkT.getTime())/2);
            //弹性时间后下班1
            Date LeaveFlexEndTime1 = new Date(workTimeEnd1MarkT.getTime() - LeaveFlexMilliSecondTime);
            //弹性时间后上班2
            Date lateFlexBeginTime2 = new Date(workTimeBegin2MarkT.getTime() + lateFlexMilliSecondTime);
            //获取签到范围开始时间到中间时间点内的考勤
            attendRecord = matchAttendRecords(countStartTime, middleTime,allAttendRecords);
            //获取中间时间点到签到范围结束时间内的考勤
            List<AttendRecord> attendRecord2 = matchAttendRecords(middleTime, countEndTime,allAttendRecords);
            List<AttendList> attendList1 = equiptAttendList(LeaveFlexEndTime1, lateFlexBeginTime, workTimeBeginMarkT, workTimeEnd1MarkT, attendRecord);
            List<AttendList> attendList2 = equiptAttendList(LeaveFlexEndTime, lateFlexBeginTime2, workTimeBegin2MarkT, workTimeEndMarkT, attendRecord2);
            attendList.addAll(getAttendList(attendList1,attendList2));
        }else {
            //获取签到范围开始时间到结束时间内的考勤,展示两条记录
            attendRecord = matchAttendRecords(countStartTime, countEndTime,allAttendRecords);
            attendList = equiptAttendList(LeaveFlexEndTime, lateFlexBeginTime, workTimeBeginMarkT, workTimeEndMarkT, attendRecord);
        }
        //结合请假和出差处理卡的状态
        if(CollectionUtils.isNotEmpty(attendList)){
            for (int i = 0; i < attendList.size(); i++) {
                processCardStatus(attendList.get(i),leaveApplys,tripApplys,tripRule);
            }
        }
        return attendList;
    }

    private void processCardStatus(AttendList cardData,ITimeScope[] leaveApplys,ITimeScope[] tripApplys,BusinessTripRule tripRule){
        if(cardData.getSignState() == null || cardData.getSignState() == 0 || cardData.getMarkTimeT() == null){
            return ;
        }
        long markTime = cardData.getMarkTimeT().getTime();
        if(ArrayUtils.isNotEmpty(leaveApplys)){
            for (ITimeScope timeScope : leaveApplys) {
                if(timeScope.getScopeBeginTime().getTime() <= markTime && timeScope.getScopeEndTime().getTime() >= markTime){
                    cardData.setSignState(0);
                    break;
                }
            }
        }
        if(cardData.getSignState() == 0){
            return;
        }
        if(tripRule != null && ArrayUtils.isNotEmpty(tripApplys) && tripRule.getNeedoutsidesign() == 0){
            for (ITimeScope timeScope : tripApplys) {
                if(timeScope.getScopeBeginTime().getTime() <= markTime && timeScope.getScopeEndTime().getTime() >= markTime){
                    cardData.setSignState(0);
                    break;
                }
            }
        }
    }

    /***
     * 拼装数据
     * @param attendList
     * @return
     */
    private List<AttendList> getAttendList(List<AttendList>... attendList){
        List<AttendList> result = new ArrayList<>();
        for (List<AttendList> list : attendList) {
            result.add(list.get(0));
            result.add(list.get(1));
        }
        return result;
    }

    /***
     * 匹配当前班段时间内的打卡记录
     * @param middleTime
     * @param countEndTime
     * @param allAttendRecords
     * @return
     */
    private List<AttendRecord> matchAttendRecords(Date middleTime,Date countEndTime,AttendRecord[] allAttendRecords){
        if(ArrayUtils.isEmpty(allAttendRecords)) return null;
        List<AttendRecord> listRecords = new ArrayList<>();
        for (AttendRecord record : allAttendRecords) {
            if(record.getSigntime().getTime() >= middleTime.getTime() && record.getSigntime().getTime() <= countEndTime.getTime())
                listRecords.add(record);
        }
        return listRecords;
    }

    /**整理返回给前端的历史日期考勤列表
     * 兼容四次和两次,如果是四次打卡,则将其拆分为两个两次打卡分别进行统计
     * @param LeaveFlexEndTime   弹性下班时间
     * @param lateFlexBeginTime  弹性上班时间
     * @param workTimeBeginMarkT 班次规定上班时间
     * @param workTimeEndMarkT   班次规定下班时间
     * @param attendRecords      规定时间范围内打卡记录,如果是四次,则一段为班次
     *                           签到范围开始时间到中间时间点,另外一段为中间时
     *                           间到班次签到范围结束时间
     * @return
     */
    private List<AttendList> equiptAttendList(Date LeaveFlexEndTime, Date lateFlexBeginTime, Date workTimeBeginMarkT, Date workTimeEndMarkT, List<AttendRecord> attendRecords) {
        List<AttendList> attendList = new ArrayList<>();
        AttendList attendList1 = new AttendList();
        AttendList attendList2 = new AttendList();
        attendList1.setMarkTimeT(workTimeBeginMarkT);
        attendList2.setMarkTimeT(workTimeEndMarkT);
        if(CollectionUtils.isEmpty(attendRecords)) {
            attendList1.setSignState(3);//上班缺卡
            attendList2.setSignState(3);//上班缺卡
        }else {
            AttendRecord firstAttendRecord = attendRecords.get(0);
            if(DateUtil4Sign.compare(firstAttendRecord.getSigntime(), workTimeEndMarkT) == -1) {
                attendList1.setSignTime(firstAttendRecord.getSigntime());
                if(DateUtil4Sign.compare(firstAttendRecord.getSigntime(), lateFlexBeginTime) == 1) {
                    attendList1.setSignState(1);//上班迟到
                }else {
                    attendList1.setSignState(0);
                }
                if(attendRecords.size() >= 2) {
                    AttendRecord lastAttendRecord = attendRecords.get(attendRecords.size()-1);
                    //下班卡从上班开始时间之后算
                    if(DateUtil4Sign.compare(lastAttendRecord.getSigntime(), workTimeBeginMarkT) == -1){
                        attendList2.setSignState(3);//下班缺卡
                    }else {
                        attendList2.setSignTime(lastAttendRecord.getSigntime());
                        if (DateUtil4Sign.compare(lastAttendRecord.getSigntime(), LeaveFlexEndTime) != -1) {
                            attendList2.setSignState(0);
                        } else {
                            attendList2.setSignState(2);//下班早退
                        }
                    }
                }else {
                    attendList2.setSignState(3);//下班缺卡
                }
            }else {
                attendList1.setSignState(3);//上班缺卡
                AttendRecord lastAttendRecord = attendRecords.get(attendRecords.size()-1);
                attendList2.setSignTime(lastAttendRecord.getSigntime());
                attendList2.setSignState(0);
            }
        }
        attendList.add(attendList1);
        attendList.add(attendList2);
        return attendList;
    }
 }
