From e136f704594ddead8e78f52d53ec080825bdb1e2 Mon Sep 17 00:00:00 2001 From: Omar abu hussein Date: Fri, 2 Feb 2018 17:25:22 +0000 Subject: [PATCH] CRM-21733: Adding the ability to override membership temporarily until specific date --- CRM/Contribute/BAO/Contribution.php | 3 + CRM/Core/BAO/UFField.php | 1 + CRM/Member/BAO/Membership.php | 35 +++++ CRM/Member/DAO/Membership.php | 28 +++- CRM/Member/Form.php | 7 + CRM/Member/Form/Membership.php | 41 ++++-- CRM/Member/Import/Parser/Membership.php | 11 ++ CRM/Member/StatusOverrideTypes.php | 116 ++++++++++++++++ CRM/Upgrade/Incremental/php/FourSeven.php | 12 ++ templates/CRM/Member/Form/Membership.tpl | 43 ++++-- templates/CRM/Member/Page/Tab.hlp | 4 +- .../phpunit/CRM/Member/BAO/MembershipTest.php | 82 ++++++++++++ .../CRM/Member/Form/MembershipTest.php | 32 ++++- .../Member/Import/Parser/MembershipTest.php | 125 ++++++++++++++++-- .../CRM/Member/StatusOverrideTypesTest.php | 101 ++++++++++++++ xml/schema/Member/Membership.xml | 12 ++ 16 files changed, 615 insertions(+), 38 deletions(-) create mode 100644 CRM/Member/StatusOverrideTypes.php create mode 100644 tests/phpunit/CRM/Member/StatusOverrideTypesTest.php diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 8e37a07249..9c5cacb3fd 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -1766,6 +1766,7 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ $membership->status_id = $newStatus; $membership->is_override = TRUE; + $membership->status_override_end_date = 'null'; $membership->save(); civicrm_api3('activity', 'create', $activityParam); @@ -1814,6 +1815,7 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ if ($membership && $update) { $membership->status_id = array_search('Expired', $membershipStatuses); $membership->is_override = TRUE; + $membership->status_override_end_date = 'null'; $membership->save(); $updateResult['updatedComponents']['CiviMember'] = $membership->status_id; @@ -5474,6 +5476,7 @@ LIMIT 1;"; //we might be renewing membership, //so make status override false. $membershipParams['is_override'] = FALSE; + $membershipParams['status_override_end_date'] = 'null'; } //CRM-17723 - reset static $relatedContactIds array() // @todo move it to Civi Statics. diff --git a/CRM/Core/BAO/UFField.php b/CRM/Core/BAO/UFField.php index 10dccc79db..af399738d7 100644 --- a/CRM/Core/BAO/UFField.php +++ b/CRM/Core/BAO/UFField.php @@ -975,6 +975,7 @@ SELECT id 'membership_type_id', 'member_is_test', 'is_override', + 'status_override_end_date', 'status_id', 'member_is_pay_later' ); diff --git a/CRM/Member/BAO/Membership.php b/CRM/Member/BAO/Membership.php index 7a98cd2771..6b6ff60f5d 100644 --- a/CRM/Member/BAO/Membership.php +++ b/CRM/Member/BAO/Membership.php @@ -2193,6 +2193,7 @@ INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND $query = " SELECT civicrm_membership.id as membership_id, civicrm_membership.is_override as is_override, + civicrm_membership.status_override_end_date as status_override_end_date, civicrm_membership.membership_type_id as membership_type_id, civicrm_membership.status_id as status_id, civicrm_membership.join_date as join_date, @@ -2279,6 +2280,8 @@ WHERE civicrm_membership.is_test = 0"; continue; } + self::processOverriddenUntilDateMembership($dao); + //update membership records where status is NOT - Pending OR Cancelled. //as well as membership is not override. //skipping Expired membership records -> reduced extra processing( kiran ) @@ -2345,6 +2348,38 @@ WHERE civicrm_membership.is_test = 0"; return $result; } + /** + * Set is_override for the 'overridden until date' membership to + * False and clears the 'until date' field in case the 'until date' + * is equal or after today date. + * + * @param CRM_Core_DAO $membership + * The membership to be processed + */ + private static function processOverriddenUntilDateMembership($membership) { + $isOverriddenUntilDate = !empty($membership->is_override) && !empty($membership->status_override_end_date); + if (!$isOverriddenUntilDate) { + return; + } + + $todayDate = new DateTime(); + $todayDate->setTime(0, 0); + + $overrideEndDate = new DateTime($membership->status_override_end_date); + $overrideEndDate->setTime(0, 0); + + $datesDifference = $todayDate->diff($overrideEndDate); + $daysDifference = (int) $datesDifference->format('%R%a'); + if ($daysDifference <= 0) { + $params = array( + 'id' => $membership->membership_id, + 'is_override' => FALSE, + 'status_override_end_date' => 'null', + ); + civicrm_api3('membership', 'create', $params); + } + } + /** * Returns the membership types for a particular contact * who has lifetime membership without end date. diff --git a/CRM/Member/DAO/Membership.php b/CRM/Member/DAO/Membership.php index 7a378b086a..146751f7a2 100644 --- a/CRM/Member/DAO/Membership.php +++ b/CRM/Member/DAO/Membership.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Member/Membership.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:1a9ebe9e0d3ab9247f2a3dda32bab22a) + * (GenCodeChecksum:643f191c50bbd0244e93745a929ba499) */ /** @@ -89,6 +89,13 @@ class CRM_Member_DAO_Membership extends CRM_Core_DAO { */ public $is_override; + /** + * Then end date of membership status override if 'Override until selected date' override type is selected. + * + * @var date + */ + public $status_override_end_date; + /** * Optional FK to Parent Membership. * @@ -343,6 +350,25 @@ class CRM_Member_DAO_Membership extends CRM_Core_DAO { 'type' => 'CheckBox', ], ], + 'status_override_end_date' => [ + 'name' => 'status_override_end_date', + 'type' => CRM_Utils_Type::T_DATE, + 'title' => ts('Status Override End Date'), + 'description' => 'Then end date of membership status override if \'Override until selected date\' override type is selected.', + 'import' => TRUE, + 'where' => 'civicrm_membership.status_override_end_date', + 'headerPattern' => '', + 'dataPattern' => '', + 'export' => TRUE, + 'default' => 'NULL', + 'table_name' => 'civicrm_membership', + 'entity' => 'Membership', + 'bao' => 'CRM_Member_BAO_Membership', + 'localizable' => 0, + 'html' => [ + 'type' => 'Select Date', + ], + ], 'owner_membership_id' => [ 'name' => 'owner_membership_id', 'type' => CRM_Utils_Type::T_INT, diff --git a/CRM/Member/Form.php b/CRM/Member/Form.php index 29f91b26ab..65fe5c3482 100644 --- a/CRM/Member/Form.php +++ b/CRM/Member/Form.php @@ -140,6 +140,13 @@ class CRM_Member_Form extends CRM_Contribute_Form_AbstractEditPayment { if (isset($defaults['status'])) { $this->assign('membershipStatus', $defaults['status']); } + + if (!empty($defaults['is_override'])) { + $defaults['is_override'] = CRM_Member_StatusOverrideTypes::PERMANENT; + } + if (!empty($defaults['status_override_end_date'])) { + $defaults['is_override'] = CRM_Member_StatusOverrideTypes::UNTIL_DATE; + } } if ($this->_action & CRM_Core_Action::ADD) { diff --git a/CRM/Member/Form/Membership.php b/CRM/Member/Form/Membership.php index 2103683872..fc1b4807e5 100644 --- a/CRM/Member/Form/Membership.php +++ b/CRM/Member/Form/Membership.php @@ -596,14 +596,16 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { $this->add('select', 'status_id', ts('Membership Status'), array('' => ts('- select -')) + CRM_Member_PseudoConstant::membershipStatus(NULL, NULL, 'label') ); - $statusOverride = $this->addElement('checkbox', 'is_override', - ts('Status Override?'), NULL, - array('onClick' => 'showHideMemberStatus()') + + $statusOverride = $this->addElement('select', 'is_override', ts('Status Override?'), + CRM_Member_StatusOverrideTypes::getSelectOptions() ); if ($statusOverride) { $elements[] = $statusOverride; } + $this->add('datepicker', 'status_override_end_date', ts('Status Override End Date'), '', FALSE, array('minDate' => time(), 'time' => FALSE)); + $this->addElement('checkbox', 'record_contribution', ts('Record Membership Payment?')); $this->add('text', 'total_amount', ts('Amount')); @@ -821,9 +823,13 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { foreach ($tmp_statuses as $cur_stat) { $status_ids[] = $cur_stat['id']; } + if (empty($params['status_id']) || in_array($params['status_id'], $status_ids) == FALSE) { $errors['status_id'] = ts('Please enter a status that does NOT represent a current membership status.'); - $errors['is_override'] = ts('This must be checked because you set an End Date for a lifetime membership'); + } + + if (!empty($params['is_override']) && !CRM_Member_StatusOverrideTypes::isPermanent($params['is_override'])) { + $errors['is_override'] = ts('Because you set an End Date for a lifetime membership, This must be set to "Override Permanently"'); } } else { @@ -855,7 +861,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { } //CRM-3724, check for availability of valid membership status. - if (empty($params['is_override']) && !isset($errors['_qf_default'])) { + if ((empty($params['is_override']) || CRM_Member_StatusOverrideTypes::isNo($params['is_override'])) && !isset($errors['_qf_default'])) { $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($startDate, $endDate, $joinDate, @@ -869,7 +875,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { $errors['_qf_default'] = ts('There is no valid Membership Status available for selected membership dates.'); $status = ts('Oops, it looks like there is no valid membership status available for the given membership dates. You can Configure Membership Status Rules.', array(1 => $url)); if (!$self->_mode) { - $status .= ' ' . ts('OR You can sign up by setting Status Override? to true.'); + $status .= ' ' . ts('OR You can sign up by setting Status Override? to something other than "NO".'); } CRM_Core_Session::setStatus($status, ts('Membership Status Error'), 'error'); } @@ -880,10 +886,14 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { $errors['join_date'] = ts('Please enter the Member Since.'); } - if (isset($params['is_override']) && - $params['is_override'] && empty($params['status_id']) - ) { - $errors['status_id'] = ts('Please enter the status.'); + if (!empty($params['is_override']) && CRM_Member_StatusOverrideTypes::isOverridden($params['is_override']) && empty($params['status_id'])) { + $errors['status_id'] = ts('Please enter the Membership status.'); + } + + if (!empty($params['is_override']) && CRM_Member_StatusOverrideTypes::isUntilDate($params['is_override'])) { + if (empty($params['status_override_end_date'])) { + $errors['status_override_end_date'] = ts('Please enter the Membership override end date.'); + } } //total amount condition arise when membership type having no @@ -916,12 +926,22 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { } // get the submitted form values. $this->_params = $this->controller->exportValues($this->_name); + $this->convertIsOverrideValue(); $this->submit(); $this->setUserContext(); } + /** + * Convert the value of selected (status override?) + * option to TRUE if it indicate an overridden status + * or FALSE otherwise. + */ + private function convertIsOverrideValue() { + $this->_params['is_override'] = CRM_Member_StatusOverrideTypes::isOverridden($this->_params['is_override']); + } + /** * Send email receipt. * @@ -1181,6 +1201,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form { 'status_id', 'source', 'is_override', + 'status_override_end_date', 'campaign_id', ); diff --git a/CRM/Member/Import/Parser/Membership.php b/CRM/Member/Import/Parser/Membership.php index a66d1f2816..8ce66fddf5 100644 --- a/CRM/Member/Import/Parser/Membership.php +++ b/CRM/Member/Import/Parser/Membership.php @@ -211,6 +211,17 @@ class CRM_Member_Import_Parser_Membership extends CRM_Member_Import_Parser { } break; + case 'status_override_end_date': + if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) { + if (!CRM_Utils_Rule::date($params[$key])) { + CRM_Contact_Import_Parser_Contact::addToErrorMsg('Status Override End Date', $errorMessage); + } + } + else { + CRM_Contact_Import_Parser_Contact::addToErrorMsg('Status Override End Date', $errorMessage); + } + break; + case 'membership_type_id': $membershipTypes = CRM_Member_PseudoConstant::membershipType(); if (!CRM_Utils_Array::crmInArray($val, $membershipTypes) && diff --git a/CRM/Member/StatusOverrideTypes.php b/CRM/Member/StatusOverrideTypes.php new file mode 100644 index 0000000000..4c3d654524 --- /dev/null +++ b/CRM/Member/StatusOverrideTypes.php @@ -0,0 +1,116 @@ + 'Type 1 Label'] format + */ + public static function getSelectOptions() { + return array( + self::NO => ts('No'), + self::PERMANENT => ts('Override Permanently'), + self::UNTIL_DATE => ts('Override Until Selected Date'), + ); + } + + /** + * Determines if the override type means + * that the membership is overridden or not. + * For now, only "NO" type means that the membership + * status is not overridden. + * + * @param $overrideType + * + * @return bool + */ + public static function isOverridden($overrideType) { + if ($overrideType == self::NO) { + return FALSE; + } + + return TRUE; + } + + public static function isNo($overrideType) { + if ($overrideType != self::NO) { + return FALSE; + } + + return TRUE; + } + + public static function isPermanent($overrideType) { + if ($overrideType != self::PERMANENT) { + return FALSE; + } + + return TRUE; + } + + public static function isUntilDate($overrideType) { + if ($overrideType != self::UNTIL_DATE) { + return FALSE; + } + + return TRUE; + } + +} diff --git a/CRM/Upgrade/Incremental/php/FourSeven.php b/CRM/Upgrade/Incremental/php/FourSeven.php index 8a1947e8d8..5a9b828813 100644 --- a/CRM/Upgrade/Incremental/php/FourSeven.php +++ b/CRM/Upgrade/Incremental/php/FourSeven.php @@ -494,6 +494,18 @@ class CRM_Upgrade_Incremental_php_FourSeven extends CRM_Upgrade_Incremental_Base $this->addTask('Rebuild Multilingual Schema', 'rebuildMultilingalSchema'); } + /** + * Upgrade function. + * + * @param string $rev + */ + public function upgrade_4_7_32($rev) { + $this->addTask(ts('Upgrade DB to %1: SQL', array(1 => $rev)), 'runSql', $rev); + + $this->addTask('CRM-21733: Add status_override_end_date field to civicrm_membership table', 'addColumn', 'civicrm_membership', 'status_override_end_date', + "date DEFAULT NULL COMMENT 'The end date of membership status override if (Override until selected date) override type is selected.'"); + } + /* * Important! All upgrade functions MUST add a 'runSql' task. * Uncomment and use the following template for a new upgrade version diff --git a/templates/CRM/Member/Form/Membership.tpl b/templates/CRM/Member/Form/Membership.tpl index 460d3b712d..db3c403395 100644 --- a/templates/CRM/Member/Form/Membership.tpl +++ b/templates/CRM/Member/Form/Membership.tpl @@ -154,7 +154,13 @@ {if !$membershipMode} - {$form.is_override.label} {help id="id-status-override"}{$form.is_override.html} + + {$form.is_override.label} {help id="id-status-override"} + + {$form.is_override.html} + {$form.status_override_end_date.html} + + {* Show read-only Status block - when action is UPDATE and is_override is FALSE *} {if $action eq 2} @@ -164,7 +170,7 @@ {* Show editable status field when is_override is TRUE *} {$form.status_id.label}{$form.status_id.html}
- {ts}If Status Override is checked, the selected status will remain in force (it will NOT be modified by the automated status update script).{/ts} + {ts}When Status Override is active, the selected status will remain in force (it will NOT be subject to membership status rules) until it is cancelled or become inactive.{/ts} {/if} {if $accessContribution and !$membershipMode AND ($action neq 2 or (!$rows.0.contribution_id AND !$softCredit) or $onlinePendingContributionId)} @@ -401,15 +407,34 @@