From 17d878aefaa5f466dc52893d13912d51139a675c Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Tue, 23 Jun 2020 17:26:00 -0400 Subject: [PATCH 1/3] Member detail report: column and filter for autorenew status --- CRM/Report/Form/Member/Detail.php | 120 ++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/CRM/Report/Form/Member/Detail.php b/CRM/Report/Form/Member/Detail.php index 5bbe1d5407fe..4b277f2c1b89 100644 --- a/CRM/Report/Form/Member/Detail.php +++ b/CRM/Report/Form/Member/Detail.php @@ -223,6 +223,26 @@ public function __construct() { ], 'grouping' => 'contri-fields', ], + 'civicrm_contribution_recur' => [ + 'dao' => 'CRM_Contribute_DAO_ContributionRecur', + 'fields' => [ + 'autorenew_status_id' => [ + 'name' => 'contribution_status_id', + 'title' => ts('Auto-Renew Subscription Status'), + ], + ], + 'filters' => [ + 'autorenew_status_id' => [ + 'name' => 'contribution_status_id', + 'title' => ts('Auto-Renew Subscription Status?'), + 'operatorType' => CRM_Report_Form::OP_MULTISELECT, + 'options' => [0 => ts('None'), -1 => ts('Ended')] + CRM_Contribute_BAO_ContributionRecur::buildOptions('contribution_status_id', 'search'), + 'type' => CRM_Utils_Type::T_INT, + 'pseudofield' => TRUE, + ], + ], + 'grouping' => 'member-fields', + ], ] + $this->getAddressColumns([ // These options are only excluded because they were not previously present. 'order_by' => FALSE, @@ -243,6 +263,23 @@ public function preProcess() { parent::preProcess(); } + public function select() { + parent::select(); + if (in_array('civicrm_contribution_recur_autorenew_status_id', $this->_selectAliases)) { + // If we're getting auto-renew status we'll want to know if auto-renew has + // ended. + $this->_selectClauses[] = "{$this->_aliases['civicrm_contribution_recur']}.end_date as civicrm_contribution_recur_end_date"; + $this->_selectAliases[] = 'civicrm_contribution_recur_end_date'; + // Regenerate SELECT part of query + $this->_select = "SELECT " . implode(', ', $this->_selectClauses) . " "; + $this->_columnHeaders["civicrm_contribution_recur_end_date"] = [ + 'title' => NULL, + 'type' => NULL, + 'no_display' => TRUE, + ]; + } + } + public function from() { $this->setFromBase('civicrm_contact'); $this->_from .= " @@ -266,6 +303,80 @@ public function from() { LEFT JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']} ON cmp.contribution_id={$this->_aliases['civicrm_contribution']}.id\n"; } + if ($this->isTableSelected('civicrm_contribution_recur')) { + $this->_from .= <<_aliases['civicrm_contribution_recur']} + ON {$this->_aliases['civicrm_membership']}.contribution_recur_id = {$this->_aliases['civicrm_contribution_recur']}.id +HERESQL; + } + } + + /** + * Override to add handling for autorenew status. + */ + public function storeWhereHavingClauseArray() { + parent::storeWhereHavingClauseArray(); + + // Handle autorenew status + $op = $this->_params['autorenew_status_id_op'] ?? NULL; + $value = $this->_params['autorenew_status_id_value'] ?? NULL; + $clauseParts = []; + switch ($op) { + case 'in': + if ($value !== NULL && is_array($value) && count($value) > 0) { + $regularOptions = implode(', ', array_diff($value, [0, -1])); + // None: is null + if (in_array(0, $value)) { + $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL"; + } + // Ended: not null, end_date in past + if (in_array(-1, $value)) { + $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL + AND {$this->_aliases['civicrm_contribution_recur']}.end_date < NOW() +HERESQL; + } + // Normal statuses: IN() + if (!empty($regularOptions)) { + $clauseParts[] = "{$this->_aliases['civicrm_contribution_recur']}.contribution_status_id IN ($regularOptions)"; + } + $this->_whereClauses[] = '(' . implode(') OR (', $clauseParts) . ')'; + } + break; + + case 'notin': + if ($value !== NULL && is_array($value) && count($value) > 0) { + $regularOptions = implode(', ', array_diff($value, [0, -1])); + // None: is not null + if (in_array(0, $value)) { + $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL"; + } + // Ended: null or end_date in future + if (in_array(-1, $value)) { + $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NULL + OR {$this->_aliases['civicrm_contribution_recur']}.end_date >= NOW() + OR {$this->_aliases['civicrm_contribution_recur']}.end_date IS NULL +HERESQL; + } + // Normal statuses: null or NOT IN() + if (!empty($regularOptions)) { + $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NULL + OR {$this->_aliases['civicrm_contribution_recur']}.contribution_status_id NOT IN ($regularOptions) +HERESQL; + } + $this->_whereClauses[] = '(' . implode(') AND (', $clauseParts) . ')'; + } + break; + + case 'nll': + $this->_whereClauses[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL"; + break; + + case 'nnll': + $this->_whereClauses[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL"; + } } public function getOperationPair($type = "string", $fieldName = NULL) { @@ -369,6 +480,15 @@ public function alterDisplay(&$rows) { $rows[$rowNum]['civicrm_contribution_payment_instrument_id'] = $paymentInstruments[$value]; $entryFound = TRUE; } + if ($value = $row['civicrm_contribution_recur_autorenew_status_id'] ?? NULL) { + $rows[$rowNum]['civicrm_contribution_recur_autorenew_status_id'] = $contributionStatus[$value]; + if (!empty($row['civicrm_contribution_recur_end_date']) + && strtotime($row['civicrm_contribution_recur_end_date']) < time()) { + $ended = ts('ended'); + $rows[$rowNum]['civicrm_contribution_recur_autorenew_status_id'] .= " ($ended)"; + } + $entryFound = TRUE; + } if (array_key_exists('civicrm_membership_owner_membership_id', $row)) { $value = $row['civicrm_membership_owner_membership_id']; From a23505741de682d3d1072b4a6b6b34f55948f6e7 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Tue, 23 Jun 2020 17:26:16 -0400 Subject: [PATCH 2/3] Member detail report: test coverage for autorenew status --- .../CRM/Report/Form/Member/DetailTest.php | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tests/phpunit/CRM/Report/Form/Member/DetailTest.php diff --git a/tests/phpunit/CRM/Report/Form/Member/DetailTest.php b/tests/phpunit/CRM/Report/Form/Member/DetailTest.php new file mode 100644 index 000000000000..92c851f9a81c --- /dev/null +++ b/tests/phpunit/CRM/Report/Form/Member/DetailTest.php @@ -0,0 +1,110 @@ +_orgContactID = $this->organizationCreate(); + $this->_financialTypeId = 1; + $this->_membershipStatusID = $this->membershipStatusCreate('test status'); + $this->_membershipTypeID = $this->membershipTypeCreate(['name' => 'Test Member']); + } + + public function testAutoRenewDisplay() { + + $indContactID1 = $this->individualCreate(); + $indContactID2 = $this->individualCreate(); + $recurStatus = array_search( + 'In Progress', + CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name') + ); + $recurParams = [ + 'contact_id' => $indContactID1, + 'amount' => '5.00', + 'currency' => 'USD', + 'frequency_unit' => 'day', + 'frequency_interval' => 30, + 'create_date' => '2019-06-22', + 'start_date' => '2019-06-22', + 'contribution_status_id' => $recurStatus, + ]; + $recur1 = civicrm_api3('ContributionRecur', 'create', $recurParams); + $memParams = [ + 'membership_type_id' => $this->_membershipTypeID, + 'contact_id' => $indContactID1, + 'status_id' => $this->_membershipStatusID, + 'contribution_recur_id' => $recur1['id'], + 'join_date' => '2019-06-22', + 'start_date' => '2019-06-22', + 'end_date' => '2019-07-22', + 'source' => 'Payment', + ]; + $mem1 = civicrm_api3('Membership', 'create', $memParams); + $recurParams['end_date'] = '2019-06-23'; + $recurParams['contact_id'] = $indContactID1; + $recur2 = civicrm_api3('ContributionRecur', 'create', $recurParams); + $memParams['contact_id'] = $indContactID2; + $memParams['contribution_recur_id'] = $recur2['id']; + $mem2 = civicrm_api3('Membership', 'create', $memParams); + + $input = [ + 'fields' => ['autorenew_status_id'], + 'filters' => [ + 'tid_op' => 'in', + 'tid_value' => $this->_membershipTypeID, + 'autorenew_status_id_op' => 'in', + 'autorenew_status_id_value' => $recurStatus, + ], + ]; + $obj = $this->getReportObject('CRM_Report_Form_Member_Detail', $input); + $results = $obj->getResultSet(); + $this->assertCount(2, $results); + foreach ($results as $result) { + if ($result['civicrm_contact_id'] == $indContactID1) { + $this->assertNotContains('(ended)', $result['civicrm_contribution_recur_autorenew_status_id']); + } + if ($result['civicrm_contact_id'] == $indContactID2) { + $this->assertContains('(ended)', $result['civicrm_contribution_recur_autorenew_status_id']); + } + } + + $input['filters']['autorenew_status_id_op'] = 'nll'; + $obj = $this->getReportObject('CRM_Report_Form_Member_Detail', $input); + $results = $obj->getResultSet(); + $this->assertCount(0, $results); + + $input['filters']['autorenew_status_id_op'] = 'in'; + $input['filters']['autorenew_status_id_value'] = 0; + $obj = $this->getReportObject('CRM_Report_Form_Member_Detail', $input); + $results = $obj->getResultSet(); + $this->assertCount(0, $results); + + $input['filters']['autorenew_status_id_op'] = 'in'; + $input['filters']['autorenew_status_id_value'] = 1000; + $obj = $this->getReportObject('CRM_Report_Form_Member_Detail', $input); + $results = $obj->getResultSet(); + $this->assertCount(0, $results); + + $input['filters']['autorenew_status_id_op'] = 'notin'; + $input['filters']['autorenew_status_id_value'] = 1000; + $obj = $this->getReportObject('CRM_Report_Form_Member_Detail', $input); + $results = $obj->getResultSet(); + $this->assertCount(2, $results); + } + +} From 14f3c2abef1e73b5da7ca2528ecb7a66ea2497bb Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Mon, 6 Jul 2020 20:42:29 -0400 Subject: [PATCH 3/3] Member detail report: move autorenew filter to `whereClause()` override --- CRM/Report/Form/Member/Detail.php | 108 +++++++++++++++--------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/CRM/Report/Form/Member/Detail.php b/CRM/Report/Form/Member/Detail.php index 4b277f2c1b89..da7990ef0a98 100644 --- a/CRM/Report/Form/Member/Detail.php +++ b/CRM/Report/Form/Member/Detail.php @@ -238,7 +238,6 @@ public function __construct() { 'operatorType' => CRM_Report_Form::OP_MULTISELECT, 'options' => [0 => ts('None'), -1 => ts('Ended')] + CRM_Contribute_BAO_ContributionRecur::buildOptions('contribution_status_id', 'search'), 'type' => CRM_Utils_Type::T_INT, - 'pseudofield' => TRUE, ], ], 'grouping' => 'member-fields', @@ -314,68 +313,67 @@ public function from() { /** * Override to add handling for autorenew status. */ - public function storeWhereHavingClauseArray() { - parent::storeWhereHavingClauseArray(); - - // Handle autorenew status - $op = $this->_params['autorenew_status_id_op'] ?? NULL; - $value = $this->_params['autorenew_status_id_value'] ?? NULL; - $clauseParts = []; - switch ($op) { - case 'in': - if ($value !== NULL && is_array($value) && count($value) > 0) { - $regularOptions = implode(', ', array_diff($value, [0, -1])); - // None: is null - if (in_array(0, $value)) { - $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL"; - } - // Ended: not null, end_date in past - if (in_array(-1, $value)) { - $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL - AND {$this->_aliases['civicrm_contribution_recur']}.end_date < NOW() + public function whereClause(&$field, $op, $value, $min, $max) { + if ($field['dbAlias'] == "{$this->_aliases['civicrm_contribution_recur']}.contribution_status_id") { + $clauseParts = []; + switch ($op) { + case 'in': + if ($value !== NULL && is_array($value) && count($value) > 0) { + $regularOptions = implode(', ', array_diff($value, [0, -1])); + // None: is null + if (in_array(0, $value)) { + $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL"; + } + // Ended: not null, end_date in past + if (in_array(-1, $value)) { + $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL + AND {$this->_aliases['civicrm_contribution_recur']}.end_date < NOW() HERESQL; + } + // Normal statuses: IN() + if (!empty($regularOptions)) { + $clauseParts[] = "{$this->_aliases['civicrm_contribution_recur']}.contribution_status_id IN ($regularOptions)"; + } + return '(' . implode(') OR (', $clauseParts) . ')'; } - // Normal statuses: IN() - if (!empty($regularOptions)) { - $clauseParts[] = "{$this->_aliases['civicrm_contribution_recur']}.contribution_status_id IN ($regularOptions)"; - } - $this->_whereClauses[] = '(' . implode(') OR (', $clauseParts) . ')'; - } - break; + return; - case 'notin': - if ($value !== NULL && is_array($value) && count($value) > 0) { - $regularOptions = implode(', ', array_diff($value, [0, -1])); - // None: is not null - if (in_array(0, $value)) { - $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL"; - } - // Ended: null or end_date in future - if (in_array(-1, $value)) { - $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NULL - OR {$this->_aliases['civicrm_contribution_recur']}.end_date >= NOW() - OR {$this->_aliases['civicrm_contribution_recur']}.end_date IS NULL + case 'notin': + if ($value !== NULL && is_array($value) && count($value) > 0) { + $regularOptions = implode(', ', array_diff($value, [0, -1])); + // None: is not null + if (in_array(0, $value)) { + $clauseParts[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL"; + } + // Ended: null or end_date in future + if (in_array(-1, $value)) { + $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NULL + OR {$this->_aliases['civicrm_contribution_recur']}.end_date >= NOW() + OR {$this->_aliases['civicrm_contribution_recur']}.end_date IS NULL HERESQL; - } - // Normal statuses: null or NOT IN() - if (!empty($regularOptions)) { - $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NULL - OR {$this->_aliases['civicrm_contribution_recur']}.contribution_status_id NOT IN ($regularOptions) + } + // Normal statuses: null or NOT IN() + if (!empty($regularOptions)) { + $clauseParts[] = <<_aliases['civicrm_membership']}.contribution_recur_id IS NULL + OR {$this->_aliases['civicrm_contribution_recur']}.contribution_status_id NOT IN ($regularOptions) HERESQL; + } + return '(' . implode(') AND (', $clauseParts) . ')'; } - $this->_whereClauses[] = '(' . implode(') AND (', $clauseParts) . ')'; - } - break; + return; - case 'nll': - $this->_whereClauses[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL"; - break; + case 'nll': + return "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NULL"; - case 'nnll': - $this->_whereClauses[] = "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL"; + case 'nnll': + return "{$this->_aliases['civicrm_membership']}.contribution_recur_id IS NOT NULL"; + } + } + else { + return parent::whereClause($field, $op, $value, $min, $max); } }