Add GrantReportingHelper — spending reports, funder compliance, deadline tracking
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 28s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled

This commit is contained in:
Jonathan Miller
2026-06-21 00:49:36 -05:00
parent 98a0bd0637
commit 3f75d06efc
@@ -0,0 +1,65 @@
<?php
namespace Moko\Plugin\System\MokoSuiteNpo\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* Grant reporting — deliverable tracking, spending reports, funder compliance.
*/
class GrantReportingHelper
{
/**
* Get grant spending report — budgeted vs actual by category.
*/
public static function getSpendingReport(int $grantId): object
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('g.*, cd.name AS funder_name')
->from($db->quoteName('#__mokosuitenpo_grants', 'g'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = g.funder_contact_id')
->where('g.id = ' . (int) $grantId));
$grant = $db->loadObject();
if (!$grant) return (object) ['found' => false];
// Get expenses charged to this grant's fund
$db->setQuery($db->getQuery(true)
->select('category, COUNT(*) AS count, COALESCE(SUM(amount), 0) AS spent')
->from('#__mokosuitenpo_fund_expenses')
->where('fund_id = ' . (int) ($grant->fund_id ?? 0))
->group('category')
->order('spent DESC'));
$grant->spending_by_category = $db->loadObjectList() ?: [];
$grant->total_spent = array_sum(array_column($grant->spending_by_category, 'spent'));
$grant->remaining = max(0, (float) ($grant->amount ?? 0) - (float) $grant->total_spent);
$grant->utilization_pct = (float) ($grant->amount ?? 0) > 0
? round((float) $grant->total_spent / (float) $grant->amount * 100, 1) : 0;
return $grant;
}
/**
* Get grants requiring reports soon.
*/
public static function getUpcomingReportDeadlines(int $days = 30): array
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('g.id, g.title, g.funder, g.report_due_date, g.amount')
->select('DATEDIFF(g.report_due_date, CURDATE()) AS days_until_due')
->from($db->quoteName('#__mokosuitenpo_grants', 'g'))
->where($db->quoteName('g.status') . ' IN (' . $db->quote('active') . ',' . $db->quote('reporting') . ')')
->where($db->quoteName('g.report_due_date') . ' IS NOT NULL')
->where($db->quoteName('g.report_due_date') . ' BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL ' . (int) $days . ' DAY)')
->order('g.report_due_date ASC'));
return $db->loadObjectList() ?: [];
}
}