package com.hz.pm.api.todocenter.manage;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hz.pm.api.common.enumeration.CommonEnum;
import com.hz.pm.api.common.helper.UserInfoHelper;
import com.hz.pm.api.common.model.constant.BizConst;
import com.hz.pm.api.common.model.constant.CommonConst;
import com.hz.pm.api.common.model.constant.RegionConst;
import com.hz.pm.api.common.model.entity.ExcelExportWriter;
import com.hz.pm.api.common.util.BizUtils;
import com.hz.pm.api.common.util.ExcelDownUtil;
import com.hz.pm.api.irs.sign.IRSAPIRequest;
import com.hz.pm.api.projectdeclared.manage.DefaultDeclaredProjectManage;
import com.hz.pm.api.projectlib.manage.ProjectLibManage;
import com.hz.pm.api.projectlib.model.dto.ProjectApplicationDTO;
import com.hz.pm.api.projectlib.model.dto.ProjectDTO;
import com.hz.pm.api.projectlib.model.entity.Project;
import com.hz.pm.api.projectlib.model.entity.ProjectApplication;
import com.hz.pm.api.projectlib.model.entity.ProjectInst;
import com.hz.pm.api.projectlib.model.enumeration.*;
import com.hz.pm.api.projectlib.model.req.ProjectListReq;
import com.hz.pm.api.projectlib.service.IProjectApplicationService;
import com.hz.pm.api.projectlib.service.IProjectInstService;
import com.hz.pm.api.projectlib.service.IProjectService;
import com.hz.pm.api.signature.entity.CompanySignature;
import com.hz.pm.api.signature.service.ICompanySignatureService;
import com.hz.pm.api.sys.model.entity.Region;
import com.hz.pm.api.sys.service.IRegionService;
import com.hz.pm.api.todocenter.constant.TodoCenterConst;
import com.hz.pm.api.todocenter.enumeration.IsAppendProjectEnum;
import com.hz.pm.api.todocenter.enumeration.IsOrNotEnum;
import com.hz.pm.api.todocenter.handle.PassHandle;
import com.hz.pm.api.todocenter.handle.WithDrawHandle;
import com.hz.pm.api.todocenter.model.dto.AdjustHandleDTO;
import com.hz.pm.api.todocenter.model.dto.PdfGenerateDTO;
import com.hz.pm.api.todocenter.model.dto.SealInfoDTO;
import com.hz.pm.api.todocenter.model.dto.SignReqDTO;
import com.hz.pm.api.todocenter.model.po.TodoCenterStatisticsPO;
import com.hz.pm.api.todocenter.model.req.ProcessDetailReq;
import com.hz.pm.api.todocenter.model.req.ToBeProcessedExportReq;
import com.hz.pm.api.todocenter.model.req.ToBeProcessedReq;
import com.hz.pm.api.todocenter.model.vo.*;
import com.hz.pm.api.todocenter.service.ITodoService;
import com.hz.pm.api.todocenter.service.StatisticsService;
import com.hz.pm.api.todocenter.utils.BuildUserUtils;
import com.hz.pm.api.todocenter.utils.PdfUtils;
import com.hz.pm.api.user.security.model.UserFullInfoDTO;
import com.hz.pm.api.user.security.model.UserInfoDetails;
import com.hz.pm.api.user.util.LoginUserUtil;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.function.VUtils;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.basic.util.StrPool;
import com.ningdatech.file.entity.File;
import com.ningdatech.file.entity.vo.result.FileResultVO;
import com.ningdatech.file.service.FileService;
import com.wflow.contants.HisProInsEndActId;
import com.wflow.contants.ProcessConstant;
import com.wflow.exception.BusinessException;
import com.wflow.workflow.bean.dto.ReqAuditOpinionSaveDTO;
import com.wflow.workflow.bean.dto.ReqProcessHandlerDTO;
import com.wflow.workflow.bean.dto.TodoCenterListReqDTO;
import com.wflow.workflow.bean.process.ProgressNode;
import com.wflow.workflow.bean.process.enums.NodeTypeEnum;
import com.wflow.workflow.bean.vo.ProcessInstanceVo;
import com.wflow.workflow.bean.vo.ProcessProgressVo;
import com.wflow.workflow.bean.vo.ProcessTaskVo;
import com.wflow.workflow.service.ProcessInstanceService;
import com.wflow.workflow.service.ProcessTaskService;
import com.wflow.workflow.utils.ProcessTaskUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowElementsContainer;
import org.flowable.bpmn.model.SubProcess;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author ZPF
 * @since 2023/02/19 16:09
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class TodoCenterManage {

    private final ProcessTaskService processTaskService;
    private final HistoryService historyService;
    private final TaskService taskService;
    private final IProjectService projectService;
    private final ProjectLibManage projectLibManage;
    private final ProcessInstanceService processInstanceService;
    private final DefaultDeclaredProjectManage defaultDeclaredProjectManage;
    private final IProjectApplicationService projectApplicationService;
    private final UserInfoHelper userInfoHelper;
    private final BuildUserUtils buildUserUtils;
    private final IProjectInstService projectInstService;
    private final PdfUtils pdfUtils;
    private final FileService fileService;
    private final ICompanySignatureService companySignatureService;
    private final StatisticsService statisticsService;
    private final HandlerManage handlerManage;
    private final WithDrawHandle withDrawHandle;
    private final PassHandle passHandle;
    private final RepositoryService repositoryService;
    private final IRegionService regionService;
    private final ITodoService todoService;

    /**
     * 待办中心待我处理项目列表查询
     *
     * @param param
     * @author zpf
     * @since 2023/02/18
     */
    public PageVo<ResToBeProcessedVO> todoProjectList(ToBeProcessedReq param) {
        Long userId = Optional.ofNullable(param.getUserId()).orElseGet(LoginUserUtil::getUserId);
        String userIdStr = String.valueOf(userId);

        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        if (CollUtil.isEmpty(projects)) {
            return PageVo.empty();
        }
        Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
        // 再查出项目关联的流程实例ID
        List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());

        List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                .in(ProjectInst::getProjectId, projectIdList)
                .orderByDesc(ProjectInst::getProjectId));
        Map<String, Project> projectInfoMap = projectInstList.stream()
                .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                        !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));

        List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());
        if (CollUtil.isEmpty(instCodes)) {
            return PageVo.empty();
        }
        // 查出用户工作流
        TodoCenterListReqDTO req = new TodoCenterListReqDTO();
        req.setInstCodes(instCodes);
        Integer pageNumber = param.getPageNumber();
        Integer pageSize = param.getPageSize();
        req.setProcessDefId(param.getProcessDefId());
        req.setEmployeeCode(userIdStr);
        //有待办节点的(不包含退回)
        List<ProcessTaskVo> todoList = processTaskService.getUserTodoList(req);
        //有退回待办的
        List<ProcessTaskVo> backList = processTaskService.getBackTodoList(req);
        //合并 并且排序
        List<ProcessTaskVo> list = Stream.concat(todoList.stream(), backList.stream())
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(ProcessTaskVo::getTaskCreateTime).reversed())
                .collect(Collectors.toList());

        if (CollUtil.isEmpty(list)) {
            return PageVo.empty();
        }

        List<ProcessTaskVo> userTodoList = list.stream()
                .skip((long) (pageNumber - 1) * pageSize)
                .limit(pageSize)
                .collect(Collectors.toList());

        Set<String> processSet = Sets.newHashSet();
        Map<String, ProcessTaskVo> taskMap = userTodoList.stream()
                .sorted((t1, t2) -> t2.getTaskCreateTime().compareTo(t1.getTaskCreateTime()))
                .collect(Collectors.toList()).stream()
                .filter(v -> processSet.add(v.getNodeId()))
                .collect(Collectors.toMap(ProcessTaskVo::getNodeId, v -> v));


        List<ResToBeProcessedVO> resVos = userTodoList.stream().map(d -> {
            Project projectInfo = projectInfoMap.get(d.getInstanceId());
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            BeanUtils.copyProperties(projectInfo, res);
            res.setInstCode(d.getInstanceId());
            res.setProjectId(projectInfo.getId());
            res.setBuildOrg(projectInfo.getBuildOrgName());
            res.setDeclaredAmount(projectInfo.getDeclareAmount());
            ProcessTaskVo taskVo = taskMap.get(d.getNodeId());
            res.setNodeId(d.getNodeId());
            res.setProcessStatusName(taskVo.getStatus());
            res.setProcessLaunchTime(d.getTaskCreateTime());
            res.setProcessDefName(d.getProcessDefName());
            res.setTaskId(d.getTaskId());
            res.setNodeName(findSubProcessName(d));
            res.setPretrialFileId(projectInfo.getPretrialFileId());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, list.size());
    }

    private String findSubProcessName(ProcessTaskVo d) {
        if (StringUtils.isBlank(d.getProcessDefId())) {
            return null;
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(d.getProcessDefId());

        if (Objects.nonNull(bpmnModel)) {
            FlowElement flowElement = bpmnModel.getFlowElement(d.getTaskDefKey());
            FlowElementsContainer parentContainer = flowElement.getParentContainer();
            if (parentContainer instanceof SubProcess) {
                return ((SubProcess) parentContainer).getName();
            }
        }
        return null;
    }

    /**
     * 待办中心列表导出
     *
     * @param response \
     * @param param    \
     * @author CMM
     * @since 2023/02/01
     */
    public void exportTodoList(HttpServletResponse response, ToBeProcessedExportReq param) {

        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        // 获取登录用户浙政钉code
        String userIdStr = String.valueOf(userId);

        List<ProcessTaskVo> userTodoList = Lists.newArrayList();
        Map<String, Project> projectInfoMap = MapUtil.newHashMap();
        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        if (CollUtil.isNotEmpty(projects)) {
            Map<Long, Project> projectsMap = CollUtils.listToMap(projects, Project::getId);
            // 再查出项目关联的流程实例ID
            List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());
            List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                    .in(ProjectInst::getProjectId, projectIdList).orderByDesc(ProjectInst::getProjectId));
            projectInstList.forEach(p -> projectInfoMap.put(p.getInstCode(), projectsMap.get(p.getProjectId())));
            List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

            // 查出用户工作流
            TodoCenterListReqDTO req = new TodoCenterListReqDTO();
            req.setInstCodes(instCodes);
            req.setProcessDefId(param.getProcessDefId());
            req.setEmployeeCode(userIdStr);

            // 有待办节点的(不包含退回)
            List<ProcessTaskVo> todoList = processTaskService.getUserTodoList(req);
            // 有退回待办的
            List<ProcessTaskVo> backList = processTaskService.getBackTodoList(req);
            // 合并 并且排序
            Stream.of(todoList, backList)
                    .flatMap(Collection::stream)
                    .filter(Objects::nonNull)
                    .sorted(Comparator.comparing(ProcessTaskVo::getTaskCreateTime).reversed())
                    .forEach(userTodoList::add);
        }
        ExcelExportWriter excelExportWriter = new ExcelExportWriter();
        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_待我处理_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_待我处理_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);
        List<String> sheetsNames = Lists.newArrayList(fileName);
        List<List<String>> exportDatas = ExcelDownUtil.getTaskExportDatas(param.getExportOptionList(), userTodoList, projectInfoMap);
        // sheet列表集合
        List<List<List<String>>> sheets = Lists.newArrayList();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);
    }

    /**
     * 流程处理相关操作
     *
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/01
     */
    public String handler(ReqProcessHandlerDTO param) {
        // 获取登录用户ID
        UserInfoDetails user = LoginUserUtil.loginUserDetail();
        // 获取登录用户浙政钉code
        String userIdStr = user.getUserIdStr();
        // 获取入参
        String processInstanceId = param.getInstanceId();
        Long projectId = param.getProjectId();
        VUtils.isTrue(Objects.isNull(processInstanceId)).throwMessage("获取流程实例ID失败!");
        VUtils.isTrue(Objects.isNull(projectId)).throwMessage("获取项目ID失败!");

        // 获取当前申报项目
        Project declaredProject = projectService.getOne(Wrappers.lambdaQuery(Project.class)
                .eq(Project::getInstCode, processInstanceId)
                .eq(Project::getId, projectId));
        VUtils.isTrue(Objects.isNull(declaredProject)).throwMessage("获取项目失败!");

        // 获取当前未处理流程详情
        ProcessProgressVo currentInstanceDetail = processInstanceService.getProgressInstanceDetail(null, processInstanceId);

        // 获取当前要处理的流程实例
        HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();

        //提前判断下 是不是 已经被退回了
        checkIsBack(instance);

        //进入处理逻辑
        switch (param.getAction()) {
            // 通过
            case PASS:
                // 盖章并通过
            case SEAL_PASS: {
                //前置判断前端传的taskId是否存在不存在的情况 有可能是 或签被别人审批掉了
                Task task = taskService.createTaskQuery().taskId(param.getTaskId()).active().singleResult();
                if (ObjectUtil.isNull(task)) {
                    return TodoCenterConst.Handler.TASKID_ERROR;
                }
                String nodeId = task.getTaskDefinitionKey();
                // 通过该任务，流程到下一审核人处
                processTaskService.handleTask(param, userIdStr);
                //通过审核后 所处理的逻辑
                handlerManage.afterPassTodo(declaredProject, instance);

                return findNextTaskId(instance, userIdStr, nodeId, param.getTaskId());
            }
            // 驳回
            case REJECT: {
                //前置判断前端传的taskId是否存在不存在的情况 有可能是 或签被别人审批掉了
                Task task = taskService.createTaskQuery().taskId(param.getTaskId()).active().singleResult();
                if (ObjectUtil.isNull(task)) {
                    return TodoCenterConst.Handler.TASKID_ERROR;
                }
                // 驳回该任务，中止流程并使项目进入对应状态,给项目创建人、流程发起人发送浙政钉工作通知：
                // 【项目名称】的【流程名称】被驳回，请及时处理。
                processTaskService.handleTask(param, userIdStr);
                //驳回审核后 所处理的逻辑
                handlerManage.afterRejectTodo(declaredProject, instance);
            }
            break;
            // 退回
            case BACK: {
                //前置判断前端传的taskId是否存在不存在的情况 有可能是 或签被别人审批掉了
                Task task = taskService.createTaskQuery().taskId(param.getTaskId()).active().singleResult();
                if (ObjectUtil.isNull(task)) {
                    return TodoCenterConst.Handler.TASKID_ERROR;
                }
                // 退回该任务
                processTaskService.handleTaskLs(param, userIdStr);
                //退回审核后 所处理的逻辑
                handlerManage.afterBackTodo(declaredProject, instance);
            }
            break;
            // 撤回(流程发起人和当前流程审核人的前一个审核人操作)
            case WITHDRAW: {
                /**
                 * 新逻辑
                 * 不判断前端传的taskId
                 * 1.先判断 用户是否是ROOT 发起人 并且流程没有开始审批
                 */
                if (withDrawHandle.canRootWithDraw(instance, userIdStr)) {
                    // 登录用户是流程发起人，且是流程发起人撤回
                    processTaskService.rootWithdrawTask(param, userIdStr);
                    //发起人撤回
                    handlerManage.rootWithDraw(declaredProject);
                } else {
                    // 2.如果用户不是ROOT发起人 或者 不满足root撤回 那么必定要判断 他是不是上个节点审批人 或者 当前会签已审批的审批人
                    // 这里有个小操作 check的同时 把对应的操作人的taskId 也塞入
                    VUtils.isTrue(!withDrawHandle.checkUserIsBefore(currentInstanceDetail.getProgressInfo(), param))
                            .throwMessage("当前登录用户不是发起人,也不是上一个节点审批人或者当前会签已批审批人 无法进行撤回操作!");

                    //前置判断前端传的taskId是否存在不存在的情况 有可能是 或签被别人审批掉了
                    HistoricTaskInstance taskInst = historyService.createHistoricTaskInstanceQuery().taskId(param.getTaskId()).finished().singleResult();
                    if (ObjectUtil.isNull(taskInst)) {
                        return TodoCenterConst.Handler.TASKID_ERROR;
                    }
                    String nodeId = taskInst.getTaskDefinitionKey();
                    //上个审批人处理逻辑
                    processTaskService.lastWithdrawTask(param);

                    return findNextTaskId(instance, userIdStr, nodeId, param.getTaskId());
                }
            }
            break;
            default:
                throw new IllegalStateException("Unexpected value: " + param.getAction());
        }
        return "操作成功";
    }

    private void checkIsBack(HistoricProcessInstance instance) {
        VUtils.isTrue(Objects.nonNull(instance.getBusinessStatus()) &&
                        HisProInsEndActId.BACK.equals(instance.getBusinessStatus()))
                .throwMessage("该审核流程已被退回 暂时无法操作!");
    }

    /**
     * 调用IRS接口,获取盖章后的pdf文件,上传到OSS，并保存文件ID到项目库中
     *
     * @param req             \
     * @param declaredProject \
     * @param templateName    \
     * @param notSealedFileId \
     * @return void
     * @author CMM
     * @since 2023/03/16 21:34
     */
    private Long generateSealedPdf(SealInfoDTO req, Project declaredProject, String templateName, Long notSealedFileId) {
        // 获取预审申请单文件,并转换为Base64
        File pdfFile = fileService.getById(notSealedFileId);
        InputStream fileInputStream = fileService.getFileInputStream(pdfFile);
        String pdfEncode64 = Base64.encode(fileInputStream);
        // 装配盖章接口请求参数信息
        SignReqDTO signReq = new SignReqDTO();
        signReq.setPdfEncode64(pdfEncode64);
        String originalFileName = pdfFile.getOriginalFileName();
        signReq.setFileName(originalFileName);
        Float posX = req.getPosX();
        Float posY = req.getPosY();
        signReq.setPosX(posX);
        signReq.setPosY(posY);
        String sealSn;
        if (Objects.isNull(req.getSealSn())) {
            // 获取当前登录签章用户信息
            Long userId = LoginUserUtil.getUserId();
            UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(userId);
            // 获取印章编号
            CompanySignature companySignature = companySignatureService.getOne(Wrappers.lambdaQuery(CompanySignature.class)
                    .eq(CompanySignature::getOrganizationCode, userFullInfo.getMhUnitId()));
            sealSn = companySignature.getSealSn();
        } else {
            // 区县预审时，对有上级条线主管单位并进行了盖章的情况，需要根据上次的印章编号再次盖章
            sealSn = req.getSealSn();
        }
        signReq.setSealSn(sealSn);
        String posPage = req.getPosPage();
        signReq.setPosPage(posPage);
        Integer signType = req.getSignType();
        signReq.setSignType(signType);
        // 调用盖章接口,获取盖章后返回的pdf文件字符数组
        String signPdf = IRSAPIRequest.createSignPdf(signReq);
        ByteArrayInputStream inputStream = null;
        if (Objects.nonNull(signPdf)) {
            BASE64Decoder decoder = new BASE64Decoder();
            try {
                //转码
                byte[] b = decoder.decodeBuffer(signPdf);
                for (int i = 0; i < b.length; ++i) {
                    if (b[i] < 0) {//调整异常数据
                        b[i] += 256;
                    }
                }
                inputStream = new ByteArrayInputStream(b);
            } catch (Exception e) {
                log.error("获取盖章后的pdf文件异常：", e);
            }
        }
        // 转换成MultipartFile
        MultipartFile multipartFile;
        try {
            multipartFile = new MockMultipartFile("file", originalFileName, "application/pdf", inputStream);
        } catch (IOException e) {
            throw new BizException("生成盖章pdf文件失败!");
        }
        // 上传OSS
        FileResultVO resultVo = fileService.upload(multipartFile, "default");
        // 将返回的文件ID更新到项目库中对应的项目下
        // 根据模版名称判断是预审申请单还是建设方案申请单
        if (TodoCenterConst.SealTemplate.CITY_TEMPLATE.equals(templateName) ||
                TodoCenterConst.SealTemplate.COUNTRY_TEMPLATE.equals(templateName)) {
            //放到项目申报书字段
            declaredProject.setProjectApplicationForm(BizUtils.buildFileJson(resultVo));
            declaredProject.setUpdateOn(LocalDateTime.now());
        } else if (TodoCenterConst.SealTemplate.CONSTRUCT_TEMPLATE.equals(templateName)) {
            declaredProject.setConstructFileId(resultVo.getId());
            declaredProject.setConstructFileName(resultVo.getOriginalFileName());
            declaredProject.setUpdateOn(LocalDateTime.now());
        }
        projectService.updateById(declaredProject);
        return resultVo.getId();
    }

    /**
     * 查询流程表单数据及审批的进度步骤
     *
     * @param request 请求参数
     * @return 流程进度及表单详情
     */
    public ProcessProgressDetailVO getProcessDetail(ProcessDetailReq request) {
        String employeeCode = LoginUserUtil.loginUserDetail().getEmployeeCode();
        String instanceId = request.getInstanceId();
        String nodeId = request.getNodeId();
        Long projectId = request.getProjectId();
        String taskId = request.getTaskId();
        ProcessProgressVo progressInstanceDetail = processInstanceService.getProgressInstanceDetail(nodeId, instanceId, taskId);

        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId)
                .last(BizConst.LIMIT_1));

        if (Objects.isNull(progressInstanceDetail)) {
            return null;
        }

        List<ProgressNode> progressInfo = progressInstanceDetail.getProgressInfo();
        Boolean isHighLine = Boolean.FALSE;
        if (CollUtil.isNotEmpty(progressInfo)) {
            buildUserUtils.buildUserByProcessInfo(progressInfo);
            isHighLine = checkHighLine(progressInfo, taskId, LoginUserUtil.loginUserDetail());
        }
        ProcessProgressDetailVO res = new ProcessProgressDetailVO();
        if (Objects.nonNull(projectInst)) {
            Long projectIdThis = projectInst.getProjectId();
            Project project = projectService.getById(projectIdThis);
            res.setProjectCode(project.getProjectCode());
            res.setProjectId(projectInst.getProjectId());
        } else {
            res.setProjectId(request.getProjectId());
        }
        res.setProcessProgressVo(progressInstanceDetail);
        res.setStatus(progressInstanceDetail.getStatus());
        res.setCanWithdraw(withDrawHandle.checkCanWithdraw(instanceId, progressInstanceDetail, request.getTaskId()));
        res.setIsHighLine(isHighLine);
        res.setConstructionSuggestions(checkConstructionSuggestionsByNodeId(taskId));
        passHandle.checkCanPassOrSeal(request.getInstanceId(), request.getTaskId(), employeeCode, res);
        //是不是被 驳回|退回
        res.setIsChange(todoService.isChangeRecord(projectId));
        return res;
    }

    private Boolean checkConstructionSuggestionsByNodeId(String taskId) {
        if (StringUtils.isNotBlank(taskId)) {
            List<Task> tasks = taskService.createTaskQuery()
                    .taskId(taskId)
                    .orderByTaskId().desc().list();
            if (CollUtil.isNotEmpty(tasks)) {
                return tasks.get(0).getTaskDefinitionKey()
                        .endsWith(StrPool.DASH + ProcessConstant.Field.CONSTRUCTION);
            }
        }
        return Boolean.FALSE;
    }

    /**
     * 筛选 当前登录人 是不是 上级条线单位的审批
     *
     * @param progressInfo
     * @param taskId
     * @param user
     * @return
     */
    private Boolean checkHighLine(List<ProgressNode> progressInfo, String taskId, UserInfoDetails user) {
        if (CollUtil.isEmpty(progressInfo)) {
            return Boolean.FALSE;
        }

        final Boolean[] res = {Boolean.FALSE};
        progressInfo.forEach(p -> {
            //如果是 子流程
            if (Objects.nonNull(p.getNodeType()) && NodeTypeEnum.SUB.name().equals(p.getNodeType().name())) {
                //如果不是上级条线单位 根本不用理
                if (Boolean.FALSE.equals(p.getIsHighLine())) {
                    return;
                }
                List<ProgressNode> children = p.getChildren();
                ProgressNode progressNode = children.get(0);
                String userId = progressNode.getUserId();
                if (StrUtil.isBlank(userId)) {
                    return;
                }
                UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(Long.valueOf(userId));
                if (userFullInfo != null && user.getMhUnitId().equals(userFullInfo.getMhUnitId())) {
                    //说明是此单位的人
                    //那么要去看 taskId 是不是 在这个单位内的任务
                    boolean isContainsTask = HandlerManage.checkIsContainsTask(children, taskId);
                    if (isContainsTask) {
                        res[0] = Boolean.TRUE;
                    }
                }
            }
        });
        return res[0];
    }


    /**
     * 待办中心我已处理项目列表查询
     *
     * @param param
     * @author CMM
     * @since 2023/02/03
     */
    public PageVo<ResToBeProcessedVO> idoList(ToBeProcessedReq param) {

        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        // 获取登录用户浙政钉code
        String userIdStr = String.valueOf(userId);

        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
        // 再查出项目关联的流程实例ID
        List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());
        if (CollUtil.isEmpty(projectIdList)) {
            return PageVo.empty();
        }
        List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                .in(ProjectInst::getProjectId, projectIdList)
                .orderByDesc(ProjectInst::getProjectId));
        Map<String, Project> projectInfoMap = projectInstList.stream()
                .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                        !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
        List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

        // 查出用户工作流
        TodoCenterListReqDTO req = new TodoCenterListReqDTO();
        Integer pageNumber = param.getPageNumber();
        Integer pageSize = param.getPageSize();
        req.setProcessDefId(param.getProcessDefId());
        req.setUserId(userIdStr);
        req.setEmployeeCode(userIdStr);
        req.setInstCodes(instCodes);
        List<ProcessTaskVo> iDoList = processTaskService.getUserIdoList(req);
        if (CollUtil.isEmpty(iDoList)) {
            return PageVo.empty();
        }

        List<ProcessTaskVo> userIdoList = iDoList.stream()
                .skip((long) (pageNumber - 1) * pageSize)
                .limit(pageSize)
                .collect(Collectors.toList());

        List<ResToBeProcessedVO> resVos = userIdoList.stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            Project projectInfo = projectInfoMap.get(d.getInstanceId());
            BeanUtils.copyProperties(projectInfo, res);
            res.setInstCode(d.getInstanceId());
            res.setBuildOrg(projectInfo.getBuildOrgName());
            res.setDeclaredAmount(projectInfo.getDeclareAmount());
            res.setProjectId(projectInfo.getId());
            res.setProcessStatusName(d.getStatus());
            res.setProcessLaunchTime(d.getCreateTime());
            res.setProcessHandleTime(d.getTaskEndTime());
            res.setTaskId(d.getTaskId());
            res.setNodeName(findSubProcessName(d));
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, iDoList.size());
    }

    /**
     * 导出我已处理项目列表
     *
     * @param response 返回数据流
     * @param param    请求参数
     * @author CMM
     * @since 2023/02/06
     */
    public void idoExport(HttpServletResponse response, ToBeProcessedExportReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        // 获取登录用户浙政钉code
        String userIdStr = String.valueOf(userId);

        //因为要解耦 不能把项目和工作流一起查
        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        List<ProcessTaskVo> userIdoList = Lists.newArrayList();
        Map<String, Project> projectInfoMap = Maps.newHashMap();
        if (CollUtil.isNotEmpty(projects)) {
            Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
            // 再查出项目关联的流程实例ID
            List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());

            List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                    .in(ProjectInst::getProjectId, projectIdList)
                    .orderByDesc(ProjectInst::getProjectId));
            projectInfoMap = projectInstList.stream()
                    .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                            !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                    .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
            List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

            // 查出用户工作流
            TodoCenterListReqDTO req = new TodoCenterListReqDTO();
            req.setProcessDefId(param.getProcessDefId());
            req.setUserId(userIdStr);
            req.setEmployeeCode(userIdStr);
            req.setInstCodes(instCodes);
            userIdoList = processTaskService.getUserIdoList(req);
        }

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我已处理_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我已处理_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);

        List<List<String>> exportDatas = ExcelDownUtil.getTaskExportDatas(param.getExportOptionList(), userIdoList, projectInfoMap);
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 查询我提交的项目列表
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.hz.pm.api.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/06
     */
    public PageVo<ResToBeProcessedVO> mySubmittedList(ToBeProcessedReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        // 获取登录用户浙政钉code
        String userIdStr = String.valueOf(userId);

        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
        // 再查出项目关联的流程实例ID
        List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());

        if (CollUtil.isEmpty(projectIdList)) {
            return PageVo.empty();
        }

        List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                .in(ProjectInst::getProjectId, projectIdList)
                .orderByDesc(ProjectInst::getProjectId));
        Map<String, Project> projectInfoMap = projectInstList.stream()
                .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                        !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
        List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

        // 查出用户工作流
        TodoCenterListReqDTO req = new TodoCenterListReqDTO();
        Integer pageNumber = param.getPageNumber();
        Integer pageSize = param.getPageSize();
        req.setProcessDefId(param.getProcessDefId());
        req.setUserId(userIdStr);
        req.setEmployeeCode(userIdStr);
        req.setInstCodes(instCodes);
        List<ProcessInstanceVo> submittedList = processInstanceService.getUserSubmittedList(req);

        if (CollUtil.isEmpty(submittedList)) {
            return PageVo.empty();
        }

        List<ProcessInstanceVo> userSubmittedList = submittedList.stream()
                .skip((long) (pageNumber - 1) * pageSize)
                .limit(pageSize)
                .collect(Collectors.toList());

        List<ResToBeProcessedVO> resVos = userSubmittedList.stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            Project project = projectInfoMap.get(d.getInstanceId());
            BeanUtils.copyProperties(project, res);
            res.setInstCode(d.getInstanceId());
            res.setBuildOrg(project.getBuildOrgName());
            res.setDeclaredAmount(project.getDeclareAmount());
            res.setProjectId(project.getId());
            res.setProcessStatusName(d.getStatus());
            res.setProcessLaunchTime(d.getStartTime());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, submittedList.size());
    }

    /**
     * 待办中心-我发起的项目列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/11
     */
    public void exportMySubmittedList(HttpServletResponse response, ToBeProcessedExportReq param) {

        // 获取登录用户ID
        long userId = LoginUserUtil.getUserId();
        // 获取登录用户浙政钉code
        String userIdStr = String.valueOf(userId);

        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);

        List<ProcessInstanceVo> userSubmittedList = Lists.newArrayList();
        Map<String, Project> projectInfoMap = Maps.newHashMap();

        if (CollUtil.isNotEmpty(projects)) {
            Map<Long, Project> projectsMap = CollUtils.listToMap(projects, Project::getId);
            // 再查出项目关联的流程实例ID
            List<Long> projectIdList = CollUtils.fieldList(projects, Project::getId);
            List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                    .in(ProjectInst::getProjectId, projectIdList)
                    .orderByDesc(ProjectInst::getProjectId));
            projectInfoMap = projectInstList.stream()
                    .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                            !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                    .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
            List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

            // 查出用户工作流
            TodoCenterListReqDTO req = new TodoCenterListReqDTO();
            req.setProcessDefId(param.getProcessDefId());
            req.setUserId(userIdStr);
            req.setEmployeeCode(userIdStr);
            req.setInstCodes(instCodes);
            userSubmittedList = processInstanceService.getUserSubmittedList(req);
        }

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我发起的_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_我发起的_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);
        // 表体行数据集合
        List<List<String>> exportDatas = ExcelDownUtil.getInstExportDatas(param.getExportOptionList(), userSubmittedList, projectInfoMap);
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 待办中心-抄送我的项目列表查询
     *
     * @param param
     * @return com.ningdatech.basic.model.PageVo<com.hz.pm.api.todocenter.model.dto.res.ResToBeProcessedDTO>
     * @author CMM
     * @since 2023/02/11
     */
    public PageVo<ResToBeProcessedVO> ccmeList(ToBeProcessedReq param) {

        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        // 获取登录用户全量信息
        String userIdStr = String.valueOf(userId);
        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        if (CollUtil.isEmpty(projects)) {
            return PageVo.empty();
        }

        Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
        // 再查出项目关联的流程实例ID
        List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());
        List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                .in(ProjectInst::getProjectId, projectIdList)
                .orderByDesc(ProjectInst::getProjectId));
        Map<String, Project> projectInfoMap = projectInstList.stream()
                .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                        !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
        List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

        // 查出用户工作流
        TodoCenterListReqDTO req = new TodoCenterListReqDTO();
        Integer pageNumber = param.getPageNumber();
        Integer pageSize = param.getPageSize();
        req.setProcessDefId(param.getProcessDefId());
        req.setUserId(userIdStr);
        req.setEmployeeCode(userIdStr);
        req.setInstCodes(instCodes);
        List<ProcessInstanceVo> ccMeList = processInstanceService.getCcMeList(req);

        if (CollUtil.isEmpty(ccMeList)) {
            return PageVo.empty();
        }

        List<ProcessInstanceVo> ccMeInstList = ccMeList.stream()
                .skip((long) (pageNumber - 1) * pageSize)
                .limit(pageSize)
                .collect(Collectors.toList());

        List<ResToBeProcessedVO> resVos = ccMeInstList.stream().map(d -> {
            ResToBeProcessedVO res = new ResToBeProcessedVO();
            Project project = projectInfoMap.get(d.getInstanceId());
            BeanUtils.copyProperties(project, res);
            res.setInstCode(d.getInstanceId());
            res.setNodeId(d.getNodeId());
            res.setProjectId(project.getId());
            res.setBuildOrg(project.getBuildOrgName());
            res.setDeclaredAmount(project.getDeclareAmount());
            res.setProcessStatusName(d.getStatus());
            res.setProcessLaunchTime(d.getStartTime());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(resVos, ccMeList.size());
    }

    /**
     * 待办中心-抄送我的项目列表导出
     *
     * @param response
     * @param param
     * @return void
     * @author CMM
     * @since 2023/02/11
     */
    public void exportCcMeProjectList(HttpServletResponse response, ToBeProcessedExportReq param) {

        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        String userIdStr = String.valueOf(userId);
        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);

        List<ProcessInstanceVo> ccMeList = Lists.newArrayList();
        Map<String, Project> projectInfoMap = Maps.newHashMap();
        if (CollUtil.isNotEmpty(projects)) {
            Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
            // 再查出项目关联的流程实例ID
            List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());
            List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                    .in(ProjectInst::getProjectId, projectIdList)
                    .orderByDesc(ProjectInst::getProjectId));
            projectInfoMap = projectInstList.stream()
                    .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                            !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                    .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
            List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());

            // 分页查出用户工作流
            TodoCenterListReqDTO req = new TodoCenterListReqDTO();
            req.setProcessDefId(param.getProcessDefId());
            req.setUserId(userIdStr);
            req.setEmployeeCode(userIdStr);
            req.setInstCodes(instCodes);
            ccMeList = processInstanceService.getCcMeList(req);
        }

        ExcelExportWriter excelExportWriter = new ExcelExportWriter();

        String fileName = null;
        if (IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_抄送我的_增补项目列表";
        } else if (IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(param.getIsTemporaryAugment())) {
            fileName = "待办中心_抄送我的_非增补项目列表";
        }
        excelExportWriter.setFileName(fileName);

        List<String> sheetsNames = new ArrayList<>();
        sheetsNames.add(fileName);
        // 表体行数据集合
        List<List<String>> exportDatas = ExcelDownUtil.getInstExportDatas(param.getExportOptionList(), ccMeList, projectInfoMap);
        // sheet列表集合
        List<List<List<String>>> sheets = new ArrayList<>();
        sheets.add(exportDatas);
        excelExportWriter.setHeads(sheets);
        excelExportWriter.setDatas(exportDatas);
        excelExportWriter.setSheets(sheetsNames);
        excelExportWriter.setHeadPropertyClass(List.class);
        excelExportWriter.setHeadPropertyFlag(Boolean.FALSE);
        ExcelDownUtil.excelExportFile(response, excelExportWriter);

    }

    /**
     * 被退回项目内容调整并重新通过
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String adjustmentAndHandle(AdjustHandleDTO dto) {
        ProjectDTO projectDto = dto.getProjectInfo();
        Project projectInfo = projectService.getById(projectDto.getId());

        Assert.notNull(projectInfo, "调整失败 此项目不存在!");

        //项目名称去重
        //项目名称去重
        if (StringUtils.isNotBlank(projectDto.getProjectName()) &&
                !projectDto.getProjectName().equals(projectInfo.getProjectName())) {
            defaultDeclaredProjectManage.checkDuplication(projectDto);
        }
        projectDto.setProjectCode(projectInfo.getProjectCode());
        defaultDeclaredProjectManage.checkAmount(projectDto);

        //修改项目内容
        if (!modifyProject(projectDto, projectInfo)) {
            throw new BusinessException("调整项目失败!");
        }

        //设置 节点的状态 为null 代表继续审核
        List<HistoricVariableInstance> approves = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(dto.getInstanceId())
                .variableNameLike("approve_%")
                .list();
        //删除 退回的评论和意见
        handlerManage.deleteBackComments(approves);

        // 更新当前流程状态为审核中
        processTaskService.updateProInstStatus(dto.getInstanceId());

        return dto.getInstanceId();
    }

    /**
     * 要改成 生成新版本号
     *
     * @param projectDto
     * @return
     */
    private Boolean modifyProject(ProjectDTO projectDto, Project oldProject) {
        //先修改项目信息
        Project project = new Project();
        Assert.notNull(oldProject, "项目不存在!");
        BeanUtil.copyProperties(oldProject, project, CopyOptions.create()
                .setIgnoreError(Boolean.TRUE).setIgnoreNullValue(Boolean.TRUE));
        BeanUtil.copyProperties(projectDto, project, CopyOptions.create()
                .setIgnoreError(Boolean.TRUE).setIgnoreNullValue(Boolean.TRUE));
        project.setVersion(oldProject.getVersion() + 1);
        project.setId(null);
        project.setCreateOn(LocalDateTime.now());
        project.setUpdateOn(LocalDateTime.now());
        projectService.save(project);

        oldProject.setIsBackReject(Boolean.TRUE);
        projectService.updateById(oldProject);

        ProjectInst oldInst = projectInstService.getOne(Wrappers.lambdaUpdate(ProjectInst.class)
                .eq(ProjectInst::getProjectId, oldProject.getId())
                .eq(ProjectInst::getInstCode, oldProject.getInstCode()));

        String instCode = oldProject.getInstCode();
        oldInst.setInstCode("EMPTY");
        projectInstService.updateById(oldInst);

        ProjectInst newPi = new ProjectInst();
        newPi.setProjectId(project.getId());
        newPi.setInstType(oldInst.getInstType());
        newPi.setInstCode(instCode);
        newPi.setCreatOn(LocalDateTime.now());
        newPi.setUpdateOn(LocalDateTime.now());
        projectInstService.save(newPi);

        projectService.reverseNewest(project.getProjectCode(), project.getId());

        //再修改应用信息
        boolean isApp = Objects.nonNull(projectDto.getIncludeApplication()) && 1 == projectDto.getIncludeApplication()
                ? Boolean.TRUE : Boolean.FALSE;
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class).eq(ProjectInst::getInstCode, instCode));
        Integer instType = projectInst.getInstType();

        //批量删除
        List<ProjectApplication> applicationList = projectApplicationService.list(Wrappers.lambdaQuery(ProjectApplication.class)
                .eq(ProjectApplication::getProjectCode, project.getProjectCode())
                .eq(ProjectApplication::getProjectVersion, project.getVersion())
                .eq(ProjectApplication::getIsConstruct, Boolean.TRUE));
        if (CollUtil.isNotEmpty(applicationList)) {
            projectApplicationService.removeBatchByIds(applicationList);
        }

        List<ProjectApplicationDTO> applications = projectDto.getApplicationList();
        if (isApp && CollUtil.isNotEmpty(applications)) {
            // 添加应用关联的核心业务
            for (ProjectApplicationDTO application : applications) {
                ProjectApplication projectApplication = new ProjectApplication();
                BeanUtils.copyProperties(application, projectApplication);
                projectApplication.setId(null);
                projectApplication.setProjectId(project.getId());
                projectApplication.setProjectCode(project.getProjectCode());
                projectApplication.setProjectVersion(project.getVersion());
                if (!InstTypeEnum.UNIT_INNER_AUDIT.getCode().equals(instType) &&
                        !InstTypeEnum.PRELIMINARY_PREVIEW.getCode().equals(instType) &&
                        !InstTypeEnum.DEPT_UNITED_REVIEW.getCode().equals(instType)) {
                    projectApplication.setIsConstruct(Boolean.TRUE);
                }
                projectApplication.setProjectVersion(project.getVersion());
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 获取未盖章的预审申请单/建设方案申请单pdf文件
     *
     * @param req
     * @return java.lang.Long
     * @author CMM
     * @since 2023/03/16 22:46
     */
    public NotSealedInfoVO getNotSealedPdf(SealInfoDTO req) {
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        Long projectId = req.getProjectId();
        String auditOpinion = req.getAuditOpinion();
        String auditDate = NdDateUtils.format(LocalDateTime.now(), NdDateUtils.DEFAULT_DATE_TIME_FORMAT);
        NotSealedInfoVO notSealedInfoVo = new NotSealedInfoVO();
        String mhUnitId = user.getMhUnitId().toString();
        // 判断项目盖章审批类型
        Project project = projectService.getById(projectId);
        String buildOrgCode = project.getBuildOrgCode();
        String instanceId = project.getInstCode();
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId));
        Integer instType = projectInst.getInstType();
        // 如果为预审申报审批
        if (InstTypeEnum.PRELIMINARY_PREVIEW.getCode().equals(instType)) {
            // 再根据项目所属区域判断是市级预审还是区县预审
            Long pretrialFileId = project.getPretrialFileId();
            // 获取项目申报时是否选择了主管单位和上级条线主管单位
            // 主管单位信息
            Integer isSuperOrg = project.getIsSuperOrg();
            String superOrgCode = project.getSuperOrgCode();
            // 上级条线主管单位信息
            // 如果项目关联的预审文件ID不为空，说明已经提交过审核意见，直接返回文件ID
            if (Boolean.TRUE.equals(getCityNotSealedInfoVO(pretrialFileId, notSealedInfoVo))) {
                return notSealedInfoVo;
            }
            String cityTemplateName = TodoCenterConst.SealTemplate.CITY_TEMPLATE;
            // 设置pdf模板参数
            JSONObject cityParamsMap = assemblyPdfParams(project, cityTemplateName);
            // 盖章审核为主管单位审核
            if (CommonEnum.YES.getCode().equals(isSuperOrg)) {
                // 有主管单位信息且登录用户所在单位是主管单位
                checkEmpPosUnitCode(mhUnitId, auditOpinion, auditDate, superOrgCode, cityParamsMap);
            } else if (CommonEnum.NO.getCode().equals(isSuperOrg)) {
                // 没有选主管单位，默认为项目建设单位且登录用户所在单位为项目建设单位
                checkEmpPosUnitCode(mhUnitId, auditOpinion, auditDate, buildOrgCode, cityParamsMap);
            }
            Long cityPdfId = generateNotSealedPdf(projectId, cityTemplateName, cityParamsMap);
            notSealedInfoVo.setFileId(cityPdfId);
            checkAuditOpinion(auditOpinion, notSealedInfoVo);
            return notSealedInfoVo;
        }
        // 如果为建设方案申报审批
        else if (InstTypeEnum.CONSTRUCTION_PLAN_REVIEW.getCode().equals(instType)) {
            Long constructFileId = project.getConstructFileId();
            // 如果项目关联的建设方案文件ID不为空，说明已经提交过审核意见，直接返回文件ID
            if (Boolean.TRUE.equals(getCityNotSealedInfoVO(constructFileId, notSealedInfoVo))) {
                return notSealedInfoVo;
            }
            // 设置pdf模板参数
            String constructTemplateName = TodoCenterConst.SealTemplate.CONSTRUCT_TEMPLATE;
            JSONObject constructParamsMap = assemblyPdfParams(project, constructTemplateName);
            // 审核为指定大数据局盖章
            // 获取流程配置的指定单位信息
            constructParamsMap.put("bigDataBureauOpinion", auditOpinion);
            constructParamsMap.put("bigDataBureauAuditDate", auditDate);
            Long constructNotFileId = generateNotSealedPdf(projectId, constructTemplateName, constructParamsMap);
            notSealedInfoVo.setFileId(constructNotFileId);
            checkAuditOpinion(auditOpinion, notSealedInfoVo);
            return notSealedInfoVo;
        }
        return notSealedInfoVo;
    }

    private void checkAuditOpinion(String auditOpinion, NotSealedInfoVO notSealedInfoVo) {
        if (StringUtils.isNotBlank(auditOpinion)) {
            notSealedInfoVo.setIsCommit(Boolean.TRUE);
        }
    }

    private Boolean getCityNotSealedInfoVO(Long fileId, NotSealedInfoVO notSealedInfoVo) {
        if (Objects.nonNull(fileId)) {
            notSealedInfoVo.setFileId(fileId);
            notSealedInfoVo.setIsCommit(Boolean.TRUE);
            return true;
        }
        return false;
    }

    private void checkEmpPosUnitCode(String empPosUnitCode, String auditOpinion, String auditDate, String orgCode, JSONObject cityParamsMap) {
        if (empPosUnitCode.equals(orgCode)) {
            cityParamsMap.put("superOrgOpinion", auditOpinion);
            cityParamsMap.put("superOrgAuditDate", auditDate);
        } else {
            cityParamsMap.put("superOrgOpinion", null);
            cityParamsMap.put("superOrgAuditDate", null);
        }
    }

    private Long generateNotSealedPdf(Long projectId, String templateName, JSONObject paramsMap) {
        // 获取本地目录的pdf模板
        String fileName;
        if (templateName.contains(StrPool.DASH)) {
            int index = templateName.indexOf(StrPool.DASH);
            fileName = templateName.substring(index + 1);
        } else {
            fileName = templateName;
        }
        InputStream htmlInputStream =
                this.getClass().getClassLoader().getResourceAsStream("template" + java.io.File.separator + templateName + ".html");
        // 生成pdf字节数组
        byte[] pdf = pdfUtils.generatePdf(htmlInputStream, paramsMap);

        // 转换成MultipartFile
        MultipartFile multipartFile = new MockMultipartFile("file", fileName + ".pdf", "application/pdf", pdf);

        // 上传OSS
        FileResultVO resultVo = fileService.upload(multipartFile, "default");

        // 将带审核意见的返回的文件ID更新到项目库中对应的项目下
        Project declaredProject = projectService.getById(projectId);
        // 根据模版名称判断是预审申请单还是建设方案申请单
        String superOrgOpinion = paramsMap.getString("superOrgOpinion");
        String higherOrgOpinion = paramsMap.getString("higherOrgOpinion");
        if (StringUtils.isNotBlank(superOrgOpinion) || StringUtils.isNotBlank(higherOrgOpinion)) {
            if (TodoCenterConst.SealTemplate.CITY_TEMPLATE.equals(templateName) ||
                    TodoCenterConst.SealTemplate.COUNTRY_TEMPLATE.equals(templateName)) {
                //放到项目申报书字段
                declaredProject.setProjectApplicationForm(BizUtils.buildFileJson(resultVo));
                declaredProject.setUpdateOn(LocalDateTime.now());
            } else if (TodoCenterConst.SealTemplate.CONSTRUCT_TEMPLATE.equals(templateName)) {
                declaredProject.setConstructFileId(resultVo.getId());
                declaredProject.setConstructFileName(resultVo.getOriginalFileName());
                declaredProject.setUpdateOn(LocalDateTime.now());
            }
            projectService.updateById(declaredProject);
        }
        try {
            if (null != htmlInputStream) {
                htmlInputStream.close();
            }

            // 删除输出文件夹下的临时文件
            java.io.File directory = new java.io.File("");
            //pdf输出路径
            String filePath = directory.getAbsolutePath() + java.io.File.separator + "fileout";
            java.io.File directoryPath = new java.io.File(filePath);
            FileUtils.cleanDirectory(directoryPath);
        } catch (IOException e) {
            log.error(e.getMessage());
        }

        // 返回文件id供前端下载
        return resultVo.getId();
    }

    /**
     * 装配pdf模板参数
     *
     * @param project
     * @param templateName
     * @return com.alibaba.fastjson.JSONObject
     * @author CMM
     * @since 2023/03/16 22:28
     */
    private JSONObject assemblyPdfParams(Project project, String templateName) {
        // 获取本地目录的pdf模板
        String fileName;
        if (templateName.contains(StrPool.DASH)) {
            int index = templateName.indexOf(StrPool.DASH);
            fileName = templateName.substring(index + 1);
        } else {
            fileName = templateName;
        }
        Long projectId = project.getId();
        // 获取表单数据
        PdfGenerateDTO pdfGenerateDTO = new PdfGenerateDTO();
        BeanUtils.copyProperties(project, pdfGenerateDTO);

        JSONObject paramsMap = JSONObject.parseObject(JSONObject.toJSONString(pdfGenerateDTO));
        // 设置title
        String title = "【" + project.getProjectName() + "】" + fileName;
        paramsMap.put("title", title);
        // 获取预审申报的开始时间
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getProjectId, projectId)
                .eq(ProjectInst::getInstType, InstTypeEnum.PRELIMINARY_PREVIEW.getCode())
                .orderByDesc(ProjectInst::getCreatOn)
                .last("limit 1"));
        LocalDateTime time = null;
        if (Objects.nonNull(projectInst)) {
            time = projectInst.getCreatOn();
            paramsMap.put("time", NdDateUtils.format(time, "yyyy-MM-dd HH:mm"));
        } else {
            paramsMap.put("time", null);
        }
        // 获取是否临时增补项目
        String isTemporaryAugment = IsOrNotEnum.getDescByCode(project.getIsTemporaryAugment());
        paramsMap.put("isTemporaryAugment", isTemporaryAugment);
        // 获取项目类型
        String projectType = project.getProjectType();
        paramsMap.put("projectType", ProjectTypeNewEnum.getDesc(projectType));
        // 获取是否首次新建
        String isFirst = IsOrNotEnum.getDescByCode(project.getIsFirst());
        paramsMap.put("isFirst", isFirst);
        // 获取是否一地创新全省共享项目
        Boolean isInnovateWholeProvinceShare = project.getIsInnovateWholeProvinceShare();
        paramsMap.put("isInnovateWholeProvinceShare", Boolean.TRUE.equals(isInnovateWholeProvinceShare) ?
                IsOrNotEnum.IS.getDesc() : IsOrNotEnum.NOT.getDesc());
        // 获取项目建设起始时间
        String beginTime = project.getBeginTime();
        String endTime = project.getEndTime();
        String beginAndEndTime = beginTime + StrUtil.DASHED + endTime;
        paramsMap.put("beginAndEndTime", beginAndEndTime);
        // 获取四大体系
        String fourSystem = FourSystemEnum.getDescByCode(project.getFourSystems());
        paramsMap.put("fourSystems", fourSystem);
        // 获取是否数字化改革项目
        String isDigitalReform = IsOrNotEnum.getDescByCode(project.getIsDigitalReform());
        paramsMap.put("isDigitalReform", isDigitalReform);
        if (IsOrNotEnum.NOT.getDesc().equals(isDigitalReform)) {
            paramsMap.put("bizDomain", null);
        } else {
            paramsMap.put("bizDomain", BizDomainEnum.getDescByCode(project.getBizDomain()));
        }
        // 获取是否上云
        String isCloud = IsOrNotEnum.getDescByCode(project.getIsCloud());
        paramsMap.put("isCloud", isCloud);
        if (IsOrNotEnum.NOT.getDesc().equals(isCloud)) {
            paramsMap.put("cloudType", null);
        } else {
            String cloudType = project.getCloudType();
            String cloudTypeName;
            // 云类型为多选
            if (cloudType.contains(StrPool.COMMA)) {
                cloudTypeName = Arrays.stream(cloudType.split(StrPool.COMMA))
                        .map(s -> CloudTypeEnum.getDescByCode(Integer.valueOf(s)))
                        .collect(Collectors.joining(StrPool.COMMA));

            } else {
                cloudTypeName = CloudTypeEnum.getDescByCode(Integer.valueOf(cloudType));
            }
            paramsMap.put("cloudType", cloudTypeName);
        }
        // 获取本年计划投资金额
        BigDecimal yearPlanInvest = project.getAnnualPlanAmount();
        paramsMap.put("yearPlanInvest", yearPlanInvest);
        // 根据地区编码获取当地大数据局(中心)的名称
        paramsMap.put("bigDataBureauName", null);
        // 备注模块如果为空
        if (Objects.isNull(pdfGenerateDTO.getProjectRemarks())) {
            paramsMap.put("projectRemarks", null);
        }

        // 获取立项依据
        String buildBasis = project.getBuildBasis();
        StringBuilder builder = new StringBuilder();
        List<JSONObject> fileArray = JSON.parseArray(buildBasis, JSONObject.class);
        for (JSONObject jsonObject : fileArray) {
            String titleStr = jsonObject.getString(CommonConst.TITLE);
            builder.append(CommonConst.ITEM_BASED);
            builder.append(titleStr);
            builder.append(StrPool.SPACE);
            String fileNameStr = jsonObject.getString(CommonConst.FILE_NAME_STR);
            builder.append(CommonConst.ITEM_BASED_FILE_NAME);
            builder.append(fileNameStr);
            builder.append(StrPool.SPACE);
            String fileInfo = jsonObject.getString(CommonConst.FILE_LIST);
            List<JSONObject> fileInfoArray = JSON.parseArray(fileInfo, JSONObject.class);
            List<String> nameList = CollUtils.fieldList(fileInfoArray, w -> w.getString(CommonConst.FILE_NAME));
            String appendixStr = String.join(StrPool.COMMA, nameList);
            builder.append(CommonConst.APPENDIX);
            builder.append(appendixStr);
            builder.append(StrPool.SEMICOLON);
            builder.append(StrPool.NEWLINE);
        }
        String buildBasisList = builder.toString();
        paramsMap.put("buildBasisList", buildBasisList);

        return paramsMap;
    }

    public Long getSealedPdf(SealInfoDTO req) {
        Long projectId = req.getProjectId();
        Long notSealedFileId = req.getNotSealedFileId();
        Project declaredProject = projectService.getById(projectId);
        String instanceId = declaredProject.getInstCode();
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId));
        Integer instType = projectInst.getInstType();
        // 如果为预审申报审批
        if (InstTypeEnum.PRELIMINARY_PREVIEW.getCode().equals(instType)) {
            // 再根据项目所属区域判断是市级预审还是区县预审
            String areaCode = declaredProject.getAreaCode();
            String areaName = declaredProject.getArea();
            Region region = regionService.getOne(Wrappers.lambdaQuery(Region.class)
                    .eq(Region::getRegionCode, areaCode)
                    .eq(Region::getRegionName, areaName));
            if (Objects.isNull(region)) {
                throw new BizException("项目所属区域不存在!");
            }
            Integer regionLevel = region.getRegionLevel();
            if (CommonEnum.LS_SBJ.getDesc().equals(areaName)) {
                regionLevel = RegionConst.RL_CITY;
            }
            switch (regionLevel) {
                // 市级预审(市本级)
                case RegionConst.RL_CITY:
                    String cityTemplateName = TodoCenterConst.SealTemplate.CITY_TEMPLATE;
                    return generateSealedPdf(req, declaredProject, cityTemplateName, notSealedFileId);
                // 区县预审
                case RegionConst.RL_COUNTY:
                    String countryTemplateName = TodoCenterConst.SealTemplate.COUNTRY_TEMPLATE;
                    return generateSealedPdf(req, declaredProject, countryTemplateName, notSealedFileId);
                default:
                    throw new IllegalStateException("Unexpected value: " + regionLevel);
            }
        }
        // 如果为建设方案申报审批
        else if (InstTypeEnum.CONSTRUCTION_PLAN_REVIEW.getCode().equals(instType)) {
            // 设置pdf模板参数
            String constructTemplateName = TodoCenterConst.SealTemplate.CONSTRUCT_TEMPLATE;
            return generateSealedPdf(req, declaredProject, constructTemplateName, notSealedFileId);
        }
        return null;
    }

    //获取当前登录人的 待办中心统计数据
    public TodoCenterStatisticsVO todoCenterStatistics(Long userId) {
        //为了查询效率 还是自己去写统计接口
        TodoCenterStatisticsPO statistics = statisticsService.getStatistics(userId);
        return BeanUtil.copyProperties(statistics, TodoCenterStatisticsVO.class);
    }

    public TodoNumVO getTodoNums(ToBeProcessedReq param) {
        // 获取登录用户ID
        Long userId = LoginUserUtil.getUserId();
        String userIdStr = String.valueOf(userId);
        // 获取登录用户全量信息
        // 获取员工浙政钉code
        TodoNumVO todoNumVo = new TodoNumVO();

        // 查出项目库项目
        ProjectListReq projectListReq = new ProjectListReq();
        BeanUtils.copyProperties(param, projectListReq);
        List<Project> projects = projectLibManage.projectList(projectListReq);
        if (CollUtil.isEmpty(projects)) {
            return todoNumVo;
        }
        // 再查出项目关联的流程实例ID
        List<Long> projectIdList = projects.stream().map(Project::getId).collect(Collectors.toList());

        List<ProjectInst> projectInstList = projectInstService.list(Wrappers.lambdaQuery(ProjectInst.class)
                .in(ProjectInst::getProjectId, projectIdList)
                .orderByDesc(ProjectInst::getProjectId));
        List<String> instCodes = projectInstList.stream().map(ProjectInst::getInstCode).collect(Collectors.toList());
        // 查出用户工作流
        TodoCenterListReqDTO req = new TodoCenterListReqDTO();
        req.setInstCodes(instCodes);
        req.setProcessDefId(param.getProcessDefId());
        req.setUserId(userIdStr);
        req.setEmployeeCode(userIdStr);
        //有待办节点的(不包含退回)
        List<ProcessTaskVo> todoList = processTaskService.getUserTodoList(req);
        //有退回待办的
        List<ProcessTaskVo> backList = processTaskService.getBackTodoList(req);
        //合并 并且排序
        List<ProcessTaskVo> list = Stream.concat(todoList.stream(), backList.stream())
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(ProcessTaskVo::getTaskCreateTime)
                        .reversed())
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(list)) {
            return todoNumVo;
        }

        Map<Long, Project> projectsMap = projects.stream().collect(Collectors.toMap(Project::getId, v -> v));
        Map<String, Project> projectInfoMap = projectInstList.stream()
                .filter(p -> StringUtils.isNotBlank(p.getInstCode()) &&
                        !TodoCenterConst.Declared.NULL_INST_CODE.equals(p.getInstCode()))
                .collect(Collectors.toMap(ProjectInst::getInstCode, p -> projectsMap.get(p.getProjectId())));
        // 关联项目信息
        List<Project> projectList = list.stream()
                .map(d -> projectInfoMap.get(d.getInstanceId()))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(projectList)) {
            todoNumVo.setTotalNum(projectList.size());
        }
        // 筛选出增补项目和非增补项目
        List<Project> appendProjects = projectList.stream()
                .filter(p -> IsAppendProjectEnum.APPEND_PROJECT.getCode().equals(p.getIsTemporaryAugment()))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(appendProjects)) {
            todoNumVo.setAppendNum(appendProjects.size());
        }
        List<Project> notAppendProjects = projectList.stream()
                .filter(p -> IsAppendProjectEnum.NOT_APPEND_PROJECT.getCode().equals(p.getIsTemporaryAugment()))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(notAppendProjects)) {
            todoNumVo.setNotAppendNum(notAppendProjects.size());
        }
        return todoNumVo;
    }

    /**
     * 寻找下一个审核task
     *
     * @param instance
     * @return
     */
    private String findNextTaskId(HistoricProcessInstance instance, String employeeCode, String nodeId, String taskId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
        Map<String, String> subNodeMap = ProcessTaskUtils.getSubNodeMap(bpmnModel);
        String subProcessId = subNodeMap.get(nodeId);
        Map<String, List<String>> allSubNodeMap = ProcessTaskUtils.loadSubNodeMap(bpmnModel);
        List<String> nodeIds = allSubNodeMap.get(subProcessId);

        List<Task> tasks = taskService.createTaskQuery()
                .processInstanceId(instance.getId())
                .taskAssignee(employeeCode)
                .taskDefinitionKeys(nodeIds)
                .orderByTaskCreateTime()
                .asc()
                .list();
        if (CollUtil.isEmpty(tasks)) {
            //如果此子单位为空 那就直接再查下 他的其它任务
            List<Task> currentTasks = taskService.createTaskQuery()
                    .processInstanceId(instance.getId())
                    .taskAssignee(employeeCode)
                    .orderByTaskCreateTime()
                    .asc()
                    .list();
            if (CollUtil.isNotEmpty(currentTasks)) {
                return currentTasks.get(0).getId();
            }
            return taskId;
        }
        return tasks.get(0).getId();
    }

    /**
     * 取当前用户的下一个taskId
     *
     * @param instanceId
     * @return
     */
    public String todoTaskId(String instanceId) {
        String employeeCode = LoginUserUtil.loginUserDetail().getEmployeeCode();
        List<Task> tasks = taskService.createTaskQuery()
                .processInstanceId(instanceId)
                .taskAssignee(employeeCode)
                .orderByTaskCreateTime()
                .asc()
                .list();
        if (CollUtil.isEmpty(tasks)) {
            return null;
        }
        return tasks.get(0).getId();
    }

    public Object saveAuditOpinion(ReqAuditOpinionSaveDTO param) {
        // 获取登录用户ID
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        //前置判断前端传的taskId是否存在不存在的情况 有可能是 或签被别人审批掉了
        Task task = taskService.createTaskQuery().taskId(param.getTaskId()).active().singleResult();
        if (ObjectUtil.isNull(task)) {
            return TodoCenterConst.Handler.TASKID_ERROR;
        }
        return processTaskService.saveAuditOpinion(param, user.getUserIdStr());
    }
}
