package com.qmth.sop.business.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.sop.business.bean.dto.DataPermissionDto;
import com.qmth.sop.business.bean.dto.DateFormDto;
import com.qmth.sop.business.bean.dto.RoleDto;
import com.qmth.sop.business.bean.dto.SopCrmInfo;
import com.qmth.sop.business.bean.params.DingSaveParam;
import com.qmth.sop.business.bean.result.*;
import com.qmth.sop.business.entity.*;
import com.qmth.sop.business.mapper.TBDingMapper;
import com.qmth.sop.business.service.*;
import com.qmth.sop.business.sync.FaceApiUtils;
import com.qmth.sop.common.contant.SystemConstant;
import com.qmth.sop.common.enums.*;
import com.qmth.sop.common.util.DateDisposeUtils;
import com.qmth.sop.common.util.FileStoreUtil;
import com.qmth.sop.common.util.FileUtil;
import com.qmth.sop.common.util.ServletUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 考勤打卡表 服务实现.
 *
 * @author: shudonghui
 * @date: 2023-08-10 14:23:08
 * @version: 1.0
 * @email: shudonghui@qmth.com.cn
 * @Company: www.qmth.com.cn
 */
@Service
public class TBDingServiceImpl extends ServiceImpl<TBDingMapper, TBDing> implements TBDingService {
    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private TBCrmService tbCrmService;
    @Resource
    private TBUserArchivesService tbUserArchivesService;
    @Resource
    private SysDingDateService sysDingDateService;
    @Resource
    private FileStoreUtil fileStoreUtil;
    @Resource
    private FaceApiUtils faceApiUtils;
    @Resource
    private SysConfigService sysConfigService;
    @Resource
    private TBDingHistoryService tbDingHistoryService;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private SysDingObjService sysDingObjService;
    @Resource
    private TBDingApplyService tbDingApplyService;
    @Resource
    private TFFlowLogService tfFlowLogService;
    @Resource
    private TBSopInfoService tbSopInfoService;
    @Resource
    private TBServiceService tbServiceService;

    @Override
    public DingElementResult findDingElements(String sopNo, Long userId) {

        SopCrmInfo sopCrmInfo = tbCrmService.findSopCrmInfoBySop(sopNo);
        if (Objects.isNull(sopCrmInfo)) {
            throw ExceptionResultEnum.ERROR.exception(String.format("未找到sop单号为[%s]的派单信息", sopNo));
        }

        Long serviceUnitId = sopCrmInfo.getServiceUnitId();

        DingElementResult dingElementResult = this.findDingRule(userId, serviceUnitId);
        if (Objects.nonNull(dingElementResult)) {
            Long userArchivesId = dingElementResult.getUserArchivesId();
            String currentDayStr = DateFormatUtils.format(System.currentTimeMillis(), SystemConstant.DEFAULT_DATE_DAY_PATTERN);
            List<TBDing> tbDingList = this.list(new QueryWrapper<TBDing>()
                    .lambda()
                    .eq(TBDing::getSopNo, sopNo)
                    .eq(TBDing::getUserArchivesId, userArchivesId)
                    .eq(TBDing::getSignDate, currentDayStr));
            if (CollectionUtils.isNotEmpty(tbDingList)) {
                // 有考勤记录
                if (tbDingList.size() > 1) {
                    throw ExceptionResultEnum.ERROR.exception("考勤记录异常");
                }
                TBDing tbDing = tbDingList.get(0);
                Long signInTime = tbDing.getSignInTime();
                String singInAddress = tbDing.getSignInAddress();
                Long signOutTime = tbDing.getSignOutTime();
                String signOutAddress = tbDing.getSignOutAddress();

                dingElementResult.setSignInTime(signInTime);
                dingElementResult.setSignInAddress(singInAddress);
                dingElementResult.setSignOutAddress(signOutAddress);
                dingElementResult.setSignOutTime(signOutTime);
                dingElementResult.setDingSopNo(tbDing.getDingSopNo());
            }
        }
        return dingElementResult;
    }

