feat: ImpactReportHelper + NpoReportsController with cancel fix #18

Merged
jmiller merged 3 commits from dev into main 2026-06-20 20:31:20 +00:00
3 changed files with 125 additions and 2 deletions
@@ -0,0 +1,123 @@
<?php
namespace Moko\Component\MokoSuiteNpo\Api\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Controller\BaseController;
/**
* NPO Reports + Recurring Donations API.
*
* GET /reports/annual/{year} — Annual impact summary
* GET /reports/donor/{id} — Donor impact statement
* GET /recurring — Active recurring donations
* POST /recurring — Create recurring pledge
* POST /recurring/{id}/cancel — Cancel recurring donation
*/
class NpoReportsController extends BaseController
{
private function requireAuth(string $action = 'core.manage'): void
{
$user = Factory::getApplication()->getIdentity();
if (!$user || $user->guest || (!$user->authorise('core.admin') && !$user->authorise($action, 'com_mokosuitenpo'))) {
http_response_code(403);
echo json_encode(['error' => 'Access denied']);
Factory::getApplication()->close();
}
}
public function annualReport(): void
{
$this->requireAuth();
$year = Factory::getApplication()->getInput()->getInt('year', (int) date('Y'));
$summary = \Moko\Plugin\System\MokoSuiteNpo\Helper\ImpactReportHelper::getAnnualSummary($year);
$this->sendJson($summary);
}
public function donorImpact(): void
{
$this->requireAuth();
$input = Factory::getApplication()->getInput();
$impact = \Moko\Plugin\System\MokoSuiteNpo\Helper\ImpactReportHelper::getDonorImpact(
$input->getInt('donor_id', 0),
$input->getInt('year', (int) date('Y'))
);
$this->sendJson($impact);
}
public function listRecurring(): void
{
$this->requireAuth();
$db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('p.*, cd.name AS donor_name')
->from($db->quoteName('#__mokosuitenpo_pledges', 'p'))
->join('INNER', $db->quoteName('#__mokosuitenpo_donors', 'd') . ' ON d.id = p.donor_id')
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = d.contact_id')
->where($db->quoteName('p.status') . ' = ' . $db->quote('active'))
->where('p.saved_payment_id IS NOT NULL')
->order('p.next_charge_date ASC'));
$this->sendJson($db->loadObjectList() ?: []);
}
public function createRecurring(): void
{
$this->requireAuth('npo.donations');
$input = Factory::getApplication()->getInput();
$id = \Moko\Plugin\System\MokoSuiteNpo\Helper\RecurringDonationHelper::createPledge(
$input->getInt('donor_id', 0),
$input->getFloat('amount', 0),
$input->getString('frequency', 'monthly'),
$input->getInt('saved_payment_id', 0),
$input->getInt('fund_id', 0) ?: null
);
$this->sendJson(['success' => true, 'pledge_id' => $id]);
}
public function cancelRecurring(): void
{
$this->requireAuth('npo.donations');
$id = Factory::getApplication()->getInput()->getInt('id', 0);
$db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
// Verify pledge exists and is active before cancelling
$db->setQuery($db->getQuery(true)
->select('id, status')
->from('#__mokosuitenpo_pledges')
->where('id = ' . (int) $id));
$pledge = $db->loadObject();
if (!$pledge) {
http_response_code(404);
$this->sendJson(['success' => false, 'error' => 'Pledge not found']);
return;
}
if ($pledge->status !== 'active') {
$this->sendJson(['success' => false, 'error' => 'Pledge is not active']);
return;
}
$db->setQuery($db->getQuery(true)
->update('#__mokosuitenpo_pledges')
->set($db->quoteName('status') . ' = ' . $db->quote('cancelled'))
->set($db->quoteName('cancelled_at') . ' = ' . $db->quote(Factory::getDate()->toSql()))
->where('id = ' . (int) $id)
->where($db->quoteName('status') . ' = ' . $db->quote('active')));
$db->execute();
$this->sendJson(['success' => true]);
}
private function sendJson(mixed $data): void
{
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
Factory::getApplication()->close();
}
}
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>01.01.00</version>
<version>01.01.01</version>
<php_minimum>8.3</php_minimum>
<description>MokoSuite NPO component</description>
<namespace path="src">Moko\Component\MokoSuiteNpo</namespace>
+1 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuite NPO</name>
<packagename>mokosuitenpo</packagename>
<version>01.01.00</version>
<version>01.01.01</version>
<creationDate>2026-06-11</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>