feat: ImpactReportHelper #16
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace Moko\Plugin\System\MokoSuiteNpo\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Impact reporting — donor impact statements, program metrics, annual report data.
|
||||
*/
|
||||
class ImpactReportHelper
|
||||
{
|
||||
/**
|
||||
* Generate annual impact summary for a fiscal year.
|
||||
*/
|
||||
public static function getAnnualSummary(int $year): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$from = $year . '-01-01';
|
||||
$to = $year . '-12-31';
|
||||
|
||||
// Fundraising
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*) AS donation_count, COALESCE(SUM(amount), 0) AS total_raised, COUNT(DISTINCT donor_id) AS unique_donors')
|
||||
->from('#__mokosuitenpo_donations')
|
||||
->where('donation_date BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to)));
|
||||
$fundraising = $db->loadObject();
|
||||
|
||||
// Campaigns
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*) AS campaigns_run')
|
||||
->select('SUM(CASE WHEN COALESCE((SELECT SUM(d.amount) FROM #__mokosuitenpo_donations d WHERE d.campaign_id = c.id), 0) >= c.goal THEN 1 ELSE 0 END) AS goals_met')
|
||||
->from($db->quoteName('#__mokosuitenpo_campaigns', 'c'))
|
||||
->where('c.start_date BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to)));
|
||||
$campaigns = $db->loadObject();
|
||||
|
||||
// Volunteer hours
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(DISTINCT volunteer_id) AS active_volunteers, COALESCE(SUM(hours), 0) AS total_hours')
|
||||
->from('#__mokosuitenpo_volunteer_hours')
|
||||
->where('date BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to)));
|
||||
$volunteers = $db->loadObject();
|
||||
|
||||
// Events
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*) AS events_held')
|
||||
->select('(SELECT COUNT(*) FROM #__mokosuitenpo_event_registrations er JOIN #__mokosuitenpo_events e ON e.id = er.event_id WHERE e.event_date BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to) . ') AS total_attendees')
|
||||
->from('#__mokosuitenpo_events')
|
||||
->where('event_date BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to)));
|
||||
$events = $db->loadObject();
|
||||
|
||||
// Grants
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*) AS grants_received, COALESCE(SUM(amount), 0) AS grant_total')
|
||||
->from('#__mokosuitenpo_grants')
|
||||
->where($db->quoteName('status') . ' IN (' . $db->quote('awarded') . ',' . $db->quote('active') . ')')
|
||||
->where('YEAR(awarded_date) = ' . (int) $year));
|
||||
$grants = $db->loadObject();
|
||||
|
||||
// Donor retention
|
||||
$priorYearDonors = 0;
|
||||
$retainedDonors = 0;
|
||||
try {
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(DISTINCT donor_id)')
|
||||
->from('#__mokosuitenpo_donations')
|
||||
->where('YEAR(donation_date) = ' . ($year - 1)));
|
||||
$priorYearDonors = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(DISTINCT d1.donor_id)')
|
||||
->from($db->quoteName('#__mokosuitenpo_donations', 'd1'))
|
||||
->where('YEAR(d1.donation_date) = ' . (int) $year)
|
||||
->where('d1.donor_id IN (SELECT DISTINCT d2.donor_id FROM #__mokosuitenpo_donations d2 WHERE YEAR(d2.donation_date) = ' . ($year - 1) . ')'));
|
||||
$retainedDonors = (int) $db->loadResult();
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
$retentionRate = $priorYearDonors > 0 ? round($retainedDonors / $priorYearDonors * 100, 1) : 0;
|
||||
|
||||
return (object) [
|
||||
'year' => $year,
|
||||
'total_raised' => (float) $fundraising->total_raised,
|
||||
'donation_count' => (int) $fundraising->donation_count,
|
||||
'unique_donors' => (int) $fundraising->unique_donors,
|
||||
'donor_retention' => $retentionRate,
|
||||
'campaigns_run' => (int) ($campaigns->campaigns_run ?? 0),
|
||||
'goals_met' => (int) ($campaigns->goals_met ?? 0),
|
||||
'volunteer_count' => (int) ($volunteers->active_volunteers ?? 0),
|
||||
'volunteer_hours' => (float) ($volunteers->total_hours ?? 0),
|
||||
'events_held' => (int) ($events->events_held ?? 0),
|
||||
'event_attendees' => (int) ($events->total_attendees ?? 0),
|
||||
'grants_received' => (int) ($grants->grants_received ?? 0),
|
||||
'grant_total' => (float) ($grants->grant_total ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate donor-specific impact statement.
|
||||
*/
|
||||
public static function getDonorImpact(int $donorId, int $year): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COALESCE(SUM(amount), 0) AS given_this_year, COUNT(*) AS gift_count')
|
||||
->from('#__mokosuitenpo_donations')
|
||||
->where('donor_id = ' . (int) $donorId)
|
||||
->where('YEAR(donation_date) = ' . (int) $year));
|
||||
$giving = $db->loadObject();
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('lifetime_giving, first_gift_date, gift_count AS total_gifts')
|
||||
->from('#__mokosuitenpo_donors')
|
||||
->where('id = ' . (int) $donorId));
|
||||
$donor = $db->loadObject();
|
||||
|
||||
return (object) [
|
||||
'year' => $year,
|
||||
'given_this_year' => (float) ($giving->given_this_year ?? 0),
|
||||
'gifts_this_year' => (int) ($giving->gift_count ?? 0),
|
||||
'lifetime_giving' => (float) ($donor->lifetime_giving ?? 0),
|
||||
'total_gifts' => (int) ($donor->total_gifts ?? 0),
|
||||
'member_since' => $donor->first_gift_date ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user