CRM-21733: Adding the ability to override membership temporarily until specific date
authorOmar abu hussein <opetmar91@gmail.com>
Fri, 2 Feb 2018 17:25:22 +0000 (17:25 +0000)
committerOmar abu hussein <opetmar91@gmail.com>
Sat, 3 Feb 2018 00:38:10 +0000 (02:38 +0200)
16 files changed:
CRM/Contribute/BAO/Contribution.php
CRM/Core/BAO/UFField.php
CRM/Member/BAO/Membership.php
CRM/Member/DAO/Membership.php
CRM/Member/Form.php
CRM/Member/Form/Membership.php
CRM/Member/Import/Parser/Membership.php
CRM/Member/StatusOverrideTypes.php [new file with mode: 0644]
CRM/Upgrade/Incremental/php/FourSeven.php
templates/CRM/Member/Form/Membership.tpl
templates/CRM/Member/Page/Tab.hlp
tests/phpunit/CRM/Member/BAO/MembershipTest.php
tests/phpunit/CRM/Member/Form/MembershipTest.php
tests/phpunit/CRM/Member/Import/Parser/MembershipTest.php
tests/phpunit/CRM/Member/StatusOverrideTypesTest.php [new file with mode: 0644]
xml/schema/Member/Membership.xml

index 8e37a072499916ebe42aec2992775b201f31dc79..9c5cacb3fd8d1fb5200cb47a478a7bd7c58ee3f9 100644 (file)
@@ -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.
index 10dccc79db1286851ed85bfbdfff1986ee062d88..af399738d76db05a8e2bb45383006cb3c3410853 100644 (file)
@@ -975,6 +975,7 @@ SELECT  id
         'membership_type_id',
         'member_is_test',
         'is_override',
+        'status_override_end_date',
         'status_id',
         'member_is_pay_later'
       );
index 7a98cd277150840b66f8a7ee38e262125d635b18..6b6ff60f5d66e528c0c5228817aebde967d7064c 100644 (file)
@@ -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.
index 7a378b086a76cf9a4252b265c03e6a917a5edabd..146751f7a2976aec52d8b9c890d14ea55480bdc0 100644 (file)
@@ -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,
index 29f91b26abe798240dbc86bfe71dcb79a60a261d..65fe5c34829861ce4074b6849f6e4929f11db991 100644 (file)
@@ -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) {
index 210368387295580013a1c7103b73b6f689d63747..fc1b4807e5bbf3238f4158b0a96eef3ab6344d58 100644 (file)
@@ -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 <a href="%1">Configure Membership Status Rules</a>.', 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',
     );
 
