From b019b13008bbc4e630f8b6a40d5b7c3a73916c5f Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 26 May 2014 22:40:29 -0700 Subject: [PATCH] CRM-14727 - Implement Civi\CCase\SequenceListener --- Civi/CCase/SequenceListener.php | 115 ++++++++++++++++++ Civi/Core/Container.php | 1 + .../Civi/CCase/HousingSupportWithSequence.xml | 83 +++++++++++++ .../Civi/CCase/SequenceListenerTest.php | 95 +++++++++++++++ 4 files changed, 294 insertions(+) create mode 100644 Civi/CCase/SequenceListener.php create mode 100644 tests/phpunit/Civi/CCase/HousingSupportWithSequence.xml create mode 100644 tests/phpunit/Civi/CCase/SequenceListenerTest.php diff --git a/Civi/CCase/SequenceListener.php b/Civi/CCase/SequenceListener.php new file mode 100644 index 0000000000..5eaa32d0ca --- /dev/null +++ b/Civi/CCase/SequenceListener.php @@ -0,0 +1,115 @@ +". If + * a change is made to any record in case-type which uses "", then + * it attempts to add the next step in the sequence. + */ +class SequenceListener implements CaseChangeListener { + + /** + * @var SequenceListener + */ + private static $singleton; + + /** + * @param bool $reset whether to forcibly rebuild the entire container + * @return \Symfony\Component\DependencyInjection\TaggedContainerInterface + */ + public static function singleton($reset = FALSE) { + if ($reset || self::$singleton === NULL) { + self::$singleton = new SequenceListener(); + } + return self::$singleton; + } + + public static function onCaseChange_static(\Civi\CCase\Event\CaseChangeEvent $event) { + self::singleton()->onCaseChange($event); + } + + private $isActive = array(); + + public function onCaseChange(\Civi\CCase\Event\CaseChangeEvent $event) { + /** @var \Civi\CCase\Analyzer $analyzer */ + $analyzer = $event->analyzer; + + if (isset($this->isActive[$analyzer->getCaseId()])) { + return; + } + $this->isActive[$analyzer->getCaseId()] = 1; + + $activitySetXML = $this->getSequenceXml($analyzer->getXml()); + if (!$activitySetXML) { + return; + } + + $actTypes = array_flip(\CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name')); + $actStatuses = array_flip(\CRM_Core_PseudoConstant::activityStatus('name')); + + $actIndex = $analyzer->getActivityIndex(array('activity_type_id', 'status_id')); + + foreach ($activitySetXML->ActivityTypes->ActivityType as $actTypeXML) { + $actTypeId = $actTypes[(string) $actTypeXML->name]; + if (empty($actIndex[$actTypeId])) { + // Haven't tried this step yet! + $this->createActivity($analyzer, $actTypeXML); + unset($this->isActive[$analyzer->getCaseId()]); + return; + } + elseif (empty($actIndex[$actTypeId][$actStatuses['Completed']])) { + // Haven't gotten past this step yet! + unset($this->isActive[$analyzer->getCaseId()]); + return; + } + } + + // OK, the activity has completed every step in the sequence! + civicrm_api3('Case', 'create', array( + 'id' => $analyzer->getCaseId(), + 'status_id' => 'Closed', + )); + $analyzer->flush(); + + // Wrap-up + unset($this->isActive[$analyzer->getCaseId()]); + } + + /** + * Find the ActivitySet which defines the pipeline. + * + * @param \SimpleXMLElement $xml + * @return \SimpleXMLElement|NULL + */ + public function getSequenceXml($xml) { + if ($xml->ActivitySets && $xml->ActivitySets->ActivitySet) { + foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) { + $seq = (string) $activitySetXML->sequence; + if ($seq && strtolower($seq) == 'true') { + if ($activitySetXML->ActivityTypes && $activitySetXML->ActivityTypes->ActivityType) { + return $activitySetXML; + } + else { + return NULL; + } + } + } + } + return NULL; + } + + /** + * @param Analyzer $analyzer the case being analyzed -- to which we want to add an activity + * @param \SimpleXMLElement $actXML the tag which describes the new activity + */ + public function createActivity(Analyzer $analyzer, \SimpleXMLElement $actXML) { + $params = array( + 'activity_type_id' => (string) $actXML->name, + 'status_id' => 'Scheduled', + 'activity_date_time' => \CRM_Utils_Time::getTime('YmdHis'), + 'case_id' => $analyzer->getCaseId(), + ); + $r = civicrm_api3('Activity', 'create', $params); + $analyzer->flush(); + } +} \ No newline at end of file diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index 3c4c846e23..faecd4b407 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -92,6 +92,7 @@ class Container { $dispatcher->addListener('hook_civicrm_post::Activity', array('\Civi\CCase\Events', 'fireCaseChange')); $dispatcher->addListener('hook_civicrm_post::Case', array('\Civi\CCase\Events', 'fireCaseChange')); $dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\Events', 'delegateToXmlListeners')); + $dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\SequenceListener', 'onCaseChange_static')); return $dispatcher; } diff --git a/tests/phpunit/Civi/CCase/HousingSupportWithSequence.xml b/tests/phpunit/Civi/CCase/HousingSupportWithSequence.xml new file mode 100644 index 0000000000..419d35173e --- /dev/null +++ b/tests/phpunit/Civi/CCase/HousingSupportWithSequence.xml @@ -0,0 +1,83 @@ + + + + Housing Support + + + Open Case + 1 + + + Medical evaluation + + + Mental health evaluation + + + Secure temporary housing + + + Income and benefits stabilization + + + Long-term housing plan + + + Follow up + + + Change Case Type + + + Change Case Status + + + Change Case Start Date + + + Link Cases + + + + + standard_timeline + + true + + + Open Case + Completed + + + + + my_sequence + + true + + + Medical evaluation + + + Mental health evaluation + + + Secure temporary housing + + + + + + + Homeless Services Coordinator + 1 + 1 + + + Health Services Coordinator + + + Benefits Specialist + + + diff --git a/tests/phpunit/Civi/CCase/SequenceListenerTest.php b/tests/phpunit/Civi/CCase/SequenceListenerTest.php new file mode 100644 index 0000000000..eed57bb98e --- /dev/null +++ b/tests/phpunit/Civi/CCase/SequenceListenerTest.php @@ -0,0 +1,95 @@ +_params = array( + 'case_type' => 'Housing Support', // FIXME: $this->caseType, + 'subject' => 'Test case', + 'contact_id' => 17, + ); + } + + public function testSequence() { + $actStatuses = array_flip(\CRM_Core_PseudoConstant::activityStatus('name')); + $caseStatuses = array_flip(\CRM_Case_PseudoConstant::caseStatus('name')); + + // Create case; schedule first activity + \CRM_Utils_Time::setTime('2013-11-30 01:00:00'); + $case = $this->callAPISuccess('case', 'create', $this->_params); + + $analyzer = new \Civi\CCase\Analyzer($case['id']); + $this->assertEquals($caseStatuses['Open'], $analyzer->getCase()['status_id']); + $this->assertEquals('2013-11-30 01:00:00', $analyzer->getSingleActivity('Medical evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Scheduled'], $analyzer->getSingleActivity('Medical evaluation')['status_id']); + $this->assertFalse($analyzer->hasActivity('Mental health evaluation')); + $this->assertFalse($analyzer->hasActivity('Secure temporary housing')); + + // Complete first activity; schedule second + \CRM_Utils_Time::setTime('2013-11-30 02:00:00'); + $this->callApiSuccess('Activity', 'create', array( + 'id' => $analyzer->getSingleActivity('Medical evaluation')['id'], + 'status_id' => $actStatuses['Completed'], + )); + $analyzer->flush(); + $this->assertEquals($caseStatuses['Open'], $analyzer->getCase()['status_id']); + $this->assertEquals('2013-11-30 01:00:00', $analyzer->getSingleActivity('Medical evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Completed'], $analyzer->getSingleActivity('Medical evaluation')['status_id']); + $this->assertEquals('2013-11-30 02:00:00', $analyzer->getSingleActivity('Mental health evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Scheduled'], $analyzer->getSingleActivity('Mental health evaluation')['status_id']); + $this->assertFalse($analyzer->hasActivity('Secure temporary housing')); + + // Complete second activity; schedule third + \CRM_Utils_Time::setTime('2013-11-30 03:00:00'); + $this->callApiSuccess('Activity', 'create', array( + 'id' => $analyzer->getSingleActivity('Mental health evaluation')['id'], + 'status_id' => $actStatuses['Completed'], + )); + $analyzer->flush(); + $this->assertEquals($caseStatuses['Open'], $analyzer->getCase()['status_id']); + $this->assertEquals('2013-11-30 01:00:00', $analyzer->getSingleActivity('Medical evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Completed'], $analyzer->getSingleActivity('Medical evaluation')['status_id']); + $this->assertEquals('2013-11-30 02:00:00', $analyzer->getSingleActivity('Mental health evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Completed'], $analyzer->getSingleActivity('Mental health evaluation')['status_id']); + $this->assertEquals('2013-11-30 03:00:00', $analyzer->getSingleActivity('Secure temporary housing')['activity_date_time']); + $this->assertEquals($actStatuses['Scheduled'], $analyzer->getSingleActivity('Secure temporary housing')['status_id']); + + // Complete third activity; close case + \CRM_Utils_Time::setTime('2013-11-30 04:00:00'); + $this->callApiSuccess('Activity', 'create', array( + 'id' => $analyzer->getSingleActivity('Secure temporary housing')['id'], + 'status_id' => $actStatuses['Completed'], + )); + $analyzer->flush(); + $this->assertEquals('2013-11-30 01:00:00', $analyzer->getSingleActivity('Medical evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Completed'], $analyzer->getSingleActivity('Medical evaluation')['status_id']); + $this->assertEquals('2013-11-30 02:00:00', $analyzer->getSingleActivity('Mental health evaluation')['activity_date_time']); + $this->assertEquals($actStatuses['Completed'], $analyzer->getSingleActivity('Mental health evaluation')['status_id']); + $this->assertEquals('2013-11-30 03:00:00', $analyzer->getSingleActivity('Secure temporary housing')['activity_date_time']); + $this->assertEquals($actStatuses['Completed'], $analyzer->getSingleActivity('Secure temporary housing')['status_id']); + $this->assertEquals($caseStatuses['Closed'], $analyzer->getCase()['status_id']); + } + + /** + * @param $caseTypes + * @see \CRM_Utils_Hook::caseTypes + */ + function hook_caseTypes(&$caseTypes) { + $caseTypes[$this->caseType] = array( + 'module' => 'org.civicrm.hrcase', + 'name' => $this->caseType, + 'file' => __DIR__ . '/HousingSupportWithSequence.xml', + ); + } + + function assertApproxTime($expected, $actual, $tolerance = 1) { + $diff = abs(strtotime($expected) - strtotime($actual)); + $this->assertTrue($diff <= $tolerance, sprintf("Check approx time equality. expected=[%s] actual=[%s] tolerance=[%s]", + $expected, $actual, $tolerance + )); + } +} \ No newline at end of file -- 2.25.1