CRM-21198 Update membership status appropriately when adding a payment completes...
authoreileen <emcnaughton@wikimedia.org>
Wed, 20 Sep 2017 08:44:44 +0000 (20:44 +1200)
committereileen <emcnaughton@wikimedia.org>
Tue, 26 Sep 2017 08:07:06 +0000 (21:07 +1300)
CRM/Contribute/BAO/Contribution.php
tests/phpunit/CRM/Core/Payment/PayPalPNTest.php
tests/phpunit/CRM/Member/Form/MembershipTest.php

index 332f2bc3e31c7a835794ee02d1692ceb183a8e5a..8bb11a19e2d04b41ea117a1b03eba2d53e78759d 100644 (file)
@@ -3820,6 +3820,10 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
       $participantId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $contributionId, 'participant_id', 'contribution_id');
     }
 
+    // load related memberships on basis of $contributionDAO object
+    $membershipIDs = array();
+    $contributionDAO->loadRelatedMembershipObjects($membershipIDs);
+
     // build params for recording financial trxn entry
     $params['contribution'] = $contributionDAO;
     $params = array_merge($defaults, $params);
@@ -3900,6 +3904,16 @@ WHERE eft.entity_table = 'civicrm_contribution'
           }
         }
 
+        // update membership details
+        if (!empty($contributionDAO->_relatedObjects['membership'])) {
+          self::updateMembershipBasedOnCompletionOfContribution(
+            $contributionDAO,
+            $contributionDAO->_relatedObjects['membership'],
+            $contributionId,
+            $trxnsData['trxn_date']
+          );
+        }
+
         // update financial item statuses
         $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId);
         $sqlFinancialItemUpdate = "