index a66d1f2816e810ec15715551a4bb6e9761428942..8ce66fddf5059e6e53fe78365614802041ce1257 100644 (file)
@@ -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 (file)
index 0000000..4c3d654
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2017                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Membership status override types.
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2017
+ *
+ */
+class CRM_Member_StatusOverrideTypes {
+  /**
+   * The membership status is not overridden
+   * and its is subject to membership status rules.
+   */
+  const NO = 0;
+
+  /**
+   * The membership will stay at the selected status
+   * and its status is NOT subject to membership
+   * status rules.
+   */
+  const PERMANENT = 1;
+
+  /**
+   * The membership status will stay at the
+   * selected status and it is NOT subject to membership status rules.
+   * However, on the selected date(status_override_end_date),
+   * the status override type will automatically change to "NO" thus then
+   * the membership becomes subject to membership status rules.
+   */
+  const UNTIL_DATE = 2;
+
+  /**
+   * Gets the list of override types
+   * as a list of options to be used
+   * for select input.
+   *
+   * @return array
+   *   In ['Type 1 Value' => '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;
+  }
+
+}
index 8a1947e8d8deb1331e7efbac06f243917aea963d..5a9b8288133368b28eb3acd200c8545b2e5d1cb9 100644 (file)
@@ -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
index 460d3b712d8d5c834d1901722ffbc832bde9619e..db3c40339592fe41ec24d960b90a0deda448cf0f 100644 (file)
           </td>
         </tr>
         {if !$membershipMode}
-          <tr><td class="label">{$form.is_override.label} {help id="id-status-override"}</td><td>{$form.is_override.html}</td></tr>
+          <tr>
+            <td class="label">{$form.is_override.label} {help id="id-status-override"}</td>
+            <td>
+              <span id="is-override">{$form.is_override.html}</span>
+              <span id="status-override-end-date">{$form.status_override_end_date.html}</span>
+            </td>
+          </tr>
           {* Show read-only Status block - when action is UPDATE and is_override is FALSE *}
           <tr id="memberStatus_show">
             {if $action eq 2}
 
           {* Show editable status field when is_override is TRUE *}
           <tr id="memberStatus"><td class="label">{$form.status_id.label}</td><td>{$form.status_id.html}<br />
-            <span class="description">{ts}If <strong>Status Override</strong> is checked, the selected status will remain in force (it will NOT be modified by the automated status update script).{/ts}</span></td></tr>
+            <span class="description">{ts}When <strong>Status Override</strong> 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}</span></td></tr>
         {/if}
 
         {if $accessContribution and !$membershipMode AND ($action neq 2 or (!$rows.0.contribution_id AND !$softCredit) or $onlinePendingContributionId)}
     <script type="text/javascript">
 
     {/literal}{if !$membershipMode}{literal}
+    cj( "#is_override" ).change(function() {
+      showHideMemberStatus();
+    });
+
     showHideMemberStatus();
     function showHideMemberStatus() {
-      if ( cj( "#is_override" ).prop('checked') ) {
-        cj('#memberStatus').show( );
-        cj('#memberStatus_show').hide( );
-      }
-      else {
-        cj('#memberStatus').hide( );
-        cj('#memberStatus_show').show( );
+      var isOverride = cj( "#is_override" ).val();
+      switch (isOverride) {
+        case '0':
+          cj('#memberStatus').hide();
+          cj('#memberStatus_show').show();
+          cj('#status-override-end-date').hide();
+          break;
+        case '1':
+          cj('#memberStatus').show();
+          cj('#memberStatus_show').hide();
+          cj('#status-override-end-date').hide();
+          break;
+        case '2':
+          cj('#memberStatus').show();
+          cj('#memberStatus_show').hide();
+          cj('#status-override-end-date').show();
+          break;
+        default :
+          cj('#memberStatus').hide( );
+          cj('#memberStatus_show').show( );
+          cj('#status-override-end-date').hide();
+          break;
       }
     }
     {/literal}{/if}
index 0be22e287f86ac4bdca99f99500d35d2fde16531..04111e4f2cab886fa9f98d7c6d41155a45bfcd63 100644 (file)
@@ -27,8 +27,8 @@
   {ts}Override Status{/ts}
 {/htxt}
 {htxt id="id-status-override"}
-  <p>{ts}Membership status is normally assigned and updated automatically based on your configured membership status rules. However, if you want to assign a status manually and bypass automated status setting, check this box. Then you can select from the available status options.{/ts}</p>
-  <p>{ts}The status you assign will remain in force, unless it is again modified on this screen. As long as the Status Override flag is checked, the automated membership status update script will NOT update this membership record.{/ts}</p>
+  <p>{ts}Membership status is normally assigned and updated automatically based on your configured membership status rules. However, if you want to assign a status manually and bypass automated status setting, choose either "Override Permanently" or "Override Until Selected Date", Then you can select from the available status options.{/ts}</p>
+  <p>{ts}If you select "Override Permanently", The status you assign will remain in force, unless it is again modified on this screen. If you select "Override Until Selected Date", it will behave same as "Override Permanently" but will revert to "No" once the selected date is reached. As long as the Membership Override is active, the automated membership status update script will NOT update this membership record.{/ts}</p>
 {/htxt}
 
 {htxt id="id-contribution_contact-title"}
index a18d468b81557f24b39cd50859e026f0297d0688..1585b0260441020ec7af0a28be0d0cdf97b40f18 100644 (file)
@@ -617,4 +617,86 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase {
     $this->contactDelete($contactId);
   }
 
+  public function testUpdateAllMembershipStatusConvertExpiredOverriddenStatusToNormal() {
+    $params = array(
+      'contact_id' => $this->individualCreate(),
+      'membership_type_id' => $this->_membershipTypeID,
+      'join_date' => date('Ymd', time()),
+      'start_date' => date('Ymd', time()),
+      'end_date' => date('Ymd', strtotime('+1 year')),
+      'source' => 'Payment',
+      'is_override' => 1,
+      'status_override_end_date' => date('Ymd', strtotime('-1 day')),
+      'status_id' => $this->_membershipStatusID,
+    );
+    $ids = array();
+    $createdMembership = CRM_Member_BAO_Membership::create($params, $ids);
+
+    CRM_Member_BAO_Membership::updateAllMembershipStatus();
+
+    $membershipAfterProcess = civicrm_api3('Membership', 'get', array(
+      'sequential' => 1,
+      'id' => $createdMembership->id,
+      'return' => array('id', 'is_override', 'status_override_end_date'),
+    ))['values'][0];
+
+    $this->assertEquals($createdMembership->id, $membershipAfterProcess['id']);
+    $this->assertArrayNotHasKey('is_override', $membershipAfterProcess);
+    $this->assertArrayNotHasKey('status_override_end_date', $membershipAfterProcess);
+  }
+
+  public function testUpdateAllMembershipStatusHandleOverriddenWithEndOverrideDateEqualTodayAsExpired() {
+    $params = array(
+      'contact_id' => $this->individualCreate(),
+      'membership_type_id' => $this->_membershipTypeID,
+      'join_date' => date('Ymd', time()),
+      'start_date' => date('Ymd', time()),
+      'end_date' => date('Ymd', strtotime('+1 year')),
+      'source' => 'Payment',
+      'is_override' => 1,
+      'status_override_end_date' => date('Ymd', time()),
+      'status_id' => $this->_membershipStatusID,
+    );
+    $ids = array();
+    $createdMembership = CRM_Member_BAO_Membership::create($params, $ids);
+
+    CRM_Member_BAO_Membership::updateAllMembershipStatus();
+
+    $membershipAfterProcess = civicrm_api3('Membership', 'get', array(
+      'sequential' => 1,
+      'id' => $createdMembership->id,
+      'return' => array('id', 'is_override', 'status_override_end_date'),
+    ))['values'][0];
+
+    $this->assertEquals($createdMembership->id, $membershipAfterProcess['id']);
+    $this->assertArrayNotHasKey('is_override', $membershipAfterProcess);
+    $this->assertArrayNotHasKey('status_override_end_date', $membershipAfterProcess);
+  }
+
+  public function testUpdateAllMembershipStatusDoesNotConvertOverridenMembershipWithoutEndOverrideDateToNormal() {
+    $params = array(
+      'contact_id' => $this->individualCreate(),
+      'membership_type_id' => $this->_membershipTypeID,
+      'join_date' => date('Ymd', time()),
+      'start_date' => date('Ymd', time()),
+      'end_date' => date('Ymd', strtotime('+1 year')),
+      'source' => 'Payment',
+      'is_override' => 1,
+      'status_id' => $this->_membershipStatusID,
+    );
+    $ids = array();
+    $createdMembership = CRM_Member_BAO_Membership::create($params, $ids);
+
+    CRM_Member_BAO_Membership::updateAllMembershipStatus();
+
+    $membershipAfterProcess = civicrm_api3('Membership', 'get', array(
+      'sequential' => 1,
+      'id' => $createdMembership->id,
+      'return' => array('id', 'is_override', 'status_override_end_date'),
+    ))['values'][0];
+
+    $this->assertEquals($createdMembership->id, $membershipAfterProcess['id']);
+    $this->assertEquals(1, $membershipAfterProcess['is_override']);
+  }
+
 }
index 023c4ad9046a517e75e83def31b4c1106ab280a3..390d9769e36dea57912fb8d440dccb797b1e0717 100644 (file)
@@ -267,9 +267,9 @@ class CRM_Member_Form_MembershipTest extends CiviUnitTestCase {
 
   /**
    *  Test CRM_Member_Form_Membership::formRule() with a parameter
-   *  that has an override and no status
+   *  that has permanent override and no status
    */
-  public function testFormRuleOverrideNoStatus() {
+  public function testFormRulePermanentOverrideWithNoStatus() {
     $unixNow = time();
     $params = array(
       'join_date' => date('m/d/Y', $unixNow),
@@ -283,6 +283,34 @@ class CRM_Member_Form_MembershipTest extends CiviUnitTestCase {
     $this->assertTrue(array_key_exists('status_id', $rc));
   }
 
+  public function testFormRuleUntilDateOverrideWithValidOverrideEndDate() {
+    $params = array(
+      'join_date' => date('m/d/Y', time()),
+      'membership_type_id' => array('23', '25'),
+      'is_override' => TRUE,
+      'status_id' => 1,
+      'status_override_end_date' => date('m/d/Y', time()),
+    );
+    $files = array();
+    $membershipForm = new CRM_Member_Form_Membership();
+    $validationResponse = $membershipForm->formRule($params, $files, $membershipForm);
+    $this->assertTrue($validationResponse);
+  }
+
+  public function testFormRuleUntilDateOverrideWithNoOverrideEndDate() {
+    $params = array(
+      'join_date' => date('m/d/Y', time()),
+      'membership_type_id' => array('23', '25'),
+      'is_override' => CRM_Member_StatusOverrideTypes::UNTIL_DATE,
+      'status_id' => 1,
+    );
+    $files = array();
+    $membershipForm = new CRM_Member_Form_Membership();
+    $validationResponse = $membershipForm->formRule($params, $files, $membershipForm);
+    $this->assertType('array', $validationResponse);
+    $this->assertEquals('Please enter the Membership override end date.', $validationResponse['status_override_end_date']);
+  }
+
   /**
    *  Test CRM_Member_Form_Membership::formRule() with a join date
    *  of one month from now and a rolling membership type
index ffbae1b35908a72caf850a6a74a41e818529e0fc..c39b1618d561651433fb27b3a39341612e39c12f 100644 (file)
@@ -102,7 +102,7 @@ class CRM_Member_Import_Parser_MembershipTest extends CiviUnitTestCase {
    *  Test Import.
    */
   public function testImport() {
-    $contactId = $this->individualCreate();
+    $this->individualCreate();
     $contact2Params = array(
       'first_name' => 'Anthonita',
       'middle_name' => 'J.',
@@ -112,7 +112,8 @@ class CRM_Member_Import_Parser_MembershipTest extends CiviUnitTestCase {
       'email' => 'b@c.com',
       'contact_type' => 'Individual',
     );
-    $contactId = $this->individualCreate($contact2Params);
+
+    $this->individualCreate($contact2Params);
     $year = date('Y') - 1;
     $startDate2 = date('Y-m-d', mktime(0, 0, 0, 9, 10, $year));
     $params = array(
@@ -132,19 +133,7 @@ class CRM_Member_Import_Parser_MembershipTest extends CiviUnitTestCase {
       'mapper[1][0]' => 'membership_type_id',
       'mapper[2][0]' => 'membership_start_date',
     );
-    /*
 
-    $params = array(
-    'contact_id' => $contactId,
-    'membership_type_id' => $this->_membershipTypeID,
-    'join_date' => '2006-01-21',
-    'start_date' => '2006-01-21',
-    'end_date' => '2006-12-21',
-    'source' => 'Payment',
-    'is_override' => 1,
-    'status_id' => $this->_mebershipStatusID,
-    );
-     */
     $importObject = new CRM_Member_Import_Parser_Membership($fieldMapper);
     $importObject->init();
     $importObject->_contactType = 'Individual';
@@ -155,4 +144,112 @@ class CRM_Member_Import_Parser_MembershipTest extends CiviUnitTestCase {
     $this->assertEquals(2, $result['count']);
   }
 
+  public function testImportOverriddenMembershipButWithoutStatus() {
+    $this->individualCreate(array('email' => 'anthony_anderson2@civicrm.org'));
+
+    $fieldMapper = array(
+      'mapper[0][0]' => 'email',
+      'mapper[1][0]' => 'membership_type_id',
+      'mapper[2][0]' => 'membership_start_date',
+      'mapper[3][0]' => 'is_override',
+    );
+    $membershipImporter = new CRM_Member_Import_Parser_Membership($fieldMapper);
+    $membershipImporter->init();
+    $membershipImporter->_contactType = 'Individual';
+
+    $importValues = array(
+      'anthony_anderson2@civicrm.org',
+      $this->_membershipTypeID,
+      date('Y-m-d'),
+      TRUE,
+    );
+
+    $importResponse = $membershipImporter->import(CRM_Import_Parser::DUPLICATE_UPDATE, $importValues);
+    $this->assertEquals(CRM_Import_Parser::ERROR, $importResponse);
+    $this->assertContains('Required parameter missing: Status', $importValues);
+  }
+
+  public function testImportOverriddenMembershipWithStatus() {
+    $this->individualCreate(array('email' => 'anthony_anderson3@civicrm.org'));
+
+    $fieldMapper = array(
+      'mapper[0][0]' => 'email',
+      'mapper[1][0]' => 'membership_type_id',
+      'mapper[2][0]' => 'membership_start_date',
+      'mapper[3][0]' => 'is_override',
+      'mapper[4][0]' => 'status_id',
+    );
+    $membershipImporter = new CRM_Member_Import_Parser_Membership($fieldMapper);
+    $membershipImporter->init();
+    $membershipImporter->_contactType = 'Individual';
+
+    $importValues = array(
+      'anthony_anderson3@civicrm.org',
+      $this->_membershipTypeID,
+      date('Y-m-d'),
+      TRUE,
+      'New',
+    );
+
+    $importResponse = $membershipImporter->import(CRM_Import_Parser::DUPLICATE_UPDATE, $importValues);
+    $this->assertEquals(CRM_Import_Parser::VALID, $importResponse);
+  }
+
+  public function testImportOverriddenMembershipWithValidOverrideEndDate() {
+    $this->individualCreate(array('email' => 'anthony_anderson4@civicrm.org'));
+
+    $fieldMapper = array(
+      'mapper[0][0]' => 'email',
+      'mapper[1][0]' => 'membership_type_id',
+      'mapper[2][0]' => 'membership_start_date',
+      'mapper[3][0]' => 'is_override',
+      'mapper[4][0]' => 'status_id',
+      'mapper[5][0]' => 'status_override_end_date',
+    );
+    $membershipImporter = new CRM_Member_Import_Parser_Membership($fieldMapper);
+    $membershipImporter->init();
+    $membershipImporter->_contactType = 'Individual';
+
+    $importValues = array(
+      'anthony_anderson4@civicrm.org',
+      $this->_membershipTypeID,
+      date('Y-m-d'),
+      TRUE,
+      'New',
+      date('Y-m-d'),
+    );
+
+    $importResponse = $membershipImporter->import(CRM_Import_Parser::DUPLICATE_UPDATE, $importValues);
+    $this->assertEquals(CRM_Import_Parser::VALID, $importResponse);
+  }
+
+  public function testImportOverriddenMembershipWithInvalidOverrideEndDate() {
+    $this->individualCreate(array('email' => 'anthony_anderson5@civicrm.org'));
+
+    $fieldMapper = array(
+      'mapper[0][0]' => 'email',
+      'mapper[1][0]' => 'membership_type_id',
+      'mapper[2][0]' => 'membership_start_date',
+      'mapper[3][0]' => 'is_override',
+      'mapper[4][0]' => 'status_id',
+      'mapper[5][0]' => 'status_override_end_date',
+    );
+    $membershipImporter = new CRM_Member_Import_Parser_Membership($fieldMapper);
+    $membershipImporter->init();
+    $membershipImporter->_contactType = 'Individual';
+
+    $importValues = array(
+      'anthony_anderson5@civicrm.org',
+      'New',
+      date('Y-m-d'),
+      TRUE,
+      $this->_mebershipStatusID,
+      'abc',
+    );
+
+    $importResponse = $membershipImporter->import(CRM_Import_Parser::DUPLICATE_UPDATE, $importValues);
+    $this->assertEquals(CRM_Import_Parser::ERROR, $importResponse);
+    $this->assertContains('Required parameter missing: Status', $importValues);
+  }
+
 }
diff --git a/tests/phpunit/CRM/Member/StatusOverrideTypesTest.php b/tests/phpunit/CRM/Member/StatusOverrideTypesTest.php
new file mode 100644 (file)
index 0000000..665d994
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2017                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Member_BAO_MembershipTest
+ * @group headless
+ */
+class CRM_Member_StatusOverrideTypesTest extends CiviUnitTestCase {
+  public function setUp() {
+    parent::setUp();
+  }
+
+  public function testIsOverriddenReturnFalseForNoStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isOverridden = $statusOverrideTypes::isOverridden(CRM_Member_StatusOverrideTypes::NO);
+    $this->assertFalse($isOverridden);
+  }
+
+  public function testIsOverriddenReturnTrueForPermanentStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isOverridden = $statusOverrideTypes::isOverridden(CRM_Member_StatusOverrideTypes::PERMANENT);
+    $this->assertTrue($isOverridden);
+  }
+
+
+  public function testIsOverriddenReturnTrueForUntilDateStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isOverridden = $statusOverrideTypes::isOverridden(CRM_Member_StatusOverrideTypes::UNTIL_DATE);
+    $this->assertTrue($isOverridden);
+  }
+
+  public function testIsNoReturnTrueForNoStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isNo = $statusOverrideTypes::isNo(CRM_Member_StatusOverrideTypes::NO);
+    $this->assertTrue($isNo);
+  }
+
+  public function testIsNoReturnFalseForOtherStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isNo = $statusOverrideTypes::isNo(CRM_Member_StatusOverrideTypes::PERMANENT);
+    $this->assertFalse($isNo);
+
+    $isNo = $statusOverrideTypes::isNo(CRM_Member_StatusOverrideTypes::UNTIL_DATE);
+    $this->assertFalse($isNo);
+  }
+
+  public function testisPermanentReturnTrueForPermanentStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isPermanent = $statusOverrideTypes::isPermanent(CRM_Member_StatusOverrideTypes::PERMANENT);
+    $this->assertTrue($isPermanent);
+  }
+
+  public function testisPermanentReturnFalseForOtherStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isPermanent = $statusOverrideTypes::isPermanent(CRM_Member_StatusOverrideTypes::NO);
+    $this->assertFalse($isPermanent);
+
+    $isPermanent = $statusOverrideTypes::isPermanent(CRM_Member_StatusOverrideTypes::UNTIL_DATE);
+    $this->assertFalse($isPermanent);
+  }
+
+  public function testisUntilDateReturnTrueForUntilDateStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isUntilDate = $statusOverrideTypes::isUntilDate(CRM_Member_StatusOverrideTypes::UNTIL_DATE);
+    $this->assertTrue($isUntilDate);
+  }
+
+  public function testisUntilDateReturnFalseForOtherStatusOverrideType() {
+    $statusOverrideTypes = new CRM_Member_StatusOverrideTypes();
+    $isUntilDate = $statusOverrideTypes::isUntilDate(CRM_Member_StatusOverrideTypes::NO);
+    $this->assertFalse($isUntilDate);
+
+    $isUntilDate = $statusOverrideTypes::isUntilDate(CRM_Member_StatusOverrideTypes::PERMANENT);
+    $this->assertFalse($isUntilDate);
+  }
+
+}
index 7a51751bd71035dc87c76b16558d932cf72a8e7b..3be81314907cca0e95133af84d6de3e67ebe6b33 100644 (file)
     </html>
     <add>1.5</add>
   </field>
+  <field>
+    <name>status_override_end_date</name>
+    <title>Status Override End Date</title>
+    <type>date</type>
+    <default>NULL</default>
+    <import>true</import>
+    <comment>Then end date of membership status override if 'Override until selected date' override type is selected.</comment>
+    <add>4.7</add>
+    <html>
+      <type>Select Date</type>
+    </html>
+  </field>
   <field>
     <name>owner_membership_id</name>
     <type>int unsigned</type>