dev/financial#6 exclude template contributions from the contact summary and add a...
authorJaap Jansma <jaap.jansma@civicoop.org>
Wed, 30 Jun 2021 11:04:08 +0000 (13:04 +0200)
committerJaap Jansma <jaap.jansma@civicoop.org>
Mon, 26 Jul 2021 15:16:16 +0000 (17:16 +0200)
dev/financial#6 exclude template contributions from the contact summary and add a button to view them to the recurring contributions tab

dev/financial#6 exclude template contributions from the contact summary and add a button to view them to the recurring contributions tab

dev/financial#6 exclude template contributions from the contact summary and add a button to view them to the recurring contributions tab

CRM/Contribute/BAO/ContributionRecur.php
CRM/Contribute/Form/Contribution.php
CRM/Contribute/Form/ContributionView.php
CRM/Contribute/Page/Tab.php
templates/CRM/Contribute/Form/Contribution.tpl
templates/CRM/Contribute/Form/ContributionView.tpl
tests/phpunit/CRM/Contribute/BAO/ContributionRecurTest.php

index 0fa36236573b223ee293a2e389ac2db5d9318f50..b468a8fa37c8d3730fe5672eec0287d211e0af55 100644 (file)
@@ -403,6 +403,89 @@ INNER JOIN civicrm_contribution       con ON ( con.id = mp.contribution_id )
     return CRM_Contribute_BAO_Contribution::isSingleLineItem($contribution['id']);
   }
 
