From 7d238e3572ee3d61e988dd7dd32545e9820e0753 Mon Sep 17 00:00:00 2001 From: Jaap Jansma Date: Wed, 30 Jun 2021 13:04:08 +0200 Subject: [PATCH] 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 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 | 83 +++++++++++++++++++ CRM/Contribute/Form/Contribution.php | 7 ++ CRM/Contribute/Form/ContributionView.php | 22 ++++- CRM/Contribute/Page/Tab.php | 12 +++ .../CRM/Contribute/Form/Contribution.tpl | 14 +++- .../CRM/Contribute/Form/ContributionView.tpl | 8 +- .../Contribute/BAO/ContributionRecurTest.php | 82 ++++++++++++++++++ 7 files changed, 220 insertions(+), 8 deletions(-) diff --git a/CRM/Contribute/BAO/ContributionRecur.php b/CRM/Contribute/BAO/ContributionRecur.php index 0fa3623657..b468a8fa37 100644 --- a/CRM/Contribute/BAO/ContributionRecur.php +++ b/CRM/Contribute/BAO/ContributionRecur.php @@ -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. * diff --git a/CRM/Contribute/Form/Contribution.php b/CRM/Contribute/Form/Contribution.php index 43133c429a..ae1f4da7e2 100644 --- a/CRM/Contribute/Form/Contribution.php +++ b/CRM/Contribute/Form/Contribution.php @@ -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')); } diff --git a/CRM/Contribute/Form/ContributionView.php b/CRM/Contribute/Form/ContributionView.php index f0cbfbe943..fa37ed5590 100644 --- a/CRM/Contribute/Form/ContributionView.php +++ b/CRM/Contribute/Form/ContributionView.php @@ -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', diff --git a/CRM/Contribute/Page/Tab.php b/CRM/Contribute/Page/Tab.php index 2d75e2096c..695ebfc6e7 100644 --- a/CRM/Contribute/Page/Tab.php +++ b/CRM/Contribute/Page/Tab.php @@ -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') diff --git a/templates/CRM/Contribute/Form/Contribution.tpl b/templates/CRM/Contribute/Form/Contribution.tpl index 185d5970d1..627e28b309 100644 --- a/templates/CRM/Contribute/Form/Contribution.tpl +++ b/templates/CRM/Contribute/Form/Contribution.tpl @@ -141,7 +141,7 @@ {* 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))} {$form.contribution_status_id.label} {$form.contribution_status_id.html} @@ -188,12 +188,14 @@ + {if empty($is_template)} {$form.receive_date.label} {$form.receive_date.html}
{ts}The date this contribution was received.{/ts} + {/if} {/if} {if $form.revenue_recognition_date && !$payNow} @@ -202,36 +204,40 @@ {/if} - {if $email and $outBound_option != 2} + {if empty($is_template) and $email and $outBound_option != 2} {$form.is_email_receipt.label} {$form.is_email_receipt.html}  {ts 1=$email}Automatically email a receipt for this payment to %1?{/ts} - {elseif $context eq 'standalone' and $outBound_option != 2 } + {elseif empty($is_template) and $context eq 'standalone' and $outBound_option != 2 } {$form.is_email_receipt.label} {$form.is_email_receipt.html} {ts}Automatically email a receipt for this payment to {/ts}? {/if} + {if empty($is_template)} {$form.from_email_address.label} {$form.from_email_address.html} {help id="id-from_email" file="CRM/Contact/Form/Task/Email.hlp" isAdmin=$isAdmin} + {/if} + {if empty($is_template)} {$form.receipt_date.label} {$form.receipt_date.html}
{ts}Date that a receipt was sent to the contributor.{/ts} + {/if} {if $form.payment_processor_id} {$form.payment_processor_id.label} * {$form.payment_processor_id.html} {/if} - {if !$contributionMode} + {if !$contributionMode && empty($is_template)}
{ts}Payment Details{/ts} diff --git a/templates/CRM/Contribute/Form/ContributionView.tpl b/templates/CRM/Contribute/Form/ContributionView.tpl index ea457bdf9f..5cc3f25ac6 100644 --- a/templates/CRM/Contribute/Form/ContributionView.tpl +++ b/templates/CRM/Contribute/Form/ContributionView.tpl @@ -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)}
@@ -69,10 +69,12 @@ {ts}Source{/ts} {$source} + {if empty($is_template)} {ts}Received{/ts} {if $receive_date}{$receive_date|crmDate}{else}({ts}not available{/ts}){/if} + {/if} {if $displayLineItems} {ts}Contribution Amount{/ts} @@ -136,11 +138,13 @@ {$to_financial_account} {/if} + {if empty($is_template)} {ts}Contribution Status{/ts} {$contribution_status} {if $contribution_status_id eq 2} {if $is_pay_later}: {ts}Pay Later{/ts} {else} : {ts}Incomplete Transaction{/ts} {/if}{/if} + {/if} {if $cancel_date} @@ -227,10 +231,12 @@ {$thankyou_date|crmDate} {/if} + {if empty($is_template)} {ts}Payment Details{/ts} {include file="CRM/Contribute/Form/PaymentInfoBlock.tpl"} + {/if} {if $addRecordPayment} {ts}Payment Summary{/ts} diff --git a/tests/phpunit/CRM/Contribute/BAO/ContributionRecurTest.php b/tests/phpunit/CRM/Contribute/BAO/ContributionRecurTest.php index 108a540892..464bd3a81f 100644 --- a/tests/phpunit/CRM/Contribute/BAO/ContributionRecurTest.php +++ b/tests/phpunit/CRM/Contribute/BAO/ContributionRecurTest.php @@ -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 * -- 2.25.1