package com.horizon.wf.action.base;

import com.alibaba.fastjson.JSONArray;
import com.horizon.utils.json.JsonUtil;
import com.horizon.wf.action.common.ActionCommon;
import com.horizon.wf.action.common.MemoryOperator;
import com.horizon.wf.bean.WorkParaBean;
import com.horizon.wf.bean.WorkResultBean;
import com.horizon.wf.config.HZResourceBundle;
import com.horizon.wf.config.PubInfo;
import com.horizon.wf.core.node.INextNode;
import com.horizon.wf.core.node.ITaskNode;
import com.horizon.wf.core.runtime.RunningData;
import com.horizon.wf.core.track.json.impl.ITrackJson;
import com.horizon.wf.core.work.data.InitData;
import com.horizon.wf.definition.pub.IFlowInfo;
import com.horizon.wf.definition.pub.IFlowNode;
import com.horizon.wf.definition.pub.node.INodeAuthUser;
import com.horizon.wf.definition.tools.NodeTypeEnum;
import com.horizon.wf.entity.db.*;
import com.horizon.wf.entity.user.INodeUser;
import com.horizon.wf.entity.user.IRunUser;
import com.horizon.wf.entity.user.impl.AuthUserImpl;
import com.horizon.wf.expand.impl.IHZCalendar;
import com.horizon.wf.expand.impl.IOrganizationInterface;
import com.horizon.wf.global.*;
import com.horizon.wf.plugins.PluginsUtil;
import com.horizon.wf.pool.XMLTodoPub;
import com.horizon.wf.rule.event.EventUtil;
import com.horizon.wf.tools.DateUtil;
import com.horizon.wf.tools.CStrUtil;
import com.horizon.wf.tools.StringUtil;
import com.horizon.wf.util.PushWorkflowToPortal;

import java.util.*;

/**
 * 流程实例流转操作用到的公用方法和属性变量.
 *
 * @author liys
 * @version 7.2.2
 */
public abstract class BaseAction {
    /**
     * 操作中使用到的一些通用方法类
     */
    protected ActionCommon ac = StaticFunExtend.getActionCommon();
    /**
     * 存放操作中需要的办理人
     */
    protected Map<String, String> selectedUsers = new LinkedHashMap<String, String>();
    /**
     * 存放操作中需要的办理人中文显示名称
     */
    protected Map<String, String> selectedUserNames = new LinkedHashMap<String, String>();
    /**
     * 存放临时人员,比如减签时,存放当前现有的办理人
     */
    protected Map<String, String> tmpUsers = new LinkedHashMap<String, String>();
    /**
     * 操作执行时传入的参数对象
     */
    protected WorkParaBean paraBean;
    /**
     * 操作结果返回参数存放对象
     */
    protected WorkResultBean resultBean = new WorkResultBean();
    /**
     * 标记是否新建流程实例
     */
    protected int isnewdoc = -1;
    /**
     * 存放当前操作需要发送的消息对象,包含消息的变更对象
     */
    protected List<DBTodo> todoLst = new ArrayList<DBTodo>();

    /**
     * 运行期的环境对象,包含当前实例的各项参数
     */
    protected RunningData runningdata;
    /**
     * 当前操作执行的结果编号.编号查看StaticVarExtends类
     */
    protected int result = StaticVarExtend.F_STATUS_Success;
    /**
     * 当前操作执行结果
     */
    protected String msg = "";
    /**
     * 是否允许写库操作
     */
    protected boolean isNotSaveToDB = false;
    /**
     * 语言文件对象
     */
    protected PubInfo pubInfo;
    /**
     * 选择的下一节点字符串
     */
    protected String nextNodeId = "";
    /**
     * 下一节点列表
     */
    protected List<String> nextNodeList = new ArrayList<String>();
    /**
     * 记录操作内容，用于日志记录
     */
    protected StringBuilder memoBuffer = new StringBuilder(5000);
    /**
     * 数据源id
     */
    protected String identifier;
    /**
     * 是否需要获取memo内容
     */
    private boolean isGetMemo = false;

    /**
     * 各个操作需要执行的具体内容
     * @param curTaskNode 当前节点运行对象
     * @return 成功返回true,失败返回false
     */
    public abstract boolean execute(ITaskNode curTaskNode);
    /**
     * 租户id
     */
    private String tenantid;

    /**
     * 组织接口对象
     */
    private IOrganizationInterface org;

    /**
     * 接口调用操作时的入口方法
     * 各操作都需要的一些处理
     * @return 成功返回true,失败返回false
     */
    public final boolean doAction() {
        runningdata.putMsgToConsole("[msg]operator start.");
        // 获取语言包对象
        pubInfo = PubInfo.getPubInfo(runningdata.getLangInfo());

        ITaskNode curTaskNode = runningdata.getCurTaskNode();
        resultBean.setRunningdata(runningdata);
        // 获取传入的的参数对象
        Object obj = runningdata.getWorkParaBean();
        if (null == obj) {
            runningdata.setBackMsg(pubInfo.getInfo("Action_Msg0001", "获取参数对象失败！"));
            runningdata.setResultCode(StaticVarExtend.F_STATUS_OtherError);
            return false;
        }
        // 转换对象
        paraBean = (WorkParaBean) obj;
        // 从传入对象中获取操作id,此为默认值,各个具体操作中可能会覆盖此值
        String funname = paraBean.getFunname();// 操作id
        runningdata.putMsgToConsole("[msg]funname:" + funname);
        if (StringUtilExtend.isNull(funname)) {
            funname = "unknowOperator";
            runningdata.putMsgToConsole("[msg]funname is null,default value:" + funname);
        }
        // 20150911增加默认赋值
        runningdata.setFunname(funname);
        // 从传入对象中获取标题内容
        if (paraBean.getTitle() != null) {
            runningdata.setTitle(paraBean.getTitle());
        }
        if(!funname.equals("autofirst") && !this.getCurTrackid().equals(paraBean.getTrackId())){
            runningdata.setBackMsg(pubInfo.getInfo("Action_Msg0015","trackid不合法!"));
            runningdata.setResultCode(StaticVarExtend.F_STATUS_OtherError);
            return false;

        }
        // 获取前台传入的选择节点内容
        // 格式：rId1~nId1|rId2~nId2&rId3~nId3
        nextNodeId = paraBean.getNextNodeId();
        // 从传入对象中获取设置的办理人
        selectedUsers = paraBean.getSelectAuthorMap();
        selectedUserNames = paraBean.getSelectAuthorNameMap();
        runningdata.putMsgToConsole("[msg]execute operator start.");
        // 获取是否为新建流程实例
        isnewdoc = runningdata.isNewDoc();

        isNotSaveToDB = !isSaveToDB();

        isGetMemo = CStrUtil.isNull(paraBean.getMemo());

        tenantid = runningdata.getInitData().getTenantid();
        identifier = runningdata.getInitData().getFlowIdentifier();
        org = PluginsUtil.getMultiInstance().getOrganization(tenantid);

        // 执行具体操作内容
        if (!execute(curTaskNode)) {
            setResultBaseObject();
            return false;
        }
        runningdata.putMsgToConsole("[msg]execute operator end.");

        runningdata.putMsgToConsole("[msg]set result  parameters.");

        // 返回参数对象设置
        setResultObject();
        // 设置otherPara中的值到hashMap中
        setOtherParaToHashMap(paraBean.getOtherPara());
        // 如果前台没有传入memo值，则给memo赋值
        if (isGetMemo) {
            paraBean.setMemo(memoBuffer.toString());
        }

        // 根据前台传入的参数设置操作后是否需要清空内存中的值
        runningdata.setClearMemAfterAction(paraBean.isClearMemAfterAction());
        // 根据前台传入的参数设置多个操作是否需要事务一致处理
        if (paraBean.isMultiInstanceTransaction()) {
            runningdata.putMsgToConsole("[msg]multi instance transaction.");
            runningdata.setMultiInstanceTransaction(true);
        }
        runningdata.putMsgToConsole("[msg]operator end.");

        return true;
    }

