From e26d0d27a9316298ba30ee8cb91fc39b44ba1ce5 Mon Sep 17 00:00:00 2001 From: eileen Date: Wed, 30 Dec 2020 21:37:41 +1300 Subject: [PATCH] dev/core#2206 Move cancel pledge into Contribution.create and fix Per https://lab.civicrm.org/dev/core/-/issues/2206 I think updating pledge payments is a data issue rather than a business process issue. Notably you can't record a new contribution against the pledge unless the existing one is removed This is the last thing that needs to be done before we can unhide contributioncancelactions --- CRM/Contribute/BAO/Contribution.php | 43 +++++++++++++++++++ .../contributioncancelactions.php | 24 ----------- .../tests/phpunit/CancelTest.php | 24 ----------- .../CRM/Contribute/BAO/ContributionTest.php | 28 ++++++++++++ 4 files changed, 71 insertions(+), 48 deletions(-) diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index e27dba77b2..03df483b95 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -13,6 +13,7 @@ use Civi\Api4\Activity; use Civi\Api4\ContributionPage; use Civi\Api4\ContributionRecur; use Civi\Api4\PaymentProcessor; +use Civi\Api4\PledgePayment; /** * @@ -501,6 +502,10 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { CRM_Contribute_BAO_ContributionSoft::processSoftContribution($params, $contribution); + if (!empty($params['id']) && !empty($params['contribution_status_id']) + ) { + self::disconnectPledgePaymentsIfCancelled((int) $params['id'], CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution_status_id'])); + } $transaction->commit(); if (empty($contribution->contact_id)) { @@ -1281,6 +1286,44 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { return TRUE; } + /** + * Disconnect pledge payments from cancelled or failed contributions. + * + * If the contribution has been cancelled or has failed check to + * see if it is linked to a pledge and unlink it. + * + * @param int $pledgePaymentID + * @param string $contributionStatus + * + * @throws \API_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + protected static function disconnectPledgePaymentsIfCancelled(int $pledgePaymentID, $contributionStatus): void { + if (!in_array($contributionStatus, ['Failed', 'Cancelled'], TRUE)) { + return; + } + // Check first since just doing an update could be locking under load. + $pledgePayment = PledgePayment::get(FALSE) + ->addWhere('contribution_id', '=', $pledgePaymentID) + ->setSelect(['id', 'pledge_id', 'scheduled_date', 'scheduled_amount']) + ->execute() + ->first(); + if (!empty($pledgePayment)) { + PledgePayment::update(FALSE)->setValues([ + 'contribution_id' => NULL, + 'actual_amount' => NULL, + 'status_id:name' => 'Pending', + // We need to set these fields for now because the PledgePayment::create + // function doesn't handled updates well at the moment. Test cover + // in testCancelOrderWithPledge. + 'scheduled_date' => $pledgePayment['scheduled_date'], + 'installment_amount' => $pledgePayment['scheduled_amount'], + 'installments' => 1, + 'pledge_id' => $pledgePayment['pledge_id'], + ])->addWhere('id', '=', $pledgePayment['id'])->execute(); + } + } + /** * @inheritDoc */ diff --git a/ext/contributioncancelactions/contributioncancelactions.php b/ext/contributioncancelactions/contributioncancelactions.php index d72a390ef7..26fea16341 100644 --- a/ext/contributioncancelactions/contributioncancelactions.php +++ b/ext/contributioncancelactions/contributioncancelactions.php @@ -29,34 +29,10 @@ function contributioncancelactions_civicrm_post($op, $objectName, $objectId, $ob )) { contributioncancelactions_cancel_related_pending_memberships((int) $objectId); contributioncancelactions_cancel_related_pending_participant_records((int) $objectId); - contributioncancelactions_update_related_pledge((int) $objectId, (int) $objectRef->contribution_status_id); } } } -/** - * Update any related pledge when a contribution is cancelled. - * - * This updates the status of the pledge and amount paid. - * - * The functionality should probably be give more thought in that it currently - * does not un-assign the contribution id from the pledge payment. However, - * at time of writing the goal is to move rather than fix functionality. - * - * @param int $contributionID - * @param int $contributionStatusID - * - * @throws CiviCRM_API3_Exception - */ -function contributioncancelactions_update_related_pledge(int $contributionID, int $contributionStatusID) { - $pledgePayments = civicrm_api3('PledgePayment', 'get', ['contribution_id' => $contributionID])['values']; - if (!empty($pledgePayments)) { - $pledgePaymentIDS = array_keys($pledgePayments); - $pledgePayment = reset($pledgePayments); - CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgePayment['pledge_id'], $pledgePaymentIDS, $contributionStatusID); - } -} - /** * Find and cancel any pending participant records. * diff --git a/ext/contributioncancelactions/tests/phpunit/CancelTest.php b/ext/contributioncancelactions/tests/phpunit/CancelTest.php index 567da3f45c..eb475e2a76 100644 --- a/ext/contributioncancelactions/tests/phpunit/CancelTest.php +++ b/ext/contributioncancelactions/tests/phpunit/CancelTest.php @@ -256,30 +256,6 @@ class CancelTest extends TestCase implements HeadlessInterface, HookInterface, T $this->callAPISuccessGetCount('Participant', ['status_id' => 'Cancelled'], 1); } - /** - * Test cancel order api when a pledge is linked. - * - * The pledge status should be updated. I believe the contribution should also be unlinked but - * the goal at this point is no change. - * - * @throws CRM_Core_Exception - * @throws API_Exception - */ - public function testCancelOrderWithPledge(): void { - $this->createContact(); - $pledgeID = (int) $this->callAPISuccess('Pledge', 'create', ['contact_id' => $this->ids['contact'][0], 'amount' => 4, 'installments' => 2, 'frequency_unit' => 'month', 'original_installment_amount' => 2, 'create_date' => 'now', 'financial_type_id' => 'Donation', 'start_date' => '+5 days'])['id']; - $orderID = (int) $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids['contact'][0], 'total_amount' => 2, 'financial_type_id' => 'Donation', 'api.Payment.create' => ['total_amount' => 2]])['id']; - $pledgePayments = $this->callAPISuccess('PledgePayment', 'get')['values']; - $this->callAPISuccess('PledgePayment', 'create', ['id' => key($pledgePayments), 'pledge_id' => $pledgeID, 'contribution_id' => $orderID, 'status_id' => 'Completed', 'actual_amount' => 2]); - $beforePledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]); - $this->assertEquals(2, $beforePledge['pledge_total_paid']); - $this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]); - - $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']); - $afterPledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]); - $this->assertEquals('', $afterPledge['pledge_total_paid']); - } - /** * Test cancelling a contribution with a membership on the contribution edit * form. diff --git a/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php b/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php index 20de47222c..83bbddffaf 100644 --- a/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php +++ b/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php @@ -9,6 +9,7 @@ +--------------------------------------------------------------------+ */ use Civi\Api4\Activity; +use Civi\Api4\PledgePayment; /** * Class CRM_Contribute_BAO_ContributionTest @@ -1660,4 +1661,31 @@ WHERE eft.entity_id = %1 AND ft.to_financial_account_id <> %2"; $this->assertDBNotNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail with the permission to allow update receipt date must be set'); } + /** + * Test cancel order api when a pledge is linked. + * + * The pledge status should be updated. I believe the contribution should + * also be unlinked but the goal at this point is no change. + * + * @throws CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + * @throws \API_Exception + */ + public function testCancelOrderWithPledge(): void { + $this->ids['contact'][0] = $this->individualCreate(); + $pledgeID = (int) $this->callAPISuccess('Pledge', 'create', ['contact_id' => $this->ids['contact'][0], 'amount' => 4, 'installments' => 2, 'frequency_unit' => 'month', 'original_installment_amount' => 2, 'create_date' => 'now', 'financial_type_id' => 'Donation', 'start_date' => '+5 days'])['id']; + $orderID = (int) $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids['contact'][0], 'total_amount' => 2, 'financial_type_id' => 'Donation', 'api.Payment.create' => ['total_amount' => 2]])['id']; + $pledgePayments = $this->callAPISuccess('PledgePayment', 'get')['values']; + $this->callAPISuccess('PledgePayment', 'create', ['id' => key($pledgePayments), 'pledge_id' => $pledgeID, 'contribution_id' => $orderID, 'status_id' => 'Completed', 'actual_amount' => 2]); + $beforePledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]); + $this->assertEquals(2, $beforePledge['pledge_total_paid']); + $this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]); + + $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']); + $afterPledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]); + $this->assertEquals('', $afterPledge['pledge_total_paid']); + $payments = PledgePayment::get(FALSE)->addWhere('contribution_id', 'IS NOT NULL')->execute(); + $this->assertCount(0, $payments); + } + } -- 2.25.1