@@ -4579,91 +4593,13 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
       }
 
       if (!empty($memberships)) {
-        foreach ($memberships as $membershipTypeIdKey => $membership) {
-          if ($membership) {
-            $membershipParams = array(
-              'id' => $membership->id,
-              'contact_id' => $membership->contact_id,
-              'is_test' => $membership->is_test,
-              'membership_type_id' => $membership->membership_type_id,
-              'membership_activity_status' => 'Completed',
-            );
-
-            $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membershipParams['contact_id'],
-              $membershipParams['membership_type_id'],
-              $membershipParams['is_test'],
-              $membershipParams['id']
-            );
-
-            // CRM-8141 update the membership type with the value recorded in log when membership created/renewed
-            // this picks up membership type changes during renewals
-            // @todo this is almost certainly an obsolete sql call, the pre-change
-            // membership is accessible via $this->_relatedObjects
-            $sql = "
-SELECT    membership_type_id
-FROM      civicrm_membership_log
-WHERE     membership_id={$membershipParams['id']}
-ORDER BY  id DESC
-LIMIT 1;";
-            $dao = CRM_Core_DAO::executeQuery($sql);
-            if ($dao->fetch()) {
-              if (!empty($dao->membership_type_id)) {
-                $membershipParams['membership_type_id'] = $dao->membership_type_id;
-              }
-            }
-            $dao->free();
-
-            if (CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', CRM_Utils_Array::value('contribution_status_id', $input)) === 'Pending') {
-              $membershipParams['num_terms'] = 0;
-            }
-            else {
-              $membershipParams['num_terms'] = $contribution->getNumTermsByContributionAndMembershipType(
-                $membershipParams['membership_type_id'],
-                $primaryContributionID
-              );
-              // @todo remove all this stuff in favour of letting the api call further down handle in
-              // (it is a duplication of what the api does).
-              $dates = array_fill_keys(array('join_date', 'start_date', 'end_date'), NULL);
-              if ($currentMembership) {
-                /*
-                 * Fixed FOR CRM-4433
-                 * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status
-                 * when Contribution mode is notify and membership is for renewal )
-                 */
-                CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeDate);
-
-                // @todo - we should pass membership_type_id instead of null here but not
-                // adding as not sure of testing
-                $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membershipParams['id'],
-                  $changeDate, NULL, $membershipParams['num_terms']
-                );
-
-                $dates['join_date'] = $currentMembership['join_date'];
-              }
-
-              //get the status for membership.
-              $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'],
-                $dates['end_date'],
-                $dates['join_date'],
-                'today',
-                TRUE,
-                $membershipParams['membership_type_id'],
-                $membershipParams
-              );
-
-              unset($dates['end_date']);
-              $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New');
-              //we might be renewing membership,
-              //so make status override false.
-              $membershipParams['is_override'] = FALSE;
-            }
-            //CRM-17723 - reset static $relatedContactIds array()
-            // @todo move it to Civi Statics.
-            $var = TRUE;
-            CRM_Member_BAO_Membership::createRelatedMemberships($var, $var, TRUE);
-            civicrm_api3('Membership', 'create', $membershipParams);
-          }
-        }
+        self::updateMembershipBasedOnCompletionOfContribution(
+          $contribution,
+          $memberships,
+          $primaryContributionID,
+          $changeDate,
+          CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', CRM_Utils_Array::value('contribution_status_id', $input))
+        );
       }
     }
     else {
@@ -5410,6 +5346,112 @@ LEFT JOIN  civicrm_contribution on (civicrm_contribution.contact_id = civicrm_co
     return FALSE;
   }
 
+  /**
+   * Update the memberships associated with a contribution if it has been completed.
+   *
+   * Note that the way in which $memberships are loaded as objects is pretty messy & I think we could just
+   * load them in this function. Code clean up would compensate for any minor performance implication.
+   *
+   * @param array $memberships
+   * @param int $primaryContributionID
+   * @param string $changeDate
+   * @param string $contributionStatus
+   *   This shouldn't be required but historical function overload by repeattransaction probably requires it.
+   *
+   * @todo investigate completely bypassing this function if $contributionStatus != Completed.
+   */
+  protected static function updateMembershipBasedOnCompletionOfContribution($contribution, $memberships, $primaryContributionID, $changeDate, $contributionStatus = 'Completed') {
+    foreach ($memberships as $membershipTypeIdKey => $membership) {
+      if ($membership) {
+        $membershipParams = array(
+          'id' => $membership->id,
+          'contact_id' => $membership->contact_id,
+          'is_test' => $membership->is_test,
+          'membership_type_id' => $membership->membership_type_id,
+          'membership_activity_status' => 'Completed',
+        );
+
+        $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membershipParams['contact_id'],
+          $membershipParams['membership_type_id'],
+          $membershipParams['is_test'],
+          $membershipParams['id']
+        );
+
+        // CRM-8141 update the membership type with the value recorded in log when membership created/renewed
+        // this picks up membership type changes during renewals
+        // @todo this is almost certainly an obsolete sql call, the pre-change
+        // membership is accessible via $this->_relatedObjects
+        $sql = "
+SELECT    membership_type_id
+FROM      civicrm_membership_log
+WHERE     membership_id={$membershipParams['id']}
+ORDER BY  id DESC
+LIMIT 1;";
+        $dao = CRM_Core_DAO::executeQuery($sql);
+        if ($dao->fetch()) {
+          if (!empty($dao->membership_type_id)) {
+            $membershipParams['membership_type_id'] = $dao->membership_type_id;
+          }
+        }
+        $dao->free();
+
+        // Unclear why this is here but this function is overloaded by repeattransaction.
+        if ($contributionStatus === 'Pending') {
+          $membershipParams['num_terms'] = 0;
+        }
+        else {
+          $membershipParams['num_terms'] = $contribution->getNumTermsByContributionAndMembershipType(
+            $membershipParams['membership_type_id'],
+            $primaryContributionID
+          );
+          // @todo remove all this stuff in favour of letting the api call further down handle in
+          // (it is a duplication of what the api does).
+          $dates = array_fill_keys(array(
+            'join_date',
+            'start_date',
+            'end_date',
+          ), NULL);
+          if ($currentMembership) {
+            /*
+             * Fixed FOR CRM-4433
+             * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status
+             * when Contribution mode is notify and membership is for renewal )
+             */
+            CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeDate);
+
+            // @todo - we should pass membership_type_id instead of null here but not
+            // adding as not sure of testing
+            $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membershipParams['id'],
+              $changeDate, NULL, $membershipParams['num_terms']
+            );
+            $dates['join_date'] = $currentMembership['join_date'];
+          }
+
+          //get the status for membership.
+          $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'],
+            $dates['end_date'],
+            $dates['join_date'],
+            'today',
+            TRUE,
+            $membershipParams['membership_type_id'],
+            $membershipParams
+          );
+
+          unset($dates['end_date']);
+          $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New');
+          //we might be renewing membership,
+          //so make status override false.
+          $membershipParams['is_override'] = FALSE;
+        }
+        //CRM-17723 - reset static $relatedContactIds array()
+        // @todo move it to Civi Statics.
+        $var = TRUE;
+        CRM_Member_BAO_Membership::createRelatedMemberships($var, $var, TRUE);
+        civicrm_api3('Membership', 'create', $membershipParams);
+      }
+    }
+  }
+
   /**
    * Assign Test Value.
    *
index e95e47993ea86693eca5ee26a845bead072761ae..2b2a4c38f3eab8c87a2f645cc31489e469f5d767 100644 (file)
@@ -76,8 +76,8 @@ class CRM_Core_Payment_PayPalIPNTest extends CiviUnitTestCase {
   public function testInvoiceSentOnIPNPaymentSuccess() {
     $this->enableTaxAndInvoicing();
 
-    $pendingStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
-    $completedStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
+    $pendingStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
+    $completedStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
     $params = array(
       'payment_processor_id' => $this->_paymentProcessorID,
       'contact_id' => $this->_contactID,
index 084e0a4a22196138527f68cf32185f614b30a736..c43ac6e039c24ebc7d06f2a9c6353fedd39c49ab 100644 (file)
@@ -608,6 +608,78 @@ class CRM_Member_Form_MembershipTest extends CiviUnitTestCase {
     $this->assertEquals(-25, $payment['balance']);
   }
 
+  /**
+   * Test the submit function of the membership form for partial payment.
+   */
+  public function testSubmitPartialPayment() {
+    // Step 1: submit a partial payment for a membership via backoffice
+    $form = $this->getForm();
+    $form->preProcess();
+    $this->mut = new CiviMailUtils($this, TRUE);
+    $this->createLoggedInUser();
+    $priceSet = $this->callAPISuccess('PriceSet', 'Get', array("extends" => "CiviMember"));
+    $form->set('priceSetId', $priceSet['id']);
+    $partiallyPaidAmount = 25;
+    CRM_Price_BAO_PriceSet::buildPriceSet($form);
+    $params = array(
+      'cid' => $this->_individualId,
+      'join_date' => date('m/d/Y', time()),
+      'start_date' => '',
+      'end_date' => '',
+      // This format reflects the 23 being the organisation & the 25 being the type.
+      'membership_type_id' => array(23, $this->membershipTypeAnnualFixedID),
+      'record_contribution' => 1,
+      'total_amount' => $partiallyPaidAmount,
+      'receive_date' => date('m/d/Y', time()),
+      'receive_date_time' => '08:36PM',
+      'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
+      'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Partially paid'),
+      'financial_type_id' => '2', //Member dues, see data.xml
+      'payment_processor_id' => $this->_paymentProcessorID,
+    );
+    $form->_contactID = $this->_individualId;
+    $form->testSubmit($params);
+    $membership = $this->callAPISuccessGetSingle('Membership', array('contact_id' => $this->_individualId));
+    // check the membership status after partial payment, if its Pending
+    $this->assertEquals(array_search('Pending', CRM_Member_PseudoConstant::membershipStatus()), $membership['status_id']);
+    $contribution = $this->callAPISuccessGetSingle('Contribution', array(
+      'contact_id' => $this->_individualId,
+    ));
+    $this->assertEquals('Partially paid', $contribution['contribution_status']);
+    // $this->assertEquals(50.00, $contribution['total_amount']);
+    // $this->assertEquals(25.00, $contribution['net_amount']);
+
+    // Step 2: submit the other half of the partial payment
+    //  via AdditionalPayment form to complete the related contribution
+    $form = new CRM_Contribute_Form_AdditionalPayment();
+    $submitParams = array(
+      'contribution_id' => $contribution['contribution_id'],
+      'contact_id' => $this->_individualId,
+      'total_amount' => $partiallyPaidAmount,
+      'currency' => 'USD',
+      'financial_type_id' => 2,
+      'receive_date' => '04/21/2015',
+      'receive_date_time' => '11:27PM',
+      'trxn_date' => '2017-04-11 13:05:11',
+      'payment_processor_id' => 0,
+      'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
+      'check_number' => 'check-12345',
+    );
+    $form->cid = $this->_individualId;
+    $form->testSubmit($submitParams);
+    $membership = $this->callAPISuccessGetSingle('Membership', array('contact_id' => $this->_individualId));
+    // check the membership status after additional payment, if its changed to 'New'
+    $this->assertEquals(array_search('New', CRM_Member_PseudoConstant::membershipStatus()), $membership['status_id']);
+
+    // check the contribution status and net amount after additional payment
+    $contribution = $this->callAPISuccessGetSingle('Contribution', array(
+      'contact_id' => $this->_individualId,
+    ));
+    $this->assertEquals('Completed', $contribution['contribution_status']);
+    // $this->assertEquals(50.00, $contribution['total_amount']);
+    // $this->assertEquals(50.00, $contribution['net_amount']);
+  }
+
   /**
    * Test the submit function of the membership form.
    */