From 53d5384d8b132979cc38ccd9a8587f5d62bcd677 Mon Sep 17 00:00:00 2001 From: olayiwola-compucorp Date: Wed, 14 Feb 2024 22:09:12 +0100 Subject: [PATCH 1/3] Pro rate payment lineitems amount with correct values --- CRM/Financial/BAO/Payment.php | 10 +++++ .../Contribute/Form/AdditionalPaymentTest.php | 38 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index d5ab9090ec07..db23a20ce253 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -609,6 +609,16 @@ protected static function getAmountOfLineItemPaid($lineItemID) { $paid += $entityFinancialTrxn['amount']; } } + + $lineItem = \Civi\Api4\LineItem::get(FALSE) + ->addWhere('id', '=', $lineItemID) + ->execute() + ->first(); + if (!empty($lineItem['tax_amount']) && $paid > 0) { + $total = floatval($lineItem['line_total']); + $tax = floatval($lineItem['tax_amount']); + $paid = ($total * $paid) / ($tax + $total); + } return (float) $paid; } diff --git a/tests/phpunit/CRM/Contribute/Form/AdditionalPaymentTest.php b/tests/phpunit/CRM/Contribute/Form/AdditionalPaymentTest.php index 6fb604415d8f..7e4fca0c095b 100644 --- a/tests/phpunit/CRM/Contribute/Form/AdditionalPaymentTest.php +++ b/tests/phpunit/CRM/Contribute/Form/AdditionalPaymentTest.php @@ -72,6 +72,13 @@ class CRM_Contribute_Form_AdditionalPaymentTest extends CiviUnitTestCase { */ protected $paymentProcessorID; + /** + * Financial Type ID. + * + * @var int + */ + protected $_financialTypeID = 1; + /** * Setup function. * @@ -86,7 +93,7 @@ public function setUp(): void { 'total_amount' => 100, 'currency' => 'USD', 'contact_id' => $this->_individualId, - 'financial_type_id' => 1, + 'financial_type_id' => $this->_financialTypeID, ]; $this->_processorParams = [ 'domain_id' => 1, @@ -342,6 +349,35 @@ public function testMultiplePaymentForPendingPayLaterContributionWithOneCreditCa $this->checkResults([50, 20, 20, 10], 4); } + /** + * Test that multiple payment has correct line item allocations. + * + * @throws \CRM_Core_Exception + */ + public function testMultiplePaymentForPendingPayLaterContributionHasCorrectionAllocation(): void { + $this->enableTaxAndInvoicing(); + $this->addTaxAccountToFinancialType($this->_financialTypeID, ['tax_rate' => 20]); + $this->createPendingOrder(['amount' => 120]); + + $this->submitPayment(60); + $this->submitPayment(30); + $result = $this->callAPISuccess('EntityFinancialTrxn', 'get', [ + 'version' => 4, + 'where' => [ + ['entity_table', '=', 'civicrm_contribution'], + ['entity_id', '=', $this->_contributionId], + ], + 'chain' => [ + 'line_items' => ['EntityFinancialTrxn', 'get', ['where' => [['financial_trxn_id', '=', '$financial_trxn_id'], ['id', '!=', '$id']]]], + ], + ])['values']; + + // Assert that the line items have the correct allocatioins from the paid amount + foreach ($result as $entityFnTrxn) { + $this->assertEquals(round($entityFnTrxn['amount'], 2), round(array_sum(array_column($entityFnTrxn['line_items'], 'amount')), 2)); + } + } + /** * Function to submit payments for contribution. * From 0de16605d901c605884eec47394e650b71944520 Mon Sep 17 00:00:00 2001 From: olayiwola-compucorp Date: Tue, 26 Mar 2024 07:05:30 +0100 Subject: [PATCH 2/3] Compute allocated tax amount in getPayableLineItems --- CRM/Financial/BAO/Payment.php | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index db23a20ce253..3d39dda7556d 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -43,7 +43,7 @@ public static function create(array $params): CRM_Financial_DAO_FinancialTrxn { $contribution = civicrm_api3('Contribution', 'getsingle', ['id' => $params['contribution_id']]); $contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution['contribution_status_id']); $isPaymentCompletesContribution = self::isPaymentCompletesContribution($params['contribution_id'], $params['total_amount'], $contributionStatus); - $lineItems = self::getPayableLineItems($params); + $lineItems = self::getPayableLineItems($params, $contribution); $whiteList = ['check_number', 'payment_processor_id', 'fee_amount', 'total_amount', 'contribution_id', 'net_amount', 'card_type_id', 'pan_truncation', 'trxn_result_code', 'payment_instrument_id', 'trxn_id', 'trxn_date', 'order_reference']; $paymentTrxnParams = array_intersect_key($params, array_fill_keys($whiteList, 1)); @@ -163,19 +163,13 @@ public static function create(array $params): CRM_Financial_DAO_FinancialTrxn { if ($financialItem['financial_item.entity_id'] === (int) $lineItem['id'] && in_array($financialItem['financial_item.financial_account_id'], $salesTaxFinancialAccount, TRUE) ) { - // If we find a "Sales Tax" lineitem we record a tax entry in entityFiancncialTrxn - // @todo - this is expected to be broken - it should be fixed to - // a) have the getPayableLineItems add the amount to allocate for tax - // b) call EntityFinancialTrxn directly - per above. - // - see https://github.com/civicrm/civicrm-core/pull/14763 - $entityParams = [ - 'contribution_total_amount' => $contribution['total_amount'], - 'trxn_total_amount' => $params['total_amount'], - 'trxn_id' => $trxn->id, - 'line_item_amount' => $financialItem['tax_amount'], + $eftParams = [ + 'entity_table' => 'civicrm_financial_item', + 'financial_trxn_id' => $trxn->id, + 'entity_id' => $financialItem['financial_item.id'], + 'amount' => $lineItem['tax_allocation'], ]; - $eftParams['entity_id'] = $financialItem['financial_item.id']; - CRM_Contribute_BAO_Contribution::createProportionalEntry($entityParams, $eftParams); + civicrm_api3('EntityFinancialTrxn', 'create', $eftParams); } } } @@ -537,11 +531,12 @@ private static function updateContributionStatus(int $contributionID, string $st * - if overrides have been passed in we use those amounts instead. * * @param $params + * @param $contribution * * @return array * @throws \CRM_Core_Exception */ - protected static function getPayableLineItems($params): array { + protected static function getPayableLineItems($params, $contribution): array { $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($params['contribution_id']); $lineItemOverrides = []; if (!empty($params['line_item'])) { @@ -577,8 +572,14 @@ protected static function getPayableLineItems($params): array { else { $lineItems[$lineItemID]['allocation'] = $lineItems[$lineItemID]['balance'] * $ratio; } + + if (!empty($lineItem['tax_amount'])) { + $lineItems[$lineItemID]['tax_allocation'] = $lineItem['tax_amount'] * ($params['total_amount'] / $contribution['total_amount']); + } + } } + return $lineItems; } From 1e2449ea0d18b08cb57543740484bb3002f93954 Mon Sep 17 00:00:00 2001 From: olayiwola-compucorp Date: Tue, 26 Mar 2024 11:06:01 +0100 Subject: [PATCH 3/3] Ensure payment amount are allocated to lineitems without roundoff error --- CRM/Financial/BAO/Payment.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index 3d39dda7556d..2570bcba9d75 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -547,6 +547,7 @@ protected static function getPayableLineItems($params, $contribution): array { } } $outstandingBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($params['contribution_id']); + $isPaymentCompletesContribution = self::isPaymentCompletesContribution($params['contribution_id'], $params['total_amount'], ''); if ($outstandingBalance !== 0.0) { $ratio = $params['total_amount'] / $outstandingBalance; } @@ -567,19 +568,32 @@ protected static function getPayableLineItems($params, $contribution): array { } else { if (empty($lineItems[$lineItemID]['balance']) && !empty($ratio) && $params['total_amount'] < 0) { - $lineItems[$lineItemID]['allocation'] = $lineItem['subTotal'] * $ratio; + $lineItems[$lineItemID]['allocation'] = round($lineItem['subTotal'] * $ratio, 2); + } + elseif ($isPaymentCompletesContribution) { + $lineItems[$lineItemID]['allocation'] = $lineItems[$lineItemID]['balance']; } else { - $lineItems[$lineItemID]['allocation'] = $lineItems[$lineItemID]['balance'] * $ratio; + $lineItems[$lineItemID]['allocation'] = round($lineItems[$lineItemID]['balance'] * $ratio, 2); } if (!empty($lineItem['tax_amount'])) { - $lineItems[$lineItemID]['tax_allocation'] = $lineItem['tax_amount'] * ($params['total_amount'] / $contribution['total_amount']); + $lineItems[$lineItemID]['tax_allocation'] = round($lineItem['tax_amount'] * ($params['total_amount'] / $contribution['total_amount']), 2); } } } + if (empty($lineItemOverrides) && !empty($ratio)) { + $totalTaxAllocation = array_sum(array_column($lineItems, 'tax_allocation')); + $totalAllocation = array_sum(array_column($lineItems, 'allocation')); + $total = $totalTaxAllocation + $totalAllocation; + $leftPayment = $params['total_amount'] - $total; + + // assign any leftover amount, to the last lineitem + $lineItems[$lineItemID]['allocation'] += $leftPayment; + } + return $lineItems; }