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
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.
*
$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') {
$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'));
}
$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');
$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',
'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')
{* 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}
<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}
{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>
$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
*