<?php

use Illuminate\Support\Facades\Log;
use ProcessMaker\Model\Application as ModelApplication;
use ProcessMaker\Model\Delegation;
use ProcessMaker\Model\SubApplication as ModelSubApplication;

class Derivation
{
    var $case;
    protected $appCurrentUser;
    protected $arraySiblings;
    protected $aSP;
    protected $context;
    protected $flagControl;
    protected $flagControlMulInstance;
    protected $flagUpdateList;
    protected $iNewDelIndex;
    protected $regexpTaskTypeToInclude;
    protected $removeList;
    protected $sys;
    public $node;
    public $userLogged = null;

    public function __construct()
    {
        $this->userLogged = new Users();
        $this->setRegexpTaskTypeToInclude("GATEWAYTOGATEWAY|END-MESSAGE-EVENT|END-EMAIL-EVENT");
    }

    /**
     * @return mixed
     */
    public function getRegexpTaskTypeToInclude()
    {
        return $this->regexpTaskTypeToInclude;
    }

    /**
     * @param mixed $regexpTaskTypeToInclude
     */
    public function setRegexpTaskTypeToInclude($regexpTaskTypeToInclude)
    {
        $this->regexpTaskTypeToInclude = $regexpTaskTypeToInclude;
    }

    /**
     * prepareInformationTask
     *
     * @param array $arrayTaskData Task data (derivation)
     *
     * @return array Return array
     * @throws Exception
     */
    protected function prepareInformationTask(array $arrayTaskData)
    {
        try {
            $task = new Task();

            $arrayTaskData = G::array_merges($arrayTaskData, $task->load($arrayTaskData["TAS_UID"]));

            //2. If next case is an special case
            if ((int)($arrayTaskData["ROU_NEXT_TASK"]) < 0) {
                $arrayTaskData["NEXT_TASK"]["TAS_UID"] = (int)($arrayTaskData["ROU_NEXT_TASK"]);
                $arrayTaskData["NEXT_TASK"]["TAS_ASSIGN_TYPE"] = "nobody";
                $arrayTaskData["NEXT_TASK"]["TAS_PRIORITY_VARIABLE"] = "";
                $arrayTaskData["NEXT_TASK"]["TAS_DEF_PROC_CODE"] = "";
                $arrayTaskData["NEXT_TASK"]["TAS_PARENT"] = "";
                $arrayTaskData["NEXT_TASK"]["TAS_TRANSFER_FLY"] = "";

                switch ($arrayTaskData["ROU_NEXT_TASK"]) {
                    case -1:
                        $arrayTaskData["NEXT_TASK"]["TAS_TITLE"] = G::LoadTranslation("ID_END_OF_PROCESS");
                        break;
                    case -2:
                        $arrayTaskData["NEXT_TASK"]["TAS_TITLE"] = G::LoadTranslation("ID_TAREA_COLGANTE");
                        break;
                }

                $arrayTaskData["NEXT_TASK"]["USR_UID"] = "";
                $arrayTaskData["NEXT_TASK"]["USER_ASSIGNED"] = array("USR_UID" => "", "USR_USERNAME" => "");
            } else {
                //3. Load the task information of normal NEXT_TASK
                $arrayTaskData["NEXT_TASK"] = $task->load($arrayTaskData["ROU_NEXT_TASK"]); //print $arrayTaskData["ROU_NEXT_TASK"]." **** ".$arrayTaskData["NEXT_TASK"]["TAS_TYPE"]."<hr>";

                if ($arrayTaskData["NEXT_TASK"]["TAS_TYPE"] == "SUBPROCESS") {
                    $taskParent = $arrayTaskData["NEXT_TASK"]["TAS_UID"];

                    $criteria = new Criteria("workflow");
                    $criteria->add(SubProcessPeer::PRO_PARENT, $arrayTaskData["PRO_UID"]);
                    $criteria->add(SubProcessPeer::TAS_PARENT, $arrayTaskData["NEXT_TASK"]["TAS_UID"]);
                    $rsCriteria = SubProcessPeer::doSelectRS($criteria);
                    $rsCriteria->setFetchmode(ResultSet::FETCHMODE_ASSOC);
                    $rsCriteria->next();
                    $row = $rsCriteria->getRow();

                    $arrayTaskData["ROU_NEXT_TASK"] = $row["TAS_UID"]; //print "<hr>Life is just a lonely highway";
                    $arrayTaskData["NEXT_TASK"] = $task->load($arrayTaskData["ROU_NEXT_TASK"]); //print "<hr>Life is just a lonely highway";print"<hr>";

                    $process = new Process();
                    $row = $process->load($row["PRO_UID"]);

                    $arrayTaskData["NEXT_TASK"]["TAS_TITLE"] .= " (" . $row["PRO_TITLE"] . ")";
                    $arrayTaskData["NEXT_TASK"]["TAS_PARENT"] = $taskParent;

                    //unset($task, $process, $row, $taskParent);
                } else {
                    $arrayTaskData["NEXT_TASK"]["TAS_PARENT"] = "";
                }

                $regexpTaskTypeToExclude = "GATEWAYTOGATEWAY|END-MESSAGE-EVENT|SCRIPT-TASK|SERVICE-TASK|INTERMEDIATE-CATCH-TIMER-EVENT|INTERMEDIATE-THROW-MESSAGE-EVENT|END-EMAIL-EVENT|INTERMEDIATE-THROW-EMAIL-EVENT|INTERMEDIATE-CATCH-MESSAGE-EVENT";

                $arrayTaskData["NEXT_TASK"]["USER_ASSIGNED"] = (!preg_match("/^(?:" . $regexpTaskTypeToExclude . ")$/", $arrayTaskData["NEXT_TASK"]["TAS_TYPE"]))? $this->getNextAssignedUser($arrayTaskData) : array("USR_UID" => "", "USR_FULLNAME" => "");
            }
            //Return
            return $arrayTaskData;
        } catch (Exception $e) {
            throw $e;
        }
    }

