$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;
{$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
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
$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})";
$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);
'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' => '<p>Your membership expired today</p>',
+ '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' => '<p>Your membership expired yesterday</p>',
+ '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',
'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')),
+ ),
+ */
+ ));
}
);
}
- 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',