From d28682517f67d167d6c04869843d7518e0b63db8 Mon Sep 17 00:00:00 2001 From: monishdeb Date: Wed, 7 Jan 2015 13:33:52 +0530 Subject: [PATCH] Added unit-tests and other fixes --- CRM/Core/BAO/ActionSchedule.php | 26 +-- CRM/Upgrade/Incremental/php/FourSix.php | 61 +++++-- .../CRM/Core/BAO/ActionScheduleTest.php | 164 +++++++++++++++++- 3 files changed, 224 insertions(+), 27 deletions(-) mode change 100644 => 100755 CRM/Core/BAO/ActionSchedule.php mode change 100644 => 100755 tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php diff --git a/CRM/Core/BAO/ActionSchedule.php b/CRM/Core/BAO/ActionSchedule.php old mode 100644 new mode 100755 index ee849744b3..5c8e4d3292 --- a/CRM/Core/BAO/ActionSchedule.php +++ b/CRM/Core/BAO/ActionSchedule.php @@ -1099,8 +1099,8 @@ WHERE reminder.action_schedule_id = %1 AND reminder.action_date_time IS NULL $mStatus = implode(',', $membershipStatus); $where[] = "e.status_id IN ({$mStatus})"; - // We are not tracking the reference date for 'repeated' schedule reminders as - // it will violate the repeat use-case, for further details please check CRM-15376 + // We are not tracking the reference date for 'repeated' schedule reminders, + // for further details please check CRM-15376 if ($actionSchedule->start_action_date && $actionSchedule->is_repeat == FALSE) { $select[] = $dateField; $selectColumns = "reference_date, " . $selectColumns; @@ -1266,25 +1266,27 @@ LEFT JOIN {$reminderJoinClause} {$whereClause} {$limitWhereClause} AND {$dateClause} {$notINClause} "; - // In some cases reference_date got outdated due to many reason - // e.g. In Membership renewal end_date got extended which means reference date mismatches with the end_date - // in other words reference_date != end_date where end_date may be used as the start_action_date criteria - // for some schedule reminder so in order to send new reminder we INSERT new record with new reference_date value - // via UNION operation + // In some cases reference_date got outdated due to many reason e.g. In Membership renewal end_date got extended + // which means reference date mismatches with the end_date where end_date may be used as the start_action_date + // criteria for some schedule reminder so in order to send new reminder we INSERT new reminder with new reference_date + // value via UNION operation if (strpos($selectColumns, 'reference_date') !== FALSE) { + $dateClause = str_replace('reminder.id IS NULL', 'reminder.id IS NOT NULL', $dateClause); $query .= " UNION {$selectClause} {$fromClause} {$joinClause} -LEFT JOIN {$reminderJoinClause} -{$whereClause} {$limitWhereClause} {$notINClause} AND + LEFT JOIN {$reminderJoinClause} +{$whereClause} {$limitWhereClause} {$notINClause} AND {$dateClause} AND + reminder.action_date_time IS NOT NULL AND (reminder.reference_date IS NOT NULL AND reminder.reference_date != {$dateField}) "; - //Those reminders which are sent in past and there reference_date doesn't reflect the - //newly changed entity's action_start_date, we need to update those so that we never - //get new reminder redundantly as because of the above usage of UNION clause + // As per the usage of UNION clause above we always INSERT a new reminder if reference_date (RD) + // got outdated or mismatches to start_action_date criteria so we need to update RD with actual + // start_action_date of already sent reminder, so to prevent redeundancy in sending new reminder + // due to above INSERT-UNION query $updateQuery = "UPDATE civicrm_action_log reminder INNER JOIN {$mapping->entity} e ON e.id = reminder.entity_id AND reminder.reference_date IS NOT NULL AND reminder.action_date_time IS NOT NULL diff --git a/CRM/Upgrade/Incremental/php/FourSix.php b/CRM/Upgrade/Incremental/php/FourSix.php index 0e94e841d1..f4c240892a 100644 --- a/CRM/Upgrade/Incremental/php/FourSix.php +++ b/CRM/Upgrade/Incremental/php/FourSix.php @@ -114,7 +114,7 @@ class CRM_Upgrade_Incremental_php_FourSix { function upgrade_4_6_alpha3($rev) { // task to process sql - $this->addTask(ts('Adding and updating column reference_date for Schedule Reminders'), 'updateReferenceDate'); + $this->addTask(ts('Add and update reference_date column for Schedule Reminders'), 'updateReferenceDate'); } // CRM-15728, Add new column reference_date to civicrm_action_log in order to track @@ -129,18 +129,21 @@ class CRM_Upgrade_Incremental_php_FourSix { $query = "SELECT schedule.* FROM civicrm_action_schedule schedule LEFT JOIN civicrm_action_mapping mapper ON mapper.id = schedule.mapping_id AND mapper.entity = 'civicrm_membership' AND schedule.is_repeat = 0"; + + // construct basic where clauses + $where = array( + 'reminder.reference_date IS NOT NULL', + '( m.is_override IS NULL OR m.is_override = 0 )', + 'reminder.action_date_time >= DATE_SUB(reminder.action_date_time, INTERVAL 9 MONTH)' + ); $dao = CRM_Core_DAO::executeQuery($query); while($dao->fetch()) { + //if absolute date is chosen then bypass if (empty($dao->start_action_date)) { continue; } $referenceColumn = str_replace('membership_', "m.", $dao->start_action_date); - $where = array( - 'reminder.reference_date IS NOT NULL', - '( m.is_override IS NULL OR m.is_override = 0 )', - 'reminder.action_date_time >= DATE_SUB(reminder.action_date_time, INTERVAL 9 MONTH)' - ); $value = implode(', ', explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($dao->entity_value, CRM_Core_DAO::VALUE_SEPARATOR))); if (!empty($value)) { $where[] = "m.membership_type_id IN ({$value})"; @@ -149,17 +152,47 @@ class CRM_Upgrade_Incremental_php_FourSix { $where[] = "m.membership_type_id IS NULL"; } - // Update reference_date with action_start_date chosen, - // only to those which falls under date limits configured on schedule reminder - $startDateClause = array(); - $op = ($dao->start_action_condition == 'before' ? '<=' : '>='); - $operator = ($dao->start_action_condition == 'before' ? 'DATE_SUB' : 'DATE_ADD'); - $date = $operator . "({$referenceColumn}, INTERVAL {$dao->start_action_offset} {$dao->start_action_unit})"; - $where[] = "NOW() >= {$date}"; - $where[] = "DATE_SUB(NOW(), INTERVAL 1 DAY ) <= {$date}"; + // Update reference_date with action_start_date chosen, only to those which are not additional contacts + // (Additional contacts are include via Group/Smart Group or Manual Recipients) + $addtionalGroupJoin = NULL; + if (!is_null($dao->limit_to) && $dao->limit_to == 0) { + if ($dao->group_id) { + // CRM-13577 If smart group then use Cache table + $group = CRM_Contact_DAO_Group::getTableName(); + // Get the group information + $sql = " +SELECT $group.id, $group.cache_date, $group.saved_search_id, $group.children +FROM $group +WHERE $group.id = {$dao->group_id} +"; + $groupDAO = CRM_Core_DAO::executeQuery($sql); + + $isSmartGroup = FALSE; + if ($groupDAO->fetch() && !empty($groupDAO->saved_search_id)) { + // Check that the group is in place in the cache and up to date + CRM_Contact_BAO_GroupContactCache::check($dao->group_id); + // Set smart group flag + $isSmartGroup = TRUE; + } + if ($isSmartGroup) { + $addtionalGroupJoin = " INNER JOIN civicrm_group_contact_cache grp ON reminder.contact_id = grp.contact_id"; + $where[] = " grp.group_id NOT IN ({$dao->group_id})"; + } + else { + $addtionalGroupJoin = " INNER JOIN civicrm_group_contact grp ON + reminder.contact_id = grp.contact_id AND grp.status = 'Added'"; + $where[] = " grp.group_id NOT IN ({$dao->group_id})"; + } + } + if (!empty($dao->recipient_manual)) { + $rList = CRM_Utils_Type::escape($dao->recipient_manual, 'String'); + $where[] = "reminder.contact_id NOT IN ({$rList})"; + } + } $sql = "UPDATE civicrm_action_log reminder LEFT JOIN civicrm_membership m ON reminder.entity_id = m.id + {$addtionalGroupJoin} SET reminder.reference_date = {$referenceColumn} WHERE " . implode(" AND ", $where); CRM_Core_DAO::executeQuery($sql); diff --git a/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php b/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php old mode 100644 new mode 100755 index 0313d4faaf..c20699aae5 --- a/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php +++ b/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php @@ -206,6 +206,34 @@ class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase { 'start_action_unit' => 'week', 'subject' => 'subject sched_membership_end_2week', ); + $this->fixtures['sched_on_membership_end_date'] = array( // create() + 'name' => 'sched_on_membership_end_date', + 'title' => 'sched_on_membership_end_date', + 'body_html' => '

