Skip to content

Commit

Permalink
Merge pull request #20685 from jaapjansma/dev_financials_6_contactsum…
Browse files Browse the repository at this point in the history
…mary_3

 dev/financial#6: Button for create/view template contribution on contact summary
  • Loading branch information
mattwire authored Jul 26, 2021
2 parents 31efa0d + 7d238e3 commit 9ab92a3
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 8 deletions.
83 changes: 83 additions & 0 deletions CRM/Contribute/BAO/ContributionRecur.php
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,89 @@ public static function supportsFinancialTypeChange($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.
*
Expand Down
7 changes: 7 additions & 0 deletions CRM/Contribute/Form/Contribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ public function preProcess() {
$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') {
Expand All @@ -299,6 +303,9 @@ public function preProcess() {
$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'));
}
Expand Down
22 changes: 19 additions & 3 deletions CRM/Contribute/Form/ContributionView.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ public function preProcess() {

$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');
Expand Down Expand Up @@ -172,16 +184,20 @@ public function preProcess() {
$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',
Expand Down
12 changes: 12 additions & 0 deletions CRM/Contribute/Page/Tab.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ public static function recurLinks(int $recurID, $context = 'contribution') {
'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')
Expand Down
14 changes: 10 additions & 4 deletions templates/CRM/Contribute/Form/Contribution.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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))}
<tr class="crm-contribution-form-block-contribution_status_id">
<td class="label">{$form.contribution_status_id.label}</td>
<td>{$form.contribution_status_id.html}
Expand Down Expand Up @@ -188,12 +188,14 @@
</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">
Expand All @@ -202,36 +204,40 @@
</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}
Expand Down
8 changes: 7 additions & 1 deletion templates/CRM/Contribute/Form/ContributionView.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -69,10 +69,12 @@
<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>
Expand Down Expand Up @@ -136,11 +138,13 @@
<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>
Expand Down Expand Up @@ -227,10 +231,12 @@
<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>
Expand Down
82 changes: 82 additions & 0 deletions tests/phpunit/CRM/Contribute/BAO/ContributionRecurTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,88 @@ public function testGetTemplateContributionMatchTest(): void {
$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
*
Expand Down

0 comments on commit 9ab92a3

Please sign in to comment.