Skip to content

Commit

Permalink
Feature: Add query builder for Campaign Donations (#7544)
Browse files Browse the repository at this point in the history
  • Loading branch information
kjohnson authored Sep 18, 2024
1 parent 9be3b13 commit 05fe5be
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 0 deletions.
92 changes: 92 additions & 0 deletions src/Campaigns/CampaignDonationQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Give\Campaigns;

use Give\Campaigns\Models\Campaign;
use Give\Donations\ValueObjects\DonationMetaKeys;
use Give\Framework\QueryBuilder\JoinQueryBuilder;
use Give\Framework\QueryBuilder\QueryBuilder;

/**
* @unreleased
*/
class CampaignDonationQuery extends QueryBuilder
{
/**
* @unreleased
*/
public function __construct(Campaign $campaign)
{
$this->from('posts', 'donation');
$this->where('post_type', 'give_payment');

// Include only valid statuses
$this->whereIn('donation.post_status', ['publish', 'give_subscription']);

// Include only current payment "mode"
$this->joinDonationMeta(DonationMetaKeys::MODE, 'paymentMode');
$this->where('paymentMode.meta_value', give_is_test_mode() ? 'test' : 'live');

// Include only forms associated with the Campaign.
$this->joinDonationMeta(DonationMetaKeys::FORM_ID, 'formId');
$this->join(function (JoinQueryBuilder $builder) {
$builder->leftJoin('give_campaign_forms', 'campaign_forms')
->on('campaign_forms.form_id', 'formId.meta_value');
});
$this->where('campaign_forms.campaign_id', $campaign->id);
}

/**
* Returns a calculated sum of the intended amounts (without recovered fees) for the donations.
*
* @unreleased
*
* @return int|float
*/
public function sumIntendedAmount()
{
$this->joinDonationMeta(DonationMetaKeys::AMOUNT, 'amount');
$this->joinDonationMeta('_give_fee_donation_amount', 'intendedAmount');
return $this->sum(
/**
* The intended amount meta and the amount meta could either be 0 or NULL.
* So we need to use the NULLIF function to treat the 0 values as NULL.
* Then we coalesce the values to select the first non-NULL value.
* @link https://github.com/impress-org/givewp/pull/7411
*/
'COALESCE(NULLIF(intendedAmount.meta_value,0), NULLIF(amount.meta_value,0), 0)'
);
}

/**
* @unreleased
*/
public function countDonations(): int
{
return $this->count('donation.ID');
}

/**
* @unreleased
*/
public function countDonors(): int
{
$this->joinDonationMeta(DonationMetaKeys::DONOR_ID, 'donorId');
return $this->count('DISTINCT donorId.meta_value');
}

/**
* An opinionated join method for the donation meta table.
* @unreleased
*/
protected function joinDonationMeta($key, $alias): self
{
$this->join(function (JoinQueryBuilder $builder) use ($key, $alias) {
$builder
->leftJoin('give_donationmeta', $alias)
->on('donation.ID', $alias . '.donation_id')
->andOn($alias . '.meta_key', $key, true);
});
return $this;
}
}
119 changes: 119 additions & 0 deletions tests/Unit/Campaigns/CampaignDonationQueryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace Give\Tests\Unit\Campaigns;

use Give\Campaigns\CampaignDonationQuery;
use Give\Campaigns\Models\Campaign;
use Give\DonationForms\Models\DonationForm;
use Give\Donations\Models\Donation;
use Give\Donations\ValueObjects\DonationStatus;
use Give\Framework\Database\DB;
use Give\Framework\Support\ValueObjects\Money;
use Give\Tests\TestCase;
use Give\Tests\TestTraits\RefreshDatabase;

/**
* @unreleased
*/
final class CampaignDonationQueryTest extends TestCase
{
use RefreshDatabase;

/**
* @unreleased
*/
public function testCountCampaignDonations()
{
$campaign = Campaign::factory()->create();
$form = DonationForm::factory()->create();
Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1000, 'USD'),
]);
Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1000, 'USD'),
]);

$db = DB::table('give_campaign_forms');
$db->insert(['form_id' => $form->id, 'campaign_id' => $campaign->id]);

$query = new CampaignDonationQuery($campaign);

$this->assertEquals(2, $query->countDonations());
}

/**
* @unreleased
*/
public function testSumCampaignDonations()
{
$campaign = Campaign::factory()->create();
$form = DonationForm::factory()->create();
Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1000, 'USD'),
]);
Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1000, 'USD'),
]);

$db = DB::table('give_campaign_forms');
$db->insert(['form_id' => $form->id, 'campaign_id' => $campaign->id]);

$query = new CampaignDonationQuery($campaign);

$this->assertEquals(20.00, $query->sumIntendedAmount());
}

/**
* @unreleased
*/
public function testCountCampaignDonors()
{
$campaign = Campaign::factory()->create();
$form = DonationForm::factory()->create();
Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1000, 'USD'),
]);
Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1000, 'USD'),
]);

$db = DB::table('give_campaign_forms');
$db->insert(['form_id' => $form->id, 'campaign_id' => $campaign->id]);

$query = new CampaignDonationQuery($campaign);

$this->assertEquals(2, $query->countDonors());
}

public function testCoalesceIntendedAmountWithoutRecoveredFees()
{
$campaign = Campaign::factory()->create();
$form = DonationForm::factory()->create();

$db = DB::table('give_campaign_forms');
$db->insert(['form_id' => $form->id, 'campaign_id' => $campaign->id]);

$donation = Donation::factory()->create([
'formId' => $form->id,
'status' => DonationStatus::COMPLETE(),
'amount' => new Money(1070, 'USD'),
]);
give_update_meta($donation->id, '_give_fee_donation_amount', 10.00);

$query = new CampaignDonationQuery($campaign);

$this->assertEquals(10.00, $query->sumIntendedAmount());
}
}

0 comments on commit 05fe5be

Please sign in to comment.