    /**
     * 设置返回值对象
     */
    private void setResultObject() {
        resultBean.setNextTrackInfos(runningdata.getNextTrackList());// 设置下一路径
        resultBean.setWorkParaBean(paraBean);
        resultBean.setTmpAuthorMap(tmpUsers);
        resultBean.setSelectAuthorMap(selectedUsers);

        resultBean.setNextNodeids(nextNodeId);

        InitData initdata = runningdata.getInitData();
        if (null != initdata) {
            resultBean.setHashMap(initdata.getHashMap());
            resultBean.setFlowVarMap(initdata.getFlowVarMap());
        }

        setResultBaseObject();

        List<INextNode> nextNodes = resultBean.getNextNodes();
        if ((nextNodes == null || nextNodes.isEmpty())
                && !runningdata.getFunname().equals(StaticVarExtend.OPERATOR_FUNNAME_JUMP_REJECT)) {
            nextNodes = new ArrayList<INextNode>();
            nextNodes.add(runningdata.getCurTaskNode().getCurNode());

            resultBean.setNextNodes(nextNodes);
        }
    }

    /**
     * 设置返回值对象
     */
    private void setResultBaseObject() {
        resultBean.setCurTrackInfo(getCurTrack());
        resultBean.setFlowInfo(runningdata.getInstanceDefinition().getFlowinfo());
        resultBean.setResult(runningdata.getResultCode());
        resultBean.setBackMsg(runningdata.getBackMsg());
        resultBean.setCurUser(getCurUser());
        resultBean.setCurNode(runningdata.getCurFlowNode());
        resultBean.setWork(runningdata.getWork());
    }

    /**
     * 根据传入参数,返回当前操作是否可以进行数据持久化
     *
     * @return 是否需要保存到数据库
     */
    private boolean isSaveToDB() {
        String submitflag = paraBean.getSubmitflag();// 从前台获取是否确认提交 1:前后台交互
        if (StaticVarExtend.Submit_Flag.equals(submitflag.trim())) {
            runningdata.setContinueSave(false);
            return false;
        }
        return true;
    }

    /**
     * 设置otherPara中的值到hashMap中
     *
     * @param otherPara 存放其他业务内容
     */
    private void setOtherParaToHashMap(Map<String, Object> otherPara) {
        if (CollectionUtil.isMapNotEmpty(otherPara)) {
            for (Map.Entry<String, Object> e : otherPara.entrySet()) {
                runningdata.putValueToHashMap(e.getKey(), e.getValue());
            }
        }
    }
    /**
     * 运行环境对象赋值,由引擎内部调用
     * @param runningdata   运行环境对象
     */
    public void setRunningData(RunningData runningdata) {
        this.runningdata = runningdata;
    }



    /**
     * 保存流程变量
     *
     * @param work 实现对象
     * @param saveMap 需要保存数据的sql和对应参数
     */
    private void saveFlowVarMap(DBWork work, Map<String, List<Object>> saveMap) {
        String workid = work.getId();
        // 流程变量,根据需要存储
        // Map<String, String> map = runningdata.getInitData().getFlowVarMap();
        // 扩展的变量,根据需要存储
        // Map<String,Object> mapVar = runningdata.getInitData().getAllVariable();

        //清理临时内存中的变量信息
        MemoryOperator.clearValueByKey(workid);
    }

    /**
     * 获取当前用户对象
     *
     * @return  当前处理人的信息对象
     */
    protected IRunUser getCurUser() {
        return runningdata.getCurUser();
    }

    /**
     * 获取当前操作人员id
     *
     * @return  用户id
     */
    protected String getCurUserid() {
        return getCurUser().getUserid();
    }

    /**
     * 获取实例对象
     *
     * @return  实例对象
     */
    protected DBWork getWork() {
        return runningdata.getWork();
    }

    /**
     * 获取当前实例id
     *
     * @return 实例id
     */
    protected String getWorkid() {
        return getWork().getId();
    }

    /**
     * 获取当前节点定义对象
     *
     * @return 节点xml对象
     */
    protected IFlowNode getCurFlowNode() {
        return runningdata.getCurFlowNode();
    }

    /**
     * 获取当前节点定义对象
     * @deprecated
     * @return
     */
    protected IFlowNode getCurXMLNode() {
        return runningdata.getCurFlowNode();
    }

    /**
     * 获取当前节点类型
     *
     * @return 节点类型
     */
    protected int getCurNodeType() {
        return getCurFlowNode().getNodetype();
    }

    /**
     * 获取当前节点id
     *
     * @return 节点id
     */
    protected String getCurNodeid() {
        return getCurFlowNode().getNodeid();
    }

    /**
     * 获取当前路径对象
     *
     * @return  路径对象
     */
    protected DBTrack getCurTrack() {
        return runningdata.getCurTrack();
    }

    /**
     * 获取当前路径id
     *
     * @return 路径id
     */
    protected String getCurTrackid() {
        return getCurTrack().getId();
    }

    /**
     * 获取流程当前分支状态
     *
     * @return  当前分支状态
     */
    protected int getFlowstatus() {
        return getCurTrack().getFlowstatus();
    }

    /**
     * 获取当前人权限
     *
     * @return 当前人权限名称
     */
    protected String getCurAuthName() {
        return getCurUser().getAuthName();
    }

    /**
     * 获取当前节点指定权限的数据
     *
     * @param authname 权限名称
     * @return  权限对象列表
     */
    protected List<DBWorkAuth> getXAuthList(String authname) {
        return runningdata.getXAuthListForGet(authname);
    }

    /**
     * 获取传入的下一节点和办理人信息
     */
    protected void initNextNodePara() {
        runningdata.putMsgToConsole("[msg]init nextNode start.");
        runningdata.putMsgToConsole("[msg]nextNodeId:" + nextNodeId);
        if (StringUtilExtend.isNotNull(nextNodeId)) {
            nextNodeList = Arrays.asList(nextNodeId.split(StaticVarExtend.MARK_NODE_SPLIT));
        }
        runningdata.putMsgToConsole("[msg]init nextNode end.nextNodeList size:"
                + ((null == nextNodeList) ? "" : nextNodeList.size()));
    }

    /**
     * 获取节点办理时限
     * 办理时限类型(0=工作日,1=自然日)<P>
     * 办理时长<P>
     * 警告时长<P>
     * 允许催办次数<P>
     * 超期处理方式
     * @param nodeid 节点id
     * @return int[]
     */
    protected int[] getNodeLimit(String nodeid) {
        if (StringUtilExtend.isNull(nodeid)) {
            return null;
        }
        else if (nodeid.equals(getCurFlowNode().getNodeid())){
            return getCurFlowNode().getNodeLimit();
        }
        List<INextNode> nodes = resultBean.getNextNodes();
        if (CollectionUtil.isListNotEmpty(nodes)) {
            for (INextNode node : nodes) {
                if (node.getNodeid().equals(nodeid)) {
                    return node.getFlowNode().getNodeLimit();
                }
            }
        }
        return null;
    }

    /**
     * 标记文档状态 0=新建,未保存过也为提交过,1=保存但未提交,2=已提交
     *
     * @param isnewdoc 是否新建标识
     */
    public void isNewDoc(String isnewdoc) {
        runningdata.isNewDoc(isnewdoc);
    }