     /**
     * prepareInformation
     *
     * @param array  $arrayData Data
     * @param string $taskUid   Unique id of Task
     *
     * @return array
     * @throws Exception
     */
    public function prepareInformation(array $arrayData, $taskUid = "")
    {
        try {
            $this->case = new Cases();
            $task = new Task();

            $arrayApplicationData = $this->case->loadCase($arrayData["APP_UID"]);

            $arrayNextTask = [];
            $arrayNextTaskDefault = [];
            $i = 0;

            $criteria = new Criteria("workflow");

            $criteria->addSelectColumn(RoutePeer::TAS_UID);
            $criteria->addSelectColumn(RoutePeer::ROU_NEXT_TASK);
            $criteria->addSelectColumn(RoutePeer::ROU_TYPE);
            $criteria->addSelectColumn(RoutePeer::ROU_DEFAULT);
            $criteria->addSelectColumn(RoutePeer::ROU_CONDITION);

            if ($taskUid != "") {
                $criteria->add(RoutePeer::TAS_UID, $taskUid, Criteria::EQUAL);
                $criteria->addAscendingOrderByColumn(RoutePeer::ROU_CASE);

                $rsCriteria = RoutePeer::doSelectRS($criteria);
            } else {
                $criteria->addJoin(AppDelegationPeer::TAS_UID, TaskPeer::TAS_UID, Criteria::LEFT_JOIN);
                $criteria->addJoin(AppDelegationPeer::TAS_UID, RoutePeer::TAS_UID, Criteria::LEFT_JOIN);
                $criteria->add(AppDelegationPeer::APP_UID, $arrayData["APP_UID"], Criteria::EQUAL);
                $criteria->add(AppDelegationPeer::DEL_INDEX, $arrayData["DEL_INDEX"], Criteria::EQUAL);
                $criteria->addAscendingOrderByColumn(RoutePeer::ROU_CASE);

                $rsCriteria = AppDelegationPeer::doSelectRS($criteria);
            }

            $rsCriteria->setFetchmode(ResultSet::FETCHMODE_ASSOC);

            $flagDefault = false;
            $aSecJoin = [];
            $count = 0;

            while ($rsCriteria->next()) {
                $arrayRouteData = G::array_merges($rsCriteria->getRow(), $arrayData);

                if ((int)($arrayRouteData["ROU_DEFAULT"]) == 1) {
                    $arrayNextTaskDefault = $arrayRouteData;
                    $flagDefault = true;
                    continue;
                }

                $flagAddDelegation = true;

                //Evaluate the condition if there are conditions defined
                if (trim($arrayRouteData["ROU_CONDITION"]) != "" && $arrayRouteData["ROU_TYPE"] != "SELECT") {
                    $pmScript = new PMScript();
                    $pmScript->setFields($arrayApplicationData["APP_DATA"]);
                    $pmScript->setScript($arrayRouteData["ROU_CONDITION"]);
                    $pmScript->setExecutedOn(PMScript::CONDITION);
                    $flagAddDelegation = $pmScript->evaluate();
                }

                if (trim($arrayRouteData['ROU_CONDITION']) == '' && $arrayRouteData['ROU_NEXT_TASK'] != '-1') {
                    $arrayTaskData = $task->load($arrayRouteData['ROU_NEXT_TASK']);

                    if ($arrayRouteData['ROU_TYPE'] != 'SEC-JOIN' && $arrayTaskData['TAS_TYPE'] == 'GATEWAYTOGATEWAY') {
                        $flagAddDelegation = true;
                    }

                    if($arrayRouteData['ROU_TYPE'] == 'SEC-JOIN'){
                       $aSecJoin[$count]['ROU_PREVIOUS_TASK'] = $arrayRouteData['ROU_NEXT_TASK'];
                       $aSecJoin[$count]['ROU_PREVIOUS_TYPE'] = 'SEC-JOIN';
                       $count++;
                    }
                }

                if ($arrayRouteData['ROU_TYPE'] == 'EVALUATE' && !empty($arrayNextTask)) {
                    $flagAddDelegation = false;
                }

                if ($flagAddDelegation &&
                    preg_match("/^(?:EVALUATE|PARALLEL-BY-EVALUATION)$/", $arrayRouteData["ROU_TYPE"]) &&
                    trim($arrayRouteData["ROU_CONDITION"]) == ""
                ) {
                    $flagAddDelegation = false;
                }

                if ($flagAddDelegation) {
                    $arrayNextTask[++$i] = $this->prepareInformationTask($arrayRouteData);
                }
            }

            if (count($arrayNextTask) == 0 && count($arrayNextTaskDefault) > 0) {
                $arrayNextTask[++$i] = $this->prepareInformationTask($arrayNextTaskDefault);
            }

            //Check Task GATEWAYTOGATEWAY, END-MESSAGE-EVENT, END-EMAIL-EVENT
            $arrayNextTaskBackup = $arrayNextTask;

            $arrayNextTask = [];
            $i = 0;
            foreach ($arrayNextTaskBackup as $value) {
                $arrayNextTaskData = $value;
                $this->node[$value['TAS_UID']]['out'][$value['ROU_NEXT_TASK']] = $value['ROU_TYPE'];
                if ($arrayNextTaskData["NEXT_TASK"]["TAS_UID"] != "-1" &&
                    preg_match("/^(?:" . $this->regexpTaskTypeToInclude . ")$/", $arrayNextTaskData["NEXT_TASK"]["TAS_TYPE"])
                ) {
                    $arrayAux = $this->prepareInformation($arrayData, $arrayNextTaskData["NEXT_TASK"]["TAS_UID"]);
                    $this->node[$value['ROU_NEXT_TASK']]['in'][$value['TAS_UID']] = $value['ROU_TYPE'];
                    foreach ($arrayAux as $value2) {
                        $key = ++$i;
                        $arrayNextTask[$key] = $value2;
                        $prefix = substr($value['ROU_NEXT_TASK'], 0, 4);
                        if($prefix!=='gtg-'){
                            $arrayNextTask[$key]['SOURCE_UID'] = $value['ROU_NEXT_TASK'];
                        }
                        foreach($aSecJoin as $rsj){
                          $arrayNextTask[$i]["NEXT_TASK"]["ROU_PREVIOUS_TASK"] = $rsj["ROU_PREVIOUS_TASK"];
                          $arrayNextTask[$i]["NEXT_TASK"]["ROU_PREVIOUS_TYPE"] = "SEC-JOIN";
                        }
                    }
                } else {
                    $regexpTaskTypeToInclude = "END-MESSAGE-EVENT|END-EMAIL-EVENT|INTERMEDIATE-THROW-EMAIL-EVENT";

                    if ($arrayNextTaskData["NEXT_TASK"]["TAS_UID"] == "-1" &&
                        preg_match("/^(?:" . $regexpTaskTypeToInclude . ")$/", $arrayNextTaskData["TAS_TYPE"])
                    ) {
                        $arrayNextTaskData["NEXT_TASK"]["TAS_UID"] = $arrayNextTaskData["TAS_UID"] . "/" . $arrayNextTaskData["NEXT_TASK"]["TAS_UID"];
                    }
                    $prefix = substr($value['ROU_NEXT_TASK'], 0, 4);
                    if($prefix!=='gtg-'){
                        $arrayNextTaskData['SOURCE_UID'] = $value['ROU_NEXT_TASK'];
                    }
                    $arrayNextTask[++$i] = $arrayNextTaskData;
                    foreach($aSecJoin as $rsj){
                        $arrayNextTask[$i]["NEXT_TASK"]["ROU_PREVIOUS_TASK"] = $rsj["ROU_PREVIOUS_TASK"];
                        $arrayNextTask[$i]["NEXT_TASK"]["ROU_PREVIOUS_TYPE"] = "SEC-JOIN";
                    }
                    //Start-Timer with Script-task
                    $criteriaE = new Criteria("workflow");
                    $criteriaE->addSelectColumn(ElementTaskRelationPeer::ELEMENT_UID);
                    $criteriaE->addJoin(BpmnEventPeer::EVN_UID, ElementTaskRelationPeer::ELEMENT_UID, Criteria::LEFT_JOIN);
                    $criteriaE->add(ElementTaskRelationPeer::TAS_UID, $arrayNextTaskData["TAS_UID"], Criteria::EQUAL);
                    $criteriaE->add(BpmnEventPeer::EVN_TYPE, 'START', Criteria::EQUAL);
                    $criteriaE->add(BpmnEventPeer::EVN_MARKER, 'TIMER', Criteria::EQUAL);
                    $rsCriteriaE = AppDelegationPeer::doSelectRS($criteriaE);
                    $rsCriteriaE->setFetchmode(ResultSet::FETCHMODE_ASSOC);
                    while ($rsCriteriaE->next()) {
                        if($arrayNextTaskData["NEXT_TASK"]["TAS_TYPE"] == "SCRIPT-TASK"){
                            if(isset($arrayNextTaskData["NEXT_TASK"]["USER_ASSIGNED"]["USR_UID"]) && $arrayNextTaskData["NEXT_TASK"]["USER_ASSIGNED"]["USR_UID"] == ""){
                                $useruid = "00000000000000000000000000000001";
                                $userFields = $this->getUsersFullNameFromArray( $useruid );
                                $arrayNextTask[$i]["NEXT_TASK"]["USER_ASSIGNED"] = $userFields;
                            }
                        }
                    }
                }
            }

            //1. There is no rule
            if (empty($arrayNextTask)) {
                $bpmn = new \ProcessMaker\Project\Bpmn();

                throw new Exception(G::LoadTranslation(
                    'ID_NO_DERIVATION_' . (($bpmn->exists($arrayApplicationData['PRO_UID']))? 'BPMN_RULE' : 'RULE')
                ));
            }

            //Return
            return $arrayNextTask;
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * getRouteCondition
     *
     * @param array $aData
     * @return $routeCondition
     */
    function getRouteCondition ($aData)
    {
        $c = new Criteria( 'workflow' );
        $c->clearSelectColumns();
        $c->addSelectColumn( AppDelegationPeer::TAS_UID );
        $c->addSelectColumn( RoutePeer::ROU_CONDITION );
        $c->addSelectColumn( RoutePeer::ROU_NEXT_TASK );
        $c->addSelectColumn( RoutePeer::ROU_TYPE );
        $c->addSelectColumn( RoutePeer::ROU_OPTIONAL );
        $c->addJoin( AppDelegationPeer::TAS_UID, TaskPeer::TAS_UID, Criteria::LEFT_JOIN );
        $c->addJoin( AppDelegationPeer::TAS_UID, RoutePeer::TAS_UID, Criteria::LEFT_JOIN );
        $c->add( AppDelegationPeer::APP_UID, $aData['APP_UID'] );
        $c->add( AppDelegationPeer::DEL_INDEX, $aData['DEL_INDEX'] );
        $c->addAscendingOrderByColumn( RoutePeer::ROU_CASE );
        $rs = AppDelegationPeer::doSelectRs( $c );
        $rs->setFetchmode( ResultSet::FETCHMODE_ASSOC );
        $rs->next();
        $aDerivation = $rs->getRow();
        while (is_array( $aDerivation )) {
            return $aDerivation;
        }

    }

    function GetAppParentIndex ($aData)
    {
        //('SELECT * FROM APP_THREAD WHERE APP_UID='".$aData['APP_UID']."' AND DEL_INDEX = '".$aData['DEL_INDEX']."'");
        try {
            $aThreads = array ();
            $c = new Criteria();
            $c->addSelectColumn( AppThreadPeer::APP_THREAD_PARENT );
            $c->add( AppThreadPeer::APP_UID, $aData['APP_UID'] );
            $c->add( AppThreadPeer::DEL_INDEX, $aData['DEL_INDEX'] );
            $rs = AppThreadPeer::doSelectRs( $c );
            $rs->setFetchmode( ResultSet::FETCHMODE_ASSOC );
            $rs->next();
            $row = $rs->getRow();
            while (is_array( $row )) {
                $aThreads = $row;
                $rs->next();
                $row = $rs->getRow();
            }
            return $aThreads;
        } catch (exception $e) {
            throw ($e);
        }
    }

    /* get all users, from any task, if the task have Groups, the function expand the group
     *
     * @param   string  $sTasUid  the task uidUser
     * @param   bool    $flagIncludeAdHocUsers
     * @return  Array   $users an array with userID order by USR_UID
     */
    function getAllUsersFromAnyTask($sTasUid, $flagIncludeAdHocUsers = false)
    {
        $users = array ();
        $c = new Criteria( 'workflow' );
        $c->clearSelectColumns();
        $c->addSelectColumn( TaskUserPeer::USR_UID );
        $c->addSelectColumn( TaskUserPeer::TU_RELATION );
        $c->add( TaskUserPeer::TAS_UID, $sTasUid );

        if ($flagIncludeAdHocUsers) {
            $c->add(
                $c->getNewCriterion(TaskUserPeer::TU_TYPE, 1, Criteria::EQUAL)->addOr(
                $c->getNewCriterion(TaskUserPeer::TU_TYPE, 2, Criteria::EQUAL))
            );
        } else {
            $c->add(TaskUserPeer::TU_TYPE, 1);
        }

        $rs = TaskUserPeer::DoSelectRs( $c );
        $rs->setFetchmode( ResultSet::FETCHMODE_ASSOC );
        $rs->next();
        $row = $rs->getRow();
        while (is_array( $row )) {
            if ($row['TU_RELATION'] == '2') {
                $cGrp = new Criteria( 'workflow' );
                $cGrp->add( GroupwfPeer::GRP_STATUS, 'ACTIVE' );
                $cGrp->add( GroupUserPeer::GRP_UID, $row['USR_UID'] );
                $cGrp->addJoin( GroupUserPeer::GRP_UID, GroupwfPeer::GRP_UID, Criteria::LEFT_JOIN );
                $cGrp->addJoin( GroupUserPeer::USR_UID, UsersPeer::USR_UID, Criteria::LEFT_JOIN );
                $cGrp->add( UsersPeer::USR_STATUS, 'INACTIVE', Criteria::NOT_EQUAL );
                $rsGrp = GroupUserPeer::DoSelectRs( $cGrp );
                $rsGrp->setFetchmode( ResultSet::FETCHMODE_ASSOC );
                $rsGrp->next();
                $rowGrp = $rsGrp->getRow();
                while (is_array( $rowGrp )) {
                    $users[$rowGrp['USR_UID']] = $rowGrp['USR_UID'];
                    $rsGrp->next();
                    $rowGrp = $rsGrp->getRow();
                }
            } else {
                //filter to users that is in vacation or has an inactive estatus, and others
                $oUser = UsersPeer::retrieveByPK( $row['USR_UID'] );
                if ($oUser->getUsrStatus() == 'ACTIVE') {
                    $users[$row['USR_UID']] = $row['USR_UID'];
                } else {
                    $userUID = $this->checkReplacedByUser( $oUser );
                    if ($userUID != '') {
                        $users[$userUID] = $userUID;
                    }
                }
            }
            $rs->next();
            $row = $rs->getRow();
        }
        //to do: different types of sort
        sort( $users );

        return $users;
    }

    /* get an array of users, and returns the same arrays with User's fullname and other fields
     *
     * @param   Array   $aUsers      the task uidUser
     * @return  Array   $aUsersData  an array with with User's fullname
     */
    function getUsersFullNameFromArray ($aUsers)
    {
        $oUser = new Users();
        $aUsersData = array ();
        if (is_array( $aUsers )) {
            foreach ($aUsers as $key => $val) {
                // $userFields = $oUser->load( $val );
                $userFields = $oUser->userVacation( $val );
                $auxFields['USR_UID'] = $userFields['USR_UID'];
                $auxFields['USR_USERNAME'] = $userFields['USR_USERNAME'];
                $auxFields['USR_FIRSTNAME'] = $userFields['USR_FIRSTNAME'];
                $auxFields['USR_LASTNAME'] = $userFields['USR_LASTNAME'];
                $auxFields['USR_FULLNAME'] = $userFields['USR_LASTNAME'] . ($userFields['USR_LASTNAME'] != '' ? ', ' : '') . $userFields['USR_FIRSTNAME'];
                $auxFields['USR_EMAIL'] = $userFields['USR_EMAIL'];
                $auxFields['USR_STATUS'] = $userFields['USR_STATUS'];
                $auxFields['USR_COUNTRY'] = $userFields['USR_COUNTRY'];
                $auxFields['USR_CITY'] = $userFields['USR_CITY'];
                $auxFields['USR_LOCATION'] = $userFields['USR_LOCATION'];
                $auxFields['DEP_UID'] = $userFields['DEP_UID'];
                $auxFields['USR_HIDDEN_FIELD'] = '';
                $aUsersData[] = $auxFields;
            }
        } else {
            $oCriteria = new Criteria();
            $oCriteria->add( UsersPeer::USR_UID, $aUsers );

            if (UsersPeer::doCount( $oCriteria ) < 1) {
                return null;
            }
            $userFields = $oUser->load( $aUsers );
            $auxFields['USR_UID'] = $userFields['USR_UID'];
            $auxFields['USR_USERNAME'] = $userFields['USR_USERNAME'];
            $auxFields['USR_FIRSTNAME'] = $userFields['USR_FIRSTNAME'];
            $auxFields['USR_LASTNAME'] = $userFields['USR_LASTNAME'];
            $auxFields['USR_FULLNAME'] = $userFields['USR_LASTNAME'] . ($userFields['USR_LASTNAME'] != '' ? ', ' : '') . $userFields['USR_FIRSTNAME'];
            $auxFields['USR_EMAIL'] = $userFields['USR_EMAIL'];
            $auxFields['USR_STATUS'] = $userFields['USR_STATUS'];
            $auxFields['USR_COUNTRY'] = $userFields['USR_COUNTRY'];
            $auxFields['USR_CITY'] = $userFields['USR_CITY'];
            $auxFields['USR_LOCATION'] = $userFields['USR_LOCATION'];
            $auxFields['DEP_UID'] = $userFields['DEP_UID'];
            $aUsersData = $auxFields;
        }
        return $aUsersData;
    }

    /* get next assigned user
     *
     * @param   Array   $tasInfo
     * @return  Array   $userFields
     */
    function getNextAssignedUser ($tasInfo)
    {
        $nextAssignedTask = $tasInfo['NEXT_TASK'];
        $lastAssigned = $tasInfo['NEXT_TASK']['TAS_LAST_ASSIGNED'];
        $sTasUid = $tasInfo['NEXT_TASK']['TAS_UID'];

        $taskNext = TaskPeer::retrieveByPK($nextAssignedTask["TAS_UID"]);
        $bpmnActivityNext = BpmnActivityPeer::retrieveByPK($nextAssignedTask["TAS_UID"]);

        $flagTaskNextIsMultipleInstance = false;
        $flagTaskNextAssignTypeIsMultipleInstance = false;

        if (!is_null($taskNext) && !is_null($bpmnActivityNext)) {
            $flagTaskNextIsMultipleInstance = $bpmnActivityNext->getActType() == "TASK" && preg_match("/^(?:EMPTY|SENDTASK|RECEIVETASK|USERTASK|SERVICETASK|MANUALTASK|BUSINESSRULE)$/", $bpmnActivityNext->getActTaskType()) && $bpmnActivityNext->getActLoopType() == "PARALLEL";
            $flagTaskNextAssignTypeIsMultipleInstance = preg_match("/^(?:MULTIPLE_INSTANCE|MULTIPLE_INSTANCE_VALUE_BASED)$/", $taskNext->getTasAssignType());
        }

        $taskNextAssignType = $taskNext->getTasAssignType();
        $taskNextAssignType = ($flagTaskNextIsMultipleInstance && !$flagTaskNextAssignTypeIsMultipleInstance)? "" : $taskNextAssignType;
        $taskNextAssignType = (!$flagTaskNextIsMultipleInstance && $flagTaskNextAssignTypeIsMultipleInstance)? "" : $taskNextAssignType;

        switch ($taskNextAssignType) {
            case 'BALANCED':
                $users = $this->getAllUsersFromAnyTask( $sTasUid );
                $uidUser = "";

                if (is_array( $users ) && count( $users ) > 0) {
                    //to do apply any filter like LOCATION assignment
                    $uidUser = $users[0];
                    $i = count( $users ) - 1;
                    while ($i > 0) {
                        if ($lastAssigned < $users[$i]) {
                            $uidUser = $users[$i];
                        }
                        $i --;
                    }
                } else {
                    throw (new Exception( G::LoadTranslation( 'ID_NO_USERS' ) ));
                }
                $userFields = $this->getUsersFullNameFromArray( $uidUser );
                break;
            case 'STATIC_MI':
            case 'CANCEL_MI':
            case 'MANUAL':
                $users = $this->getAllUsersFromAnyTask( $sTasUid );
                $userFields = $this->getUsersFullNameFromArray( $users );
                break;
            case 'EVALUATE':
                $AppFields = $this->case->loadCase( $tasInfo['APP_UID'] );
                $variable = str_replace( '@@', '', $nextAssignedTask['TAS_ASSIGN_VARIABLE'] );
                if (isset( $AppFields['APP_DATA'][$variable] )) {
                    if ($AppFields['APP_DATA'][$variable] != '') {
                        $value = $this->checkReplacedByUser( $AppFields['APP_DATA'][$variable] );
                        $userFields = $this->getUsersFullNameFromArray( $value );
                        if (is_null( $userFields )) {
                            throw (new Exception( "Task doesn't have a valid user in variable $variable." ));
                        }
                    } else {
                        throw (new Exception( "Task doesn't have a valid user in variable $variable." ));
                    }
                } else {
                    throw (new Exception( "Task doesn't have a valid user in variable $variable or this variable doesn't exist." ));
                }
                break;
            case 'REPORT_TO':
                //default error user when the reportsTo is not assigned to that user
                //look for USR_REPORTS_TO to this user
                $userFields['USR_UID'] = '';
                $userFields['USR_FULLNAME'] = 'Current user does not have a valid Reports To user';
                $userFields['USR_USERNAME'] = 'Current user does not have a valid Reports To user';
                $userFields['USR_FIRSTNAME'] = '';
                $userFields['USR_LASTNAME'] = '';
                $userFields['USR_EMAIL'] = '';

                //get the report_to user & its full info
                $lastManager = $userTasInfo = $tasInfo["USER_UID"];
                do {
                    $userTasInfo = $this->getDenpendentUser($userTasInfo);
                    $useruid = $this->checkReplacedByUser($userTasInfo);
                    //When the lastManager is INACTIVE/VACATION and does not have a Replace by, the REPORT_TO is himself
                    if($lastManager === $userTasInfo){
                        $useruid = $tasInfo["USER_UID"];
                    } else {
                        $lastManager = $userTasInfo;
                    }
                } while ($useruid === '');

                if (isset( $useruid ) && $useruid != '') {
                    $userFields = $this->getUsersFullNameFromArray( $useruid );
                }
                // if there is no report_to user info, throw an exception indicating this
                if (! isset( $userFields ) || $userFields['USR_UID'] == '') {
                    throw (new Exception( G::LoadTranslation( 'ID_MSJ_REPORSTO' ) )); // "The current user does not have a valid Reports To user.  Please contact administrator.") ) ;
                }
                break;
            case 'SELF_SERVICE':
                //Check if is Self Service Value Based Assignment
                if (!empty($nextAssignedTask['TAS_GROUP_VARIABLE'])) {
                    $appFields = $this->case->loadCase($tasInfo['APP_UID']);
                    $variable = str_replace('@@', '', $nextAssignedTask['TAS_GROUP_VARIABLE']);

                    //If the variable exists will be validate the UID's
                    if (isset($appFields['APP_DATA'][$variable])) {
                        $arrVar = $appFields['APP_DATA'][$variable];
                        if (is_array($arrVar)) {
                            $statusToCheck = $arrVar;
                        } else {
                            $statusToCheck = array($arrVar);
                        }
                        $toValidate = array('ACTIVE', 'VACATION');
                        $gpr = new GroupUser();
                        if (!$gpr->groupsUsersAvailable($statusToCheck, $toValidate)) {
                            if (!($gpr->groupsUsersAvailable($statusToCheck, $toValidate, "groups"))) {
                                throw (new Exception("Task doesn't have a valid user in variable $variable or this variable doesn't exist."));
                            }
                        }
                    } else {
                        throw (new Exception("Task doesn't have a valid user in variable $variable or this variable doesn't exist."));
                    }
                }

                //look for USR_REPORTS_TO to this user
                $userFields['USR_UID'] = '';
                $userFields['USR_FULLNAME'] = '<b>' . G::LoadTranslation('ID_UNASSIGNED') . '</b>';
                $userFields['USR_USERNAME'] = '<b>' . G::LoadTranslation('ID_UNASSIGNED') . '</b>';
                $userFields['USR_FIRSTNAME'] = '';
                $userFields['USR_LASTNAME'] = '';
                $userFields['USR_EMAIL'] = '';
                break;
            case "MULTIPLE_INSTANCE":
                $userFields = $this->getUsersFullNameFromArray($this->getAllUsersFromAnyTask($nextAssignedTask["TAS_UID"]));
                if(empty($userFields)){
                    throw (new Exception( G::LoadTranslation( 'ID_NO_USERS' ) ));
                }
                break;
            case "MULTIPLE_INSTANCE_VALUE_BASED":
                $arrayApplicationData = $this->case->loadCase($tasInfo["APP_UID"]);
                $nextTaskAssignVariable = trim($nextAssignedTask["TAS_ASSIGN_VARIABLE"], " @#");
                // Verify the variable defined
                if (
                    !empty($nextTaskAssignVariable)
                    && isset($arrayApplicationData["APP_DATA"][$nextTaskAssignVariable])
                    && !empty($arrayApplicationData["APP_DATA"][$nextTaskAssignVariable])
                ) {
                    if (is_array($arrayApplicationData["APP_DATA"][$nextTaskAssignVariable])) {
                        $listUsers = $arrayApplicationData["APP_DATA"][$nextTaskAssignVariable];
                        // Check if the array does not have an empty value
                        foreach ($listUsers as $user) {
                            if (empty($user)) {
                                throw new Exception(G::LoadTranslation("ID_ACTIVITY_INVALID_USER_DATA_VARIABLE_FOR_MULTIPLE_INSTANCE_ACTIVITY", [strtolower("ACT_UID"), $nextAssignedTask["TAS_UID"], $nextTaskAssignVariable]));
                            }
                        }
                    }
                    $userFields = $this->getUsersFullNameFromArray($arrayApplicationData["APP_DATA"][$nextTaskAssignVariable]);
                } else {
                    throw new Exception(G::LoadTranslation("ID_ACTIVITY_INVALID_USER_DATA_VARIABLE_FOR_MULTIPLE_INSTANCE_ACTIVITY", [strtolower("ACT_UID"), $nextAssignedTask["TAS_UID"], $nextTaskAssignVariable]));
                }
                break;
            default:
                throw (new Exception( 'Invalid Task Assignment method for Next Task ' ));
                break;
        }
        return $userFields;
    }

    /* getDenpendentUser
     *
     * @param   string   $USR_UID
     * @return  string   $aRow['USR_REPORTS_TO']
     */
    function getDenpendentUser ($USR_UID)
    {
        $user = new \ProcessMaker\BusinessModel\User();

        $manager = $user->getUsersManager($USR_UID);

        //Return
        return ($manager !== false)? $manager : $USR_UID;
    }

    /* setTasLastAssigned
     *
     * @param   string   $tasUid
     * @param   string   $usrUid
     * @throws  Exception $e
     * @return  void
     */
    function setTasLastAssigned ($tasUid, $usrUid)
    {
        try {
            $oTask = TaskPeer::retrieveByPk( $tasUid );
            $oTask->setTasLastAssigned( $usrUid );
            $oTask->save();
        } catch (Exception $e) {
            throw ($e);
        }
    }

    /**
     * Execute Event
     *
     * @param string $dummyTaskUid Unique id of Element Origin      (unique id of Task) This is the nextTask
     * @param array $applicationData Case data
     * @param bool $flagEventExecuteBeforeGateway Execute event before gateway
     * @param bool $flagEventExecuteAfterGateway Execute event after gateway
     * @param int $taskId
     *
     * @return void
     * @see Derivation->derivate()
     * @see Derivation->doRouteWithoutThread()
     * @see Derivation->finishProcess()
     * @see Derivation->finishTask()
     */
    private function executeEvent($dummyTaskUid, array $applicationData, $flagEventExecuteBeforeGateway = true, $flagEventExecuteAfterGateway = true, $elementOriUid='',$tasId = 0)
    {
        try {
            //Verify if the Project is BPMN
            $bpmn = new \ProcessMaker\Project\Bpmn();

            if (!$bpmn->exists($applicationData["PRO_UID"])) {
                return;
            }

            //Element origin and dest
            $arrayElement = [];
            $elementTaskRelation = new \ProcessMaker\BusinessModel\ElementTaskRelation();
            $arrayElementTaskRelationData = $elementTaskRelation->getElementTaskRelationWhere(
                    [
                        ElementTaskRelationPeer::PRJ_UID      => $applicationData["PRO_UID"],
                        ElementTaskRelationPeer::ELEMENT_TYPE => "bpmnEvent",
                        ElementTaskRelationPeer::TAS_UID      => $dummyTaskUid
                    ],
                    true
            );
            if(is_null($arrayElementTaskRelationData)){
                $arrayOtherElement = [];
                $arrayOtherElement = $bpmn->getElementsBetweenElementOriginAndElementDest(
                    $elementOriUid,
                    "bpmnActivity",
                    $dummyTaskUid,
                    "bpmnActivity"
                );
                $count = 0;
                foreach ($arrayOtherElement as $value) {
                    if($value[1] === 'bpmnEvent'){
                        $arrayElement[$count]["uid"]    = $value[0];
                        $arrayElement[$count++]["type"] = $value[1];
                    }
                }
            }
            if (!is_null($arrayElementTaskRelationData)) {
                $arrayElement[0]["uid"]  = $arrayElementTaskRelationData["ELEMENT_UID"];
                $arrayElement[0]["type"] = "bpmnEvent";
            }

            //Throw Events
            $messageApplication = new \ProcessMaker\BusinessModel\MessageApplication();
            $emailEvent = new \ProcessMaker\BusinessModel\EmailEvent();
            $arrayEventExecute = ["BEFORE" => $flagEventExecuteBeforeGateway, "AFTER" => $flagEventExecuteAfterGateway];
            $positionEventExecute = "BEFORE";

            $context = $this->context;
            $context['appUid'] = $applicationData["APP_UID"];
            $context['proUid'] = $applicationData["PRO_UID"];
            if(sizeof($arrayElement)){
                foreach ($arrayElement as $value) {
                    switch ($value['type']) {
                        case 'bpmnEvent':
                            if ($arrayEventExecute[$positionEventExecute]) {
                                $event = \BpmnEventPeer::retrieveByPK($value['uid']);

                                if (!is_null($event)) {
                                    if (preg_match("/^(?:END|INTERMEDIATE)$/", $event->getEvnType()) && $event->getEvnMarker() === 'MESSAGETHROW') {
                                        //Message-Application throw
                                        $result = $messageApplication->create($applicationData["APP_UID"], $applicationData["PRO_UID"], $value['uid'], $applicationData);

                                        $context['envUid'] = $value['uid'];
                                        $context['envType'] = $event->getEvnType();
                                        $context['envMarker'] = $event->getEvnMarker();
                                        $context['action'] = 'Message application throw';
                                        //Logger
                                        $message = 'Case Derivation';
                                        Log::channel(':CaseDerivation')->info($message, Bootstrap::context($context));
                                    }

                                    if (preg_match("/^(?:END|INTERMEDIATE)$/", $event->getEvnType()) && $event->getEvnMarker() === 'EMAIL') {
                                        //Email-Event throw
                                        $result = $emailEvent->sendEmail($applicationData["APP_UID"], $applicationData["PRO_UID"], $value['uid'], $applicationData, $tasId);

                                        $context['envUid'] = $value['uid'];
                                        $context['envType'] = $event->getEvnType();
                                        $context['envMarker'] = $event->getEvnMarker();
                                        $context['action'] = 'Email event throw';
                                        //Logger
                                        $message = 'Case Derivation';
                                        Log::channel(':CaseDerivation')->info($message, Bootstrap::context($context));
                                    }
                                }
                            }
                            break;
                        case 'bpmnGateway':
                            $positionEventExecute = 'AFTER';
                            break;
                    }
                }
            }

        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * Update counters
     *
     * @param array $arrayCurrentDelegationData
     * @param array $arrayNextDelegationData
     * @param mixed $taskNextDelegation
     * @param array $arrayApplicationData
     * @param int   $delIndexNew
     * @param mixed $aSp
     * @param bool  $removeList
     *
     * @return void
     * @deprecated function deprecated in Release 3.6.x
     */
    private function updateList(array $arrayCurrentDelegationData, array $arrayNextDelegationData, $taskNextDelegation, array $arrayApplicationData, $delIndexNew, $aSp, $removeList)
    {
        /*----------------------------------********---------------------------------*/
        try {
            if ($arrayNextDelegationData["TAS_UID"] != "-1") {
                $regexpTaskTypeToExclude = "WEBENTRYEVENT|END-MESSAGE-EVENT|START-MESSAGE-EVENT|INTERMEDIATE-THROW-MESSAGE-EVENT|INTERMEDIATE-CATCH-MESSAGE-EVENT|SCRIPT-TASK|INTERMEDIATE-CATCH-TIMER-EVENT";

                if (!preg_match("/^(?:" . $regexpTaskTypeToExclude . ")$/", $taskNextDelegation->getTasType())) {
                    if (!empty($delIndexNew) && empty($aSp)) {
                        $appDelegation = AppDelegationPeer::retrieveByPK($arrayApplicationData["APP_UID"], $delIndexNew);
                        $arrayApplicationData2 = $appDelegation->toArray(BasePeer::TYPE_FIELDNAME);

                        $arrayApplicationData2["APP_STATUS"] = $arrayCurrentDelegationData["APP_STATUS"];

                        $taskCurrent = TaskPeer::retrieveByPK($arrayCurrentDelegationData["TAS_UID"]);

                        if ($taskCurrent->getTasType() == "INTERMEDIATE-CATCH-MESSAGE-EVENT") {
                            $removeList = false;
                        }

                        $arrayApplicationData2["REMOVED_LIST"] = $removeList;

                        // Force to delete the row for avoid problems with duplicate key insert
                        $inbox = new ListInbox();
                        $inbox->remove($arrayApplicationData2['APP_UID'], $arrayApplicationData2['DEL_INDEX']);
                        // Update the new row
                        $inbox = new ListInbox();
                        $inbox->newRow($arrayApplicationData2, $arrayApplicationData["CURRENT_USER_UID"], (($arrayNextDelegationData["TAS_ASSIGN_TYPE"] == "SELF_SERVICE")? true : false));
                    }
                }
            }
        } catch (Exception $e) {
            throw $e;
        }
        /*----------------------------------********---------------------------------*/
    }

    /**
     * This function prepare the information before call the derivate function
     *
     * We can route a case from differents ways from cases_Derivate and derivateCase used in PMFDerivateCase
     * before this we need to process the information
     *
     * @param array $aDataForPrepareInfo
     * @param array $tasks
     * @param string $rouType
     * @param array $aCurrentDerivation
     * @return array $arrayDerivationResult
     */
    function beforeDerivate($aDataForPrepareInfo, $tasks, $rouType, $aCurrentDerivation)
    {
        $aPInformation = $this->prepareInformation($aDataForPrepareInfo);
        $oRoute = new \ProcessMaker\Core\RoutingScreen();
        $nextTasks = $oRoute->mergeDataDerivation($tasks, $aPInformation, $rouType);

        //Get all route types
        $aRouteTypes = [];
        foreach ($aPInformation as $key => $value) {
            $aRouteTypes[$key]['ROU_NEXT_TASK'] = $value['ROU_NEXT_TASK'];
            $aRouteTypes[$key]['ROU_TYPE'] = $value['ROU_TYPE'];
        }
        $aCurrentDerivation['ROUTE_TYPES'] = $aRouteTypes;

        //Derivate the case
        $arrayDerivationResult = $this->derivate($aCurrentDerivation, $nextTasks);

        return $arrayDerivationResult;
    }

    /**
     * Route the case
     * If need to create another thread we can execute the doDerivate
     *
     * @param array $currentDelegation
     * @param array $nextDelegations
     * @param bool $removeList
     *
     * @return void
     * @throws Exception
     *
     * @see beforeDerivate()
     * @see doDerivation()
     * @see verifyIsCaseChild()
     */
    function derivate(array $currentDelegation, array $nextDelegations, $removeList = true)
    {
        $this->sysSys = (!empty(config("system.workspace")))? config("system.workspace") : "Undefined";
        $this->context = Bootstrap::context();
        $context = $this->context;
        $this->removeList = $removeList;
        $arrayDerivationResult = [];

        //define this...
        if (! defined( 'TASK_FINISH_PROCESS' )) {
            define( 'TASK_FINISH_PROCESS', - 1 );
        }
        if (! defined( 'TASK_FINISH_TASK' )) {
            define( 'TASK_FINISH_TASK', - 2 );
        }

        $this->case = new Cases();

        //Get data for this DEL_INDEX current
        $appFields = $this->case->loadCase( $currentDelegation['APP_UID'], $currentDelegation['DEL_INDEX'] );
        $context['appUid'] = $currentDelegation['APP_UID'];
        $context['delIndex'] = $currentDelegation['DEL_INDEX'];

        // Remove the fields that will update with the thread creation
        unset($appFields['APP_ROUTING_DATA']);
        $this->appCurrentUser = $appFields['APP_CUR_USER'];
        unset($appFields['APP_CUR_USER']);

        //We close the current derivation, then we'll try to derivate to each defined route
        $this->case->CloseCurrentDelegation( $currentDelegation['APP_UID'], $currentDelegation['DEL_INDEX'] );

        //Set THE APP_STATUS
        $appFields['APP_STATUS'] = $currentDelegation['APP_STATUS'];

        //Get data for current delegation (current Task)
        $task = TaskPeer::retrieveByPK($currentDelegation["TAS_UID"]);
        $bpmnActivity = BpmnActivityPeer::retrieveByPK($currentDelegation["TAS_UID"]);

        $flagTaskIsMultipleInstance = false;
        $flagTaskAssignTypeIsMultipleInstance = false;

        if (!is_null($task) && !is_null($bpmnActivity)) {
            $flagTaskIsMultipleInstance = $bpmnActivity->getActType() == "TASK" && preg_match("/^(?:EMPTY|USERTASK|MANUALTASK)$/", $bpmnActivity->getActTaskType()) && $bpmnActivity->getActLoopType() == "PARALLEL";
            $flagTaskAssignTypeIsMultipleInstance = preg_match("/^(?:MULTIPLE_INSTANCE|MULTIPLE_INSTANCE_VALUE_BASED)$/", $task->getTasAssignType());
        }

        $currentDelegation["TAS_ASSIGN_TYPE"] = $task->getTasAssignType();
        $currentDelegation["TAS_MI_COMPLETE_VARIABLE"] = $task->getTasMiCompleteVariable();
        $currentDelegation["TAS_MI_INSTANCE_VARIABLE"] = $task->getTasMiInstanceVariable();

        $arrayNextDerivation = [];
        $flagFirstIteration = true;

        foreach ($nextDelegations as $nextDel) {
            //BpmnEvent - END-MESSAGE-EVENT, END-EMAIL-EVENT, INTERMEDIATE-THROW-EMAIL-EVENT
            //Check and get unique id
            if (preg_match("/^(.{32})\/(\-1)$/", $nextDel["TAS_UID"], $arrayMatch)) {
                $nextDel["TAS_UID"] = $arrayMatch[2];
                $nextDel["TAS_UID_DUMMY"] = $arrayMatch[1];
            }

            //Review if is a Subprocesses then we update the $nextDel
            if (!empty($nextDel['TAS_PARENT'])) {
                $subProcessNextDel = $this->getNextInfoSubProcess($nextDel, $appFields['PRO_UID']);
                if(count($subProcessNextDel) > 0) {
                    $nextDel = $subProcessNextDel;
                    $aSP = $this->aSP;
                } else {
                    continue;
                }
            }

            //get open threads
            $openThreads = $this->case->GetOpenThreads( $currentDelegation['APP_UID'] );
            //if we are derivating to finish process but there are no more open thread then we are finishing only the task, we are not finishing the whole process
            if (($nextDel['TAS_UID'] == TASK_FINISH_PROCESS) && (($openThreads + 1) > 1)) {
                $nextDel['TAS_UID'] = TASK_FINISH_TASK;
            }

            $taskNextDel = TaskPeer::retrieveByPK($nextDel["TAS_UID"]); //Get data for next delegation (next Task)
            $bpmnActivityNextDel = BpmnActivityPeer::retrieveByPK($nextDel["TAS_UID"]);

            $flagTaskNextDelIsMultipleInstance = false;
            $flagTaskNextDelAssignTypeIsMultipleInstance = false;

            if (!is_null($taskNextDel) && !is_null($bpmnActivityNextDel)) {
                $flagTaskNextDelIsMultipleInstance = $bpmnActivityNextDel->getActType() == "TASK" && preg_match("/^(?:EMPTY|USERTASK|MANUALTASK)$/", $bpmnActivityNextDel->getActTaskType()) && $bpmnActivityNextDel->getActLoopType() == "PARALLEL";
                $flagTaskNextDelAssignTypeIsMultipleInstance = preg_match("/^(?:MULTIPLE_INSTANCE|MULTIPLE_INSTANCE_VALUE_BASED)$/", $taskNextDel->getTasAssignType());
            }

            $this->flagUpdateList = true;

            $context['tasUid'] = $nextDel['TAS_UID'];
            switch ($nextDel['TAS_UID']) {
                case TASK_FINISH_PROCESS:
                    $this->finishProcess(
                        $currentDelegation,
                        $nextDel,
                        $appFields,
                        $flagFirstIteration,
                        $context
                    );
                    break;
                case TASK_FINISH_TASK:
                    $this->finishTask(
                        $currentDelegation,
                        $nextDel,
                        $appFields,
                        $flagFirstIteration,
                        $flagTaskAssignTypeIsMultipleInstance,
                        $context
                    );
                    break;
                default:
                    //Get all siblingThreads
                    $canDerivate = false;
                    $nextDel['TAS_ID'] = $taskNextDel->getTasId();

                    switch ($currentDelegation['TAS_ASSIGN_TYPE']) {
                        case 'CANCEL_MI':
                        case 'STATIC_MI':
                            $canDerivate = $this->canRouteTasAssignStaticMi($currentDelegation);
                            break;
                        default:
                            $routeType = $currentDelegation["ROU_TYPE"];
                            $routeType = ($flagTaskIsMultipleInstance && $flagTaskAssignTypeIsMultipleInstance)? "SEC-JOIN" : $routeType;
                            switch ($routeType) {
                                case "SEC-JOIN":
                                    $canDerivate = $this->canRouteTypeSecJoin(
                                        $flagTaskIsMultipleInstance,
                                        $flagTaskAssignTypeIsMultipleInstance,
                                        $currentDelegation,
                                        $appFields,
                                        $nextDel
                                    );
                                    $arraySiblings = $this->arraySiblings;
                                    break;
                                default:
                                    $canDerivate = true;
                                    //Check if the previous is a SEC-JOIN and check threads
                                    if(isset($nextDel["ROU_PREVIOUS_TYPE"])){
                                        if($nextDel["ROU_PREVIOUS_TYPE"] == "SEC-JOIN"){
                                            $arrayOpenThread = $this->case->searchOpenPreviousTasks(
                                                $nextDel["ROU_PREVIOUS_TASK"],
                                                $currentDelegation["APP_UID"]
                                            );
                                            $arraySiblings = $this->case->getOpenSiblingThreads(
                                                $nextDel["ROU_PREVIOUS_TASK"],
                                                $currentDelegation["APP_UID"],
                                                $currentDelegation["DEL_INDEX"],
                                                $currentDelegation["TAS_UID"]
                                            );
                                            if(is_array($arrayOpenThread) && is_array($arraySiblings)){
                                               $arrayOpenThread = array_merge($arrayOpenThread, $arraySiblings);
                                            }
                                            $canDerivate = empty($arrayOpenThread);
                                        }
                                    }
                                    break;
                            }
                            break;
                    }

                    if ($canDerivate) {
                        /**
                         * CREATE a new index/thread in the AppDelegation
                         */
                        $nextDel['USR_UID']  = (!isset($nextDel['USR_UID'])) ? '' : $nextDel['USR_UID'];
                        //Throw Events
                        $this->executeEvent(
                            $nextDel["TAS_UID"],
                            $appFields,
                            true,
                            true,
                            $currentDelegation["TAS_UID"],
                            !empty($nextDel["TAS_ID"]) ? $nextDel["TAS_ID"] : 0
                        );

                        //Route the case
                        $aSP = (isset($aSP))? $aSP : null;

                        $taskNextDelAssignType = ($flagTaskNextDelIsMultipleInstance && $flagTaskNextDelAssignTypeIsMultipleInstance)? $taskNextDel->getTasAssignType() : "";

                        switch ($taskNextDelAssignType) {
                            case "MULTIPLE_INSTANCE":
                            case "MULTIPLE_INSTANCE_VALUE_BASED":
                                $arrayDerivationResult = $this->routeMultipleInstance(
                                    $currentDelegation,
                                    $nextDel,
                                    $taskNextDel,
                                    $appFields,
                                    $aSP,
                                    $this->removeList
                                );
                                $iNewDelIndex = $this->iNewDelIndex;
                                 break;
                            default:
                                $iNewDelIndex = $this->doDerivation($currentDelegation, $nextDel, $appFields, $aSP);
                                $appUid = $currentDelegation['APP_UID'];
                                // Load Case Data again because the information could be change in method "doDerivation"
                                $lastData = $this->case->loadCase($appUid);
                                // Update the thread title related to the last index created
                                if (!is_null($iNewDelIndex)) {
                                    $this->case->updateThreadTitle($appUid, $lastData['APP_NUMBER'], $iNewDelIndex, $lastData['APP_DATA']);
                                }
                                $appFields['APP_DATA'] = $lastData['APP_DATA'];
                                // When the users route the case in the same time
                                if($iNewDelIndex !== 0) {
                                    $arrayDerivationResult[] = [
                                        'DEL_INDEX' => $iNewDelIndex,
                                        'TAS_UID' => $nextDel['TAS_UID'],
                                        'USR_UID' => (isset($nextDel['USR_UID']))? $nextDel['USR_UID'] : ''
                                    ];
                                }
                                break;
                        }
                        //Execute Service Task
                        if (function_exists('executeServiceTaskByActivityUid')) {
                            $appFields["APP_DATA"] = executeServiceTaskByActivityUid($nextDel["TAS_UID"], $appFields);
                        }

                        //Execute Script-Task
                        $scriptTask = new \ProcessMaker\BusinessModel\ScriptTask();
                        $appFields["APP_DATA"] = $scriptTask->execScriptByActivityUid($nextDel["TAS_UID"], $appFields);

                        //Create record in table APP_ASSIGN_SELF_SERVICE_VALUE
                        $regexpTaskTypeToExclude = "SCRIPT-TASK|INTERMEDIATE-THROW-EMAIL-EVENT|SERVICE-TASK";
                        if (!is_null($taskNextDel) && !preg_match("/^(?:" . $regexpTaskTypeToExclude . ")$/", $taskNextDel->getTasType())) {
                            $this->createRecordAppSelfServiceValue(
                                $taskNextDel,
                                $iNewDelIndex,
                                $nextDel["TAS_UID"],
                                $appFields,
                                $taskNextDel->getTasId()
                            );
                        }

                        //Elements with automatic execution
                        $aTaskTypeToInclude = array('SCRIPT-TASK', 'INTERMEDIATE-THROW-EMAIL-EVENT', 'INTERMEDIATE-THROW-MESSAGE-EVENT', 'SERVICE-TASK');
                        //Prepare information when the current is $aTaskTypeToInclude and the next element does not have user
                        if (!is_null($taskNextDel) && in_array($taskNextDel->getTasType(), $aTaskTypeToInclude)) {
                            $arrayNextDerivation[] = $this->routePrepareInformationNextTask(
                                $currentDelegation,
                                $iNewDelIndex,
                                $nextDel
                            );
                        }
                    } else {
                        /**
                         * NO CREATE a new index/thread in the AppDelegation
                         */
                        $this->doRouteWithoutThread($appFields, $currentDelegation, $nextDel, $arraySiblings, $flagTaskIsMultipleInstance, $flagTaskAssignTypeIsMultipleInstance, $flagFirstIteration);
                    }

                    break;
            }
            //$flagUpdateList is updated when is parallel
            if(!is_null($taskNextDel) && $this->flagUpdateList){
                $this->updateList(
                    $currentDelegation,
                    $nextDel,
                    $taskNextDel,
                    $appFields,
                    (isset($iNewDelIndex))? $iNewDelIndex : 0,
                    (isset($aSP))? $aSP : null,
                    $this->removeList
                );
            }

            $this->removeList = false;
            $flagFirstIteration = false;

            unset($aSP);
        }


        /* Start Block : Count the open threads of $currentDelegation['APP_UID'] */
        $openThreads = $this->case->GetOpenThreads( $currentDelegation['APP_UID'] );
        $flagUpdateCase = false;

        //Check if there is any paused thread
        $existThreadPaused = false;
        if (isset($arraySiblings['pause'])) {
            if (!empty($arraySiblings['pause'])) {
                $existThreadPaused = true;
            }
        }

        if ($openThreads == 0 && !$existThreadPaused) {
            //Close case
            $appFields["APP_STATUS"] = "COMPLETED";
            $appFields["APP_FINISH_DATE"] = "now";
            if (SubApplication::isCaseSubProcess($currentDelegation["APP_UID"])) {
                $this->verifyIsCaseChild($currentDelegation["APP_UID"], $currentDelegation["DEL_INDEX"]);
            }
            $flagUpdateCase = true;
        }

        //The variable $iNewDelIndex will be true if we created a new index the variable
        if (isset($iNewDelIndex)) {
            $appFields["DEL_INDEX"] = $iNewDelIndex;
            $excludeTasUid = array(TASK_FINISH_PROCESS, TASK_FINISH_TASK);
            //If the last TAS_UID value is not valid we will check for the valid TAS_UID value
            if (in_array($nextDel["TAS_UID"], $excludeTasUid) && is_array($arrayDerivationResult) && isset(current($arrayDerivationResult)["TAS_UID"])) {
                $appFields["TAS_UID"] = current($arrayDerivationResult)["TAS_UID"];
            } else {
                $appFields["TAS_UID"] = $nextDel["TAS_UID"];
            }
            $flagUpdateCase = true;
        }

        if ($flagUpdateCase) {
            //Start Block : UPDATES APPLICATION
            $this->case->updateCase( $currentDelegation["APP_UID"], $appFields );
            //End Block : UPDATES APPLICATION
        }

        //Start the next derivations (Script-Task)
        if (!empty($arrayNextDerivation)) {
            foreach ($arrayNextDerivation as $value) {
                $this->derivate($value["currentDelegation"], $value["nextDelegations"]);
            }
        }

        //Return
        return $arrayDerivationResult;
    }

    /** We can create a new thread in the appDelegation
     * This function is used in derivate
     *
     * @param array $currentDelegation
     * @param array $nextDel
     * @param array $appFields
     * @param array $aSP
     *
     * @return integer
     * @throws /Exception
     */
    function doDerivation ($currentDelegation, $nextDel, $appFields, $aSP = null)
    {
        $case = new \ProcessMaker\BusinessModel\Cases();
        $arrayApplicationData = $case->getApplicationRecordByPk($currentDelegation['APP_UID'], [], false);

        $arrayRoutingData = (!is_null($arrayApplicationData['APP_ROUTING_DATA']) && (string)($arrayApplicationData['APP_ROUTING_DATA']) != '')? unserialize($arrayApplicationData['APP_ROUTING_DATA']) : [];

        $iAppThreadIndex = $appFields['DEL_THREAD'];
        $delType = 'NORMAL';
        $sendNotifications = false;
        $sendNotificationsMobile = false;

        $appDelegation = new AppDelegation();
        $taskNextDel = TaskPeer::retrieveByPK($nextDel["TAS_UID"]);

        $arrayAppDelegationPrevious = $appDelegation->getPreviousDelegationValidTask($currentDelegation['APP_UID'], $currentDelegation['DEL_INDEX'], true);

        $taskUidOrigin = $arrayAppDelegationPrevious['TAS_UID'];
        $taskUidDest   = $taskNextDel->getTasUid();

        if (array_key_exists($taskUidOrigin . '/' . $taskUidDest, $arrayRoutingData)) {
            if(isset($arrayRoutingData[$taskUidOrigin . '/' . $taskUidDest]['USR_UID'])){
                $nextDel['USR_UID'] = $arrayRoutingData[$taskUidOrigin . '/' . $taskUidDest]['USR_UID'];
            }
            unset($arrayRoutingData[$taskUidOrigin . '/' . $taskUidDest]);
        }

        if ($taskNextDel->getTasType() == 'NORMAL' &&
            $taskNextDel->getTasAssignType() != 'SELF_SERVICE' &&
            (is_null($nextDel['USR_UID']) || $nextDel['USR_UID'] == '')
        ) {
            throw new Exception(G::LoadTranslation('ID_NO_USERS'));
        }

        if (is_numeric( $nextDel['DEL_PRIORITY'] )) {
            $nextDel['DEL_PRIORITY'] = (isset( $nextDel['DEL_PRIORITY'] ) ? ($nextDel['DEL_PRIORITY'] >= 1 && $nextDel['DEL_PRIORITY'] <= 5 ? $nextDel['DEL_PRIORITY'] : '3') : '3');
        } else {
            $nextDel['DEL_PRIORITY'] = 3;
        }

        $newDelegationUser = '';
        switch ($nextDel['TAS_ASSIGN_TYPE']) {
            case 'CANCEL_MI':
            case 'STATIC_MI':
                // Create new delegation depending on the no of users in the group
                $iNewDelIndex = $this->doDerivationStaticMi(
                    $appFields,
                    $currentDelegation,
                    $nextDel,
                    $iAppThreadIndex,
                    $delType
                );
                break;
            case 'BALANCED':
                $this->setTasLastAssigned($nextDel['TAS_UID'], $nextDel['USR_UID']);
                //No Break, need no execute the default ones....
            default:
                $delPrevious = 0;
                if($this->flagControlMulInstance){
                    $criteriaMulti = new Criteria("workflow");
                    $criteriaMulti->addSelectColumn(AppDelegationPeer::DEL_PREVIOUS);
                    $criteriaMulti->add(AppDelegationPeer::APP_UID, $currentDelegation['APP_UID'], Criteria::EQUAL);
                    $criteriaMulti->add(AppDelegationPeer::DEL_INDEX, $currentDelegation['DEL_INDEX'], Criteria::EQUAL);
                    $criteriaMulti->add(AppDelegationPeer::TAS_UID, $currentDelegation['TAS_UID'], Criteria::EQUAL);
                    $criteriaMultiR = AppDelegationPeer::doSelectRS($criteriaMulti);
                    $criteriaMultiR->setFetchmode(ResultSet::FETCHMODE_ASSOC);
                    $criteriaMultiR->next();
                    $row = $criteriaMultiR->getRow();
                    $delPrevious = $row['DEL_PREVIOUS'];
                }
                // Get the user that will create the new case
                $newDelegationUser = $this->verifyCurrentUserInTask($nextDel, $aSP);
                // Create new delegation
                $iNewDelIndex = $this->case->newAppDelegation(
                    $appFields['PRO_UID'],
                    $currentDelegation['APP_UID'],
                    $nextDel['TAS_UID'],
                    $newDelegationUser,
                    $currentDelegation['DEL_INDEX'],
                    $nextDel['DEL_PRIORITY'],
                    $iAppThreadIndex,
                    $nextDel,
                    $this->flagControl,
                    $this->flagControlMulInstance,
                    $delPrevious,
                    $appFields['APP_NUMBER'],
                    $appFields['PRO_ID'],
                    $nextDel['TAS_ID']
                );
                break;
        }

        if (array_key_exists('NEXT_ROUTING', $nextDel) && is_array($nextDel['NEXT_ROUTING']) && !empty($nextDel['NEXT_ROUTING'])) {
            if (array_key_exists('TAS_UID', $nextDel['NEXT_ROUTING'])) {
                $arrayRoutingData[$currentDelegation['TAS_UID'] . '/' . $nextDel['NEXT_ROUTING']['TAS_UID']] = $nextDel['NEXT_ROUTING'];
            } else {
                foreach ($nextDel['NEXT_ROUTING'] as $value) {
                    $arrayRoutingData[$currentDelegation['TAS_UID'] . '/' . $value['TAS_UID']] = $value;
                }
            }
        }

        /** Update the table application */
        $applicationFields = [
            'APP_ROUTING_DATA' => $arrayRoutingData,
            'APP_CUR_USER' => $newDelegationUser
        ];
        $colUpdated = ModelApplication::updateColumns($currentDelegation['APP_UID'], $applicationFields);
        $appFields['APP_CUR_USER'] = !empty($colUpdated['APP_CUR_USER']) ? $colUpdated['APP_CUR_USER'] : $this->appCurrentUser;

        // We updated the information relate to APP_THREAD
        $iAppThreadIndex = $appFields['DEL_THREAD'];
        $isUpdatedThread = false;
        if (isset($currentDelegation['ROUTE_TYPES']) && sizeof($currentDelegation['ROUTE_TYPES']) > 1) {
            //If the next is more than one thread: Parallel or other
            foreach ($currentDelegation['ROUTE_TYPES'] as $key => $value) {
                if ($value['ROU_NEXT_TASK'] === $nextDel['TAS_UID']) {
                    $isUpdatedThread = true;
                    $routeType = ($value['ROU_TYPE'] === 'EVALUATE') ? 'PARALLEL-AND-EXCLUSIVE' : $value['ROU_TYPE'];
                    $this->updateAppThread($routeType, $currentDelegation['APP_UID'], $iAppThreadIndex, $iNewDelIndex);
                }
            }
        }
        if (!$isUpdatedThread) {
            //If the next is a sequential derivation
            $this->updateAppThread($currentDelegation['ROU_TYPE'], $currentDelegation['APP_UID'], $iAppThreadIndex, $iNewDelIndex);
        }

        //if there are SubProcess to create
        if (isset($aSP)) {
            // Create case in the subprocess
            $subProcessFields = $this->subProcessCreation($aSP, $appFields, $currentDelegation, $iNewDelIndex, $iAppThreadIndex);

            // If is ASYNCHRONOUS we will to route the case master
            if ($aSP['SP_SYNCHRONOUS'] == 0) {
                $this->case->setDelInitDate( $currentDelegation['APP_UID'], $iNewDelIndex );
                $aDeriveTasks = $this->prepareInformation(
                    array (
                        'USER_UID' => -1,
                        'APP_UID' => $currentDelegation['APP_UID'],
                        'DEL_INDEX' => $iNewDelIndex
                    )
                );

                if (isset($aDeriveTasks[1])) {
                    if ($aDeriveTasks[1]['ROU_TYPE'] != 'SELECT') {
                        $nextDelegations2 = [];
                        foreach ($aDeriveTasks as $aDeriveTask) {
                            $nextDelegations2[] = array(
                                'TAS_UID' => $aDeriveTask['NEXT_TASK']['TAS_UID'],
                                'USR_UID' => $aDeriveTask['NEXT_TASK']['USER_ASSIGNED']['USR_UID'],
                                'TAS_ASSIGN_TYPE' => $aDeriveTask['NEXT_TASK']['TAS_ASSIGN_TYPE'],
                                'TAS_DEF_PROC_CODE' => $aDeriveTask['NEXT_TASK']['TAS_DEF_PROC_CODE'],
                                'DEL_PRIORITY' => 3,
                                'TAS_PARENT' => $aDeriveTask['NEXT_TASK']['TAS_PARENT'],
                                'ROU_PREVIOUS_TYPE' => isset($aDeriveTask['NEXT_TASK']['ROU_PREVIOUS_TYPE']) ? $aDeriveTask['NEXT_TASK']['ROU_PREVIOUS_TYPE'] : '',
                                'ROU_PREVIOUS_TASK' => isset($aDeriveTask['NEXT_TASK']['ROU_PREVIOUS_TASK']) ? $aDeriveTask['NEXT_TASK']['ROU_PREVIOUS_TASK'] : ''
                            );
                        }
                        $currentDelegation2 = array(
                            'APP_UID' => $currentDelegation['APP_UID'],
                            'DEL_INDEX' => $iNewDelIndex,
                            'APP_STATUS' => 'TO_DO',
                            'TAS_UID' => $currentDelegation['TAS_UID'],
                            'ROU_TYPE' => $aDeriveTasks[1]['ROU_TYPE'],

                        );
                        $openThreads = 0;
                        if ($currentDelegation2['ROU_TYPE'] == 'SEC-JOIN') {
                            $openThreads = $this->case->GetOpenThreads($currentDelegation['APP_UID']);
                        }
                        if ($openThreads == 0) {
                            $this->derivate($currentDelegation2, $nextDelegations2);
                        } else {
                            $subApplication = new SubApplication();
                            $subProcessFields['SA_STATUS'] = 'ACTIVE';
                            $subApplication->update($subProcessFields);
                        }
                    }
                }
            }
        }

        if ($iNewDelIndex !== 0 && !$sendNotificationsMobile) {
            $this->sendNotificationsMobile($appFields, $nextDel, $iNewDelIndex);
        }
        if ($iNewDelIndex !== 0 && !$sendNotifications) {
            $nextTaskData = $taskNextDel->toArray(BasePeer::TYPE_FIELDNAME);
            $nextTaskData['USR_UID'] = $nextDel['USR_UID'];
            $this->notifyAssignedUser($appFields, $nextTaskData, $iNewDelIndex);
        }
        return $iNewDelIndex;
    }

    /**
     * Create the sub-process
     * 
     * @param array $subProcessInfo
     * @param array $appFields
     * @param array $currentDelegation
     * @param int $delIndex
     * @param int $threadIndex
     * 
     * @return array
     */
    protected function subProcessCreation(array $subProcessInfo, array $appFields, array $currentDelegation, $delIndex, $threadIndex)
    {
        // Check if is SelfService the task in the SubProcess
        $isSelfService = empty($subProcessInfo['USR_UID']) ? true : false;

        // Create the new case in the sub-process
        // Set the initial date to null the time its created
        // The DelThreadStatus will create with CLOSED value for avoid to open the case without all the execution
        $newCase = $this->case->startCase($subProcessInfo['TAS_UID'], $subProcessInfo['USR_UID'], true, $appFields, $isSelfService);

        // Load the TAS_UID related to the SubProcess
        $taskNextDel = TaskPeer::retrieveByPK($subProcessInfo["TAS_UID"]);
        // Copy case variables to sub-process case
        $fields = unserialize($subProcessInfo['SP_VARIABLES_OUT']);
        // Load the information about the new case
        $currentFields = $this->case->loadCase($newCase['APPLICATION']);
        $newFields = [];
        $newFields['INDEX'] = 1;
        foreach ($fields as $originField => $targetField) {
            $originField = trim($originField, " @#%?$=&");
            $targetField = trim($targetField, " @#%?$=&");
            $newFields[$targetField] = isset($appFields['APP_DATA'][$originField]) ? $appFields['APP_DATA'][$originField] : '';

            if (array_key_exists($originField . '_label', $appFields['APP_DATA'])) {
                $newFields[$targetField . '_label'] = $appFields['APP_DATA'][$originField . '_label'];
            }
        }


        // We will to update the new case with the variables to define in the sub-process
        $currentFields['APP_DATA'] = array_merge($currentFields['APP_DATA'], $newFields);
        $currentFields['DEL_INDEX'] = 1;
        $currentFields['APP_STATUS'] = 'TO_DO';
        $this->case->updateCase(
            $newCase['APPLICATION'],
            $currentFields
        );

        // Create a registry in SUB_APPLICATION table
        $attributes = [
            'APP_UID' => $newCase['APPLICATION'],
            'APP_PARENT' => $currentDelegation['APP_UID'],
            'DEL_INDEX_PARENT' => $delIndex,
            'DEL_THREAD_PARENT' => $threadIndex,
            'SA_STATUS' => 'ACTIVE',
            'SA_VALUES_OUT' => serialize($newFields),
            'SA_INIT_DATE' => date('Y-m-d H:i:s')
        ];
        if ($subProcessInfo['SP_SYNCHRONOUS'] == 0) {
            $attributes['SA_STATUS'] = 'FINISHED';
            $attributes['SA_FINISH_DATE'] = $attributes['SA_INIT_DATE'];
        }
        $subprocess = ModelSubApplication::create($attributes);

        // Update the AppDelegation to execute the update trigger
        // Update the DelThreadStatus, the thread is ready for continue
        $appDelegation = AppDelegationPeer::retrieveByPK($newCase['APPLICATION'], $newCase['INDEX']);
        $appDelegation->setDelThreadStatus('OPEN');
        $appDelegation->setDelThreadStatusId(Delegation::$thread_status['OPEN']);
        $appDelegation->save();

        // Create record in table APP_ASSIGN_SELF_SERVICE_VALUE
        $tasGroupVariable = $taskNextDel->getTasGroupVariable();
        if ($taskNextDel->getTasAssignType() == "SELF_SERVICE" && !empty(trim($tasGroupVariable))) {
            $nextTaskGroupVariable = trim($tasGroupVariable, " @#");

            if (isset($currentFields["APP_DATA"][$nextTaskGroupVariable])) {
                $dataVariable = $currentFields["APP_DATA"][$nextTaskGroupVariable];
                $dataVariable = (is_array($dataVariable))? $dataVariable : trim($dataVariable);

                if (!empty($dataVariable)) {
                    $appAssignSelfServiceValue = new AppAssignSelfServiceValue();
                    $appAssignSelfServiceValue->create(
                        $newCase["APPLICATION"],
                        $newCase["INDEX"],
                        ["PRO_UID" => $newCase["PROCESS"], "TAS_UID" => $subProcessInfo["TAS_UID"], "GRP_UID" => ""],
                        $dataVariable
                    );
                }
            }
        }
        // We will to send the notifications
        $sendNotificationsMobile = $this->sendNotificationsMobile($currentFields, $subProcessInfo, $newCase['INDEX']);
        $nextTaskData = $taskNextDel->toArray(BasePeer::TYPE_FIELDNAME);
        $nextTaskData['USR_UID'] = $subProcessInfo['USR_UID'];
        $sendNotifications = $this->notifyAssignedUser($appFields, $nextTaskData, $newCase['INDEX']);

        return $attributes;
    }

    /**
     * This function returns the current user Checking cases where USR_ID exists or checking a subprocess (SYNCHRONOUS)
     * @param $nextDel
     * @param null $aSP
     * @return string
     */
    function verifyCurrentUserInTask($nextDel, $aSP = null)
    {
        if (isset($aSP) && $aSP["SP_SYNCHRONOUS"] == "1") {
            $currentUser = "";
        } else {
            $currentUser = isset($nextDel['USR_UID']) ? $nextDel['USR_UID'] : "";
        }
        return $currentUser;
    }

    /**
     * This function create, update and closed a new record related to appThread
     *
     * Related to route type we can change the records in the APP_THREAD table
     * @param  string $routeType this variable recibe information about the derivation
     * @return void
     */
    function updateAppThread($routeType, $appUid, $iAppThreadIndex, $iNewDelIndex) {
        switch ($routeType) {
            case 'PARALLEL':
            case 'PARALLEL-BY-EVALUATION':
            case 'PARALLEL-AND-EXCLUSIVE':
                $this->case->closeAppThread($appUid, $iAppThreadIndex);
                $iNewThreadIndex = $this->case->newAppThread($appUid, $iNewDelIndex, $iAppThreadIndex);
                $this->case->updateAppDelegation($appUid, $iNewDelIndex, $iNewThreadIndex);
                break;
            default:
                $this->case->updateAppThread($appUid, $iAppThreadIndex, $iNewDelIndex);
                break;
        }
    }

    /**
     * Verify if the case is child from another case
     *
     * @param string $applicationUid
     * @param int $delIndex
     *
     * @return void
     */
    function verifyIsCaseChild($applicationUid, $delIndex = 0)
    {
        //Obtain the related row in the table SUB_APPLICATION
        $subApplication = SubApplication::getSubProcessInfo($applicationUid);
        if (!empty($subApplication)) {
            //Obtain the related row in the table SUB_PROCESS
            $case = new Cases();
            $parentCase = $case->loadCase($subApplication['APP_PARENT'], $subApplication['DEL_INDEX_PARENT']);

            $subProcessParent = SubProcess::getSubProcessConfiguration($parentCase['PRO_UID'], $parentCase['TAS_UID']);
            if ($subProcessParent['SP_SYNCHRONOUS'] == 1 || $subApplication['SA_STATUS'] == 'ACTIVE') {
                $appFields = $case->loadCase($applicationUid, $delIndex);
                //Copy case variables to parent case
                $fields = unserialize($subProcessParent['SP_VARIABLES_IN']);
                $newFields = $this->getSubProcessVariables($fields, $appFields['APP_DATA'], $parentCase['APP_DATA']);
                $parentCase['APP_DATA'] = array_merge($parentCase['APP_DATA'], $newFields);
                $case->updateCase($subApplication['APP_PARENT'], $parentCase);

                /*----------------------------------********---------------------------------*/
                $inbox = new ListInbox();
                $inbox->update($parentCase);
                /*----------------------------------********---------------------------------*/

                //Update table SUB_APPLICATION
                $newSubApplication = new SubApplication();
                $newSubApplication->update([
                    'APP_UID' => $applicationUid,
                    'APP_PARENT' => $subApplication['APP_PARENT'],
                    'DEL_INDEX_PARENT' => $subApplication['DEL_INDEX_PARENT'],
                    'DEL_THREAD_PARENT' => $subApplication['DEL_THREAD_PARENT'],
                    'SA_STATUS' => 'FINISHED', 'SA_VALUES_IN' => serialize($newFields),
                    'SA_FINISH_DATE' => date('Y-m-d H:i:s')
                ]);

                //Derive the parent case
                $deriveTasks = $this->prepareInformation([
                    'USER_UID' => -1,
                    'APP_UID' => $subApplication['APP_PARENT'],
                    'DEL_INDEX' => $subApplication['DEL_INDEX_PARENT']
                ]);
                if (isset($deriveTasks[1])) {
                    if ($deriveTasks[1]['ROU_TYPE'] !== 'SELECT') {
                        $nextDelegations2 = [];
                        foreach ($deriveTasks as $deriveTask) {
                            if (!isset($deriveTask['NEXT_TASK']['USER_ASSIGNED']['USR_UID'])) {
                                $selectedUser = $deriveTask['NEXT_TASK']['USER_ASSIGNED'][0];
                                unset($deriveTask['NEXT_TASK']['USER_ASSIGNED']);
                                $deriveTask['NEXT_TASK']['USER_ASSIGNED'] = $selectedUser;
                                $myLabels = [
                                    $deriveTask['NEXT_TASK']['TAS_TITLE'],
                                    $parentCase['APP_NUMBER'],
                                    $selectedUser['USR_USERNAME'],
                                    $selectedUser['USR_FIRSTNAME'],
                                    $selectedUser['USR_LASTNAME']
                                ];
                                if ($deriveTask['NEXT_TASK']['TAS_ASSIGN_TYPE'] === 'MANUAL') {
                                    G::SendTemporalMessage('ID_TASK_WAS_ASSIGNED_TO_USER', 'warning', 'labels', 10, null, $myLabels);
                                }

                            }
                            $nextDelegations2[] = [
                                'TAS_UID' => $deriveTask['NEXT_TASK']['TAS_UID'],
                                'USR_UID' => $deriveTask['NEXT_TASK']['USER_ASSIGNED']['USR_UID'],
                                'TAS_ASSIGN_TYPE' => $deriveTask['NEXT_TASK']['TAS_ASSIGN_TYPE'],
                                'TAS_DEF_PROC_CODE' => $deriveTask['NEXT_TASK']['TAS_DEF_PROC_CODE'],
                                'DEL_PRIORITY' => 3,
                                'TAS_PARENT' => $deriveTask['NEXT_TASK']['TAS_PARENT'],
                                'ROU_PREVIOUS_TASK' => isset($deriveTask['NEXT_TASK']['ROU_PREVIOUS_TASK']) ? $deriveTask['NEXT_TASK']['ROU_PREVIOUS_TASK'] : '',
                                'ROU_PREVIOUS_TYPE' => isset($deriveTask['NEXT_TASK']['ROU_PREVIOUS_TYPE']) ? $deriveTask['NEXT_TASK']['ROU_PREVIOUS_TYPE'] : ''
                            ];
                        }
                        $currentDelegation2 = [
                            'APP_UID' => $subApplication['APP_PARENT'],
                            'DEL_INDEX' => $subApplication['DEL_INDEX_PARENT'],
                            'APP_STATUS' => 'TO_DO',
                            'TAS_UID' => $parentCase['TAS_UID'],
                            'ROU_TYPE' => $deriveTasks[1]['ROU_TYPE']
                        ];

                        $g = new G();
                        $g->sessionVarSave();
                        $_SESSION['PROCESS'] = $parentCase['PRO_UID'];
                        $this->derivate($currentDelegation2, $nextDelegations2);
                        $g->sessionVarRestore();

                        if ($delIndex > 0) {
                            $flagNotification = false;
                            if ($appFields['CURRENT_USER_UID'] == '') {
                                $criteriaTaskDummy = new Criteria('workflow');
                                $criteriaTaskDummy->add(TaskPeer::PRO_UID, $appFields['PRO_UID']);
                                $criteriaTaskDummy->add(TaskPeer::TAS_UID, $appFields['TAS_UID']);
                                $criteriaTaskDummy->add(
                                    $criteriaTaskDummy->getNewCriterion(TaskPeer::TAS_TYPE, 'SCRIPT-TASK', Criteria::EQUAL)->addOr(
                                        $criteriaTaskDummy->getNewCriterion(TaskPeer::TAS_TYPE, 'INTERMEDIATE-THROW-EMAIL-EVENT', Criteria::EQUAL))
                                );
                                $criteriaTaskDummy->setLimit(1);
                                $dataSet = AppDelegationPeer::doSelectRS($criteriaTaskDummy);
                                $dataSet->setFetchmode(\ResultSet::FETCHMODE_ASSOC);
                                $dataSet->next();
                                if ($row = $dataSet->getRow()) {
                                    $flagNotification = true;
                                }
                            }
                            if (!$flagNotification) {
                                // Send notifications - Start
                                $user = new Users();
                                $informationUser = $user->load($appFields['CURRENT_USER_UID']);

                                $sFromName = $informationUser['USR_FIRSTNAME'] . ' ' . $informationUser['USR_LASTNAME'] . ($informationUser['USR_EMAIL'] != '' ? ' <' . $informationUser['USR_EMAIL'] . '>' : '');

                                try {
                                    $case->sendNotifications(
                                        $appFields['TAS_UID'],
                                        $nextDelegations2,
                                        $appFields['APP_DATA'],
                                        $applicationUid,
                                        $delIndex,
                                        $sFromName);

                                } catch (Exception $e) {
                                    G::SendTemporalMessage(G::loadTranslation('ID_NOTIFICATION_ERROR') . ' - ' . $e->getMessage(), 'warning', 'string', null, '100%');
                                }
                                // Send notifications - End
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Will be get sub process variables
     * Get variables-in and variables-out
     *
     * @param array $fields
     * @param array $childCaseData
     * @param array $parentCaseData
     *
     * @return array
    */
    public function getSubProcessVariables($fields, $childCaseData, $parentCaseData)
    {
        $newFields = [];
        foreach ($fields as $originField => $targetField) {
            $originField = str_replace('@', '', $originField);
            $originField = str_replace('#', '', $originField);
            $originField = str_replace('%', '', $originField);
            $originField = str_replace('?', '', $originField);
            $originField = str_replace('$', '', $originField);
            $originField = str_replace('=', '', $originField);
            $originField = str_replace('&', '', $originField);
            $targetField = str_replace('@', '', $targetField);
            $targetField = str_replace('#', '', $targetField);
            $targetField = str_replace('%', '', $targetField);
            $targetField = str_replace('?', '', $targetField);
            $targetField = str_replace('$', '', $targetField);
            $targetField = str_replace('=', '', $targetField);
            $targetField = str_replace('&', '', $targetField);
            $newFields[$targetField] = isset($childCaseData[$originField]) ? $childCaseData[$originField] : '';

            if (array_key_exists($originField . '_label', $childCaseData)) {
                $newFields[$targetField . '_label'] = $childCaseData[$originField . '_label'];
            } elseif (array_key_exists($targetField . '_label', $parentCaseData)) {
                $newFields[$targetField . '_label'] = '';
            }
        }

        return $newFields;
    }

    /**  getDerivatedCases
     *  get all derivated cases and subcases from any task,
     *  this function is useful to know who users have been assigned and what task they do.
     *
     * @param   string   $sParentUid
     * @param   string   $sDelIndexParent
     * @return  array    $derivation
     *
     */
    function getDerivatedCases ($sParentUid, $sDelIndexParent)
    {
        $oCriteria = new Criteria( 'workflow' );
        $cases = array ();
        $derivation = array ();
        //get the child delegations , of parent delIndex
        $children = array ();
        $oCriteria->clearSelectColumns();
        $oCriteria->addSelectColumn( AppDelegationPeer::DEL_INDEX );
        $oCriteria->add( AppDelegationPeer::APP_UID, $sParentUid );
        $oCriteria->add( AppDelegationPeer::DEL_PREVIOUS, $sDelIndexParent );
        $oDataset = AppDelegationPeer::doSelectRS( $oCriteria );
        $oDataset->setFetchmode( ResultSet::FETCHMODE_ASSOC );
        $oDataset->next();
        $aRow = $oDataset->getRow();
        while (is_array( $aRow )) {
            $children[] = $aRow['DEL_INDEX'];

            $oDataset->next();
            $aRow = $oDataset->getRow();
        }

        //foreach child , get the info of their derivations and subprocesses
        foreach ($children as $keyChild => $child) {
            $oCriteria = new Criteria( 'workflow' );
            $oCriteria->clearSelectColumns();
            $oCriteria->addSelectColumn( SubApplicationPeer::APP_UID );
            $oCriteria->addSelectColumn( AppDelegationPeer::APP_UID );
            $oCriteria->addSelectColumn( AppDelegationPeer::DEL_INDEX );
            $oCriteria->addSelectColumn( AppDelegationPeer::PRO_UID );
            $oCriteria->addSelectColumn( AppDelegationPeer::TAS_UID );
            $oCriteria->addSelectColumn( AppDelegationPeer::USR_UID );
            $oCriteria->addSelectColumn( UsersPeer::USR_USERNAME );
            $oCriteria->addSelectColumn( UsersPeer::USR_FIRSTNAME );
            $oCriteria->addSelectColumn( UsersPeer::USR_LASTNAME );

            $oCriteria->add( SubApplicationPeer::APP_PARENT, $sParentUid );
            $oCriteria->add( SubApplicationPeer::DEL_INDEX_PARENT, $child );
            $oCriteria->addJoin( SubApplicationPeer::APP_UID, AppDelegationPeer::APP_UID );
            $oCriteria->addJoin( AppDelegationPeer::USR_UID, UsersPeer::USR_UID );
            $oDataset = SubApplicationPeer::doSelectRS( $oCriteria );
            $oDataset->setFetchmode( ResultSet::FETCHMODE_ASSOC );
            $oDataset->next();
            $aRow = $oDataset->getRow();
            while (is_array( $aRow )) {
                $oProcess = new Process();
                $proFields = $oProcess->load( $aRow['PRO_UID'] );
                $oCase = new Application();
                $appFields = $oCase->load( $aRow['APP_UID'] );
                $oTask = new Task();
                $tasFields = $oTask->load( $aRow['TAS_UID'] );
                $derivation[] = array ('processId' => $aRow['PRO_UID'],'processTitle' => $proFields['PRO_TITLE'],'caseId' => $aRow['APP_UID'],'caseNumber' => $appFields['APP_NUMBER'],'taskId' => $aRow['TAS_UID'],'taskTitle' => $tasFields['TAS_TITLE'],'userId' => $aRow['USR_UID'],'userName' => $aRow['USR_USERNAME'],'userFullname' => $aRow['USR_FIRSTNAME'] . ' ' . $aRow['USR_LASTNAME']
                );

                $oDataset->next();
                $aRow = $oDataset->getRow();
            }
        }

        return $derivation;
    }

    function getGrpUser ($aData)
    {
        $oTasks = new Tasks();
        $oGroups = new Groups();
        $oContent = new Content();
        $aGroup = array ();
        $aUsers = array ();
        $aGroup = $oTasks->getGroupsOfTask( $aData['ROU_NEXT_TASK'], 1 );
        $aGrpUid = $aGroup[0]['GRP_UID'];
        $sGrpName = $oContent->load( 'GRP_TITLE', '', $aGrpUid, 'en' );
        $aGrp['GRP_NAME'] = $sGrpName;
        $aGrp['GRP_UID'] = $aGrpUid;
        $aUsers = $oGroups->getUsersOfGroup( $aGroup[0]['GRP_UID'] );
        foreach ($aUsers as $aKey => $userid) {
            $aData[$aKey] = $userid;
        }
        return $aGrp;
    }

    /**
     * Review the replaced by configuration
     *
     * @param string $user
     *
     * @return string
     * @throws Exception
    */
    function checkReplacedByUser($user)
    {
        if (is_string($user)) {
            $userInstance = UsersPeer::retrieveByPK($user);
        } else {
            $userInstance = $user;
        }
        if (!is_object($userInstance)) {
            if (!is_string($user)) {
                $user = gettype($user);
            }
            throw new Exception("The user with the UID '" . $user . "' doesn't exist.");
        }
        if ($userInstance->getUsrStatus() == 'ACTIVE') {
            return $userInstance->getUsrUid();
        } else {
            $userReplace = trim($userInstance->getUsrReplacedBy());
            if ($userReplace != '') {
                return $this->checkReplacedByUser(UsersPeer::retrieveByPK($userReplace));
            } else {
                return '';
            }
        }
    }

    /**
     * @param $appFields
     * @param $nextDel
     * @param $iNewDelIndex
     * @return bool
     */
    private function sendNotificationsMobile($appFields, $nextDel, $iNewDelIndex)
    {
        try {
            $notificationMobile = new \ProcessMaker\BusinessModel\Light\NotificationDevice();
            if ($notificationMobile->checkMobileNotifications()) {
                $notificationMobile->routeCaseNotificationDevice($appFields, $nextDel, $iNewDelIndex);
            }
            return true;
        } catch (Exception $e) {
            \G::log(G::loadTranslation('ID_NOTIFICATION_ERROR') . '|' . $e->getMessage(), PATH_DATA, "mobile.log");
        }
    }

    /**
     * @param $appFields
     * @param $nextDel
     * @param $iNewDelIndex
     * @return bool
     */
    public function notifyAssignedUser($appFields, $nextDel, $iNewDelIndex)
    {
        try {
            if ($nextDel['TAS_RECEIVE_LAST_EMAIL'] == 'TRUE') {
                $taskData = [];
                $userLogged = $this->userLogged->load($appFields['APP_DATA']['USER_LOGGED']);
                $fromName = $userLogged['USR_FIRSTNAME'] . ' ' . $userLogged['USR_LASTNAME'];
                $sFromData = $fromName . ($userLogged['USR_EMAIL'] != '' ? ' <' . $userLogged['USR_EMAIL'] . '>' : '');
                $dataEmail = $this->case->loadDataSendEmail($nextDel, $appFields['APP_DATA'], $sFromData, 'RECEIVE');
                $dataEmail['applicationUid'] = $appFields['APP_UID'];
                $dataEmail['delIndex'] = $iNewDelIndex;
                array_push($taskData, $nextDel);
                //Load the TAS_ID
                if (isset($nextDel['TAS_ID'])) {
                    $appFields['APP_DATA']['TAS_ID'] = $nextDel['TAS_ID'];
                }
                //Load the PRO_ID
                if (isset($nextDel['PRO_ID'])) {
                    $appFields['APP_DATA']['PRO_ID'] = $nextDel['PRO_ID'];
                }
                $this->case->sendMessage($dataEmail, $appFields['APP_DATA'], $taskData);
            }
            return true;
        } catch (Exception $e) {
            \G::log(G::loadTranslation('ID_NOTIFICATION_ERROR') . '|' . $e->getMessage());
        }
    }

    /**
     * @param array $currentDelegation
     * @param array $nextDel
     * @param array $appFields
     * @param boolean $flagFirstIteration
     * @param boolean $flagTaskAssignTypeIsMultipleInstance
     * @param array $context
     * @return void
     */
    public function finishTask($currentDelegation, $nextDel, $appFields, $flagFirstIteration = true, $flagTaskAssignTypeIsMultipleInstance = false, $context = []) {
        $iAppThreadIndex = $appFields['DEL_THREAD'];
        $this->case->closeAppThread($currentDelegation['APP_UID'], $iAppThreadIndex);
        if (isset($nextDel["TAS_UID_DUMMY"])) {
            $criteria = new Criteria("workflow");
            $criteria->addSelectColumn(RoutePeer::TAS_UID);
            $criteria->addJoin(RoutePeer::TAS_UID, AppDelegationPeer::TAS_UID);
            $criteria->add(RoutePeer::PRO_UID, $appFields['PRO_UID']);
            $criteria->add(RoutePeer::ROU_NEXT_TASK, isset($nextDel['ROU_PREVIOUS_TASK']) ? $nextDel['ROU_PREVIOUS_TASK'] : '');
            $criteria->add(RoutePeer::ROU_TYPE, isset($nextDel['ROU_PREVIOUS_TYPE']) ? $nextDel['ROU_PREVIOUS_TYPE'] : '');
            $criteria->add(AppDelegationPeer::DEL_THREAD_STATUS, 'OPEN');
            $rsCriteria = RoutePeer::doSelectRS($criteria);
            $rsCriteria->setFetchmode(ResultSet::FETCHMODE_ASSOC);
            $executeEvent = ($rsCriteria->next()) ? false : true;

            $multiInstanceCompleted = true;
            if ($flagTaskAssignTypeIsMultipleInstance) {
                $multiInstanceCompleted = $this->case->multiInstanceIsCompleted(
                    $appFields['APP_UID'],
                    $appFields['TAS_UID'],
                    $appFields['DEL_PREVIOUS']);
            }

            $taskDummy = TaskPeer::retrieveByPK($nextDel["TAS_UID_DUMMY"]);
            if (preg_match("/^(?:END-MESSAGE-EVENT|END-EMAIL-EVENT)$/", $taskDummy->getTasType())
                && $multiInstanceCompleted && $executeEvent
            ) {
                $nextDel["TAS_ID"] = $taskDummy->getTasId();
                $this->executeEvent(
                    $nextDel["TAS_UID_DUMMY"],
                    $appFields,
                    $flagFirstIteration,
                    true,
                    '',
                    !empty($nextDel["TAS_ID"]) ? $nextDel["TAS_ID"] : 0
                );
            }
        }
        $context['action'] = 'finish-task';
        //Logger
        $message = 'Case Derivation';
        Log::channel(':CaseDerivation')->info($message, Bootstrap::context($context));
    }

    /**
     * Finish the process
     * Close the threads and update the status to COMPLETED
     * @param array $currentDelegation
     * @param array $nextDel
     * @param array $appFields
     * @param boolean $flagFirstIteration
     * @param array $context
     * @return void
     */
    public function finishProcess($currentDelegation, $nextDel, $appFields, $flagFirstIteration = true, $context = []){
        /*Close all delegations of $currentDelegation['APP_UID'] */
        $this->case->closeAllDelegations( $currentDelegation['APP_UID'] );
        $this->case->closeAllThreads( $currentDelegation['APP_UID'] );
        //I think we need to change the APP_STATUS to completed,
        if (!isset($nextDel['ROU_CONDITION'])) {
            $nextDel['ROU_CONDITION'] = '';
        }
        //Execute the Intermediate Event After the End of Process
        $this->executeEvent(
            $nextDel["TAS_UID"],
            $appFields,
            true,
            true,
            '',
            !empty($nextDel["TAS_ID"]) ? $nextDel["TAS_ID"] : 0
        );
        if (isset($nextDel["TAS_UID_DUMMY"]) ) {
            $taskDummy = TaskPeer::retrieveByPK($nextDel["TAS_UID_DUMMY"]);
            if (preg_match("/^(?:END-MESSAGE-EVENT|END-EMAIL-EVENT)$/", $taskDummy->getTasType())) {
                $nextDel["TAS_ID"] = $taskDummy->getTasId();
                //Throw Events
                $this->executeEvent(
                    $nextDel["TAS_UID_DUMMY"],
                    $appFields,
                    $flagFirstIteration,
                    true,
                    '',
                    !empty($nextDel["TAS_ID"]) ? $nextDel["TAS_ID"] : 0
                );
            }
        }
        $context['action'] = 'end-process';
        //Logger
        $message = 'Case Derivation';
        Log::channel(':CaseDerivation')->info($message, Bootstrap::context($context));
    }

    /**
     * Get the next route information when is a subProcess
     * This function changes the following important variables:
     * $aSP (array) with the subProcess information
     * $newNextDel (array) the next delegation related to the subProcess
     * @param array $nextDel
     * @param array $proUid
     * @return array $newNextDel
    */
    public function getNextInfoSubProcess($nextDel, $proUid)
    {
        $newNextDel = [];
        $oCriteria = new Criteria('workflow');
        $oCriteria->add(SubProcessPeer::PRO_PARENT, $proUid);
        $oCriteria->add(SubProcessPeer::TAS_PARENT, $nextDel['TAS_PARENT']);
        $oDataset = SubProcessPeer::doSelectRS( $oCriteria );
        $oDataset->setFetchmode( ResultSet::FETCHMODE_ASSOC );
        $oDataset->next();
        $aSP = $oDataset->getRow();
        if (is_array($aSP)) {
            $this->aSP = $aSP;
            $oTask = new Task();
            $aTaskNext = $oTask->load($nextDel['TAS_UID']);
            //When is MULTIPLE_INSTANCE catch the first user
            if($aTaskNext["TAS_ASSIGN_TYPE"] == "MULTIPLE_INSTANCE"){
                $spUserUid = $this->getAllUsersFromAnyTask($nextDel["TAS_UID"]);
                foreach($spUserUid as $row){
                    $firstUserUid = $row;
                    continue;
                }
                $this->aSP['USR_UID'] = $firstUserUid;
            }else{
                $this->aSP['USR_UID'] = $nextDel['USR_UID'];
            }
            $aTask = $oTask->load($nextDel['TAS_PARENT']);
            $newNextDel = array (
                'TAS_UID' => $aTask['TAS_UID'],
                'USR_UID' => $this->aSP['USR_UID'],
                'TAS_ASSIGN_TYPE' => $aTask['TAS_ASSIGN_TYPE'],
                'TAS_DEF_PROC_CODE' => $aTask['TAS_DEF_PROC_CODE'],
                'DEL_PRIORITY' => 3,
                'TAS_PARENT' => '',
                'ROU_PREVIOUS_TYPE' => isset($nextDel['ROU_PREVIOUS_TYPE']) ? $nextDel['ROU_PREVIOUS_TYPE'] : '',
                'ROU_PREVIOUS_TASK' => isset($nextDel['ROU_PREVIOUS_TASK']) ? $nextDel['ROU_PREVIOUS_TASK'] : ''
            );
        }

        return $newNextDel;
    }

     /**
      * @deprecated
      * Review if can route a case with the assign task STATIC_MI
      * @param array $currentDelegation
     * @return boolean $canRoute
     */
    public function canRouteTasAssignStaticMi($currentDelegation){
        $arrayOpenThread = $this->case->GetAllOpenDelegation($currentDelegation);
        $aData = $this->case->loadCase( $currentDelegation['APP_UID'] );
        if (isset( $aData['APP_DATA'][str_replace( '@@', '', $currentDelegation['TAS_MI_INSTANCE_VARIABLE'] )] )) {
            $sMIinstanceVar = $aData['APP_DATA'][str_replace( '@@', '', $currentDelegation['TAS_MI_INSTANCE_VARIABLE'] )];
        } else {
            $sMIinstanceVar = $aData['APP_DATA']['TAS_MI_INSTANCE_VARIABLE'];
        }
        if (isset( $aData['APP_DATA'][str_replace( '@@', '', $currentDelegation['TAS_MI_COMPLETE_VARIABLE'] )] )) {
            $sMIcompleteVar = $aData['APP_DATA'][str_replace( '@@', '', $currentDelegation['TAS_MI_COMPLETE_VARIABLE'] )];
        } else {
            $sMIcompleteVar = $aData['APP_DATA']['TAS_MI_COMPLETE_VARIABLE'];
        }
        $discriminateThread = $sMIinstanceVar - $sMIcompleteVar;
        // -1 because One App Delegation is closed by above Code
        if ($discriminateThread == count($arrayOpenThread)) {
            $canRoute = true;
        } else {
            $canRoute = false;
        }
        return $canRoute;
    }

    /**
     * Review if can route a case with Join we will check if review the siblings are open
     * @param boolean $flagMultipleInstance
     * @param boolean $flagTypeMultipleInstance
     * @param array $currentDelegation
     * @param array $appFields
     * @param array $nextDel
     * @return boolean $canRoute
     */
    public function canRouteTypeSecJoin($flagMultipleInstance, $flagTypeMultipleInstance, $currentDelegation, $appFields, $nextDel)
    {
        $arrayOpenThread = ($flagMultipleInstance && $flagTypeMultipleInstance)? $this->case->searchOpenPreviousTasks($currentDelegation["TAS_UID"], $currentDelegation["APP_UID"]) : [];

        if (
            $flagMultipleInstance
            && $flagTypeMultipleInstance
            && isset($nextDel["ROU_PREVIOUS_TYPE"])
            && $nextDel["ROU_PREVIOUS_TYPE"] == 'SEC-JOIN'
        ) {
            $appDelegation = new AppDelegation();
            $arraySiblings = $appDelegation->getAllTasksBeforeSecJoin(
                $nextDel["ROU_PREVIOUS_TASK"],
                $currentDelegation["APP_UID"],
                $appFields['DEL_PREVIOUS'],
                'OPEN'
            );
        } else {
            $arraySiblings = $this->case->getOpenSiblingThreads(
                $nextDel["TAS_UID"],
                $currentDelegation["APP_UID"],
                $currentDelegation["DEL_INDEX"],
                $currentDelegation["TAS_UID"]
            );
        }
        $this->arraySiblings = $arraySiblings;
        if(is_array($arrayOpenThread) && is_array($arraySiblings)){
            $arrayOpenThread = array_merge($arrayOpenThread, $arraySiblings);
        }
        $canRoute = empty($arrayOpenThread);
        if($canRoute){
            if($flagMultipleInstance && $flagTypeMultipleInstance){
                $this->flagControlMulInstance = true;
            }else{
                $this->flagControl = true;
            }
        }

        return $canRoute;
    }

    /**
     * Route a case with Multiple Instance
     * This function changes the following important variables:
     * flagUpdateList (boolean) if we will update the list tables
     * removeList (boolean) if will be remove the row in the list tables
     * iNewDelIndex (integer) new index created in the table appDelegation
     * $arrayDerivationResult (array) information related to the new index created in the table appDelegation
     * @param array $currentDelegation
     * @param array $nextDel
     * @param string $taskNextDel
     * @param array $appFields
     * @param array $aSP
     * @param boolean $removeList
     * @return array $arrayDerivationResult
     * @throws \Exception
     */
    public function routeMultipleInstance($currentDelegation, $nextDel, $taskNextDel, $appFields, $aSP, $removeList)
    {
        $arrayDerivationResult = [];
        $arrayUser = $this->getNextAssignedUser(
            array(
                "APP_UID" => $currentDelegation["APP_UID"],
                "NEXT_TASK" => $taskNextDel->toArray(BasePeer::TYPE_FIELDNAME)
            )
        );

        if (empty($arrayUser)) {
            throw new Exception(G::LoadTranslation("ID_NO_USERS"));
        }

        foreach ($arrayUser as $value2) {
            $currentDelegationAux = array_merge($currentDelegation, array("ROU_TYPE" => "PARALLEL"));
            $nextDelAux = array_merge($nextDel, array("USR_UID" => $value2["USR_UID"]));
            $iNewDelIndex = $this->doDerivation($currentDelegationAux, $nextDelAux, $appFields, $aSP);
            $this->updateList($currentDelegationAux, $nextDelAux, $taskNextDel, $appFields, $iNewDelIndex, $aSP, $removeList);
            $this->iNewDelIndex = $iNewDelIndex;
            $this->flagUpdateList = false;
            $this->removeList= false;
            $arrayDerivationResult[] = [
                'DEL_INDEX' => $iNewDelIndex,
                'TAS_UID' => $nextDelAux['TAS_UID'],
                'USR_UID' => (isset($nextDelAux['USR_UID']))? $nextDelAux['USR_UID'] : ''
            ];
        }

        return $arrayDerivationResult;
    }

    /**
     * When we route a case we will to create a record in the table APP_ASSIGN_SELF_SERVICE_VALUE if the task is SELF_SERVICE
     *
     * @param object $taskNextDel
     * @param integer $iNewDelIndex
     * @param string $nextTasUid
     * @param array $appFields
     * @param integer $nextTasId
     *
     * @return void
     */
    public function createRecordAppSelfServiceValue($taskNextDel, $iNewDelIndex, $nextTasUid, $appFields, $nextTasId = 0)
    {
        if ($taskNextDel->getTasAssignType() == "SELF_SERVICE" && trim($taskNextDel->getTasGroupVariable()) != "") {
            $nextTaskGroupVariable = trim($taskNextDel->getTasGroupVariable(), " @#");

            if (isset($appFields["APP_DATA"][$nextTaskGroupVariable])) {
                $dataVariable = $appFields["APP_DATA"][$nextTaskGroupVariable];
                $dataVariable = (is_array($dataVariable))? $dataVariable : trim($dataVariable);

                if (!empty($dataVariable)) {
                    $appAssignSelfServiceValue = new AppAssignSelfServiceValue();
                    $appAssignSelfServiceValue->create(
                        $appFields["APP_UID"],
                        $iNewDelIndex,
                        [
                            "PRO_UID" => $appFields["PRO_UID"],
                            "TAS_UID" => $nextTasUid,
                            "GRP_UID" => "",
                            "APP_NUMBER" => !empty($appFields["APP_NUMBER"]) ? $appFields["APP_NUMBER"] : 0,
                            "TAS_ID" => $nextTasId
                        ],
                        $dataVariable
                    );
                }
            }
        }
    }

    /**
     * When we route a case we will to get the information about the next task
     * This function changes the following important variables:
     * $arrayNextDerivation (array) information related to the new index created in the table appDelegation
     * @param array $currentDelegation
     * @param integer $iNewDelIndex
     * @param array $nextDel
     * @return array $arrayDerivationResult
     * @throws \Exception
     */
    public function routePrepareInformationNextTask($currentDelegation, $iNewDelIndex, $nextDel)
    {
        $nextDelegationsAux   = [];
        $taskNextDelNextDelRouType = "";
        $i = 0;
        //Get for $nextDel["TAS_UID"] your next Task
        $currentDelegationAux = array_merge(
            $currentDelegation,
            array(
                "DEL_INDEX" => $iNewDelIndex,
                "TAS_UID" => $nextDel["TAS_UID"]
            )
        );
        $arrayTaskNextDelNextDelegations = $this->prepareInformation(
            array(
                "USER_UID"  => $_SESSION["USER_LOGGED"],
                "APP_UID"   => $currentDelegation["APP_UID"],
                "DEL_INDEX" => $iNewDelIndex
            )
        );

        foreach ($arrayTaskNextDelNextDelegations as $key => $value) {
            $arrayTaskNextDelNextDel = $value;
            switch ($arrayTaskNextDelNextDel['NEXT_TASK']['TAS_ASSIGN_TYPE']) {
                case 'MANUAL':
                    $arrayTaskNextDelNextDel['NEXT_TASK']['USER_ASSIGNED']['USR_UID'] = '';
                    break;
                case 'MULTIPLE_INSTANCE':
                    if (!isset($arrayTaskNextDelNextDel['NEXT_TASK']['USER_ASSIGNED']['0']['USR_UID'])) {
                        throw new Exception(G::LoadTranslation('ID_NO_USERS'));
                    }

                    $arrayTaskNextDelNextDel['NEXT_TASK']['USER_ASSIGNED']['USR_UID'] = '';
                    break;
                case 'MULTIPLE_INSTANCE_VALUE_BASED':
                    $arrayTaskNextDelNextDel['NEXT_TASK']['USER_ASSIGNED']['USR_UID'] = '';
                    break;
                default:
                    if (!isset($arrayTaskNextDelNextDel['NEXT_TASK']['USER_ASSIGNED']['USR_UID'])) {
                        throw new Exception(G::LoadTranslation('ID_NO_USERS'));
                    }
                    break;
            }

            $taskNextDelNextDelRouType = $arrayTaskNextDelNextDel["ROU_TYPE"];
            $nextDelegationsAux[++$i] = array(
                "TAS_UID"           => $arrayTaskNextDelNextDel["NEXT_TASK"]["TAS_UID"],
                "USR_UID"           => $arrayTaskNextDelNextDel["NEXT_TASK"]["USER_ASSIGNED"]["USR_UID"],
                "TAS_ASSIGN_TYPE"   => $arrayTaskNextDelNextDel["NEXT_TASK"]["TAS_ASSIGN_TYPE"],
                "TAS_DEF_PROC_CODE" => $arrayTaskNextDelNextDel["NEXT_TASK"]["TAS_DEF_PROC_CODE"],
                "DEL_PRIORITY"      => "",
                "TAS_PARENT"        => $arrayTaskNextDelNextDel["NEXT_TASK"]["TAS_PARENT"],
                "ROU_PREVIOUS_TYPE" => isset($arrayTaskNextDelNextDel["NEXT_TASK"]["ROU_PREVIOUS_TYPE"]) ? $arrayTaskNextDelNextDel["NEXT_TASK"]["ROU_PREVIOUS_TYPE"] : '',
                "ROU_PREVIOUS_TASK" => isset($arrayTaskNextDelNextDel["NEXT_TASK"]["ROU_PREVIOUS_TASK"]) ? $arrayTaskNextDelNextDel["NEXT_TASK"]["ROU_PREVIOUS_TASK"] : ''
            );
        }

        $currentDelegationAux["ROU_TYPE"] = $taskNextDelNextDelRouType;
        $arrayNextDerivation = array(
            "currentDelegation" => $currentDelegationAux,
            "nextDelegations" => $nextDelegationsAux
        );
        return $arrayNextDerivation;
    }

    /**
     * When we route a case but we will not create a index/thread in the AppDelegation
     * We will to execute Events
     * We will to close thread when is a Gateway to Gateway
     * @param array $appFields
     * @param array $currentDelegation
     * @param array $nextDel
     * @param array $arraySiblings
     * @param boolean $flagMultipleInstance
     * @param boolean $flagTypeMultipleInstance
     * @param boolean $flagFirstIteration
     * @return void
     */
    public function doRouteWithoutThread($appFields, $currentDelegation, $nextDel, $arraySiblings = [], $flagMultipleInstance = false, $flagTypeMultipleInstance = false, $flagFirstIteration = false)
    {
        $iAppThreadIndex = $appFields['DEL_THREAD'];
        $routeType = $currentDelegation["ROU_TYPE"];
        $routeType = ($flagMultipleInstance && $flagTypeMultipleInstance)? "SEC-JOIN" : $routeType;
        switch ($routeType) {
            case 'SEC-JOIN':
                //If the all Siblings are done execute the events
                if (sizeof($arraySiblings) === 0 && !$flagTypeMultipleInstance) {
                    //Throw Events
                    $this->executeEvent(
                        $nextDel["TAS_UID"],
                        $appFields,
                        $flagFirstIteration,
                        false,
                        '',
                        !empty($nextDel["TAS_ID"]) ? $nextDel["TAS_ID"] : 0
                    );
                }
                //Close thread
                $this->case->closeAppThread( $currentDelegation['APP_UID'], $iAppThreadIndex );
                break;
            default:
                if ($nextDel['ROU_PREVIOUS_TYPE'] == 'SEC-JOIN') {
                    $criteria = new Criteria('workflow');
                    $criteria->clearSelectColumns();
                    $criteria->addSelectColumn(AppThreadPeer::APP_THREAD_PARENT);
                    $criteria->add(AppThreadPeer::APP_UID, $appFields['APP_UID']);
                    $criteria->add(AppThreadPeer::APP_THREAD_STATUS, 'OPEN');
                    $criteria->add(AppThreadPeer::APP_THREAD_INDEX, $iAppThreadIndex);
                    $rsCriteria = AppThreadPeer::doSelectRS($criteria);
                    $rsCriteria->setFetchmode(ResultSet::FETCHMODE_ASSOC);
                    if ($rsCriteria->next()) {
                        $this->case->closeAppThread($currentDelegation['APP_UID'], $iAppThreadIndex);
                    }
                }
                if ($currentDelegation['TAS_ASSIGN_TYPE'] == 'STATIC_MI' || $currentDelegation['TAS_ASSIGN_TYPE'] == 'CANCEL_MI') {
                    $this->case->closeAppThread( $currentDelegation['APP_UID'], $iAppThreadIndex );
                }
                break;
        }
    }
    /**
     * @deprecated
     * We create a new index in the tables with the assign task STATIC_MI
     * @param array $appFields
     * @param array $currentDelegation
     * @param array $nextDel
     * @param integer $iAppThreadIndex
     * @param string $delType
     * @return integer $iNewDelIndex, the index created
     */
    public function doDerivationStaticMi($appFields, $currentDelegation, $nextDel, $iAppThreadIndex, $delType = 'NORMAL')
    {
        $iNewAppThreadIndex = $appFields['DEL_THREAD'];
        $this->case->closeAppThread( $currentDelegation['APP_UID'], $iAppThreadIndex );

        foreach ($nextDel['NEXT_TASK']['USER_ASSIGNED'] as $key => $aValue) {
            //Incrementing the Del_thread First so that new delegation has new del_thread
            $iNewAppThreadIndex += 1;
            //Creating new delegation according to users in group
            $iMIDelIndex = $this->case->newAppDelegation(
                $appFields['PRO_UID'],
                $currentDelegation['APP_UID'],
                $nextDel['TAS_UID'],
                (isset( $aValue['USR_UID'] ) ? $aValue['USR_UID'] : ''),
                $currentDelegation['DEL_INDEX'],
                $nextDel['DEL_PRIORITY'],
                $iNewAppThreadIndex,
                $nextDel,
                $appFields['APP_NUMBER'],
                $appFields['PRO_ID'],
                $nextDel['TAS_ID']
            );

            $iNewThreadIndex = $this->case->newAppThread(
                $currentDelegation['APP_UID'],
                $iMIDelIndex,
                $iAppThreadIndex
            );

            //Setting the del Index for Updating the AppThread delIndex
            if ($key == 0) {
                $iNewDelIndex = $iMIDelIndex - 1;
            }
        }
        return $iNewDelIndex;
    }

}
