Repository: ProcessMaker/nayra (php)

Issue:
Implement the method ExecutionInstanceInterface::close()
Implement the method to close an execution instance.
```
interface ExecutionInstanceInterface
{
    /**
     * Close the execution instance.
     *
     * @return bool
     */
    public function close();
}
```

Pull request:
Implemente Close of Execution Instance. Resolves #125
This PR includes:

- Implementation of ExecutionInstance::close()
- Add a transition to close script task in failing state.



Patch:
diff --git a/src/ProcessMaker/Nayra/Bpmn/ActivityTrait.php b/src/ProcessMaker/Nayra/Bpmn/ActivityTrait.php
index beb487c..9733874 100755
--- a/src/ProcessMaker/Nayra/Bpmn/ActivityTrait.php
+++ b/src/ProcessMaker/Nayra/Bpmn/ActivityTrait.php
@@ -6,6 +6,7 @@ use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\FlowNodeInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\StateInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface;
+use ProcessMaker\Nayra\Contracts\Bpmn\TransitionInterface;
 use ProcessMaker\Nayra\Contracts\RepositoryInterface;
 
 /**
@@ -15,7 +16,6 @@ use ProcessMaker\Nayra\Contracts\RepositoryInterface;
  */
 trait ActivityTrait
 {
-
     use FlowNodeTrait;
 
     /**
@@ -60,7 +60,6 @@ trait ActivityTrait
      */
     private $closedState;
 
-
     /**
      * Build the transitions that define the element.
      *
@@ -74,16 +73,18 @@ trait ActivityTrait
         $this->failingState = new State($this, ActivityInterface::TOKEN_STATE_FAILING);
         $this->exceptionTransition = new ExceptionTransition($this, true);
         $this->closeExceptionTransition = new CloseExceptionTransition($this, true);
+        $this->completeExceptionTransition = new CompleteExceptionTransition($this, true);
         $this->transition = new Transition($this, true);
         $this->closedState = new State($this, ActivityInterface::TOKEN_STATE_COMPLETED);
 
         $this->activeState->connectTo($this->exceptionTransition);
         $this->activeState->connectTo($this->activityTransition);
+        $this->failingState->connectTo($this->completeExceptionTransition);
         $this->failingState->connectTo($this->closeExceptionTransition);
         $this->exceptionTransition->connectTo($this->failingState);
         $this->activityTransition->connectTo($this->closedState);
         $this->closedState->connectTo($this->transition);
-        $this->closeExceptionTransition->connectTo($this->closedState);
+        $this->completeExceptionTransition->connectTo($this->closedState);
 
         $this->activeState->attachEvent(
             StateInterface::EVENT_TOKEN_ARRIVED,
@@ -92,7 +93,6 @@ trait ActivityTrait
                     ->getTokenRepository()
                     ->persistActivityActivated($this, $token);
                 $this->notifyEvent(ActivityInterface::EVENT_ACTIVITY_ACTIVATED, $this, $token);
-
             }
         );
         $this->failingState->attachEvent(
@@ -102,7 +102,6 @@ trait ActivityTrait
                     ->getTokenRepository()
                     ->persistActivityException($this, $token);
                 $this->notifyEvent(ActivityInterface::EVENT_ACTIVITY_EXCEPTION, $this, $token);
-
             }
         );
         $this->closedState->attachEvent(
@@ -112,10 +111,19 @@ trait ActivityTrait
                     ->getTokenRepository()
                     ->persistActivityCompleted($this, $token);
                 $this->notifyEvent(ActivityInterface::EVENT_ACTIVITY_COMPLETED, $this, $token);
-
             }
         );
-
+        $this->closeExceptionTransition->attachEvent(
+            TransitionInterface::EVENT_AFTER_CONSUME,
+            function ($transition, $tokens) {
+                foreach ($tokens as $token) {
+                    $this->getRepository()
+                        ->getTokenRepository()
+                        ->persistActivityCompleted($this, $token);
+                }
+                $this->notifyEvent(ActivityInterface::EVENT_EVENT_TRIGGERED, $this, $transition, $tokens);
+            }
+        );
     }
 
     /**
@@ -152,7 +160,6 @@ trait ActivityTrait
                     ->getTokenRepository()
                     ->persistActivityClosed($this, $token);
                 $this->notifyEvent(ActivityInterface::EVENT_ACTIVITY_CLOSED, $this, $token);
-
             }
         );
         return $this;
diff --git a/src/ProcessMaker/Nayra/Bpmn/CloseExceptionTransition.php b/src/ProcessMaker/Nayra/Bpmn/CloseExceptionTransition.php
index 215269b..55654fe 100644
--- a/src/ProcessMaker/Nayra/Bpmn/CloseExceptionTransition.php
+++ b/src/ProcessMaker/Nayra/Bpmn/CloseExceptionTransition.php
@@ -2,7 +2,6 @@
 
 namespace ProcessMaker\Nayra\Bpmn;
 
-use ProcessMaker\Nayra\Bpmn\TransitionTrait;
 use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\TransitionInterface;
@@ -36,6 +35,6 @@ class CloseExceptionTransition implements TransitionInterface
      */
     public function assertCondition(TokenInterface $token = null, ExecutionInstanceInterface $executionInstance)
     {
-        return $token->getStatus() === ActivityInterface::TOKEN_STATE_COMPLETED;
+        return $token->getStatus() === ActivityInterface::TOKEN_STATE_CLOSED;
     }
 }