    // ==============功能方法=======================
    /**
     * 获取某节点下某种权限的消息发送方式
     *
     * @param node
     *            节点xml
     * @param authname
     *            权限名称
     * @param paraBean  传入参数对象
     * @return  消息方式
     */
    protected String getSendType(IFlowNode node, String authname, WorkParaBean paraBean) {
        // 优先从前台传入值中获取
        String sendType = paraBean.getMsgSendFlagMap().get(authname);
        runningdata.putMsgToConsole("[msg]paraBean sendtype=" + sendType);
        // 前台未传入则从后台节点xml中获取
        if (StringUtilExtend.isNull(sendType)) {
            INodeAuthUser user = node.getParticipantsUser(authname);
            if (null != user) {
                sendType = user.getSendMsgFlag();
                runningdata.putMsgToConsole("[msg]node sendtype=" + sendType);
            }
            else {
                sendType = "";
            }
        }
        runningdata.putMsgToConsole("[msg]sendtype=" + sendType);
        return sendType;
    }

    private boolean isFirstNode() {
        // 版本小于等于1时,肯定为第一个节点
        //20180926 liys < 1 时,数据尚未创建,在并发的情况下执行更新会因为数据不存在而锁表
        //              只需要在第一个节点执行操作的时候更新即可
        int version = runningdata.getWork().getVersion();
        if(version == 0){
            //此时数据尚未创建
            return false;
        }
        if (version == 1) {
            return true;
        }
        // 如果保存时增加版本则需要下面的判断,如果保存时不增加版本,则只需要上面的版本判断即可
        if (runningdata.isAddVersion()) {
            ITrackJson trackXml = runningdata.getCurTrack()._getTrackJson();
            if ("00".equals(trackXml.getPreTrackid())
                    && "".equals(trackXml.getNxtTrackid())) {
                String history = trackXml.getTrueRecord();
                int len = history.split(StaticVarExtend.StrSplitChar).length;
                if (len == 2) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 对当前节点权限有变化的信息进行更新
     * @param auths 当前权限对象列表
     */
    protected void makeSendInfoByCurAuthList(List<DBWorkAuth> auths) {
        runningdata.putMsgToConsole("[msg]todo send : do with auths start.");
        IRunUser curUser = runningdata.getCurUser();// ;
        // 20160530 liys 增加判断是否为第一个节点提交，如果是则修改已办标题
        //20180228 liys 从下面位置修改到此处,避免分表的情况下,数据已经拷贝到另外一张表而造成更新失败
        String tmpTrackid = runningdata.getCurTrack().getId();
        if (isFirstNode()) {
            // 只有在版本为1的时候执行，此时只有第一个节点的
            DBChgTodo dTodo = new DBChgTodo();
            // 修改标题
            String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                    StaticVarExtend.Send_Todo, StaticVarExtend.AUTH_AUTHOR);
            dTodo._setTitle(newTitle);//runningdata.getTitle());
            // 设定需要修改的条件
            dTodo._setWhere_and_trackid_equals(tmpTrackid);
            //下面的赋值仅仅为了在拆分库表的情况下能找到对应的库表
            dTodo.setStatus(StaticVarExtend.AUTH_AUTHOR);
            todoLst.add(dTodo);
        }else{
            if(StaticVarExtend.OPERATOR_FUNNAME_SAVE.equals(runningdata.getFunname()) && StringUtil.hasValue(runningdata.getTitle())){
                String[] authArr={StaticVarExtend.AUTH_AUTHOR,StaticVarExtend.AUTH_READED,StaticVarExtend.AUTH_DONE,StaticVarExtend.AUTH_READER};
                for (String  auth:authArr) {
                    //修改待办标题
                    DBChgTodo dTodo = new DBChgTodo();
                    if(auth.equals(StaticVarExtend.AUTH_AUTHOR)){
                        // 修改标题
                        String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                                StaticVarExtend.Send_Todo, auth);
                        dTodo._setTitle(newTitle);//runningdata.getTitle());
                        dTodo._setWhere_and_status_equals(auth);
                    }else if(auth.equals(StaticVarExtend.AUTH_READED)){
                        // 修改标题
                        String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                                StaticVarExtend.Send_Read, auth);
                        dTodo._setTitle(newTitle);//runningdata.getTitle());
                        dTodo._setWhere_and_status_equals(auth);
                    }else if(auth.equals(StaticVarExtend.AUTH_DONE)){
                        // 修改标题
                        String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                                StaticVarExtend.Send_Done, auth);
                        dTodo._setTitle(newTitle);//runningdata.getTitle());
                        dTodo._setWhere_and_status_equals(auth);
                    }else if(auth.equals(StaticVarExtend.AUTH_READER)){
                        // 修改标题
                        String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                                StaticVarExtend.Send_Read, auth);
                        dTodo._setTitle(newTitle);//runningdata.getTitle());
                        dTodo._setWhere_and_status_equals(auth);
                    }
                    // 设定需要修改的条件
                    dTodo._setWhere_and_trackid_equals(tmpTrackid);
                    //下面的赋值仅仅为了在拆分库表的情况下能找到对应的库表
                    dTodo.setStatus(auth);
                    todoLst.add(dTodo);
                }
            }
        }
        if (CollectionUtil.isListNotEmpty(auths)) {
            for (DBWorkAuth auth : auths) {
                String authname = auth.getStatus();
                String oldAuthName = auth._getOldStatus();
                runningdata.putMsgToConsole("[msg]authname:" + authname + ",oldAuthName:" + oldAuthName);

                if (authname == null || "".equals(authname) // 删除权限时
                        || oldAuthName == null || "".equals(oldAuthName) // 新建
                        || authname.equals(oldAuthName) // 权限无变化时
                        || !auth._isAddNew()) { // 不新增权限时,仅在内部退回时
                    // 新建权限,不做更新,在sendTodo方法中进行
                    continue;
                }

                DBChgTodo dTodo = makeDBChgTodoForCurAuth(auth);
                // 更新字段
                dTodo._setStatus(authname);

                if (authname.indexOf(StaticVarExtend.AUTH_DONE) != -1) {
                    if (curUser.getUserid().equalsIgnoreCase(auth.getAgentId())) {
                        dTodo._setIsagent("1");// 标记为代理人办理
                    }
                    String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                            StaticVarExtend.Send_Done, StaticVarExtend.AUTH_DONE);
                    dTodo._setTitle(newTitle);

                }
                else if (authname.indexOf(StaticVarExtend.AUTH_AUTHOR) != -1) {
                    //顺签拿回时,
                    dTodo._setSendtime(DateUtil.getNow());//重置接收时间
                }
                else if(authname.indexOf(StaticVarExtend.AUTH_CREADER) != -1){
                    String newTitle = ac.getTodoTitle(tmpTrackid, getCurFlowNode(), runningdata,
                            StaticVarExtend.Send_Read, StaticVarExtend.AUTH_CREADER);
                    dTodo._setTitle(newTitle);
                }

                todoLst.add(dTodo);
            }
        }
    }

    /**
     * 操作执行时，附带修改待阅为已阅
     * 需要的操作，在chgCurNodeAuth方法里面执行makeSendInfoByCurAuthList后调用此方法
     * @param auths 权限对象列表
     */
    protected void chgReaderToReaded(List<DBWorkAuth> auths) {

    }

    /**
     * 消息状态变化处理
     *
     * @param auth  权限对象
     * @return  待办变更对象
     */
    private DBChgTodo makeDBChgTodoForCurAuth(DBWorkAuth auth) {
        DBChgTodo dTodo = new DBChgTodo();
        // 更新字段
        dTodo._setDotime(DateUtil.getNow());
        // where条件
        dTodo.setTrackid(auth.getTrackid());
        dTodo.setNodeid(auth.getNodeid());
        String auths = auth.getAuthId();
        if (auths.indexOf("_") != -1 || auths.indexOf("/") != -1) {
            AuthUserImpl tmpUser = AuthUserImpl.getAuthUserByStr(auths);
            dTodo.setAuthId(tmpUser.getAuthId());
            dTodo.setSubjectionId(tmpUser.getSubjectionId());
        }
        else {
            dTodo.setAuthId(auths);
            dTodo.setSubjectionId(auth.getSubjectionId());
        }

        dTodo.setStatus(auth._getOldStatus());

        dTodo.setWorkid(auth.getWorkid());
        return dTodo;
    }

    /**
     * 生成默认的待办对象
     *
     * @return  待办信息对象
     */
    protected DBTodo makeDefaultDBTodo() {
        DBTodo todo = new DBTodo();
        IRunUser user = runningdata.getCurUser();
        todo.setSenduserid(user.getUserid());
        todo.setSendusername(user.getUsername());
        todo.setSendsubjectionid(user.getSubjectionid());
        todo.setSendsubjectiontype(user.getSubjectiontype());
        todo.setSendtime(DateUtil.getNow());

        todo.setFlowid(runningdata.getWork().getFlowid());
        todo.setFlowname(runningdata.getWork().getFlowname());
        todo.setWorkid(getWorkid());

        todo._setUrl(paraBean.getUrlApp());
        todo._setIdentifier(getIdentifier());
        todo._setLimit_time("0");
        todo._setLimit_type(StaticVarExtend.Day_Normal);
        todo._setIdentifier(getIdentifier());
        todo._setOtherstatus("");
        Map<String,Object> otherPara=paraBean.getOtherPara();
        if(null ==otherPara || null==otherPara.get("otherTodoField"))return todo;
        LinkedHashMap<String,String> otherField= JsonUtil.fromJson(JsonUtil.toJson(otherPara.get("otherTodoField")),LinkedHashMap.class) ;
        todo.setOtherField(otherField);
        return todo;
    }

    /**
     * 其他消息临时存储数据id,比如发送邮件时,在temptodo表中存储的数据id
     */
    private List<String> tmpIdList = new ArrayList<String>();
    //存放待办数据的相关信息,用于需要往第三方数据存储时获取使用
    private JSONArray tmpJson = new JSONArray();
    /**
     * 实例数据存入数据库前调用，用于待办数据生成发送
     *
     * @param saveMap   存放数据的sql和对应的参数
     */
    protected void actionBefore(Map<String, List<Object>> saveMap) {
        DBWork work = runningdata.getWork();
        if (isNotSaveToDB) {
            return;
        }
        runningdata.putMsgToConsole("[msg]deleteNotReceiveWork start.");
        // 20150902 liys 删除没有接收的代办时转移的待办记录
        deleteNotReceiveWork(saveMap);
        runningdata.putMsgToConsole("[msg]deleteNotReceiveWork end.");
        runningdata.putMsgToConsole("[msg]get todo sql start.");
        if (CollectionUtil.isListNotEmpty(todoLst)) {
            for (int i = 0, n = todoLst.size(); i < n; i++) {
                DBTodo tmp = todoLst.get(i);
                doWithLimit(tmp);
                tmp.getSQL(saveMap,runningdata.getResultJson());

                tmpIdList.addAll(tmp._getMsgTmpIdList());
                tmpJson.addAll(tmp._getTmpSQLWhereAndPara());
            }
        }
        runningdata.putMsgToConsole("[msg]get todo sql end.");
        saveFlowVarMap(work, saveMap);// 保存流程变量
        runningdata.putMsgToConsole("[msg]save Flow ver end.");
    }

    /**
     * 发送其他消息线程启动
     */
    protected void actionStartTodoThread() {
        String workid = getWorkid();
        String identifier = getIdentifier();
        try {
            XMLTodoPub.startThread(workid, tmpIdList, identifier);
        }
        catch (Exception e) {
            System.err.println("ewqewqe");
            e.printStackTrace();
        }
        //将数据推送给,这里设置的数据将在WorkflowHelper.sendTodoData方法中使用,
        //不能直接在这里发送门户待办的原因是因为这里的待办标题是旧的,如果标题中有内嵌表单业务编号,则无法使用最新的标题
        //所以发送待办的逻辑应该放在内嵌表单编号处理完成,和工作流待办标题处理完成之后进行
        try {
            //2022-06-29 增加workid
            PushWorkflowToPortal.sendTodoData("dataToSend", tmpJson, paraBean, workid);
        } catch (Exception e) {
            e.printStackTrace();
        }
        /**
         * 如果需要往第三方数据写入待办相关数据
         * tmpJson JSON格式的数组
         *
         * SqlType=INSERT时
         * InsertFields = 已经拼好的要插入的字段,字符串
         * InsertPara = 插入时对应的值,数组
         *
         * SqlType=UPDATE或者DELETE时
         * WhereCondition = 已经拼好的查询条件字段,字符串
         * WhereFields = 查询条件字段列表,数组
         * WherePara = 查询条件对应的参数值,数组
         *
         * SqlType=UPDATE时
         * SetStr = 已经拼好的需要更新的字段,字符串
         * SetFields = 更新字段列表,数组
         * SetPara = 更新字段对应的参数值,数组
         */

    }

    /**
     * 处理办理时限问题
     *
     * @param todo  待办数据对象
     */
    private void doWithLimit(DBTodo todo) {
        if (todo instanceof DBChgTodo) {
            return;
        }

        String limit =  todo._getLimit_time() + chgLimitType(pubInfo,todo._getLimit_type());

        todo.setLimittime(limit);

    }

    public static String chgLimitType(PubInfo pubInfo,String type){
        if("0".equals(type)){
            return pubInfo.getInfo("Calendar_Work","(日)");
        }
        else if("1".equals(type)){
            return pubInfo.getInfo("Calendar_Day","(天)");
        }
        else if("2".equals(type)){
            return pubInfo.getInfo("Calendar_Hour","(小时)");
        }
        else if("3".equals(type)){
            return pubInfo.getInfo("Calendar_Min","(分钟)");
        }
        return "";
    }

    /**
     * 会签时对新增加的权限进行信息对象的创建
     *
     * @param todo
     *            默认信息对象
     * @param authId
     *            信息接收人
     * @param status
     *            信息权限
     * @param showStatus
     *            用于显示的信息权限
     * @param sendtype
     *            信息类型,仅为Todo 或者Reader
     * @param otherSendtype
     *            其他类型,短信,即时消息,邮件等
     * @param track
     *            路径对象
     * @param node
     *            对应的节点对象
     */
    protected void makeSendInfoForNewAuthForHuiQian(DBTodo todo, String authId, String status, String showStatus,
                                                    String sendtype, String otherSendtype, DBTrack track, IFlowNode node) {
        makeSendInfoForNewAuthBase(todo, authId, status, showStatus, sendtype, otherSendtype, track, node);
    }
    /**
     * 对新增加的权限进行信息对象的创建
     *
     * @param todo
     *            默认信息对象
     * @param authId
     *            信息接收人
     * @param status
     *            信息权限
     * @param sendtype
     *            信息类型,仅为Todo 或者Reader
     * @param otherSendtype
     *            其他类型,短信,即时消息,邮件等
     * @param track
     *            路径对象
     * @param node
     *            对应的节点对象
     */
    protected void makeSendInfoForNewAuth(DBTodo todo, String authId, String status, String sendtype,
                                          String otherSendtype, DBTrack track, IFlowNode node) {
        makeSendInfoForNewAuthBase(todo, authId, status, status, sendtype, otherSendtype, track, node);
    }
    /**
     * 对新增加的权限进行信息对象的创建
     *
     * @param todo
     *            默认信息对象
     * @param authId
     *            信息接收人
     * @param status
     *            信息权限
     * @param showStatus
     *            用于显示的信息权限
     * @param sendtype
     *            信息类型,仅为Todo 或者Reader
     * @param otherSendtype
     *            其他类型,短信,即时消息,邮件等
     * @param track
     *            路径对象
     * @param node
     *            对应的节点对象
     */
    private void makeSendInfoForNewAuthBase(DBTodo todo, String authId, String status, String showStatus,
                                            String sendtype, String otherSendtype, DBTrack track, IFlowNode node) {

        int[] nodelimit = node.getNodeLimit();

        //增加节点办理时限在操作时,临时指定
        int[] tmpNs = paraBean.getNodeLimitByActionSet(node.getNodeid());
        if(tmpNs !=null){
            for(int i=0,n=tmpNs.length;i<n;i++){
                nodelimit[i] = tmpNs[i];
            }
        }
        todo._setLimit_time(String.valueOf(nodelimit[1]));
        todo._setLimit_type(String.valueOf(nodelimit[0]));

        todo.setAuthId(authId);
        todo.setTrackactive(String.valueOf(track.getActive()));
        todo.setTrackid(track.getId());
        todo.setTrackstatus(String.valueOf(track.getFlowstatus()));
        todo.setWorkver((track.getVersion()));
        todo.setNodeid(track.getNodeid());
        todo.setNodename(track.getNodename());
        // boolean isActiveTodo = true;
        // String activeTodo = isActiveTodoMap.get(track.getNodeid());
        // if(StringUtilExtend.isNotNull(activeTodo)){
        // isActiveTodo = false;
        // }
        // todo.setIsactive(isActiveTodo?"1":"0");
        // 跟路径激活保持一致
        todo.setIsactive(todo.getTrackactive());

        DBTodo nTodo = todo.copy();
        nTodo.setStatus(showStatus);
        nTodo.setTitle(ac.getTodoTitle(track.getId(), node, runningdata, sendtype, status));
        todoLst.add(nTodo);

        if (StringUtilExtend.isNull(otherSendtype)) {
            return;
        }

        String newotherSendtype = otherSendtype.replaceAll("\\|", StaticVarExtend.StrSplitChar);
        String[] msgSendTypes = newotherSendtype.split(StaticVarExtend.StrSplitChar);
        for (String str : msgSendTypes) {
            if (str.equalsIgnoreCase(sendtype)) {
                continue;
            }
            DBTodo oTodo = todo.copy();
            oTodo.setStatus("");
            oTodo._setOtherstatus(str);
            oTodo.setTitle(ac.getTodoTitle(track.getId(), node, runningdata, str, status));
            todoLst.add(oTodo);
        }
    }

    /**
     * 根据节点类型获取发送待办的人员
     * 主要是对顺签节点和内部循环节点时的处理
     * @param users 办理人员id
     * @param nodetype  节点类型
     * @param authname  权限名称
     * @return  人员id
     */
    private String getTodoUsersByNodetype(String users, int nodetype, String authname) {
        String user = users;// StaticFunExtend.subOrg(users);
        // users不为空且节点类型为顺签和逐级办理时取第一个人员发送待办
        if (StringUtilExtend.isNotNull(user)) {
            if ((NodeTypeEnum.Node_Progressively == nodetype || NodeTypeEnum.Node_Order == nodetype)
                    && StaticVarExtend.AUTH_AUTHOR.equals(authname)) {
                user = user.split(StaticVarExtend.UserSplitChar)[0];
            }
        }
        return user;
    }
    /**
     * 根据selectedUsers中的记录进行人员信息的发送准备
     */
    protected void sendTodoForSelectedUsers() {
        runningdata.putMsgToConsole("[msg]todo send start.");
        DBTodo todo = makeDefaultDBTodo();
        // 合并节点是否激活
        DBChgTodo chgTodo = null;
        for (Map.Entry<String, String> entry : selectedUsers.entrySet()) {
            String value = entry.getValue();
            if (value == null || "".equals(value)) {
                continue;
            }
            runningdata.putMsgToConsole("[msg]todo send selectedAuthName=" + entry.getKey()
                    + ";todo send selectedAuthor=" + value);
            String authname = "";
            String nodeid = "";
            String[] key = entry.getKey().split(StaticVarExtend.MARK_NODE_USER);
            String msgSendType = "";
            int nodetype = 0;
            IFlowNode node = null;
            if (key.length > 1) {
                nodeid = key[0];
                authname = key[1].indexOf(StaticVarExtend.AUTH_AUTHOR) >= 0 ? key[1] : StaticVarExtend.AUTH_READER;

            }
            else {
                node = getCurFlowNode();
                authname = entry.getKey().indexOf(StaticVarExtend.AUTH_AUTHOR) >= 0 ? entry.getKey()
                        : StaticVarExtend.AUTH_READER;
                nodeid = node.getNodeid();
            }
            //20230415 新增配置，控制操作是否触发发送待阅功能
            String actions =HZResourceBundle.getInstance().getSendReaderAction();
            if(StringUtil.hasValue(actions)){
                String[] actionArr=actions.split(",");
                if(!Arrays.asList(actionArr).contains(this.paraBean.getFunname()) && StaticVar.AUTH_READER.equals(authname)){
                    continue;
                }
            }
            DBTrack track = getTrackIdByNodeId(nodeid);// keyid
            if (null == track) {// 获取不到track信息则不发送待办
                runningdata.putMsgToConsole("[msg]get track by nodeid is null.");
                continue;
            }
            String trackId = track.getId();
            if (StringUtilExtend.isNull(trackId)) {
                continue;
            }
            if (node == null) {
                // 获取IFlowNode
                nodeid = track.getNodeid();
                node = runningdata.getInstanceDefinition().getFlowinfo().getNodeById(nodeid);
            }
            nodetype = node.getNodetype();
            msgSendType = getSendType(node, authname, paraBean);

            String dMsg = StaticVarExtend.Send_Todo;
            if (authname.equals(StaticVarExtend.AUTH_READER)) {
                dMsg = StaticVarExtend.Send_Read;
            }
            String todoUser = getTodoUsersByNodetype(entry.getValue(), nodetype, authname);
            // 20150521合并节点不激活时,办理人不发送消息,读者和协办仍然发送消息
            if (nodetype == NodeTypeEnum.Node_Merger) {
                boolean isAuthor = authname.equals(StaticVarExtend.AUTH_AUTHOR);
                if (track.getActive() == 0) {
                    if (isAuthor) {
                        runningdata.putMsgToConsole("[msg]" + nodeid + " no active(Ignore message sending).");
                        continue;
                    }
                }
                else {
                    if (chgTodo == null) {
                        chgTodo = new DBChgTodo();
                        chgTodo._setIsactive("1");
                        chgTodo._setTrackactive("1");

                        List<String> para = new ArrayList<String>(2);
                        para.add(StaticVarExtend.AUTH_READER);
                        para.add(StaticVarExtend.AUTH_SECOND_AUTHOR);
                        chgTodo._setWhere_and_status_or(para);
                        chgTodo._setWhere_and_nodeid_equals(nodeid);
                        chgTodo._setWhere_and_trackid_equals(track.getId());

                        //下面的赋值仅仅为了在拆分库表的情况下能找到对应的库表
                        chgTodo.setStatus(StaticVarExtend.AUTH_AUTHOR);


                        todoLst.add(chgTodo);
                    }
                    // 激活时,查找是否已经发送过待办,
                    if (isAuthor) {
                        DBTodo tmpTodo = new DBTodo();
                        tmpTodo.setTrackid(track.getId());
                        tmpTodo.setNodeid(nodeid);
                        tmpTodo.setStatus(StaticVarExtend.AUTH_AUTHOR);
                        tmpTodo._setIdentifier(runningdata.getInitData().getFlowIdentifier());
                        int count = tmpTodo._getCountFromDBTodo();
                        if (count > 0) {
                            runningdata.putMsgToConsole("[msg]" + nodeid
                                    + "'s message already exists(Ignore message sending).");

                            DBChgTodo aTodo = new DBChgTodo();
                            aTodo._setIsactive("1");
                            aTodo._setTrackactive("1");

                            aTodo._setWhere_and_status_equals(StaticVarExtend.AUTH_AUTHOR);
                            aTodo._setWhere_and_trackid_equals(track.getId());

                            //下面的赋值仅仅为了在拆分库表的情况下能找到对应的库表
                            aTodo.setStatus(StaticVarExtend.AUTH_AUTHOR);
                            todoLst.add(aTodo);
                            continue;
                        }
                    }
                }
            }

            makeSendInfoForNewAuth(todo, todoUser, authname, dMsg, msgSendType, track, node);
        }
        runningdata.putMsgToConsole("[msg]todo send end.");
    }

    /**
     * 根据节点id获取trackid
     *
     * @param nodeid    节点id
     * @return  路径对象
     */
    protected DBTrack getTrackIdByNodeId(String nodeid) {
        List<DBTrack> trackInfos = runningdata.getNextTrackList();
        if (CollectionUtil.isListNotEmpty(trackInfos)) {
            for (DBTrack t : trackInfos) {
                if (t._getKeyid().equals(nodeid)) {
                    return t;
                }
            }
        }
        int nodetype = getCurFlowNode().getNodetype();
        if (NodeTypeEnum.Node_Order == nodetype || NodeTypeEnum.Node_Progressively == nodetype) {
            return runningdata.getCurTrack();
        }
        return null;
    }

    /**
     * 用于操作完成时,更新所有路径信息时获取所有需要更新的路径对象 默认为null,由后台自动去库表中获取,
     * 但在撤办和结束操作中由于需要对所有路径进行处理,所以此处需要返回处理后的路径列表信息
     *
     * @return 路径对象列表
     */
    public List<DBTrack> getAllOldTrack() {
        return null;
    }

    /**
     * 删除当前执行人没有被接收的工作
     *
     * @param saveMap   需要保存数据的sql和对应的参数
     */
    private void deleteNotReceiveWork(Map<String, List<Object>> saveMap) {
        DBHandover.deleteNotReceiveWorkForSubmit(saveMap,runningdata);
    }

    /**
     * 遍历所有下一节点,并对下一节点结果进行分析处理
     * 判断下一节点结果 0.成功 1.选择办理人2.选择节点  3.未找到办理人 4.未找到下一节点
     * @param isLeaveCurNode 当前节点是否离开
     */
    protected void doWithNextNodeResult(boolean isLeaveCurNode) {
        List<INextNode> nodes = runningdata.getCurTaskNode().getGNextNodes();
        int backResult = 0;
        for (INextNode node : nodes) {
            int nextInitResult = node.getInitResult();
            runningdata.putMsgToConsole("[msg]NextNode:" + node.getNodeid() + "=" + nextInitResult);
            if (nextInitResult == StaticVarExtend.F_STATUS_Success) {
                backResult = backResult < 1 ? 0 : backResult;
            }
            else if (nextInitResult == StaticVarExtend.F_STATUS_SelectAuthor) {
                backResult = backResult < 2 ? 1 : backResult;
            }
            else if (nextInitResult == StaticVarExtend.F_STATUS_SelectNode) {
                backResult = backResult < 3 ? 2 : backResult;
            }
            else if (nextInitResult == StaticVarExtend.F_STATUS_NoAuthor) {
                backResult = backResult < 4 ? 3 : backResult;
            }
            else if (nextInitResult == StaticVarExtend.F_STATUS_NoFound) {
                backResult = backResult < 5 ? 4 : backResult;
            }
            else {
                msg = node.getBackMsg();
                result = StaticVarExtend.F_STATUS_OtherError;
                return;
            }
        }

        if (backResult == 0 && paraBean.getSubmitflag().equals(StaticVarExtend.Submit_Flag_Commit)) {
            if (isLeaveCurNode) {
                // 当前节点的离开
                IFlowNode curnode = runningdata.getCurTaskNode().getFlowNode();
                boolean excuteSuccess = EventUtil.getInstance().excuteEventsForOperator(curnode,
                        StaticVarExtend.EVENT_OUT, runningdata);
                if (!excuteSuccess) {
                    result = StaticVarExtend.F_STATUS_OtherError;
                    msg = pubInfo.getInfo("Action_Msg0002", "节点离开事件执行失败!");
                    return;
                }
            }
            // 执行节点进入事件
            for (INextNode node : nodes) {
                IFlowNode xmlNode = node.getFlowNode();
                boolean excuteSuccess = EventUtil.getInstance().excuteEventsForOperator(xmlNode,
                        StaticVarExtend.EVENT_IN, runningdata);

                if (!excuteSuccess) {
                    result = StaticVarExtend.F_STATUS_OtherError;
                    msg = pubInfo.getInfo("Action_Msg0003", "节点[|nodename|]进入事件执行失败!").replace("|nodename|",
                            node.getNodename());
                    return;
                }
            }
            StaticFunExtend.getActionExtraData().setNextNodesNoActive(runningdata);
            msg = pubInfo.getInfo("Action_Msg0004", "提交成功");
            result = StaticVarExtend.F_STATUS_Success;
            return;
        }

        if (backResult == 1) {
            msg = pubInfo.getInfo("Action_Msg0006", "请选择办理人");
            result = StaticVarExtend.F_STATUS_SelectAuthor;
            return;
        }
        if (backResult == 2) {
            result = StaticVarExtend.F_STATUS_SelectNode;
            msg = pubInfo.getInfo("Action_Msg0005", "请选择节点");
            return;
        }
        if (backResult == 3) {
            msg = pubInfo.getInfo("Action_Msg0007", "下一节点没有设置办理人,且不能跳过");
            result = StaticVarExtend.F_STATUS_NoAuthor;
            return;
        }
        if (backResult == 4) {
            result = StaticVarExtend.F_STATUS_NoFound;
            msg = pubInfo.getInfo("Action_Msg0008", "流程未能找到下一节点,请检查路由条件是否正确");
            return;
        }
    }

    /**
     * 执行操作完成事件
     * @return 成功true,失败false
     */
    protected boolean executeEventSave() {
        if (isNotSaveToDB) {
            return true;
        }
        // 操作完成前事件
        boolean ex = EventUtil.getInstance().excuteEventsForOperator(getCurFlowNode(), StaticVarExtend.EVENT_SAVE,
                runningdata);
        if (!ex) {
            runningdata.putMsgToConsole("[err]operator event excute error.");
            msg = pubInfo.getInfo("Action_Msg0009", "操作完成前事件执行失败.异常处理如果设置为跳转,挂起,终止则异常被忽略处理.");
            result = StaticVarExtend.F_STATUS_OtherError;
            return false;
        }

        return true;
    }

    /**
     * 操作执行完毕并且写库后执行的事件
     * 在需要的操作类中actionAfterSaveToDB方法里调用，
     * 默认提交操作会调用，其他操作根据项目需要添加
     * @return
     */
    protected void executeEventAfterToDB(){
        if (isNotSaveToDB) {
            return ;
        }
        //20200422 LIYS 操作完成前事件
        boolean ex = EventUtil.getInstance().excuteEventsForOperator(getCurFlowNode(), StaticVarExtend.EVENT_OVER,
                runningdata);
        if (!ex) {
            runningdata.putMsgToConsole("[err]operator event excute error.");
            msg = pubInfo.getInfo("Action_Msg0056", "操作完成后事件执行失败.");
        }
    }
    /**
     * 传入参数中没有指定操作名称时,各操作自行获取默认的操作名称,
     *
     * @param key   获取信息配置的关键字
     * @param defaultStr    默认的操作名称
     */
    protected void setActionname(String key, String defaultStr) {
        if (CStrUtil.isNull(paraBean.getActionname())) {
            paraBean.setActionname(pubInfo.getInfo(key, defaultStr));
        }
        runningdata.setActionname(paraBean.getActionname());
    }

    /**
     * 自行获取中文名称并赋值到memo
     *
     * @param nextNode  下一节点运行对象
     */
    protected void doMemoAutoGet(INextNode nextNode) {
        if (!isGetMemo) {
            return;
        }
        List<INodeUser> nodeUserList = nextNode.getListUserByNodeInit();
        if (nodeUserList == null || nodeUserList.isEmpty()) {
            memoBuffer.append("[").append(nextNode.getNodename()).append("]");
            return;
        }
        for (INodeUser nu : nodeUserList) {
            if (StaticVarExtend.AUTH_AUTHOR.equals(nu.getAuthType())) {
                String userids = nu.getNowid();
                String usernames = getUsernameFormSelectedOrFromDB("", userids);

                memoBuffer.append(pubInfo.getInfo("Action_Msg0011", "[|nodename|]处理人:|user|")
                        .replace("|nodename|", nextNode.getNodename()).replace("|user|", usernames));
            }
        }
        doMemoForOther(nodeUserList,
                pubInfo.getInfo("Action_Msg0012", ",协办人:|user|"),
                nextNode.getKeyid(), StaticVarExtend.AUTH_SECOND_AUTHOR);

        doMemoForOther(nodeUserList,
                pubInfo.getInfo("Action_Msg0013", ",读者:|user|"),
                nextNode.getKeyid(), StaticVarExtend.AUTH_READER);
    }

    /**
     * 从前台传入map中获取中文名称并赋值到memo
     *
     * @param nextNode  下一节点运行对象
     */
    protected void doMemoFromSelected(INextNode nextNode) {
        if (!isGetMemo || isNotSaveToDB) {
            return;
        }

        List<INodeUser> nodeUserList = nextNode.getListUserByNodeInit();
        if (nodeUserList == null || nodeUserList.isEmpty()) {
            memoBuffer.append("[").append(nextNode.getNodename()).append("]");
            return;
        }

        int nodeType = nextNode.getFlowNode().getNodetype();
        if (nodeType == NodeTypeEnum.Node_End
                || nodeType == NodeTypeEnum.Node_End_Instance) {
            memoBuffer.append("[").append(nextNode.getNodename()).append("]");

            doMemoForOther(nodeUserList,
                    pubInfo.getInfo("Action_Msg0013", ",读者:|user|"),
                    nextNode.getKeyid(), StaticVarExtend.AUTH_READER);
            return;
        }

        for (INodeUser nu : nodeUserList) {
            if (StaticVarExtend.AUTH_AUTHOR.equals(nu.getAuthType())) {
                String userids = nu.getNowid();
                if (userids.equals(StaticVarExtend.System_Id)) {
                    memoBuffer.append("[").append(nextNode.getNodename())
                            .append("]");
                } else {
                    String key = nextNode.getKeyid()
                            + StaticVarExtend.MARK_NODE_USER + nu.getAuthType();
                    String username = getUsernameFormSelectedOrFromDB(key,
                            userids);
                    memoBuffer
                            .append(pubInfo
                                    .getInfo("Action_Msg0011",
                                            "[|nodename|]处理人:|user|")
                                    .replace("|nodename|",
                                            nextNode.getNodename())
                                    .replace("|user|", username));
                }
                break;
            }
        }

        doMemoForOther(nodeUserList,
                pubInfo.getInfo("Action_Msg0012", ",协办人:|user|"),
                nextNode.getKeyid(), StaticVarExtend.AUTH_SECOND_AUTHOR);
        //20230415 新增功能判断，控制发送待阅的操作配置，如果不在此属性配置的操作，不记录读者信息
        String actions =HZResourceBundle.getInstance().getSendReaderAction();
        if(StringUtil.hasValue(actions)){
            String[] actionArr=actions.split(",");
            if(Arrays.asList(actionArr).contains(this.paraBean.getFunname())){
                doMemoForOther(nodeUserList,
                        pubInfo.getInfo("Action_Msg0013", ",读者:|user|"),
                        nextNode.getKeyid(), StaticVarExtend.AUTH_READER);
            }
        }
    }

    private void doMemoForOther(List<INodeUser> nodeUserList,
                                String defaultMsg, String keyid, String authType) {
        for (INodeUser nu : nodeUserList) {
            if (authType.equals(nu.getAuthType())) {
                String userids = nu.getNowid();
                if (CStrUtil.isNotNull(userids)) {
                    String key = keyid + StaticVarExtend.MARK_NODE_USER
                            + nu.getAuthType();
                    String username = getUsernameFormSelectedOrFromDB(key,
                            userids);
                    memoBuffer.append(defaultMsg.replace("|user|", username));
                }
                break;
            }
        }

    }

    /**
     * 从前台选择的办理人中获取中文名称,如果没有则库表查询
     *
     * @param key   获取办理人中文名称的key
     * @param userid  办理人id
     * @return  办理人的中文名称
     */
    protected String getUsernameFormSelectedOrFromDB(String key, String userid) {
        if (!isGetMemo || isNotSaveToDB) {
            return "";
        }
        if(userid.equals(StaticVarExtend.System_Id)){
            return pubInfo.getInfo("System_Name", "工作流系统");
        }
        if(userid.equals(StaticVarExtend.Manager_Log_Userid)){
            return pubInfo.getInfo("Manager_Log_Username", "系统管理员");
        }

        String username = CStrUtil.isNull(key) ? "" : selectedUserNames.get(key);
        if(CStrUtil.isNotNull(username)){
            if(userid.indexOf(StaticVarExtend.AgentUserSplitChar) !=-1){
                if(username.indexOf(StaticVarExtend.AgentUserSplitChar) ==-1){
                    username ="";
                }
            }
            else {
                String[] userids = userid.split(StaticVarExtend.UserSplitChar);
                String[] usernames = username.split(StaticVarExtend.UserSplitChar);
                if(userids.length != usernames.length){
                    username = "";
                }
                else{
                    for(String user:userids){
                        if("".equals(user)){
                            username ="";
                            break;
                        }
                    }
                }
            }
        }
        // 对name进行空值判断
        if (CStrUtil.isNull(username)) {
            username = getAllOrgNamesByOrgIds(userid);
            runningdata.putMsgToConsole("[IO]getUserName from DB." + key + ":" + userid + "(" + username + ")");
        }

        return username;
    }

    /**
     * 用于根据id获取对应的中文名称
     *
     * @param ids   办理人id
     * @return  对应的中文名称
     */
    private String getAllOrgNamesByOrgIds(String ids) {
        // 获取所有人员组成的Map
        String name = org.getNameByIds(ids, identifier);// (userids.toString());

        return name;
    }

    protected  void  actionForTask(Map<String, List<Object>> saveMap) {
        //任务表只有在流程结束时删除,流转中应该一直存在
        DBTrack xTrack =runningdata.getCurTrack();
        List<Object> workidpara = new ArrayList<Object>(1);
        workidpara.add(xTrack.getId());
        //当前节点任务创建或者更新
        if(xTrack._isEnd()){
            DBTask.delSQLByTrackid(saveMap, runningdata.getResultJson(), xTrack.getId());
        }
        else{
            runningdata.putMsgToConsole("[IO]get Task");
            DBTask xTask = DBTask.getTaskByTrackid(runningdata.getResultJson(),xTrack.getId(), identifier);
            runningdata.putMsgToConsole("[IO]get Task");

            createTaskObj(xTask,xTrack,saveMap,false,false);
        }
    }

    /**
     * 创建用于超期处理时的数据
     * @param xTask
     * @param xTrack
     * @param saveMap
     */

    protected void createTaskObj(DBTask xTask,DBTrack xTrack,Map<String, List<Object>> saveMap) {
        createTaskObj(xTask,xTrack,saveMap,true,true);
    }

    protected void createTaskObj(DBTask xTask,DBTrack xTrack,Map<String, List<Object>> saveMap
            ,boolean ischeck,boolean isCreateNew){
        DBWork xWork = runningdata.getWork();
        IHZCalendar calendar = PluginsUtil.getMultiInstance().getCalendar(tenantid);
        //20170508 liys 只有普通节点才需要创建
        if(xTrack.getNodeid().indexOf("Node") == -1){
            return;
        }
        if(xTask == null && ischeck) {
            //检查trackid对应的数据是否已经存在
            runningdata.putMsgToConsole("[IO]get Task");
            xTask = DBTask.getTaskByTrackid(runningdata.getResultJson(),xTrack.getId(), identifier);
            runningdata.putMsgToConsole("[IO]get Task");
        }
        if(xTask == null){
            if(!isCreateNew){
                return;
            }
            xTask = new DBTask();
            IFlowInfo flowInfo = runningdata.getInstanceDefinition().getFlowinfo();
            int[] ls = flowInfo.getLimitSet();
            IFlowNode xmlNode = flowInfo.getNodeById(xTrack.getNodeid());
            int[] ns = xmlNode.getNodeLimit();
            //外部设置办理人时,获取办理时限,为提高性能处理,放在获取办理人时,对xmlNode的设置进行临时重置

            //增加节点办理时限在操作时,临时指定
            int[] tmpNs = paraBean.getNodeLimitByActionSet(xTrack.getNodeid());
            if(tmpNs !=null){
                for(int i=0,n=tmpNs.length;i<n;i++){
                    ns[i] = tmpNs[i];
                }
            }
            xTask.setFlowlimitdate(ls[1]);
            xTask.setNodelimitdate(ns[1]);
            //20190103 liys 如果没有时限设置时,不保存task数据,用于减少task数据量
            if(xTask.getFlowlimitdate()<=0 && xTask.getNodelimitdate() <=0){
                return;
            }

            xTask.setId(StaticFunExtend.getUnid());
            xTask.setNew(true);
            xTask.setFlowid(xWork.getFlowid());
            xTask.setFlowname(xWork.getFlowname());
            xTask.setWorkid(xWork.getId());
            xTask.setNodetype(xmlNode.getNodetype());

            xTask.setFlowlimittype(String.valueOf(ls[0]));
            xTask.setStarttime(xWork.getStarttime());
            String nowtime = DateUtil.getNow();
            xTask.setReceivetime(nowtime);//节点到达时间，计入xml中

            xTask.setNodelimittype(String.valueOf(ns[0]));
            xTask.setWaringdate(ns[2]);
            xTask.setWaringtype(String.valueOf(ns[0]));
            xTask.setDooverpass(ns[4]);
            xTask.setRemsgnum(ns[3]);
            xTask.setRedonenum(0);

            //计算流程超期时间点，如果没有设置，默认为一年
            int tp  =xTask.getFlowlimitdate();
            Date date = DateUtilExtend.addDateForTask(xTask.getStarttime(),ls[0],tp,calendar);
            xTask.setFlowlimittime(DateUtil.formatDateTime(date,"yyyy-MM-dd HH:mm:ss"));

            tp  =xTask.getNodelimitdate();
            date = DateUtilExtend.addDateForTask(xTask.getReceivetime(),ns[0], tp,calendar);
            xTask.setNodelimittime(DateUtil.formatDateTime(date,"yyyy-MM-dd HH:mm:ss"));
            xTask.setTrackid(xTrack.getId());

            xTask.setStatus(StaticVarExtend.Task_Normal);
        }
        else{
            xTask.setNodetype(getCurFlowNode().getNodetype());
        }

        xTask.setFlowstatus(xTrack.getFlowstatus());        //流程状态更新
        xTask.setTitle(xWork.getTitle());

        xTask.setNodeid(xTrack.getNodeid());
        xTask.setNodename(xTrack.getNodename());
        xTask.setVersion(xTrack.getVersion());
        xTask.getSQL(saveMap,runningdata.getResultJson());
    }
    /**
     * 结果对象参数
     *
     * @return 操作返回对象
     */
    public Object getWorkResultBean() {
        return resultBean;
    }
    /**
     * 获取运行环境对象
     * @return  运行环境对象
     */
    public RunningData getRunningData() {
        return runningdata;
    }
    /**
     * 获取语言文件对象
     * @return
     */
    public PubInfo getPubInfo(){
        return pubInfo;
    }

    /**
     * 获取数据源标识
     *
     * @return 引擎数据源标识
     */
    public String getIdentifier() {
        String identifier = paraBean.getFlowIdentifier();
        return identifier;
    }

    /**
     * 获取数据源标识
     *
     * @return 业务数据源标识
     */
    protected String getDataIdentifier() {
        String identifier = paraBean.getDataIdentifier();
        if (StringUtilExtend.isNull(identifier)) {
            return paraBean.getFlowIdentifier();
        }
        return identifier;
    }
    /**
     * 当前操作是否动态创建节点
     * @return
     */
    public boolean isDynamicCreateNode(){
        return false;
    }
    /**
     * 获取租户id
     * @return
     */
    public String getTenantid(){
        return tenantid;
    }

    /**
     * 存放操作中需要的办理人
     */
    public Map<String, String> getSelectedUsers(){
        return selectedUsers;
    };
    /**
     * 存放操作中需要的办理人中文显示名称
     */
    public Map<String, String> getSelectedUserNames(){
        return selectedUserNames;
    }


    /**
     * 附带激活合并节点时,处理合并节点办理人的待办
     * 用于合并所有分支启用时,分支可能不能到达此合并节点的处理
     * @param activeTrackid
     * @param authors
     */
    public void activeTrackAction(String activeTrackid,List<DBWorkAuth> authors){
        if (isNotSaveToDB) {
            return;
        }
        DBTodo todo = makeDefaultDBTodo();
        String authname = StaticVarExtend.AUTH_AUTHOR;
        for(DBWorkAuth auth:authors){

            IFlowNode node = runningdata.getInstanceDefinition().getFlowinfo().getNodeById(auth.getNodeid());
            int nodetype = node.getNodetype();
            INodeAuthUser user = node.getParticipantsUser(authname);
            String msgSendType ="";
            if (null != user) {
                msgSendType = user.getSendMsgFlag();
            }

            int[] nodelimit = node.getNodeLimit();
            todo._setLimit_time(String.valueOf(nodelimit[1]));
            todo._setLimit_type(String.valueOf(nodelimit[0]));

            todo.setAuthId(auth._getFullNameWithAgent());
            todo.setTrackactive("1");
            todo.setTrackid(auth.getTrackid());
            todo.setTrackstatus(String.valueOf(StaticVarExtend.FlowStatus_Normal));
            todo.setWorkver((auth.getVersion()));
            todo.setNodeid(auth.getNodeid());
            todo.setNodename(node.getNodename());
            todo.setIsactive("1");

            DBTodo nTodo = todo.copy();
            nTodo.setStatus(StaticVarExtend.AUTH_AUTHOR);
            nTodo.setTitle(ac.getTodoTitle(auth.getTrackid(), node, runningdata,
                    StaticVarExtend.Send_Todo, StaticVarExtend.AUTH_AUTHOR));
            todoLst.add(nTodo);

            if (StringUtilExtend.isNull(msgSendType)) {
                continue;
            }

            String newotherSendtype = msgSendType.replaceAll("\\|", StaticVarExtend.StrSplitChar);
            String[] msgSendTypes = newotherSendtype.split(StaticVarExtend.StrSplitChar);
            for (String str : msgSendTypes) {
                if (str.equalsIgnoreCase(StaticVarExtend.Send_Todo)) {
                    continue;
                }
                DBTodo oTodo = todo.copy();
                oTodo.setStatus("");
                oTodo._setOtherstatus(str);
                oTodo.setTitle(ac.getTodoTitle(auth.getTrackid(), node, runningdata,
                        str, StaticVarExtend.AUTH_AUTHOR));
                todoLst.add(oTodo);
            }
        }
    }
}