Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move two more functions to financialProcessor #21927

Merged
merged 1 commit into from
Dec 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 2 additions & 161 deletions CRM/Contribute/BAO/Contribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -971,108 +971,6 @@ protected static function getRelatedMemberships(int $contributionID): array {
])['values'];
}

/**
* Do any accounting updates required as a result of a contribution status change.
*
* Currently we have a bit of a roundabout where adding a payment results in this being called &
* this may attempt to add a payment. We need to resolve that....
*
* The 'right' way to add payments or refunds is through the Payment.create api. That api
* then updates the contribution but this process should not also record another financial trxn.
* Currently we have weak detection fot that scenario & where it is detected the first returned
* value is FALSE - meaning 'do not continue'.
*
* We should also look at the fact that the calling function - updateFinancialAccounts
* bunches together some disparate processes rather than having separate appropriate
* functions.
*
* @param array $params
*
* @return bool
* Return indicates whether the updateFinancialAccounts function should continue.
*/
private static function updateFinancialAccountsOnContributionStatusChange(&$params) {
$previousContributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($params['prevContribution']->contribution_status_id, 'name');
$currentContributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id);

if ((($previousContributionStatus === 'Partially paid' && $currentContributionStatus === 'Completed')
|| ($previousContributionStatus === 'Pending refund' && $currentContributionStatus === 'Completed')
// This concept of pay_later as different to any other sort of pending is deprecated & it's unclear
// why it is here or where it is handled instead.
|| ($previousContributionStatus === 'Pending' && $params['prevContribution']->is_pay_later == TRUE
&& $currentContributionStatus === 'Partially paid'))
) {
return FALSE;
}

if (CRM_Contribute_BAO_FinancialProcessor::isContributionUpdateARefund($params['prevContribution']->contribution_status_id, $params['contribution']->contribution_status_id)) {
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['trxnParams']['total_amount'] = -$params['total_amount'];
}
elseif (($previousContributionStatus === 'Pending'
&& $params['prevContribution']->is_pay_later) || $previousContributionStatus === 'In Progress'
) {
$financialTypeID = !empty($params['financial_type_id']) ? $params['financial_type_id'] : $params['prevContribution']->financial_type_id;
$arAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeID, 'Accounts Receivable Account is');

if ($currentContributionStatus === 'Cancelled') {
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['trxnParams']['to_financial_account_id'] = $arAccountId;
$params['trxnParams']['total_amount'] = -$params['total_amount'];
}
else {
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['trxnParams']['from_financial_account_id'] = $arAccountId;
}
}

if (($previousContributionStatus === 'Pending'
|| $previousContributionStatus === 'In Progress')
&& ($currentContributionStatus === 'Completed')
) {
if (empty($params['line_item'])) {
//CRM-15296
//@todo - check with Joe regarding this situation - payment processors create pending transactions with no line items
// when creating recurring membership payment - there are 2 lines to comment out in contributionPageTest if fixed
// & this can be removed
return FALSE;
}
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
// This is an update so original currency if none passed in.
$params['trxnParams']['currency'] = CRM_Utils_Array::value('currency', $params, $params['prevContribution']->currency);

$transactionIDs[] = CRM_Contribute_BAO_FinancialProcessor::recordAlwaysAccountsReceivable($params['trxnParams'], $params);
$trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']);
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['entity_id'] = $transactionIDs[] = $trxn->id;

$sql = "SELECT id, amount FROM civicrm_financial_item WHERE entity_id = %1 and entity_table = 'civicrm_line_item'";

$entityParams = [
'entity_table' => 'civicrm_financial_item',
];
foreach ($params['line_item'] as $fieldId => $fields) {
foreach ($fields as $fieldValueId => $lineItemDetails) {
self::updateFinancialItemForLineItemToPaid($lineItemDetails['id']);
$fparams = [
1 => [$lineItemDetails['id'], 'Integer'],
];
$financialItem = CRM_Core_DAO::executeQuery($sql, $fparams);
while ($financialItem->fetch()) {
$entityParams['entity_id'] = $financialItem->id;
$entityParams['amount'] = $financialItem->amount;
foreach ($transactionIDs as $tID) {
$entityParams['financial_trxn_id'] = $tID;
CRM_Financial_BAO_FinancialItem::createEntityTrxn($entityParams);
}
}
}
}
return FALSE;
}
return TRUE;
}

/**
* It is possible to override the membership id that is updated from the payment processor.
*
Expand All @@ -1094,23 +992,6 @@ protected static function handleMembershipIDOverride($contributionID, $input) {
}
}

/**
* Update all financial items related to the line item tto have a status of paid.
*
* @param int $lineItemID
*/
private static function updateFinancialItemForLineItemToPaid($lineItemID) {
$fparams = [
1 => [
CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_FinancialItem', 'status_id', 'Paid'),
'Integer',
],
2 => [$lineItemID, 'Integer'],
];
$query = "UPDATE civicrm_financial_item SET status_id = %1 WHERE entity_id = %2 and entity_table = 'civicrm_line_item'";
CRM_Core_DAO::executeQuery($query, $fparams);
}

