Commit 760ac501 authored by colemanw's avatar colemanw
Browse files

CRM-20830 - Improve handling of overdue activities

parent 3ad89820
......@@ -2436,6 +2436,51 @@ AND cl.modified_id = c.id
return $result;
}
/**
* Return list of activity statuses that are considered "completed".
*
* Note: activity status options use the "grouping" field to distinguish complete from incomplete statuses.
*
* @return array
*/
public static function getCompletedStatuses() {
if (!isset(Civi::$statics[__CLASS__][__FUNCTION__])) {
$statuses = civicrm_api3('OptionValue', 'get', array(
'option_group_id' => "activity_status",
'filter' => 1,
'return' => array('value'),
'sequential' => 1,
'options' => array('limit' => 0),
));
Civi::$statics[__CLASS__][__FUNCTION__] = CRM_Utils_Array::collect('value', $statuses['values']);
}
return Civi::$statics[__CLASS__][__FUNCTION__];
}
/**
* Check if status_id is completed.
*
* Note: activity status options use the "grouping" field to distinguish complete from incomplete statuses.
*
* @param int $statusId
*
* @return bool
*/
public static function isCompleted($statusId) {
return in_array($statusId, self::getCompletedStatuses());
}
/**
* Check if activity is overdue.
*
* @param array $activity
*
* @return bool
*/
public static function isOverdue($activity) {
return !self::isCompleted($activity['status_id']) && CRM_Utils_Date::overdue($activity['activity_date_time']);
}
/**
* Get the exportable fields for Activities.
*
......@@ -2802,9 +2847,7 @@ INNER JOIN civicrm_option_group grp ON ( grp.id = val.option_group_id AND grp.n
$activity['DT_RowId'] = $activityId;
// Add class to this row if overdue.
$activity['DT_RowClass'] = "crm-entity status-id-{$values['status_id']}";
if (CRM_Utils_Date::overdue(CRM_Utils_Array::value('activity_date_time', $values))
&& CRM_Utils_Array::value('status_id', $values) == 1
) {
if (self::isOverdue($values)) {
$activity['DT_RowClass'] .= ' status-overdue';
}
else {
......
......@@ -222,17 +222,18 @@ class CRM_Admin_Form_Options extends CRM_Admin_Form {
);
}
$required = FALSE;
if ($this->_gName == 'custom_search') {
$required = TRUE;
}
elseif ($this->_gName == 'redaction_rule' || $this->_gName == 'engagement_index') {
if ($this->_gName == 'redaction_rule') {
$this->add('checkbox',
'filter',
ts('Regular Expression?')
);
}
if ($this->_gName == 'activity_status') {
$this->add('select',
'filter',
ts('Status Type'),
array(ts('Incomplete'), ts('Completed'))
);
}
if ($this->_gName == 'redaction_rule') {
$this->add('checkbox',
'filter',
ts('Regular Expression?')
);
}
if ($this->_gName == 'participant_listing') {
$this->add('text',
......@@ -246,7 +247,7 @@ class CRM_Admin_Form_Options extends CRM_Admin_Form {
$this->add('wysiwyg', 'description',
ts('Description'),
array('rows' => 4, 'cols' => 80),
$required
$this->_gName == 'custom_search'
);
}
......
......@@ -13,3 +13,8 @@ VALUES
-- CRM-20387
UPDATE `civicrm_contribution` SET `invoice_number` = `invoice_id` WHERE `invoice_id` LIKE CONCAT('%', `id`);
UPDATE `civicrm_option_value`
SET filter = 1
WHERE option_group_id = (SELECT id FROM civicrm_option_group WHERE name = 'activity_status')
AND name IN ('Completed', 'Cancelled', 'Unreachable', 'Not Required', 'No-show');
......@@ -232,7 +232,7 @@ function _civicrm_api3_activity_get_spec(&$params) {
$params['tag_id'] = array(
'title' => 'Tags',
'description' => 'Find activities with specified tags.',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Core_DAO_Tag',
'FKApiName' => 'Tag',
'supports_joins' => TRUE,
......@@ -240,45 +240,50 @@ function _civicrm_api3_activity_get_spec(&$params) {
$params['file_id'] = array(
'title' => 'Attached Files',
'description' => 'Find activities with attached files.',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Core_DAO_File',
'FKApiName' => 'File',
);
$params['case_id'] = array(
'title' => 'Cases',
'description' => 'Find activities within specified cases.',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Case_DAO_Case',
'FKApiName' => 'Case',
);
$params['contact_id'] = array(
'title' => 'Activity Contact ID',
'description' => 'Find activities involving this contact (as target, source, OR assignee).',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Contact_DAO_Contact',
'FKApiName' => 'Contact',
);
$params['target_contact_id'] = array(
'title' => 'Target Contact ID',
'description' => 'Find activities with specified target contact.',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Contact_DAO_Contact',
'FKApiName' => 'Contact',
);
$params['source_contact_id'] = array(
'title' => 'Source Contact ID',
'description' => 'Find activities with specified source contact.',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Contact_DAO_Contact',
'FKApiName' => 'Contact',
);
$params['assignee_contact_id'] = array(
'title' => 'Assignee Contact ID',
'description' => 'Find activities with specified assignee contact.',
'type' => 1,
'type' => CRM_Utils_Type::T_INT,
'FKClassName' => 'CRM_Contact_DAO_Contact',
'FKApiName' => 'Contact',
);
$params['is_overdue'] = array(
'title' => 'Is Activity Overdue',
'description' => 'Incomplete activities with a past date.',
'type' => CRM_Utils_Type::T_BOOLEAN,
);
}
/**
......@@ -296,6 +301,7 @@ function _civicrm_api3_activity_get_spec(&$params) {
*/
function civicrm_api3_activity_get($params) {
$options = _civicrm_api3_get_options_from_params($params, FALSE, 'Activity', 'get');
$sql = CRM_Utils_SQL_Select::fragment();
$recordTypes = civicrm_api3('ActivityContact', 'getoptions', array('field' => 'record_type_id'));
$recordTypes = $recordTypes['values'];
......@@ -328,6 +334,35 @@ function civicrm_api3_activity_get($params) {
}
}
// Handle is_overdue filter
if (isset($params['is_overdue'])) {
$completedStatuses = implode(',', CRM_Activity_BAO_Activity::getCompletedStatuses());
if ($params['is_overdue']) {
$sql->where('a.activity_date_time < NOW()');
$sql->where("a.status_id NOT IN ($completedStatuses)");
}
else {
$sql->where("(a.activity_date_time >= NOW() OR a.status_id IN ($completedStatuses))");
}
}
// Handle is_overdue sort
if (!empty($options['sort'])) {
$sort = explode(', ', $options['sort']);
// For each one of our special fields we swap it for the placeholder (1) so it will be ignored by the case api.
foreach ($sort as $index => &$sortString) {
// Get sort field and direction
list($sortField, $dir) = array_pad(explode(' ', $sortString), 2, 'ASC');
if ($sortField == 'is_overdue') {
$completedStatuses = implode(',', CRM_Activity_BAO_Activity::getCompletedStatuses());
$sql->orderBy("IF((a.activity_date_time >= NOW() OR a.status_id IN ($completedStatuses)), 0, 1) $dir", NULL, $index);
$sortString = '(1)';
}
}
$params['options']['sort'] = implode(', ', $sort);
}
// Define how to handle filters on some related entities.
// Subqueries are nice in (a) avoiding duplicates and (b) when the result
// list is expected to be bite-sized. Joins are nice (a) with larger
......@@ -372,6 +407,13 @@ function civicrm_api3_activity_get($params) {
}
}
}
// Ensure there's enough data for calculating is_overdue
if (!empty($options['return']['is_overdue']) && (empty($options['return']['status_id']) || empty($options['return']['activity_date_time']))) {
$options['return']['status_id'] = $options['return']['activity_date_time'] = 1;
$params['return'] = array_keys($options['return']);
}
$activities = _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params, FALSE, 'Activity', $sql);
if (!empty($params['check_permissions']) && !CRM_Core_Permission::check('view all activities')) {
// @todo get this to work at the query level - see contact_id join above.
......@@ -381,7 +423,6 @@ function civicrm_api3_activity_get($params) {
}
}
}
$options = _civicrm_api3_get_options_from_params($params, FALSE, 'Activity', 'get');
if ($options['is_count']) {
return civicrm_api3_create_success($activities, $params, 'Activity', 'get');
}
......@@ -495,6 +536,12 @@ function _civicrm_api3_activity_get_formatResult($params, $activities, $options)
}
break;
case 'is_overdue':
foreach ($activities as $key => $activityArray) {
$activities[$key]['is_overdue'] = (int) CRM_Activity_BAO_Activity::isOverdue($activityArray);
}
break;
default:
if (substr($n, 0, 6) == 'custom') {
$returnProperties[$n] = $v;
......
This diff is collapsed.
......@@ -1403,4 +1403,34 @@ class api_v3_ActivityTest extends CiviUnitTestCase {
$this->assertEquals(3, $result['count']);
}
public function testGetOverdue() {
$overdueAct = $this->callAPISuccess('Activity', 'create', array('activity_date_time' => 'now - 1 week', 'status_id' => 'Scheduled') + $this->_params);
$completedAct = $this->callAPISuccess('Activity', 'create', array('activity_date_time' => 'now - 1 week', 'status_id' => 'Completed') + $this->_params);
$ids = array($overdueAct['id'], $completedAct['id']);
// Test sorting
$completedFirst = $this->callAPISuccess('Activity', 'get', array(
'id' => array('IN' => $ids),
'options' => array('sort' => 'is_overdue ASC'),
));
$this->assertEquals(array_reverse($ids), array_keys($completedFirst['values']));
$overdueFirst = $this->callAPISuccess('Activity', 'get', array(
'id' => array('IN' => $ids),
'options' => array('sort' => 'is_overdue DESC'),
'return' => 'is_overdue',
));
$this->assertEquals($ids, array_keys($overdueFirst['values']));
// Test return value
$this->assertEquals(1, $overdueFirst['values'][$overdueAct['id']]['is_overdue']);
$this->assertEquals(0, $overdueFirst['values'][$completedAct['id']]['is_overdue']);
// Test filtering
$onlyOverdue = $this->callAPISuccess('Activity', 'get', array(
'id' => array('IN' => $ids),
'is_overdue' => 1,
));
$this->assertEquals(array($overdueAct['id']), array_keys($onlyOverdue['values']));
}
}
......@@ -639,14 +639,14 @@ VALUES
(@option_group_id_report, {localize}'{ts escape="sql"}Recurring Contributions Summary{/ts}'{/localize}, 'contribute/recursummary', 'CRM_Report_Form_Contribute_RecurSummary', NULL, 0, NULL, 49, {localize}'{ts escape="sql"}Provides simple summary for each payment instrument for which there are recurring contributions (e.g. Credit Card, Standing Order, Direct Debit, etc., NULL), showing within a given date range.{/ts}'{/localize}, 0, 0, 1, @contributeCompId, NULL, NULL),
(@option_group_id_report, {localize}'{ts escape="sql"}Deferred Revenue Details{/ts}'{/localize}, 'contribute/deferredrevenue', 'CRM_Report_Form_Contribute_DeferredRevenue', NULL, 0, NULL, 50, {localize}'{ts escape="sql"}Deferred Revenue Details Report{/ts}'{/localize}, 0, 0, 1, @contributeCompId, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Scheduled{/ts}', 1, 'Scheduled', NULL, 0, 1, 1, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Completed{/ts}', 2, 'Completed', NULL, 0, NULL, 2, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Cancelled{/ts}', 3, 'Cancelled', NULL, 0, NULL, 3, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Scheduled{/ts}', 1, 'Scheduled', NULL, 0, 1, 1, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Completed{/ts}', 2, 'Completed', NULL, 1, NULL, 2, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Cancelled{/ts}', 3, 'Cancelled', NULL, 1, NULL, 3, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Left Message{/ts}', 4, 'Left Message', NULL, 0, NULL, 4, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Unreachable{/ts}', 5, 'Unreachable', NULL, 0, NULL, 5, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Not Required{/ts}', 6, 'Not Required', NULL, 0, NULL, 6, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Available{/ts}', 7, 'Available', NULL, 0, NULL, 7, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}No-show{/ts}', 8, 'No_show', NULL, 0, NULL, 8, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Unreachable{/ts}', 5, 'Unreachable', NULL, 1, NULL, 5, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Not Required{/ts}', 6, 'Not Required', NULL, 1, NULL, 6, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}Available{/ts}', 7, 'Available', NULL, 0, NULL, 7, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_acs, '{ts escape="sql"}No-show{/ts}', 8, 'No_show', NULL, 1, NULL, 8, NULL, 0, 0, 1, NULL, NULL, NULL),
(@option_group_id_cas, '{ts escape="sql"}Ongoing{/ts}' , 1, 'Open' , 'Opened', 0, 1, 1, NULL, 0, 1, 1, NULL, NULL, NULL),
(@option_group_id_cas, '{ts escape="sql"}Resolved{/ts}', 2, 'Closed', 'Closed', 0, NULL, 2, NULL, 0, 1, 1, NULL, NULL, NULL),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment