diff --git a/CRM/Case/BAO/Case.php b/CRM/Case/BAO/Case.php index 3fa0e9113c6d3c62491c1c7092f5f005c30e7b56..222860016a0125dbbdd91beeb93e281a0613b555 100644 --- a/CRM/Case/BAO/Case.php +++ b/CRM/Case/BAO/Case.php @@ -429,121 +429,60 @@ WHERE cc.contact_id = %1 AND civicrm_case_type.name = '{$caseType}'"; 'civicrm_phone.phone as phone', 'civicrm_contact.contact_type as contact_type', 'civicrm_contact.contact_sub_type as contact_sub_type', - 't_act.activity_type_id', - 'c_type.title as case_type', + 't_act.activity_type_id as activity_type_id', 'civicrm_case.case_type_id as case_type_id', - 'cov_status.label as case_status', - 'cov_status.label as case_status_name', - 't_act.status_id', + 'civicrm_case.status_id as case_status_id', + 't_act.status_id as status_id', 'civicrm_case.start_date as case_start_date', 'case_relation_type.label_b_a as case_role', + 't_act.activity_date_time as activity_date_time', + 't_act.id as activity_id', ); - if ($type == 'upcoming') { - $selectClauses = array_merge($selectClauses, array( - 't_act.desired_date as case_scheduled_activity_date', - 't_act.id as case_scheduled_activity_id', - 't_act.act_type_name as case_scheduled_activity_type_name', - 't_act.act_type AS case_scheduled_activity_type', - )); - } - elseif ($type == 'recent') { - $selectClauses = array_merge($selectClauses, array( - 't_act.desired_date as case_recent_activity_date', - 't_act.id as case_recent_activity_id', - 't_act.act_type_name as case_recent_activity_type_name', - 't_act.act_type AS case_recent_activity_type', - )); - } - elseif ($type == 'any') { - $selectClauses = array_merge($selectClauses, array( - 't_act.desired_date as case_activity_date', - 't_act.id as case_activity_id', - 't_act.act_type_name as case_activity_type_name', - 't_act.act_type AS case_activity_type', - )); - } - $query = CRM_Contact_BAO_Query::appendAnyValueToSelect($selectClauses, 'case_id'); - $query .= " FROM civicrm_case - INNER JOIN civicrm_case_contact ON civicrm_case.id = civicrm_case_contact.case_id - INNER JOIN civicrm_contact ON civicrm_case_contact.contact_id = civicrm_contact.id "; - - if ($type == 'upcoming') { - // This gets the earliest activity per case that's scheduled within 14 days from now. - // Note we have an inner select to get the min activity id in order to remove duplicates in case there are two with the same datetime. - // In this case we don't really care which one, so min(id) works. - // optimized in CRM-11837 - $query .= " INNER JOIN -( - SELECT case_id, act.id, activity_date_time AS desired_date, activity_type_id, status_id, aov.name AS act_type_name, aov.label AS act_type - FROM ( - SELECT * - FROM ( - SELECT * - FROM civicrm_view_case_activity_upcoming - ORDER BY activity_date_time ASC, id ASC - ) AS upcomingOrdered - ) AS act - LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' - LEFT JOIN civicrm_option_value aov ON ( aov.option_group_id = aog.id AND aov.value = act.activity_type_id ) -) AS t_act -"; - } - elseif ($type == 'recent') { - // Similarly, the most recent activity in the past 14 days, and exclude scheduled. - //improve query performance - CRM-10598 - $query .= " INNER JOIN -( - SELECT case_id, act.id, activity_date_time AS desired_date, activity_type_id, status_id, aov.name AS act_type_name, aov.label AS act_type - FROM ( - SELECT * - FROM ( - SELECT * - FROM civicrm_view_case_activity_recent - ORDER BY activity_date_time DESC, id ASC - ) AS recentOrdered - ) AS act -LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' - LEFT JOIN civicrm_option_value aov ON ( aov.option_group_id = aog.id AND aov.value = act.activity_type_id ) -) AS t_act "; - } - elseif ($type == 'any') { - $query .= " LEFT JOIN -( - SELECT ca4.case_id, act4.id AS id, act4.activity_date_time AS desired_date, act4.activity_type_id, act4.status_id, aov.name AS act_type_name, aov.label AS act_type - FROM civicrm_activity act4 - LEFT JOIN civicrm_case_activity ca4 - ON ca4.activity_id = act4.id - AND act4.is_current_revision = 1 - LEFT JOIN civicrm_option_group aog - ON aog.name='activity_type' - LEFT JOIN civicrm_option_value aov - ON aov.option_group_id = aog.id - AND aov.value = act4.activity_type_id -) AS t_act"; - } - - $query .= " - ON t_act.case_id = civicrm_case.id - LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = civicrm_contact.id AND civicrm_phone.is_primary=1) - LEFT JOIN civicrm_relationship case_relationship - ON ( case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active AND case_relationship.case_id = civicrm_case.id ) - LEFT JOIN civicrm_relationship_type case_relation_type - ON ( case_relation_type.id = case_relationship.relationship_type_id - AND case_relation_type.id = case_relationship.relationship_type_id ) - - LEFT JOIN civicrm_case_type c_type - ON civicrm_case.case_type_id = c_type.id - - LEFT JOIN civicrm_option_group cog_status - ON cog_status.name = 'case_status' - - LEFT JOIN civicrm_option_value cov_status - ON ( civicrm_case.status_id = cov_status.value - AND cog_status.id = cov_status.option_group_id ) -"; + $query .= <<<HERESQL + FROM civicrm_case + INNER JOIN civicrm_case_contact ON civicrm_case.id = civicrm_case_contact.case_id + INNER JOIN civicrm_contact ON civicrm_case_contact.contact_id = civicrm_contact.id +HERESQL; + + switch ($type) { + case 'upcoming': + case 'recent': + // civicrm_view_case_activity_upcoming and + // civicrm_view_case_activity_recent are views that show the next + // scheduled and most recent not-scheduled activity on each case, + // respectively. + $query .= <<<HERESQL + INNER JOIN civicrm_view_case_activity_$type t_act + ON t_act.case_id = civicrm_case.id +HERESQL; + break; + + case 'any': + $query .= <<<HERESQL + LEFT JOIN civicrm_case_activity ca4 + ON civicrm_case.id = ca4.case_id + LEFT JOIN civicrm_activity t_act + ON t_act.id = ca4.activity_id + AND t_act.is_current_revision = 1 +HERESQL; + } + + $query .= <<<HERESQL + LEFT JOIN civicrm_phone + ON civicrm_phone.contact_id = civicrm_contact.id + AND civicrm_phone.is_primary = 1 + LEFT JOIN civicrm_relationship case_relationship + ON case_relationship.contact_id_a = civicrm_case_contact.contact_id + AND case_relationship.contact_id_b = {$userID} + AND case_relationship.is_active + AND case_relationship.case_id = civicrm_case.id + LEFT JOIN civicrm_relationship_type case_relation_type + ON case_relation_type.id = case_relationship.relationship_type_id + AND case_relation_type.id = case_relationship.relationship_type_id +HERESQL; if ($condition) { // CRM-8749 backwards compatibility - callers of this function expect to start $condition with "AND" @@ -551,20 +490,7 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' } $query .= " GROUP BY case_id "; - if ($order) { - $query .= $order; - } - else { - if ($type == 'upcoming') { - $query .= " ORDER BY case_scheduled_activity_date ASC "; - } - elseif ($type == 'recent') { - $query .= " ORDER BY case_recent_activity_date ASC "; - } - elseif ($type == 'any') { - $query .= " ORDER BY case_activity_date ASC "; - } - } + $query .= ($order) ?: ' ORDER BY activity_date_time ASC'; if ($limit) { $query .= $limit; @@ -601,20 +527,6 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' $type = CRM_Utils_Array::value('type', $params, 'upcoming'); $userID = CRM_Core_Session::singleton()->get('userID'); - $caseActivityTypeColumn = 'case_activity_type_name'; - $caseActivityDateColumn = 'case_activity_date'; - $caseActivityIDColumn = 'case_activity_id'; - if ($type == 'upcoming') { - $caseActivityDateColumn = 'case_scheduled_activity_date'; - $caseActivityTypeColumn = 'case_scheduled_activity_type'; - $caseActivityIDColumn = 'case_scheduled_activity_id'; - } - elseif ($type == 'recent') { - $caseActivityDateColumn = 'case_recent_activity_date'; - $caseActivityTypeColumn = 'case_recent_activity_type'; - $caseActivityIDColumn = 'case_recent_activity_id'; - } - // validate access for all cases. if ($allCases && !CRM_Core_Permission::check('access all cases and activities')) { $allCases = FALSE; @@ -654,7 +566,7 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' $order = NULL; if (!empty($params['sortBy'])) { if (strstr($params['sortBy'], 'date ')) { - $params['sortBy'] = str_replace('date', $caseActivityDateColumn, $params['sortBy']); + $params['sortBy'] = str_replace('date', 'activity_date_time', $params['sortBy']); } $order = "ORDER BY " . $params['sortBy']; } @@ -662,8 +574,6 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' $query = self::getCaseActivityQuery($type, $userID, $condition, $limit, $order); $result = CRM_Core_DAO::executeQuery($query); - $caseStatus = CRM_Core_OptionGroup::values('case_status', FALSE, FALSE, FALSE, " AND v.name = 'Urgent' "); - // we're going to use the usual actions, so doesn't make sense to duplicate definitions $actions = CRM_Case_Selector_Search::links(); @@ -679,7 +589,12 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' } $mask = CRM_Core_Action::mask($permissions); + // Pseudoconstants to populate labels + $caseStatuses = CRM_Case_PseudoConstant::caseStatus('label', FALSE); $caseTypes = CRM_Case_PseudoConstant::caseType('name'); + $caseTypeTitles = CRM_Case_PseudoConstant::caseType('title', FALSE); + $activityTypeLabels = CRM_Activity_BAO_Activity::buildOptions('activity_type_id'); + foreach ($result->fetchAll() as $case) { $key = $case['case_id']; $casesList[$key] = array(); @@ -701,30 +616,31 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' $case['case_id'] ); $casesList[$key]['subject'] = $case['case_subject']; - $casesList[$key]['case_status'] = in_array($case['case_status'], $caseStatus) ? sprintf('<strong>%s</strong>', strtoupper($case['case_status'])) : $case['case_status']; - $casesList[$key]['case_type'] = $case['case_type']; + $casesList[$key]['case_status'] = CRM_Utils_Array::value($case['case_status_id'], $caseStatuses); + if ($case['case_status_id'] == CRM_Case_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Urgent')) { + $casesList[$key]['case_status'] = sprintf('<strong>%s</strong>', strtoupper($casesList[$key]['case_status'])); + } + $casesList[$key]['case_type'] = CRM_Utils_Array::value($case['case_type_id'], $caseTypeTitles); $casesList[$key]['case_role'] = CRM_Utils_Array::value('case_role', $case, '---'); $casesList[$key]['manager'] = self::getCaseManagerContact($caseTypes[$case['case_type_id']], $case['case_id']); - $casesList[$key]['date'] = $case[$caseActivityTypeColumn]; - if (($actId = CRM_Utils_Array::value('case_scheduled_activity_id', $case)) || - ($actId = CRM_Utils_Array::value('case_recent_activity_id', $case)) - ) { + $casesList[$key]['date'] = CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels); + if ($actId = CRM_Utils_Array::value('activity_id', $case)) { if (self::checkPermission($actId, 'view', $case['activity_type_id'], $userID)) { if ($type == 'recent') { $casesList[$key]['date'] = sprintf('<a class="action-item crm-hover-button" href="%s" title="%s">%s</a>', - CRM_Utils_System::url('civicrm/case/activity/view', array('reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case[$caseActivityIDColumn])), + CRM_Utils_System::url('civicrm/case/activity/view', array('reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case['activity_id'])), ts('View activity'), - $case[$caseActivityTypeColumn] + CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels) ); } else { - $status = CRM_Utils_Date::overdue($case[$caseActivityDateColumn]) ? 'status-overdue' : 'status-scheduled'; + $status = CRM_Utils_Date::overdue($case['activity_date_time']) ? 'status-overdue' : 'status-scheduled'; $casesList[$key]['date'] = sprintf('<a class="crm-popup %s" href="%s" title="%s">%s</a> ', $status, - CRM_Utils_System::url('civicrm/case/activity/view', array('reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case[$caseActivityIDColumn])), + CRM_Utils_System::url('civicrm/case/activity/view', array('reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case['activity_id'])), ts('View activity'), - $case[$caseActivityTypeColumn] + CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels) ); } } @@ -735,7 +651,7 @@ LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' ); } } - $casesList[$key]['date'] .= "<br/>" . CRM_Utils_Date::customFormat($case[$caseActivityDateColumn]); + $casesList[$key]['date'] .= "<br/>" . CRM_Utils_Date::customFormat($case['activity_date_time']); $casesList[$key]['links'] = CRM_Core_Action::formLink($actions['primaryActions'], $mask, array( 'id' => $case['case_id'], @@ -1627,18 +1543,19 @@ SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS c AND civicrm_case.is_deleted = {$cases['case_deleted']}"; $query = self::getCaseActivityQuery($type, $userID, $condition); + $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id'); $res = CRM_Core_DAO::executeQuery($query); $activityInfo = array(); while ($res->fetch()) { if ($type == 'upcoming') { - $activityInfo[$res->case_id]['date'] = $res->case_scheduled_activity_date; - $activityInfo[$res->case_id]['type'] = $res->case_scheduled_activity_type; + $activityInfo[$res->case_id]['date'] = $res->activity_date_time; + $activityInfo[$res->case_id]['type'] = CRM_Utils_Array::value($res->activity_type_id, $activityTypes); } else { - $activityInfo[$res->case_id]['date'] = $res->case_recent_activity_date; - $activityInfo[$res->case_id]['type'] = $res->case_recent_activity_type; + $activityInfo[$res->case_id]['date'] = $res->activity_date_time; + $activityInfo[$res->case_id]['type'] = CRM_Utils_Array::value($res->activity_type_id, $activityTypes); } } @@ -3055,8 +2972,8 @@ WHERE id IN (' . implode(',', $copiedActivityIds) . ')'; AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id FROM civicrm_case_activity ca INNER JOIN civicrm_activity a ON ca.activity_id=a.id - WHERE a.activity_date_time = -(SELECT b.activity_date_time FROM civicrm_case_activity bca + WHERE a.id = +(SELECT b.id FROM civicrm_case_activity bca INNER JOIN civicrm_activity b ON bca.activity_id=b.id WHERE b.activity_date_time <= DATE_ADD( NOW(), INTERVAL 14 DAY ) AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id = $scheduled_id @@ -3068,8 +2985,8 @@ WHERE id IN (' . implode(',', $copiedActivityIds) . ')'; AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id FROM civicrm_case_activity ca INNER JOIN civicrm_activity a ON ca.activity_id=a.id - WHERE a.activity_date_time = -(SELECT b.activity_date_time FROM civicrm_case_activity bca + WHERE a.id = +(SELECT b.id FROM civicrm_case_activity bca INNER JOIN civicrm_activity b ON bca.activity_id=b.id WHERE b.activity_date_time >= DATE_SUB( NOW(), INTERVAL 14 DAY ) AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id <> $scheduled_id diff --git a/tests/phpunit/CRM/Case/BAO/CaseTest.php b/tests/phpunit/CRM/Case/BAO/CaseTest.php index 7c8f1ed47f8ba97b734e660d07bef8e30c7a9662..0d1c270d5fb6a7331d01bb15e80f6daa3751c761 100644 --- a/tests/phpunit/CRM/Case/BAO/CaseTest.php +++ b/tests/phpunit/CRM/Case/BAO/CaseTest.php @@ -31,6 +31,28 @@ class CRM_Case_BAO_CaseTest extends CiviUnitTestCase { CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase'); } + /** + * Make sure that the latest case activity works accurately. + */ + public function testCaseActivity() { + $userID = $this->createLoggedInUser(); + + $addTimeline = civicrm_api3('Case', 'addtimeline', [ + 'case_id' => 1, + 'timeline' => "standard_timeline", + ]); + + $query = CRM_Case_BAO_Case::getCaseActivityQuery('recent', $userID, ' civicrm_case.id IN( 1 )'); + $res = CRM_Core_DAO::executeQuery($query); + $openCaseType = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Open Case'); + while ($res->fetch()) { + $message = 'Failed asserting that the case activity query has a activity_type_id property:'; + $this->assertObjectHasAttribute('activity_type_id', $res, $message . PHP_EOL . print_r($res, TRUE)); + $message = 'Failed asserting that the latest activity from Case ID 1 was "Open Case":'; + $this->assertEquals($openCaseType, $res->activity_type_id, $message . PHP_EOL . print_r($res, TRUE)); + } + } + protected function tearDown() { parent::tearDown(); $this->quickCleanup($this->tablesToTruncate, TRUE);