/**
* Get transaction information about the contribution.
*
Expand Down Expand Up @@ -3431,7 +3312,7 @@ public static function recordFinancialAccounts(&$params) {
$params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id
) {
//Update Financial Records
$callUpdateFinancialAccounts = self::updateFinancialAccountsOnContributionStatusChange($params);
$callUpdateFinancialAccounts = CRM_Contribute_BAO_FinancialProcessor::updateFinancialAccountsOnContributionStatusChange($params);
if ($callUpdateFinancialAccounts) {
CRM_Contribute_BAO_FinancialProcessor::updateFinancialAccounts($params, 'changedStatus');
CRM_Core_BAO_FinancialTrxn::createDeferredTrxn(CRM_Utils_Array::value('line_item', $params), $params['contribution'], TRUE, 'changedStatus');
Expand All @@ -3448,7 +3329,7 @@ public static function recordFinancialAccounts(&$params) {
$params['trxnParams']['payment_instrument_id'] = $params['contribution']->payment_instrument_id;
$params['trxnParams']['check_number'] = $params['check_number'] ?? NULL;

if (self::isPaymentInstrumentChange($params, $pendingStatus)) {
if (CRM_Contribute_BAO_FinancialProcessor::isPaymentInstrumentChange($params, $pendingStatus)) {
$updated = CRM_Core_BAO_FinancialTrxn::updateFinancialAccountsOnPaymentInstrumentChange($params);
}

Expand Down Expand Up @@ -4312,46 +4193,6 @@ private static function getOriginalContribution($contributionID) {
return self::getValues(['id' => $contributionID]);
}

/**
* Does this transaction reflect a payment instrument change.
*
* @param array $params
* @param array $pendingStatuses
*
* @return bool
*/
protected static function isPaymentInstrumentChange(&$params, $pendingStatuses) {
$contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id);

if (array_key_exists('payment_instrument_id', $params)) {
if (CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id) &&
!CRM_Utils_System::isNull($params['payment_instrument_id'])
) {
//check if status is changed from Pending to Completed
// do not update payment instrument changes for Pending to Completed
if (!($contributionStatus == 'Completed' &&
in_array($params['prevContribution']->contribution_status_id, $pendingStatuses))
) {
return TRUE;
}
}
elseif ((!CRM_Utils_System::isNull($params['payment_instrument_id']) &&
!CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id)) &&
$params['payment_instrument_id'] != $params['prevContribution']->payment_instrument_id
) {
return TRUE;
}
elseif (!CRM_Utils_System::isNull($params['contribution']->check_number) &&
$params['contribution']->check_number != $params['prevContribution']->check_number
) {
// another special case when check number is changed, create new financial records
// create financial trxn with negative amount
return TRUE;
}
}
return FALSE;
}

/**
* Update the memberships associated with a contribution if it has been completed.
*
Expand Down
159 changes: 159 additions & 0 deletions CRM/Contribute/BAO/FinancialProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,125 @@ public static function isContributionUpdateARefund($previousContributionStatusID
return CRM_Contribute_BAO_Contribution::isContributionStatusNegative($currentContributionStatusID);
}

/**
* Do any accounting updates required as a result of a contribution status change.
*
* Currently we have a bit of a roundabout where adding a payment results in this being called &
* this may attempt to add a payment. We need to resolve that....
*
* The 'right' way to add payments or refunds is through the Payment.create api. That api
* then updates the contribution but this process should not also record another financial trxn.
* Currently we have weak detection fot that scenario & where it is detected the first returned
* value is FALSE - meaning 'do not continue'.
*
* We should also look at the fact that the calling function - updateFinancialAccounts
* bunches together some disparate processes rather than having separate appropriate
* functions.
*
* @param array $params
*
* @return bool
* Return indicates whether the updateFinancialAccounts function should continue.
*/
public static function updateFinancialAccountsOnContributionStatusChange(&$params) {
$previousContributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($params['prevContribution']->contribution_status_id, 'name');
$currentContributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id);

if ((($previousContributionStatus === 'Partially paid' && $currentContributionStatus === 'Completed')
|| ($previousContributionStatus === 'Pending refund' && $currentContributionStatus === 'Completed')
// This concept of pay_later as different to any other sort of pending is deprecated & it's unclear
// why it is here or where it is handled instead.
|| ($previousContributionStatus === 'Pending' && $params['prevContribution']->is_pay_later == TRUE
&& $currentContributionStatus === 'Partially paid'))
) {
return FALSE;
}