+  /**
+   * Create a template contribution based on the first contribution of an
+   * recurring contribution.
+   * When a template contribution already exists this function will not try to create
+   * a new one.
+   * This way we make sure only one template contribution exists.
+   *
+   * @param int $id
+   *
+   * @throws \API_Exception
+   * @throws \CiviCRM_API3_Exception
+   * @throws \Civi\API\Exception\UnauthorizedException
+   * @return int|NULL the ID of the newly created template contribution.
+   */
+  public static function ensureTemplateContributionExists(int $id) {
+    // Check if a template contribution already exists.
+    $templateContributions = Contribution::get(FALSE)
+      ->addWhere('contribution_recur_id', '=', $id)
+      ->addWhere('is_template', '=', 1)
+      // we need this line otherwise the is test contribution don't work.
+      ->addWhere('is_test', 'IN', [0, 1])
+      ->addOrderBy('receive_date', 'DESC')
+      ->setLimit(1)
+      ->execute();
+    if ($templateContributions->count()) {
+      // A template contribution already exists.
+      // Skip the creation of a new one.
+      return $templateContributions->first()['id'];
+    }
+
+    // Retrieve the most recently added contribution
+    $mostRecentContribution = Contribution::get(FALSE)
+      ->addWhere('contribution_recur_id', '=', $id)
+      ->addWhere('is_template', '=', 0)
+      // we need this line otherwise the is test contribution don't work.
+      ->addWhere('is_test', 'IN', [0, 1])
+      ->addOrderBy('receive_date', 'DESC')
+      ->setLimit(1)
+      ->execute()
+      ->first();
+    if (!$mostRecentContribution) {
+      // No first contribution is found.
+      return NULL;
+    }
+
+    $order = new CRM_Financial_BAO_Order();
+    $order->setTemplateContributionID($mostRecentContribution['id']);
+    $order->setOverrideFinancialTypeID($overrides['financial_type_id'] ?? NULL);
+    $order->setOverridableFinancialTypeID($mostRecentContribution['financial_type_id']);
+    $order->setOverrideTotalAmount($mostRecentContribution['total_amount'] ?? NULL);
+    $order->setIsPermitOverrideFinancialTypeForMultipleLines(FALSE);
+    $line_items = $order->getLineItems();
+    $mostRecentContribution['line_item'][$order->getPriceSetID()] = $line_items;
+
+    // If the template contribution was made on-behalf then add the
+    // relevant values to ensure the activity reflects that.
+    $relatedContact = CRM_Contribute_BAO_Contribution::getOnbehalfIds($mostRecentContribution['id']);
+
+    $templateContributionParams = [];
+    $templateContributionParams['is_test'] = $mostRecentContribution['is_test'];
+    $templateContributionParams['is_template'] = '1';
+    $templateContributionParams['skipRecentView'] = TRUE;
+    $templateContributionParams['contribution_recur_id'] = $id;
+    $templateContributionParams['line_item'] = $mostRecentContribution['line_item'];
+    $templateContributionParams['status_id'] = 'Template';
+    foreach (['contact_id', 'campaign_id', 'financial_type_id', 'currency', 'source', 'amount_level', 'address_id', 'on_behalf', 'source_contact_id', 'tax_amount', 'contribution_page_id', 'total_amount'] as $fieldName) {
+      if (isset($mostRecentContribution[$fieldName])) {
+        $templateContributionParams[$fieldName] = $mostRecentContribution[$fieldName];
+      }
+    }
+    if (!empty($relatedContact['individual_id'])) {
+      $templateContributionParams['on_behalf'] = TRUE;
+      $templateContributionParams['source_contact_id'] = $relatedContact['individual_id'];
+    }
+    $templateContributionParams['source'] = $templateContributionParams['source'] ?? ts('Recurring contribution');
+    $templateContribution = civicrm_api3('Contribution', 'create', $templateContributionParams);
+    $temporaryObject = new CRM_Contribute_BAO_Contribution();
+    $temporaryObject->copyCustomFields($mostRecentContribution['id'], $templateContribution['id']);
+    // Add new soft credit against current $contribution.
+    CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($templateContributionParams['contribution_recur_id'], $templateContribution['id']);
+    return $templateContribution['id'];
+  }
+
   /**
    * Get the contribution to be used as the template for later contributions.
    *
index 43133c429a9cc4dec8ba861505da83dc92d6dc99..ae1f4da7e2122b829374eccf24a70009ba5a16e2 100644 (file)
@@ -278,6 +278,10 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP
       $this->applyCustomData('Contribution', $this->getFinancialTypeID(), $this->_id);
     }
 
+    if (!empty($this->_values['is_template'])) {
+      $this->assign('is_template', TRUE);
+    }
+
     $this->_lineItems = [];
     if ($this->_id) {
       if (!empty($this->_compId) && $this->_compContext === 'participant') {
@@ -299,6 +303,9 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP
       $this->assign('payNow', $this->_payNow);
       CRM_Utils_System::setTitle(ts('Pay with Credit Card'));
     }
+    elseif (!empty($this->_values['is_template'])) {
+      $this->setPageTitle(ts('Template Contribution'));
+    }
     elseif ($this->_mode) {
       $this->setPageTitle($this->_ppID ? ts('Credit Card Pledge Payment') : ts('Credit Card Contribution'));
     }
index f0cbfbe943519891a1a669a4cb564d0e083805e8..fa37ed55908e8fc79f6fc8fc24d309d9ad45274a 100644 (file)
@@ -31,6 +31,18 @@ class CRM_Contribute_Form_ContributionView extends CRM_Core_Form {
 
     $values = CRM_Contribute_BAO_Contribution::getValuesWithMappings($params);
 
+    $force_create_template = CRM_Utils_Request::retrieve('force_create_template', 'Boolean', $this, FALSE, FALSE);
+    if ($force_create_template && !empty($values['contribution_recur_id']) && empty($values['is_template'])) {
+      // Create a template contribution.
+      $templateContributionId = CRM_Contribute_BAO_ContributionRecur::ensureTemplateContributionExists($values['contribution_recur_id']);
+      if (!empty($templateContributionId)) {
+        $id = $templateContributionId;
+        $params = ['id' => $id];
+        $values = CRM_Contribute_BAO_Contribution::getValuesWithMappings($params);
+      }
+    }
+    $this->assign('is_template', $values['is_template']);
+
     if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus() && $this->_action & CRM_Core_Action::VIEW) {
       $financialTypeID = CRM_Contribute_PseudoConstant::financialType($values['financial_type_id']);
       CRM_Financial_BAO_FinancialType::checkPermissionedLineItems($id, 'view');
@@ -172,16 +184,20 @@ class CRM_Contribute_Form_ContributionView extends CRM_Core_Form {
       $this->assign('totalTaxAmount', $values['tax_amount']);
     }
 
+    // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container
     $displayName = CRM_Contact_BAO_Contact::displayName($values['contact_id']);
     $this->assign('displayName', $displayName);
-
     // Check if this is default domain contact CRM-10482
     if (CRM_Contact_BAO_Contact::checkDomainContact($values['contact_id'])) {
       $displayName .= ' (' . ts('default organization') . ')';
     }
 
-    // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container
-    CRM_Utils_System::setTitle(ts('View Contribution from') . ' ' . $displayName);
+    if (empty($values['is_template'])) {
+      CRM_Utils_System::setTitle(ts('View Contribution from') . ' ' . $displayName);
+    }
+    else {
+      CRM_Utils_System::setTitle(ts('View Template Contribution from') . ' ' . $displayName);
+    }
 
     // add viewed contribution to recent items list
     $url = CRM_Utils_System::url('civicrm/contact/view/contribution',
index 2d75e2096c0c1576c76f712a405e88b88c015931..695ebfc6e7c7845ae57098f780400283c96e1e74 100644 (file)
@@ -57,6 +57,18 @@ class CRM_Contribute_Page_Tab extends CRM_Core_Page {
         'qs' => "reset=1&id=%%crid%%&cid=%%cid%%&context={$context}",
       ],
     ];
+
+    $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($recurID);
+    if (!empty($templateContribution['id']) && $paymentProcessorObj->supportsEditRecurringContribution()) {
+      // Use constant CRM_Core_Action::PREVIEW as there is no such thing as view template.
+      // And reusing view will mangle the actions.
+      $links[CRM_Core_Action::PREVIEW] = [
+        'name' => ts('View Template'),
+        'title' => ts('View Template Contribution'),
+        'url' => 'civicrm/contact/view/contribution',
+        'qs' => "reset=1&id={$templateContribution['id']}&cid=%%cid%%&action=view&context={$context}&force_create_template=1",
+      ];
+    }
     if (
       (CRM_Core_Permission::check('edit contributions') || $context !== 'contribution') &&
       ($paymentProcessorObj->supports('ChangeSubscriptionAmount')
index 185d5970d11bbfaed4654980030bc4474320b494..627e28b309726449d290135984cc02489b1e8eda 100644 (file)
         {* CRM-7362 --add campaign to contributions *}
         {include file="CRM/Campaign/Form/addCampaignToComponent.tpl" campaignTrClass="crm-contribution-form-block-campaign_id"}
 
-        {if !$contributionMode || $payNow}
+        {if (empty($is_template) && (!$contributionMode || $payNow))}
           <tr class="crm-contribution-form-block-contribution_status_id">
             <td class="label">{$form.contribution_status_id.label}</td>
             <td>{$form.contribution_status_id.html}
               </fieldset>
             </td>
           </tr>
+          {if empty($is_template)}
           <tr class="crm-contribution-form-block-receive_date">
             <td class="label">{$form.receive_date.label}</td>
             <td>{$form.receive_date.html}<br />
               <span class="description">{ts}The date this contribution was received.{/ts}</span>
             </td>
           </tr>
+          {/if}
         {/if}
         {if $form.revenue_recognition_date && !$payNow}
           <tr class="crm-contribution-form-block-revenue_recognition_date">
           </tr>
         {/if}
 
-        {if $email and $outBound_option != 2}
+        {if empty($is_template) and $email and $outBound_option != 2}
           <tr class="crm-contribution-form-block-is_email_receipt">
             <td class="label">{$form.is_email_receipt.label}</td>
             <td>{$form.is_email_receipt.html}&nbsp;
               <span class="description">{ts 1=$email}Automatically email a receipt for this payment to %1?{/ts}</span>
             </td>
           </tr>
-        {elseif $context eq 'standalone' and $outBound_option != 2 }
+        {elseif empty($is_template) and $context eq 'standalone' and $outBound_option != 2 }
           <tr id="email-receipt" style="display:none;" class="crm-contribution-form-block-is_email_receipt">
             <td class="label">{$form.is_email_receipt.label}</td>
             <td>{$form.is_email_receipt.html} <span class="description">{ts}Automatically email a receipt for this payment to {/ts}<span id="email-address"></span>?</span>
             </td>
           </tr>
         {/if}
+        {if empty($is_template)}
         <tr id="fromEmail" class="crm-contribution-form-block-receipt_date" style="display:none;">
           <td class="label">{$form.from_email_address.label}</td>
           <td>{$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Email.hlp" isAdmin=$isAdmin}</td>
         </tr>
+        {/if}
+        {if empty($is_template)}
         <tr id="receiptDate" class="crm-contribution-form-block-receipt_date">
           <td class="label">{$form.receipt_date.label}</td>
           <td>{$form.receipt_date.html}<br />
             <span class="description">{ts}Date that a receipt was sent to the contributor.{/ts}</span>
           </td>
         </tr>
+        {/if}
         {if $form.payment_processor_id}
           <tr class="crm-contribution-form-block-payment_processor_id"><td class="label nowrap">{$form.payment_processor_id.label}<span class="crm-marker"> * </span></td><td>{$form.payment_processor_id.html}</td></tr>
         {/if}
       </table>
 
-    {if !$contributionMode}
+    {if !$contributionMode && empty($is_template)}
       <fieldset class="payment-details_group">
         <legend>
           {ts}Payment Details{/ts}
index ea457bdf9f087dd132f88ad92d5cc014e900d8d0..5cc3f25ac67af01934b45261afb674a7bbbc6643 100644 (file)
@@ -35,7 +35,7 @@
     {include file="CRM/common/formButtons.tpl" location="top"}
     {assign var='pdfUrlParams' value="reset=1&id=$id&cid=$contact_id"}
     {assign var='emailUrlParams' value="reset=1&id=$id&cid=$contact_id&select=email"}
-    {if $invoicing}
+    {if $invoicing && empty($is_template)}
       <div class="css_right">
         <a class="button no-popup" href="{crmURL p='civicrm/contribute/invoice' q=$pdfUrlParams}">
           <i class="crm-i fa-print" aria-hidden="true"></i>
     <td class="label">{ts}Source{/ts}</td>
     <td>{$source}</td>
   </tr>
+  {if empty($is_template)}
   <tr>
     <td class="label">{ts}Received{/ts}</td>
     <td>{if $receive_date}{$receive_date|crmDate}{else}({ts}not available{/ts}){/if}</td>
   </tr>
+  {/if}
   {if $displayLineItems}
     <tr>
       <td class="label">{ts}Contribution Amount{/ts}</td>
       <td>{$to_financial_account}</td>
     </tr>
   {/if}
+  {if empty($is_template)}
   <tr>
     <td class="label">{ts}Contribution Status{/ts}</td>
     <td {if $contribution_status_id eq 3} class="font-red bold"{/if}>{$contribution_status}
       {if $contribution_status_id eq 2} {if $is_pay_later}: {ts}Pay Later{/ts} {else} : {ts}Incomplete Transaction{/ts} {/if}{/if}</td>
   </tr>
+  {/if}
 
   {if $cancel_date}
     <tr>
       <td>{$thankyou_date|crmDate}</td>
     </tr>
   {/if}
+  {if empty($is_template)}
   <tr>
     <td class="label">{ts}Payment Details{/ts}</td>
     <td>{include file="CRM/Contribute/Form/PaymentInfoBlock.tpl"}</td>
   </tr>
+  {/if}
   {if $addRecordPayment}
     <tr>
       <td class='label'>{ts}Payment Summary{/ts}</td>
index 108a540892ef1d6f3b1721155c9f12968b92de12..464bd3a81f2412adf1e42a604c5990d1ff1c790d 100644 (file)
@@ -214,6 +214,88 @@ class CRM_Contribute_BAO_ContributionRecurTest extends CiviUnitTestCase {
     $this->assertEquals($firstContrib['id'], $fetchedTemplate['id']);
   }
 
+  /**
+   * Check whether template contribution is created based on the first contribution.
+   *
+   * There are three contributions created. Each of them with a different value at a custom field.
+   * The first contribution created should be copied as a template contribution.
+   * The other two should not be used as a template.
+   *
+   * Then we delete the template contribution and make sure a new one exists.
+   * At that time the second contribution should be used a template as that is the most recent one (according to the date).
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   * @throws \CiviCRM_API3_Exception
+   * @throws \Civi\API\Exception\UnauthorizedException
+   */
+  public function testCreateTemplateContributionFromFirstContributionTest(): void {
+    $custom_group = $this->customGroupCreate(['extends' => 'Contribution', 'name' => 'template']);
+    $custom_field = $this->customFieldCreate(['custom_group_id' => $custom_group['id'], 'name' => 'field']);
+
+    $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params);
+    // Create a first test contrib
+    $date = new DateTime();
+    $firstContrib = $this->callAPISuccess('Contribution', 'create', [
+      'contribution_recur_id' => $contributionRecur['id'],
+      'total_amount' => '3.00',
+      'financial_type_id' => 1,
+      'payment_instrument_id' => 1,
+      'currency' => 'USD',
+      'contact_id' => $this->_params['contact_id'],
+      'contribution_status_id' => 1,
+      'receive_date' => $date->format('YmdHis'),
+      'custom_' . $custom_field['id'] => 'First Contribution',
+    ]);
+    $date->modify('+2 days');
+    $secondContrib = $this->callAPISuccess('Contribution', 'create', [
+      'contribution_recur_id' => $contributionRecur['id'],
+      'total_amount' => '3.00',
+      'financial_type_id' => 1,
+      'payment_instrument_id' => 1,
+      'currency' => 'USD',
+      'contact_id' => $this->_params['contact_id'],
+      'contribution_status_id' => 1,
+      'receive_date' => $date->format('YmdHis'),
+      'custom_' . $custom_field['id'] => 'Second and most recent Contribution',
+    ]);
+
+    $date->modify('-1 week');
+    $thirdContrib = $this->callAPISuccess('Contribution', 'create', [
+      'contribution_recur_id' => $contributionRecur['id'],
+      'total_amount' => '3.00',
+      'financial_type_id' => 1,
+      'payment_instrument_id' => 1,
+      'currency' => 'USD',
+      'contact_id' => $this->_params['contact_id'],
+      'contribution_status_id' => 1,
+      'receive_date' => $date->format('YmdHis'),
+      'custom_' . $custom_field['id'] => 'Third Contribution',
+    ]);
+
+    // Make sure a template contribution exists.
+    $templateContributionId = CRM_Contribute_BAO_ContributionRecur::ensureTemplateContributionExists($contributionRecur['id']);
+    $fetchedTemplate = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']);
+    $templateContribution = \Civi\Api4\Contribution::get(FALSE)
+      ->addSelect('*', 'custom.*')
+      ->addWhere('contribution_recur_id', '=', $contributionRecur['id'])
+      ->addWhere('is_template', '=', 1)
+      ->addWhere('is_test', '=', 0)
+      ->addOrderBy('id', 'DESC')
+      ->execute();
+
+    $this->assertNotEquals($firstContrib['id'], $fetchedTemplate['id']);
+    $this->assertNotEquals($secondContrib['id'], $fetchedTemplate['id']);
+    $this->assertNotEquals($thirdContrib['id'], $fetchedTemplate['id']);
+    $this->assertEquals($templateContributionId, $fetchedTemplate['id']);
+    $this->assertTrue($fetchedTemplate['is_template']);
+    $this->assertFalse($fetchedTemplate['is_test']);
+    $this->assertEquals(1, $templateContribution->count());
+    $templateContribution = $templateContribution->first();
+    $this->assertNotNull($templateContribution['template.field']);
+    $this->assertEquals('Second and most recent Contribution', $templateContribution['template.field']);
+  }
+
   /**
    * Test that is_template contribution is used where available
    *