    @Override
    public DingStatisticResult findDingStatistic(String sopNo, Long userId) {
        // 截取年分割截止
        final int yearEnd = 4;
        // 截取日分割开始
        final int dayStart = 5;

        // 根据sopNo查询日期表单
        DateFormDto dateFormDto = this.buildSignDateFormBySop(sopNo);
        List<String> dateFormList = dateFormDto.getDateFormList();


        int workCount = 0;
        int exceptionCount = 0;
        List<DingFormResult> formList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(dateFormList)) {

            List<TBDing> tbDingList = this.list(new QueryWrapper<TBDing>().lambda()
                    .eq(TBDing::getSopNo, sopNo)
                    .eq(TBDing::getUserId, userId)
                    .orderByAsc(TBDing::getId));

            // 签到表
            List<DingFormResult> formDingList = new ArrayList<>();
            for (TBDing tbDing : tbDingList) {
                String signDate = tbDing.getSignDate();
                String signYear = tbDing.getSignYear();

                Long signInTime = tbDing.getSignInTime();
                String signInAddress = tbDing.getSignInAddress();
                String axisInX = tbDing.getAxisInX();
                String axisInY = tbDing.getAxisInY();
                String dingSopNo = tbDing.getDingSopNo();

                DingInfoResult signInInfo = new DingInfoResult();
                if (signInTime == null || signInTime == 0) {
                    signInInfo.setStatus(DingStatusEnum.NO_SIGN);
                } else {
                    if (sopNo.equals(dingSopNo)) {
                        signInInfo.setStatus(DingStatusEnum.SIGN);
                    } else {
                        signInInfo.setStatus(DingStatusEnum.OTHER);
                    }
                    signInInfo.setInOutType(InOutTypeEnum.IN);
                    signInInfo.setSignTime(signInTime);
                    signInInfo.setSignAddress(signInAddress);
                    signInInfo.setAxisX(axisInX);
                    signInInfo.setAxisY(axisInY);
                }

                Long signOutTime = tbDing.getSignOutTime();
                String signOutAddress = tbDing.getSignOutAddress();
                String axisOutX = tbDing.getAxisOutX();
                String axisOutY = tbDing.getAxisOutY();

                DingInfoResult signOutInfo = new DingInfoResult();
                if (signOutTime == null || signOutTime == 0) {
                    signOutInfo.setStatus(DingStatusEnum.NO_SIGN);
                } else {
                    if (sopNo.equals(dingSopNo)) {
                        signOutInfo.setStatus(DingStatusEnum.SIGN);
                    } else {
                        signOutInfo.setStatus(DingStatusEnum.OTHER);
                    }
                    signOutInfo.setInOutType(InOutTypeEnum.OUT);
                    signOutInfo.setSignTime(signOutTime);
                    signOutInfo.setSignAddress(signOutAddress);
                    signOutInfo.setAxisX(axisOutX);
                    signOutInfo.setAxisY(axisOutY);
                }
                DingFormResult formResult = new DingFormResult();
                formResult.setDingId(tbDing.getId());
                formResult.setSignDate(signDate);
                formResult.setYear(signYear.substring(0,yearEnd));
                formResult.setSignInInfo(signInInfo);
                formResult.setSignOutInfo(signOutInfo);
                formDingList.add(formResult);
            }

            String endDate = dateFormDto.getEndDate();
            endDate = endDate.substring(dayStart);

            // 查询正在补卡中的记录
            List<TBDingApply> resigningList = tbDingApplyService.list(new QueryWrapper<TBDingApply>()
                    .lambda()
                    .select(TBDingApply::getType, TBDingApply::getApplyTime)
                    .eq(TBDingApply::getSopNo, sopNo)
                    .eq(TBDingApply::getCreateId, userId)
                    .isNull(TBDingApply::getApprove));

            for (String date : dateFormList) {
                String year = date.substring(0, yearEnd);
                String signDate = date.substring(dayStart);

                List<DingFormResult> dingFormResultList = formDingList.stream()
                        .filter(e -> year.equals(e.getYear().substring(0,yearEnd)) && signDate.equals(e.getSignDate()))
                        .collect(Collectors.toList());

                DingFormResult dingFormResult = new DingFormResult();
                if (CollectionUtils.isNotEmpty(dingFormResultList)) {
                    // 有这一天的打卡记录
                    dingFormResult = dingFormResultList.get(0);
                    // 考勤异常天数增加
                    if (dingFormResult.getSignInInfo().getStatus().equals(DingStatusEnum.NO_SIGN)) {
                        if (!endDate.equals(signDate)) {
                            exceptionCount++;
                            dingFormResult.getSignInInfo().setStatus(DingStatusEnum.RE_SIGN);
                        }
                    }
                    if (dingFormResult.getSignOutInfo().getStatus().equals(DingStatusEnum.NO_SIGN)) {
                        if (!endDate.equals(signDate)) {
                            exceptionCount++;
                            dingFormResult.getSignOutInfo().setStatus(DingStatusEnum.RE_SIGN);
                        }
                    }
                    // 出勤天数增加
                    if ((dingFormResult.getSignInInfo().getStatus().equals(DingStatusEnum.SIGN) && dingFormResult.getSignOutInfo().getStatus().equals(DingStatusEnum.SIGN)) ||
                            (dingFormResult.getSignInInfo().getStatus().equals(DingStatusEnum.OTHER) && dingFormResult.getSignOutInfo().getStatus().equals(DingStatusEnum.OTHER))) {
                        workCount++;
                    }
                } else {
                    // 完全没有这天的打卡记录
                    DingInfoResult in = new DingInfoResult();
                    DingInfoResult out = new DingInfoResult();
                    if (!endDate.equals(signDate)) {
                        exceptionCount = exceptionCount + 2;
                        in.setStatus(DingStatusEnum.RE_SIGN);
                        out.setStatus(DingStatusEnum.RE_SIGN);
                    } else {
                        in.setStatus(DingStatusEnum.NO_SIGN);
                        out.setStatus(DingStatusEnum.NO_SIGN);
                    }
                    dingFormResult.setSignInInfo(in);
                    dingFormResult.setSignOutInfo(out);
                    dingFormResult.setSignDate(signDate);
                    dingFormResult.setYear(year);
                }


                // 当天补卡中的记录
                List<TBDingApply> applyRecordList = resigningList.stream().filter(e -> {
                    Long applyTime = e.getApplyTime();
                    String applyTimeStr = DateFormatUtils.format(applyTime, SystemConstant.DEFAULT_DATE_YMD_S_PATTERN);
                    String applyYear = applyTimeStr.substring(0, yearEnd);
                    String applyDay = applyTimeStr.substring(dayStart);
                    return year.equals(applyYear) && signDate.equals(applyDay);
                }).collect(Collectors.toList());

                // 签到 补卡记录
                List<TBDingApply> applyInRecord = applyRecordList.stream().filter(e -> InOutTypeEnum.IN.equals(e.getType())).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(applyInRecord)) {
                    dingFormResult.getSignInInfo().setStatus(DingStatusEnum.RE_SIGN_ING);
                }

                // 签退 补卡记录
                List<TBDingApply> applyOutRecord = applyRecordList.stream().filter(e -> InOutTypeEnum.OUT.equals(e.getType())).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(applyOutRecord)) {
                    dingFormResult.getSignOutInfo().setStatus(DingStatusEnum.RE_SIGN_ING);
                }
                formList.add(dingFormResult);
            }
        }
        DingStatisticResult dingStatisticResult = new DingStatisticResult();
        dingStatisticResult.setDingFormList(formList);
        dingStatisticResult.setWorkDays(workCount);
        dingStatisticResult.setExceptionCount(exceptionCount);
        dingStatisticResult.setRemainCount(this.findRemainCount(userId, sopNo));
        return dingStatisticResult;
    }

    @Override
    public void dingSave(DingSaveParam dingSaveParam, Long userId) throws IOException {
        String sopNo = dingSaveParam.getSopNo();
        SopCrmInfo sopCrmInfo = tbCrmService.findSopCrmInfoBySop(sopNo);
        if (Objects.isNull(sopCrmInfo)) {
            throw ExceptionResultEnum.ERROR.exception(String.format("未找到sop单号为[%s]的派单信息", sopNo));
        }

        Long serviceUnitId = sopCrmInfo.getServiceUnitId();
        DingElementResult dingElementResult = this.findDingRule(userId, serviceUnitId);
        if (Objects.isNull(dingElementResult)) {
            throw ExceptionResultEnum.ERROR.exception("未能获取考勤相关规则");
        }

        Long signTime = dingSaveParam.getSignTime();
        DingDateTypeEnum dateType = sysDingDateService.getDingDateType(DateDisposeUtils.timestampToLocalDate(signTime));

        InOutTypeEnum signType = dingSaveParam.getSignType();
        String signAddress = dingSaveParam.getSignAddress();
        String facePhotoPath = dingSaveParam.getFacePhotoPath();
        String axisX = dingSaveParam.getAxisX();
        String axisY = dingSaveParam.getAxisY();

        TBUserArchives tbUserArchives = tbUserArchivesService.findByUserId(userId);
        Long userArchivesId = tbUserArchives.getId();
        // 人脸识别
        Boolean faceOpen = dingElementResult.getFaceOpen();
        BigDecimal score = null;
        Boolean facePass = null;
        if (faceOpen) {
            // 开启人脸识别
            if (facePhotoPath == null || facePhotoPath.length() == 0) {
                throw ExceptionResultEnum.ERROR.exception("缺少人脸识别照片");
            }
            String basePhotoPath = tbUserArchives.getBasePhotoPath();
            String basePhotoSuffix = basePhotoPath.substring(basePhotoPath.lastIndexOf(".")).toLowerCase();
            File basePhotoTemp = SystemConstant.getFileTempVar(basePhotoSuffix);
            String facePhotoSuffix = facePhotoPath.substring(facePhotoPath.lastIndexOf(".")).toLowerCase();
            File facePhotoTemp = SystemConstant.getFileTempVar(facePhotoSuffix);
            try {
                basePhotoTemp = fileStoreUtil.ossDownload(basePhotoPath, basePhotoTemp, UploadFileEnum.FILE.getFssType());
                facePhotoTemp = fileStoreUtil.ossDownload(facePhotoPath, facePhotoTemp, UploadFileEnum.FILE.getFssType());
                score = faceApiUtils.faceMatch(basePhotoTemp, facePhotoTemp);
            } catch (Exception e) {
                throw ExceptionResultEnum.ERROR.exception(e.getMessage());
            } finally {
                FileUtil.deleteFile(basePhotoTemp);
                FileUtil.deleteFile(facePhotoTemp);
            }
            String limitStr = sysConfigService.findByConfigKey(SystemConstant.BAIDU_FACE_COMPARE_SCORE).getConfigValue();
            BigDecimal limit = new BigDecimal(limitStr);
            facePass = score.compareTo(limit) > -1;
        }

        String crmNo = sopCrmInfo.getCrmNo();
        Long serviceId = sopCrmInfo.getServiceUnitId();
        Long flowId = sopCrmInfo.getFlowId();
        RoleTypeEnum roleType = sysRoleService.findRoleTypeByUserCrm(userId, crmNo);
        String signDate = DateFormatUtils.format(signTime, SystemConstant.DEFAULT_DATE_DAY_PATTERN);
        String signYear = DateFormatUtils.format(signTime, SystemConstant.DEFAULT_DATE_YEAR_PATTERN);
        String currentDayStr = DateFormatUtils.format(System.currentTimeMillis(), SystemConstant.DEFAULT_DATE_DAY_PATTERN);
        if (!currentDayStr.equals(signDate)) {
            throw ExceptionResultEnum.ERROR.exception("打卡失败，时间异常");
        }

        // 新增打卡历史记录
        TBDingHistory history = new TBDingHistory();
        history.setSopNo(sopNo);
        history.setSignDate(signDate);
        history.setDateType(dateType);
        history.setSignTime(signTime);
        history.setAxisX(axisX);
        history.setAxisY(axisY);
        history.setFaceOpen(faceOpen);
        history.setFacePass(facePass);
        history.setFacePhotoPath(facePhotoPath);
        history.setMatchScore(score);
        history.setCreateId(userId);
        tbDingHistoryService.save(history);

        if (faceOpen && !facePass) {
            log.error("人脸识别不通过,score ：" + score);
            throw ExceptionResultEnum.ERROR.exception("人脸识别不通过!");
        }

        List<TBDing> tbDingList = this.list(new QueryWrapper<TBDing>().lambda()
                .eq(TBDing::getSopNo, sopNo)
                .eq(TBDing::getUserId, userId)
                .eq(TBDing::getSignYear, signYear)
                .eq(TBDing::getSignDate, signDate));

        if (CollectionUtils.isEmpty(tbDingList)) {
            // 没有这天的打卡记录 - 新增打卡记录
            TBDing tbDing = new TBDing();
            tbDing.setSopNo(sopNo);
            tbDing.setDingSopNo(sopNo);
            tbDing.setCrmNo(crmNo);
            tbDing.setServiceId(serviceId);
            tbDing.setUserArchivesId(userArchivesId);
            tbDing.setRoleType(roleType);
            tbDing.setUserId(userId);
            tbDing.setFlowId(flowId);
            tbDing.setSignDate(signDate);
            tbDing.setSignYear(signYear);
            tbDing.setDateType(dateType);
            switch (signType) {
                case IN: // 签到打卡
                    tbDing.setSignInTime(signTime);
                    tbDing.setSignInAddress(signAddress);
                    tbDing.setAxisInX(axisX);
                    tbDing.setAxisInY(axisY);
                    tbDing.setFaceInPass(facePass);
                    break;
                case OUT: // 签退打卡
                    tbDing.setSignOutTime(signTime);
                    tbDing.setSignOutAddress(signAddress);
                    tbDing.setAxisOutX(axisX);
                    tbDing.setAxisOutY(axisY);
                    tbDing.setFaceOutPass(facePass);
                    break;
            }
            this.save(tbDing);
        } else {
            // 有这天的打卡记录 - 签退打卡或更新打卡
            if (tbDingList.size() > 1) {
                throw ExceptionResultEnum.ERROR.exception(String.format("用户id为[%s],sop单号为[%s],在[%s]的打卡记录异常", userId, sopNo, signDate));
            }
            TBDing tbDing = tbDingList.get(0);
            switch (signType) {
                case IN: // 签到打卡
                    tbDing.setSignInTime(signTime);
                    tbDing.setSignInAddress(signAddress);
                    tbDing.setAxisInX(axisX);
                    tbDing.setAxisInY(axisY);
                    tbDing.setFaceInPass(facePass);
                    break;
                case OUT: // 签退打卡
                    tbDing.setSignOutTime(signTime);
                    tbDing.setSignOutAddress(signAddress);
                    tbDing.setAxisOutX(axisX);
                    tbDing.setAxisOutY(axisY);
                    tbDing.setFaceOutPass(facePass);
                    break;
            }
            this.updateById(tbDing);
        }
    }

    @Override
    public IPage<TBDingSubmitQueryResult> query(IPage<Map> iPage, Long serviceId, String status, Long createId, String userName, Long supplierId, String custom, String sopNo, Long days) {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        Long userId = requestUser.getId();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(userId);
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商", RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }

        return this.baseMapper.query(iPage, serviceId, status, createId, userName, supplierId, custom, sopNo, days, dpr);
    }

    @Override
    public List<TBDingSubmitQueryResult> query(SysUser requestUser, Long serviceId, String status, Long createId, String userName, Long supplierId, String custom, String sopNo, Long days) {
        Long userId = requestUser.getId();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(userId);
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商",RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }
        return this.baseMapper.query(serviceId, status, createId, userName, supplierId, custom, sopNo, days, dpr);
    }

    @Override
    public TBDingCountResult count(Long serviceId, Long createId, String userName, Long supplierId, String custom, String sopNo) {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        Long userId = requestUser.getId();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(userId);
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商",RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }
        return this.baseMapper.count(serviceId, createId, userName, supplierId, custom, sopNo, dpr);
    }

    @Override
    public IPage<TBDingAttendanceResult> attendanceQuery(IPage<Map> iPage, Long serviceId, String userName, Long startTime, Long endTime, Long supplierId, String custom, String sopNo, InOutTypeEnum type, DingResultEnum status, Long abnormal) {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        Long userId = requestUser.getId();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(userId);
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商",RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }
        return this.baseMapper.attendanceQuery(iPage, serviceId, userName, startTime, endTime, supplierId, custom, sopNo, Objects.nonNull(type) ? type.name() : null, Objects.nonNull(status) ? status.name() : null, dpr, abnormal);
    }

    @Override
    public TBDingAttendanceCountResult attendanceCount(Long serviceId, String userName, Long startTime, Long endTime, Long supplierId, String custom, String sopNo) {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        Long userId = requestUser.getId();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(userId);
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商",RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }
        return this.baseMapper.attendanceCount(serviceId, userName, startTime, endTime, supplierId, custom, sopNo, dpr);
    }

    @Override
    public List<TBDingAttendanceResult> attendanceQuery(Long serviceId, String userName, Long startTime, Long endTime, Long supplierId, String custom, String sopNo, InOutTypeEnum type, DingResultEnum status, Long abnormal) {
        return this.baseMapper.attendanceQuery(serviceId, userName, startTime, endTime, supplierId, custom, sopNo, Objects.nonNull(type) ? type.name() : null, Objects.nonNull(status) ? status.name() : null, abnormal);
    }

    @Override
    public DateFormDto buildSignDateFormBySop(String sopNo) {
        DateFormDto result = new DateFormDto();
        List<String> dateList = new ArrayList<>();
        if (sopNo != null && sopNo.length() > 0) {
            List<TFFlowApprove> tfFlowApproves = this.baseMapper.findDateAndStatusBySopNo(sopNo);
            if (CollectionUtils.isNotEmpty(tfFlowApproves)) {
                if (tfFlowApproves.size() > 1) {
                    throw ExceptionResultEnum.ERROR.exception(String.format("sop编号为[%s]的有多条流程审核记录", sopNo));
                }
                TFFlowApprove tfFlowApprove = tfFlowApproves.get(0);
                FlowStatusEnum flowStatusEnum = tfFlowApprove.getStatus();
                Long updateTime = tfFlowApprove.getUpdateTime();
                Long currentTime = System.currentTimeMillis();

                Long flowId = tfFlowApprove.getFlowId();
                TFFlowLog tfFlowLog = tfFlowLogService.getOne(new QueryWrapper<TFFlowLog>().lambda()
                        .eq(TFFlowLog::getFlowId, flowId)
                        .eq(TFFlowLog::getApproveRemark, "通过")
                        .eq(TFFlowLog::getApproveSetup, 1)
                        .eq(TFFlowLog::getApproveOperation, FlowApproveOperationEnum.START)
                        .orderByDesc(TFFlowLog::getId)
                        .last("limit 1"));
                if (Objects.isNull(tfFlowLog)) {
                    throw ExceptionResultEnum.ERROR.exception("未找到流程提交记录");
                }
                Long createTime = tfFlowLog.getCreateTime();


                if (FlowStatusEnum.FINISH.equals(flowStatusEnum)) {
                    dateList = DateDisposeUtils.getDaysBetween(createTime, updateTime, SystemConstant.DEFAULT_DATE_YMD_S_PATTERN);
                    result.setSopIsEnd(true);
                } else {
                    dateList = DateDisposeUtils.getDaysBetween(createTime, currentTime, SystemConstant.DEFAULT_DATE_YMD_S_PATTERN);
                    result.setSopIsEnd(false);
                }
                dateList.sort(Collections.reverseOrder());
                result.setDateFormList(dateList);
            }
        }
        if (CollectionUtils.isNotEmpty(dateList)) {
            result.setEndDate(dateList.get(0));
        }
        return result;
    }

    @Override
    public DingElementResult findDingRule(Long userId, Long serviceUnitId) {
        List<Long> roleIdList = sysRoleService.listRolesByUserId(userId).stream().map(RoleDto::getId).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(roleIdList)) {
            throw ExceptionResultEnum.ERROR.exception("用户缺少角色");
        }
        List<DingElementResult> dingElementResultList = this.baseMapper.findDingResultByUserId(userId, serviceUnitId);
        List<Long> dingGroupIds = dingElementResultList.stream().map(DingElementResult::getDingGroupId).distinct().collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(dingGroupIds)) {
            List<SysDingObj> sysDingObjList = sysDingObjService.list(new QueryWrapper<SysDingObj>()
                    .lambda()
                    .in(SysDingObj::getDingGroupId, dingGroupIds)
                    .in(SysDingObj::getRoleId, roleIdList)
                    .eq(SysDingObj::getType, DingObjTypeEnum.DING));

            if (CollectionUtils.isNotEmpty(sysDingObjList) && sysDingObjList.size() == 1) {
                dingElementResultList = dingElementResultList
                        .stream()
                        .filter(e -> e.getDingGroupId()
                                .equals(sysDingObjList.get(0).getDingGroupId()))
                        .collect(Collectors.toList());
                if (dingElementResultList.size() == 1) {
                    return dingElementResultList.get(0);
                }
            }
        }
        return null;
    }

    @Override
    public int findRemainCount(Long userId, String sopNo) {
        // TODO: 2023/11/8 查明为什么会有sop找不到对应的派单 (脏数据)
        SopCrmInfo sopCrmInfo = tbCrmService.findSopCrmInfoBySop(sopNo);
        if (Objects.isNull(sopCrmInfo)) {
            return 0;
        }
        Long serviceUnitId = sopCrmInfo.getServiceUnitId();
        DingElementResult dingElementResult = this.findDingRule(userId, serviceUnitId);
        int reissueCardCount = dingElementResult != null ? dingElementResult.getReissueCardCount() : 0;
        int applyCount = this.baseMapper.findDingExceptionApplyCount(userId, sopNo);
        return reissueCardCount != 0 ? reissueCardCount - applyCount : 0;
    }

    @Transactional
    @Override
    public void autoAddDingRecord(String signYear, String signDay) {
        Long signTime = DateDisposeUtils.dateTime(SystemConstant.DEFAULT_DATE_YMD_S_PATTERN, signYear + signDay).getTime();
        List<TBService> tbServiceList = tbServiceService.list(new QueryWrapper<TBService>().lambda()
                .eq(TBService::getStatus, ServiceStatusEnum.PUBLISH));

        Set<Long> needDingUserIdSet = new HashSet<>();

        for (TBService tbService : tbServiceList) {
            Long serviceUnitId = tbService.getId();
            List<Map<String, Object>> sopPassageMap = tbSopInfoService.findSopPassage(serviceUnitId);
            if (CollectionUtils.isNotEmpty(sopPassageMap)) {
                Map<String, Object> map = sopPassageMap.get(0);
                if (Objects.nonNull(map)) {
                    String engineerUserIds = String.valueOf(map.get("engineerUserIds"));
                    String assistantEngineerUserIds = String.valueOf(map.get("assistantEngineerUserIds"));

                    if (engineerUserIds != null && engineerUserIds.length() > 0 && !engineerUserIds.equals("null")) {
                        List<Long> engineerUserIdCell = Arrays.stream(engineerUserIds.split(","))
                                .map(Long::parseLong)
                                .filter(userId -> Objects.nonNull(this.findDingRule(userId, serviceUnitId)))
                                .distinct()
                                .collect(Collectors.toList());
                        needDingUserIdSet.addAll(engineerUserIdCell);
                    }

                    if (assistantEngineerUserIds != null && assistantEngineerUserIds.length() > 0 && !assistantEngineerUserIds.equals("null")) {
                        List<Long> assistantEngineerUserIdCell = Arrays.stream(assistantEngineerUserIds.split(","))
                                .map(Long::parseLong)
                                .filter(userId -> Objects.nonNull(this.findDingRule(userId, serviceUnitId)))
                                .distinct()
                                .collect(Collectors.toList());
                        needDingUserIdSet.addAll(assistantEngineerUserIdCell);
                    }
                }
            }

        }

        List<TBDing> willAddDingList = new ArrayList<>();
        for (Long userId : needDingUserIdSet) {
            List<TBCrm> tbCrmList = tbCrmService.findOccupiedCrm(userId, null);
            for (TBCrm tbCrm : tbCrmList) {
                String crmNo = tbCrm.getCrmNo();
                List<String> sopNoList = tbSopInfoService.findFlowPassageByCrmNo(tbCrm.getCrmNo(), userId);
                for (String sopNo : sopNoList) {
                    TBDing tbDing = this.getOne(new QueryWrapper<TBDing>().lambda()
                            .eq(TBDing::getSopNo, sopNo)
                            .eq(TBDing::getCrmNo, crmNo)
                            .eq(TBDing::getUserId, userId)
                            .eq(TBDing::getSignYear, signYear)
                            .eq(TBDing::getSignDate, signDay)
                            .last("limit 1"));

                    if (Objects.isNull(tbDing)) {
                        SopCrmInfo sopCrmInfo = tbCrmService.findSopCrmInfoBySop(sopNo);
                        DingDateTypeEnum dateType = sysDingDateService.getDingDateType(DateDisposeUtils.timestampToLocalDate(signTime));
                        TBDing addDing = new TBDing();
                        addDing.setSopNo(sopNo);
                        addDing.setDingSopNo(sopNo);
                        addDing.setCrmNo(crmNo);
                        addDing.setServiceId(tbCrm.getServiceId());
                        addDing.setUserId(userId);
                        addDing.setUserArchivesId(tbUserArchivesService.findByUserId(userId).getId());
                        addDing.setRoleType(sysRoleService.findRoleTypeByUserCrm(userId, crmNo));
                        addDing.setFlowId(sopCrmInfo.getFlowId());
                        addDing.setSignYear(signYear);
                        addDing.setSignDate(signDay);
                        addDing.setDateType(dateType);
                        willAddDingList.add(addDing);
                    }
                }
            }
        }
        this.saveBatch(willAddDingList);
    }
}