--- /dev/null
+<?php
+namespace Civi\CCase;
+
+/**
+ * The sequence-listener looks for CiviCase XML tags with "<sequence>". If
+ * a change is made to any record in case-type which uses "<sequence>", 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 <ActivityType> 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
$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;
}
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<CaseType>
+ <name>Housing Support</name>
+ <ActivityTypes>
+ <ActivityType>
+ <name>Open Case</name>
+ <max_instances>1</max_instances>
+ </ActivityType>
+ <ActivityType>
+ <name>Medical evaluation</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Mental health evaluation</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Secure temporary housing</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Income and benefits stabilization</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Long-term housing plan</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Follow up</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Change Case Type</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Change Case Status</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Change Case Start Date</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Link Cases</name>
+ </ActivityType>
+ </ActivityTypes>
+ <ActivitySets>
+ <ActivitySet>
+ <name>standard_timeline</name>
+ <label>Standard Timeline</label>
+ <timeline>true</timeline>
+ <ActivityTypes>
+ <ActivityType>
+ <name>Open Case</name>
+ <status>Completed</status>
+ </ActivityType>
+ </ActivityTypes>
+ </ActivitySet>
+ <ActivitySet>
+ <name>my_sequence</name>
+ <label>My Sequence</label>
+ <sequence>true</sequence>
+ <ActivityTypes>
+ <ActivityType>
+ <name>Medical evaluation</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Mental health evaluation</name>
+ </ActivityType>
+ <ActivityType>
+ <name>Secure temporary housing</name>
+ </ActivityType>
+ </ActivityTypes>
+ </ActivitySet>
+ </ActivitySets>
+ <CaseRoles>
+ <RelationshipType>
+ <name>Homeless Services Coordinator</name>
+ <creator>1</creator>
+ <manager>1</manager>
+ </RelationshipType>
+ <RelationshipType>
+ <name>Health Services Coordinator</name>
+ </RelationshipType>
+ <RelationshipType>
+ <name>Benefits Specialist</name>
+ </RelationshipType>
+ </CaseRoles>
+</CaseType>
--- /dev/null
+<?php
+namespace Civi\CCase;
+
+require_once 'CiviTest/CiviCaseTestCase.php';
+
+class SequenceListenerTest extends \CiviCaseTestCase {
+
+ public function setUp() {
+ parent::setUp();
+ $this->_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