Your membership expired today

', + 'body_text' => 'Your membership expired today', + 'is_active' => 1, + 'mapping_id' => 4, + 'record_activity' => 1, + 'start_action_condition' => 'after', + 'start_action_date' => 'membership_end_date', + 'start_action_offset' => '0', + 'start_action_unit' => 'hour', + 'subject' => 'subject send reminder on membership_end_date', + ); + $this->fixtures['sched_after_1day_membership_end_date'] = array( // create() + 'name' => 'sched_after_1day_membership_end_date', + 'title' => 'sched_after_1day_membership_end_date', + 'body_html' => '

Your membership expired yesterday

', + 'body_text' => 'Your membership expired yesterday', + 'is_active' => 1, + 'mapping_id' => 4, + 'record_activity' => 1, + 'start_action_condition' => 'after', + 'start_action_date' => 'membership_end_date', + 'start_action_offset' => '1', + 'start_action_unit' => 'day', + 'subject' => 'subject send reminder on membership_end_date', + ); $this->fixtures['sched_membership_end_2month'] = array( 'name' => 'sched_membership_end_2month', @@ -689,6 +717,34 @@ class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase { 'recipients' => array(array('test-member@example.com')), ), )); + + // Now suppose user has renewed for rolling membership after 3 months, so upcoming assertion is written + // to ensure that new reminder is sent 2 week before the new end_date i.e. '2012-09-15' + $membership->end_date = '2012-09-15'; + $membership->save(); + + //change the email id of chosen membership contact to assert + //recipient of not the previously sent mail but the new one + $result = $this->callAPISuccess('Email', 'create', array( + 'is_primary' => 1, + 'contact_id' => $membership->contact_id, + 'email' => 'member2@example.com' + )); + $this->assertAPISuccess($result); + + // end_date=2012-09-15 ; schedule is 2 weeks before end_date + $this->assertCronRuns(array( + array( // Before the 2-week mark, no email + 'time' => '2012-08-31 01:00:00', + 'recipients' => array(), + ), + /* TODO + array( // After the 2-week mark, send an email + 'time' => '2012-09-01 01:00:00', + 'recipients' => array(array('member2@example.com')), + ), + */ + )); } @@ -901,7 +957,113 @@ class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase { ); } - public function testContactCustomDateAnniv() { + public function testMembershipOnMultipleReminder() { + $membership = $this->createTestObject('CRM_Member_DAO_Membership', array_merge($this->fixtures['rolling_membership'], array('status_id' => 2))); + print_r($membership ); + + $this->assertTrue(is_numeric($membership->id)); + $result = $this->callAPISuccess('Email', 'create', array( + 'contact_id' => $membership->contact_id, + 'email' => 'member@example.com', + )); + + $result = $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], array('contact_id' => $membership->contact_id))); + $this->assertAPISuccess($result); + + $actionScheduleBefore = $this->fixtures['sched_membership_end_2week']; // Send email 2 weeks before end_date + $actionScheduleOn = $this->fixtures['sched_on_membership_end_date']; // Send email on end_date/expiry date + $actionScheduleAfter = $this->fixtures['sched_after_1day_membership_end_date']; // Send email 1 day after end_date/grace period + $actionScheduleBefore['entity_value'] = $actionScheduleOn['entity_value'] = $actionScheduleAfter['entity_value'] = $membership->membership_type_id; + foreach (array('actionScheduleBefore', 'actionScheduleOn', 'actionScheduleAfter') as $value) { + $$value = CRM_Core_BAO_ActionSchedule::add($$value); + $this->assertTrue(is_numeric($$value->id)); + } + + $this->assertCronRuns( + array( + array( // 1day 2weeks before membership end date(MED), don't send mail + 'time' => '2012-05-31 01:00:00', + 'recipients' => array(), + ), + array( // 2 weeks before MED, send an email + 'time' => '2012-06-01 01:00:00', + 'recipients' => array(array('member@example.com')), + ), + array( // 1day before MED, don't send mail + 'time' => '2012-06-14 01:00:00', + 'recipients' => array(), + ), + array( // On MED, send an email + 'time' => '2012-06-15 00:00:00', + 'recipients' => array(array('member@example.com')), + ), + array( // After 1day of MED, send an email + 'time' => '2012-06-16 01:00:00', + 'recipients' => array(array('member@example.com')), + ), + array( // After 1day 1min of MED, don't send an email + 'time' => '2012-06-17 00:01:00', + 'recipients' => array(), + ), + )); + + // Assert the timestamp as of when the emails of respective three reminders as configured + // 2 weeks before, on and 1 day after MED, are sent + $this->assertEquals('2012-06-01 01:00:00', + CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleBefore->id, 'action_date_time', 'action_schedule_id', TRUE)); + $this->assertEquals('2012-06-15 00:00:00', + CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleOn->id, 'action_date_time', 'action_schedule_id', TRUE)); + $this->assertEquals('2012-06-16 01:00:00', + CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleAfter->id, 'action_date_time', 'action_schedule_id', TRUE)); + + //extend MED to 2 weeks after the current MED (that may signifies as memberhip renewal activity) + // and lets assert as of when the new set of reminders will be sent against their respective Schedule Reminders(SR) + $membership->end_date = '2012-06-29'; + $membership->save(); + print_r($membership ); + + //change the email id of chosen membership contact to assert + //recipient of not the previously sent mail but the new one + $result = $this->callAPISuccess('Email', 'create', array( + 'is_primary' => 1, + 'contact_id' => $membership->contact_id, + 'email' => 'member2@example.com' + )); + $this->assertAPISuccess($result); + + $this->assertCronRuns( + array( + array( // 1day 2weeks before membership end date(MED), don't send mail + 'time' => '2012-06-14 01:00:00', + 'recipients' => array(), + ), + //TODO : Add asssertion for before, on and after SR impact on new MED + /* + array( // 2 weeks before MED, send an email + 'time' => '2012-06-15 01:00:00', + 'recipients' => array(array('member2@example.com')), + ), + array( // 1day before MED, don't send mail + 'time' => '2012-06-28 01:00:00', + 'recipients' => array(), + ), + array( // On MED, send an email + 'time' => '2012-06-29 00:00:00', + 'recipients' => array(array('member@example.com')), + ), + array( // After 1day of MED, send an email + 'time' => '2012-06-30 01:00:00', + 'recipients' => array(array('member@example.com')), + ), + array( // After 1day 1min of MED, don't send an email + 'time' => '2012-07-01 00:01:00', + 'recipients' => array(), + ), + */ + )); + } + + public function testContactCustomDate_Anniv() { $group = array( 'title' => 'Test_Group now', 'name' => 'test_group_now', -- 2.25.1