diff --git a/src/ProcessMaker/Nayra/Bpmn/CompleteExceptionTransition.php b/src/ProcessMaker/Nayra/Bpmn/CompleteExceptionTransition.php
new file mode 100644
index 0000000..5d37edc
--- /dev/null
+++ b/src/ProcessMaker/Nayra/Bpmn/CompleteExceptionTransition.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace ProcessMaker\Nayra\Bpmn;
+
+use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
+use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface;
+use ProcessMaker\Nayra\Contracts\Bpmn\TransitionInterface;
+use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface;
+
+/**
+ * Transition rule for an activity in FAILING state.
+ *
+ * @package ProcessMaker\Nayra\Bpmn
+ */
+class CompleteExceptionTransition implements TransitionInterface
+{
+    use TransitionTrait;
+
+    /**
+     * Initialize transition.
+     *
+     */
+    protected function initActivityTransition()
+    {
+        $this->setPreserveToken(true);
+    }
+
+    /**
+     * Condition required to transit an activity in FAILING state.
+     *
+     * @param TokenInterface|null $token
+     * @param ExecutionInstanceInterface $executionInstance
+     *
+     * @return bool
+     */
+    public function assertCondition(TokenInterface $token = null, ExecutionInstanceInterface $executionInstance)
+    {
+        return $token->getStatus() === ActivityInterface::TOKEN_STATE_COMPLETED;
+    }
+}
diff --git a/src/ProcessMaker/Nayra/Bpmn/ProcessTrait.php b/src/ProcessMaker/Nayra/Bpmn/ProcessTrait.php
index da4c319..d4b06e0 100755
--- a/src/ProcessMaker/Nayra/Bpmn/ProcessTrait.php
+++ b/src/ProcessMaker/Nayra/Bpmn/ProcessTrait.php
@@ -9,11 +9,11 @@ use ProcessMaker\Nayra\Contracts\Bpmn\CollectionInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreCollectionInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\DiagramInterface;
-use ProcessMaker\Nayra\Contracts\Bpmn\EndEventInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\EventCollectionInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\EventInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\FlowCollectionInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\FlowElementInterface;
+use ProcessMaker\Nayra\Contracts\Bpmn\FlowNodeInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\GatewayCollectionInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\GatewayInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\ProcessInterface;
@@ -21,7 +21,6 @@ use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\TransitionInterface;
 use ProcessMaker\Nayra\Contracts\Engine\EngineInterface;
 use ProcessMaker\Nayra\Contracts\Engine\ExecutionInstanceInterface;
-use ProcessMaker\Nayra\Contracts\EventBusInterface;
 use ProcessMaker\Nayra\Contracts\RepositoryInterface;
 
 /**
@@ -236,7 +235,7 @@ trait ProcessTrait
     public function addProperty($name, $value)
     {
         $this->baseAddProperty($name, $value);
-        if($value instanceof FlowElementInterface) {
+        if ($value instanceof FlowElementInterface) {
             $value->setOwnerProcess($this);
         }
         return $this;
@@ -255,41 +254,38 @@ trait ProcessTrait
             return $this->transitions;
         }
         //Build the runtime elements
-        foreach($this->getProperty('events') as $event) {
+        foreach ($this->getProperty('events') as $event) {
             $event->buildTransitions($factory);
         }
-        foreach($this->getProperty('activities') as $activity) {
+        foreach ($this->getProperty('activities') as $activity) {
             $activity->buildTransitions($factory);
         }
-        foreach($this->getProperty('gateways') as $gateway) {
+        foreach ($this->getProperty('gateways') as $gateway) {
             $gateway->buildTransitions($factory);
         }
         //Build the runtime flows
-        foreach($this->getProperty('events') as $event) {
+        foreach ($this->getProperty('events') as $event) {
             $event->buildFlowTransitions($factory);
         }
-        foreach($this->getProperty('activities') as $activity) {
+        foreach ($this->getProperty('activities') as $activity) {
             $activity->buildFlowTransitions($factory);
         }
-        foreach($this->getProperty('gateways') as $gateway) {
+        foreach ($this->getProperty('gateways') as $gateway) {
             $gateway->buildFlowTransitions($factory);
         }
         //Get the transitions
         $transitions = [];
-        foreach($this->getProperty('events') as $event) {
+        foreach ($this->getProperty('events') as $event) {
             $transitions = array_merge($transitions, $event->getTransitions());
         }
-        foreach($this->getProperty('activities') as $activity) {
+        foreach ($this->getProperty('activities') as $activity) {
             $transitions = array_merge($transitions, $activity->getTransitions());
         }
-        foreach($this->getProperty('gateways') as $gateway) {
+        foreach ($this->getProperty('gateways') as $gateway) {
             $transitions = array_merge($transitions, $gateway->getTransitions());
         }
         //Catch the conclusion of a process
-        $this->attachEvent(EventInterface::EVENT_EVENT_TRIGGERED, function (EventInterface $event, TransitionInterface $transition, CollectionInterface $tokens) {
-            if ($tokens->count() === 0 || !$event instanceof EndEventInterface) {
-                return;
-            }
+        $this->attachEvent(EventInterface::EVENT_EVENT_TRIGGERED, function (FlowNodeInterface $node, TransitionInterface $transition, CollectionInterface $tokens) {
             $instance = $tokens->item(0)->getInstance();
             if ($instance->getTokens()->count() !== 0) {
                 return;
@@ -297,7 +293,7 @@ trait ProcessTrait
 
             $instanceRepo = $this->getRepository()->createExecutionInstanceRepository();
             $instanceRepo->persistInstanceCompleted($instance);
-            $this->notifyInstanceEvent(ProcessInterface::EVENT_PROCESS_INSTANCE_COMPLETED, $instance, $event);
+            $this->notifyInstanceEvent(ProcessInterface::EVENT_PROCESS_INSTANCE_COMPLETED, $instance, $node);
         });
         $this->transitions = new Collection($transitions);
         return $this->transitions;
@@ -443,7 +439,7 @@ trait ProcessTrait
             $dataStore = $this->getRepository()->createDataStore();
         }
         $instance = $this->getEngine()->createExecutionInstance($this, $dataStore);
-        $this->getEvents()->find(function(EventInterface $event) use($instance) {
+        $this->getEvents()->find(function (EventInterface $event) use ($instance) {
             if ($event instanceof StartEventInterface && $event->getEventDefinitions()->count() === 0) {
                 $event->start($instance);
             }
diff --git a/src/ProcessMaker/Nayra/Contracts/Bpmn/ActivityInterface.php b/src/ProcessMaker/Nayra/Contracts/Bpmn/ActivityInterface.php
index c58eb54..be6d961 100755
--- a/src/ProcessMaker/Nayra/Contracts/Bpmn/ActivityInterface.php
+++ b/src/ProcessMaker/Nayra/Contracts/Bpmn/ActivityInterface.php
@@ -2,8 +2,6 @@
 
 namespace ProcessMaker\Nayra\Contracts\Bpmn;
 
-use ProcessMaker\Nayra\Contracts\Bpmn\FlowNodeInterface;
-
 /**
  * Activity interface.
  *
@@ -11,7 +9,6 @@ use ProcessMaker\Nayra\Contracts\Bpmn\FlowNodeInterface;
  */
 interface ActivityInterface extends FlowNodeInterface
 {
-
     /**
      * Events defined for Activity
      */
@@ -19,6 +16,7 @@ interface ActivityInterface extends FlowNodeInterface
     const EVENT_ACTIVITY_COMPLETED = 'ActivityCompleted';
     const EVENT_ACTIVITY_EXCEPTION = 'ActivityException';
     const EVENT_ACTIVITY_CLOSED = 'ActivityClosed';
+    const EVENT_EVENT_TRIGGERED = 'EventTriggered';
 
     /**
      * Token states defined for Activity
diff --git a/src/ProcessMaker/Nayra/Engine/ExecutionInstanceTrait.php b/src/ProcessMaker/Nayra/Engine/ExecutionInstanceTrait.php
index 232dee3..e9732c3 100644
--- a/src/ProcessMaker/Nayra/Engine/ExecutionInstanceTrait.php
+++ b/src/ProcessMaker/Nayra/Engine/ExecutionInstanceTrait.php
@@ -4,6 +4,7 @@ namespace ProcessMaker\Nayra\Engine;
 
 use ProcessMaker\Nayra\Bpmn\BaseTrait;
 use ProcessMaker\Nayra\Bpmn\Collection;
+use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\ProcessInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\TokenInterface;
@@ -149,6 +150,10 @@ trait ExecutionInstanceTrait
      */
     public function close()
     {
+        $tokens = $this->getTokens()->toArray();
+        foreach ($tokens as $token) {
+            $token->setStatus(ActivityInterface::TOKEN_STATE_CLOSED);
+        }
         return true;
     }
 


Test patch:
diff --git a/tests/Feature/Engine/ScriptTaskTest.php b/tests/Feature/Engine/ScriptTaskTest.php
index cd82903..0fee576 100644
--- a/tests/Feature/Engine/ScriptTaskTest.php
+++ b/tests/Feature/Engine/ScriptTaskTest.php
@@ -4,12 +4,10 @@ namespace Tests\Feature\Engine;
 
 use ProcessMaker\Nayra\Bpmn\Models\Process;
 use ProcessMaker\Nayra\Contracts\Bpmn\ActivityInterface;
-use ProcessMaker\Nayra\Contracts\Bpmn\DataStoreInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\EndEventInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\EventInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\ProcessInterface;
 use ProcessMaker\Nayra\Contracts\Bpmn\ScriptTaskInterface;
-use ProcessMaker\Nayra\Contracts\Bpmn\StartEventInterface;
 
 /**
  * Tests for the ScriptTask element
@@ -254,6 +252,64 @@ class ScriptTaskTest extends EngineTestCase
 
         return $process;
     }
-}
 
+    /**
+     * Tests that when a script fails, then it is closed
+     */
+    public function testScriptTaskThatFailsAndIsClosed()
+    {
+        //Load a process
+        $process = $this->getProcessWithOneScriptTask();
+        $dataStore = $this->repository->createDataStore();
+
+        //create an instance of the process
+        $instance = $this->engine->createExecutionInstance($process, $dataStore);
 
+        //Get References
+        $start = $process->getEvents()->item(0);
+        $activity1 = $process->getActivities()->item(0);
+        $scriptTask = $process->getActivities()->item(1);
+
+        //set an script that evaluates with an error
+        $scriptTask->setScript('throw new Exception ("test exception");');
+
+        //start the process an instance of the process
+        $start->start($instance);
+        $this->engine->runToNextState();
+
+        //Assert: that the process is stared and the first activity activated
+        $this->assertEvents([
+            ProcessInterface::EVENT_PROCESS_INSTANCE_CREATED,
+            EventInterface::EVENT_EVENT_TRIGGERED,
+            ActivityInterface::EVENT_ACTIVITY_ACTIVATED,
+        ]);
+
+        //complete the first activity (that is a manual one)
+        $token = $activity1->getTokens($instance)->item(0);
+        $activity1->complete($token);
+        $this->engine->runToNextState();
+
+        $scriptToken = $scriptTask->getTokens($instance)->item(0);
+        $scriptTask->runScript($scriptToken);
+        $this->engine->runToNextState();
+
+        //Assertion: Verify that the token was set to a failed state
+        $this->assertEquals($scriptToken->getStatus(), ActivityInterface::TOKEN_STATE_FAILING);
+
+        // Close the script task
+        $scriptToken = $scriptTask->getTokens($instance)->item(0);
+        $scriptToken->setStatus(ActivityInterface::TOKEN_STATE_CLOSED);
+        $this->engine->runToNextState();
+
+        //Assertion: Verify that the script was
+        $this->assertEvents([
+            ActivityInterface::EVENT_ACTIVITY_COMPLETED,
+            ActivityInterface::EVENT_ACTIVITY_CLOSED,
+            ActivityInterface::EVENT_ACTIVITY_ACTIVATED,
+            ScriptTaskInterface::EVENT_SCRIPT_TASK_ACTIVATED,
+            ActivityInterface::EVENT_ACTIVITY_EXCEPTION,
+            ActivityInterface::EVENT_EVENT_TRIGGERED,
+            ProcessInterface::EVENT_PROCESS_INSTANCE_COMPLETED,
+        ]);
+    }
+}