diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 33cf916..75a6963 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 01.05.00 +# VERSION: 01.00.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/source/packages/com_mokosuitenpo/mokosuitenpo.xml b/source/packages/com_mokosuitenpo/mokosuitenpo.xml index c530e3e..67abe57 100644 --- a/source/packages/com_mokosuitenpo/mokosuitenpo.xml +++ b/source/packages/com_mokosuitenpo/mokosuitenpo.xml @@ -7,7 +7,7 @@ GPL-3.0-or-later hello@mokoconsulting.tech https://mokoconsulting.tech - 01.05.00 + 01.05.04 8.3 MokoSuite NPO component Moko\Component\MokoSuiteNpo diff --git a/source/packages/plg_system_mokosuitenpo/src/Helper/DonorRetentionHelper.php b/source/packages/plg_system_mokosuitenpo/src/Helper/DonorRetentionHelper.php index 6b20186..20c7cf3 100644 --- a/source/packages/plg_system_mokosuitenpo/src/Helper/DonorRetentionHelper.php +++ b/source/packages/plg_system_mokosuitenpo/src/Helper/DonorRetentionHelper.php @@ -28,7 +28,7 @@ class DonorRetentionHelper ->join('INNER', $db->quoteName('#__mokosuitenpo_donations', 'd') . ' ON d.contact_id = cd.id') ->where('YEAR(d.donation_date) = ' . $lastYear) ->where('cd.id NOT IN (SELECT d2.contact_id FROM #__mokosuitenpo_donations d2 WHERE YEAR(d2.donation_date) = ' . $currentYear . ')') - ->group('cd.id') + ->group('cd.id, cd.name, cd.email_to, cd.telephone') ->order('last_year_total DESC')); return $db->loadObjectList() ?: []; @@ -53,7 +53,7 @@ class DonorRetentionHelper ->join('INNER', $db->quoteName('#__mokosuitenpo_donations', 'd') . ' ON d.contact_id = cd.id') ->where('YEAR(d.donation_date) BETWEEN ' . $startYear . ' AND ' . $lastYear) ->where('cd.id NOT IN (SELECT d2.contact_id FROM #__mokosuitenpo_donations d2 WHERE YEAR(d2.donation_date) = ' . $currentYear . ')') - ->group('cd.id') + ->group('cd.id, cd.name, cd.email_to') ->order('lifetime_total DESC')); return $db->loadObjectList() ?: []; diff --git a/source/packages/plg_system_mokosuitenpo/src/Helper/InKindDonationHelper.php b/source/packages/plg_system_mokosuitenpo/src/Helper/InKindDonationHelper.php new file mode 100644 index 0000000..07bdc93 --- /dev/null +++ b/source/packages/plg_system_mokosuitenpo/src/Helper/InKindDonationHelper.php @@ -0,0 +1,96 @@ +get(DatabaseInterface::class); + $filter = \Joomla\Filter\InputFilter::getInstance(); + + $donation = (object) [ + 'contact_id' => $contactId, + 'description' => $filter->clean($description, 'STRING'), + 'fair_market_value'=> $fairMarketValue, + 'category' => $category, + 'status' => 'received', + 'received_at' => Factory::getDate()->toSql(), + ]; + + $db->insertObject('#__mokosuitenpo_inkind_donations', $donation, 'id'); + + return (object) ['success' => true, 'donation_id' => (int) $donation->id]; + } + + /** + * Get in-kind donation summary by category for a period. + */ + public static function getSummary(string $from = '', string $to = ''): array + { + $from = $from ?: date('Y-01-01'); + $to = $to ?: date('Y-m-d'); + + if (!\DateTime::createFromFormat('Y-m-d', $from) || !\DateTime::createFromFormat('Y-m-d', $to)) { + throw new \InvalidArgumentException('Date parameters must be Y-m-d format.'); + } + + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('ik.category') + ->select('COUNT(*) AS donation_count') + ->select('SUM(ik.fair_market_value) AS total_value') + ->select('AVG(ik.fair_market_value) AS avg_value') + ->from($db->quoteName('#__mokosuitenpo_inkind_donations', 'ik')) + ->where('DATE(ik.received_at) BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to)) + ->group('ik.category') + ->order('total_value DESC')); + + $results = $db->loadObjectList() ?: []; + + foreach ($results as &$r) { + $r->avg_value = round((float) $r->avg_value, 2); + } + + return $results; + } + + /** + * Get donations needing appraisal (over $5,000 threshold per IRS rules). + */ + public static function getNeedingAppraisal(): array + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('ik.*, cd.name AS donor_name') + ->from($db->quoteName('#__mokosuitenpo_inkind_donations', 'ik')) + ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = ik.contact_id') + ->where('ik.fair_market_value > 5000') + ->where('ik.appraisal_date IS NULL') + ->where($db->quoteName('ik.category') . ' NOT IN (' . $db->quote('securities') . ')') + ->order('ik.fair_market_value DESC')); + + return $db->loadObjectList() ?: []; + } +} diff --git a/source/pkg_mokosuitenpo.xml b/source/pkg_mokosuitenpo.xml index 44fa230..edc0f4d 100644 --- a/source/pkg_mokosuitenpo.xml +++ b/source/pkg_mokosuitenpo.xml @@ -2,7 +2,7 @@ Package - MokoSuite NPO mokosuitenpo - 01.05.00 + 01.05.04 2026-06-11 Moko Consulting hello@mokoconsulting.tech