if (CRM_Contribute_BAO_FinancialProcessor::isContributionUpdateARefund($params['prevContribution']->contribution_status_id, $params['contribution']->contribution_status_id)) {
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['trxnParams']['total_amount'] = -$params['total_amount'];
}
elseif (($previousContributionStatus === 'Pending'
&& $params['prevContribution']->is_pay_later) || $previousContributionStatus === 'In Progress'
) {
$financialTypeID = !empty($params['financial_type_id']) ? $params['financial_type_id'] : $params['prevContribution']->financial_type_id;
$arAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeID, 'Accounts Receivable Account is');

if ($currentContributionStatus === 'Cancelled') {
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['trxnParams']['to_financial_account_id'] = $arAccountId;
$params['trxnParams']['total_amount'] = -$params['total_amount'];
}
else {
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['trxnParams']['from_financial_account_id'] = $arAccountId;
}
}

if (($previousContributionStatus === 'Pending'
|| $previousContributionStatus === 'In Progress')
&& ($currentContributionStatus === 'Completed')
) {
if (empty($params['line_item'])) {
//CRM-15296
//@todo - check with Joe regarding this situation - payment processors create pending transactions with no line items
// when creating recurring membership payment - there are 2 lines to comment out in contributionPageTest if fixed
// & this can be removed
return FALSE;
}
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
// This is an update so original currency if none passed in.
$params['trxnParams']['currency'] = CRM_Utils_Array::value('currency', $params, $params['prevContribution']->currency);

$transactionIDs[] = CRM_Contribute_BAO_FinancialProcessor::recordAlwaysAccountsReceivable($params['trxnParams'], $params);
$trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']);
// @todo we should stop passing $params by reference - splitting this out would be a step towards that.
$params['entity_id'] = $transactionIDs[] = $trxn->id;

$sql = "SELECT id, amount FROM civicrm_financial_item WHERE entity_id = %1 and entity_table = 'civicrm_line_item'";

$entityParams = [
'entity_table' => 'civicrm_financial_item',
];
foreach ($params['line_item'] as $fieldId => $fields) {
foreach ($fields as $fieldValueId => $lineItemDetails) {
self::updateFinancialItemForLineItemToPaid($lineItemDetails['id']);
$fparams = [
1 => [$lineItemDetails['id'], 'Integer'],
];
$financialItem = CRM_Core_DAO::executeQuery($sql, $fparams);
while ($financialItem->fetch()) {
$entityParams['entity_id'] = $financialItem->id;
$entityParams['amount'] = $financialItem->amount;
foreach ($transactionIDs as $tID) {
$entityParams['financial_trxn_id'] = $tID;
CRM_Financial_BAO_FinancialItem::createEntityTrxn($entityParams);
}
}
}
}
return FALSE;
}
return TRUE;
}

/**
* Update all financial items related to the line item tto have a status of paid.
*
* @param int $lineItemID
*/
private static function updateFinancialItemForLineItemToPaid($lineItemID) {
$fparams = [
1 => [
CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_FinancialItem', 'status_id', 'Paid'),
'Integer',
],
2 => [$lineItemID, 'Integer'],
];
$query = "UPDATE civicrm_financial_item SET status_id = %1 WHERE entity_id = %2 and entity_table = 'civicrm_line_item'";
CRM_Core_DAO::executeQuery($query, $fparams);
}

/**
* Create Accounts Receivable financial trxn entry for Completed Contribution.
*
Expand Down Expand Up @@ -279,4 +398,44 @@ public static function recordAlwaysAccountsReceivable(&$trxnParams, $contributio
return $trxn->id;
}

/**
* Does this transaction reflect a payment instrument change.
*
* @param array $params
* @param array $pendingStatuses
*
* @return bool
*/
public static function isPaymentInstrumentChange(&$params, $pendingStatuses) {
$contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id);

if (array_key_exists('payment_instrument_id', $params)) {
if (CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id) &&
!CRM_Utils_System::isNull($params['payment_instrument_id'])
) {
//check if status is changed from Pending to Completed
// do not update payment instrument changes for Pending to Completed
if (!($contributionStatus == 'Completed' &&
in_array($params['prevContribution']->contribution_status_id, $pendingStatuses))
) {
return TRUE;
}
}
elseif ((!CRM_Utils_System::isNull($params['payment_instrument_id']) &&
!CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id)) &&
$params['payment_instrument_id'] != $params['prevContribution']->payment_instrument_id
) {
return TRUE;
}
elseif (!CRM_Utils_System::isNull($params['contribution']->check_number) &&
$params['contribution']->check_number != $params['prevContribution']->check_number
) {
// another special case when check number is changed, create new financial records
// create financial trxn with negative amount
return TRUE;
}
}
return FALSE;
}

}