feat: build out core helpers — 2 files added
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 11s

This commit is contained in:
Jonathan Miller
2026-06-21 17:37:50 -05:00
parent fa498b1eda
commit 2d503fdf14
2 changed files with 210 additions and 0 deletions
@@ -0,0 +1,108 @@
<?php
namespace Moko\Plugin\System\MokoSuiteRealty\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* Offer management — submit, counter, accept/reject, contingency tracking.
*/
class OfferHelper
{
/**
* Submit an offer on a listing.
*/
public static function submit(int $listingId, int $buyerContactId, float $offerAmount, array $conditions = []): object
{
if ($offerAmount <= 0) {
throw new \InvalidArgumentException('Offer amount must be positive.');
}
$db = Factory::getContainer()->get(DatabaseInterface::class);
$offer = (object) [
'listing_id' => $listingId,
'buyer_contact_id' => $buyerContactId,
'offer_amount' => round($offerAmount, 2),
'conditions' => !empty($conditions) ? json_encode($conditions) : null,
'status' => 'submitted',
'submitted_at' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokosuiterealty_offers', $offer, 'id');
return (object) ['success' => true, 'offer_id' => (int) $offer->id];
}
/**
* Get all offers for a listing.
*/
public static function getForListing(int $listingId): array
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('o.*, cd.name AS buyer_name, cd.email_to')
->from($db->quoteName('#__mokosuiterealty_offers', 'o'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = o.buyer_contact_id')
->where('o.listing_id = ' . (int) $listingId)
->order('o.offer_amount DESC'));
return $db->loadObjectList() ?: [];
}
/**
* Accept an offer — marks listing as pending and rejects other offers.
*/
public static function accept(int $offerId): object
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('listing_id')
->from('#__mokosuiterealty_offers')
->where('id = ' . (int) $offerId));
$listingId = (int) $db->loadResult();
if (!$listingId) {
return (object) ['success' => false, 'error' => 'Offer not found'];
}
$db->transactionStart();
try {
// Accept this offer
$db->setQuery($db->getQuery(true)
->update('#__mokosuiterealty_offers')
->set($db->quoteName('status') . ' = ' . $db->quote('accepted'))
->set('accepted_at = ' . $db->quote(Factory::getDate()->toSql()))
->where('id = ' . (int) $offerId));
$db->execute();
// Reject all other offers on this listing
$db->setQuery($db->getQuery(true)
->update('#__mokosuiterealty_offers')
->set($db->quoteName('status') . ' = ' . $db->quote('rejected'))
->where('listing_id = ' . $listingId)
->where('id != ' . (int) $offerId)
->where($db->quoteName('status') . ' = ' . $db->quote('submitted')));
$db->execute();
// Update listing to pending
$db->setQuery($db->getQuery(true)
->update('#__mokosuiterealty_listings')
->set($db->quoteName('status') . ' = ' . $db->quote('pending'))
->where('id = ' . $listingId));
$db->execute();
$db->transactionCommit();
return (object) ['success' => true, 'listing_id' => $listingId];
} catch (\Throwable $e) {
$db->transactionRollback();
return (object) ['success' => false, 'error' => 'Accept failed'];
}
}
}
@@ -0,0 +1,102 @@
<?php
namespace Moko\Plugin\System\MokoSuiteRealty\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* Showing management — schedule, feedback, calendar, open house sign-in.
*/
class ShowingHelper
{
/**
* Schedule a showing.
*/
public static function schedule(int $listingId, int $buyerContactId, int $agentId, string $datetime): object
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$showing = (object) [
'listing_id' => $listingId,
'buyer_contact_id' => $buyerContactId,
'agent_id' => $agentId,
'scheduled_at' => $datetime,
'status' => 'requested',
'created_at' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokosuiterealty_showings', $showing, 'id');
return (object) ['success' => true, 'showing_id' => (int) $showing->id];
}
/**
* Get showings for a listing.
*/
public static function getForListing(int $listingId): array
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('s.*, cd_buyer.name AS buyer_name, cd_agent.name AS agent_name')
->from($db->quoteName('#__mokosuiterealty_showings', 's'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd_buyer') . ' ON cd_buyer.id = s.buyer_contact_id')
->join('LEFT', $db->quoteName('#__contact_details', 'cd_agent') . ' ON cd_agent.id = s.agent_id')
->where('s.listing_id = ' . (int) $listingId)
->order('s.scheduled_at DESC'));
return $db->loadObjectList() ?: [];
}
/**
* Get agent calendar — all showings for an agent in a date range.
*/
public static function getAgentCalendar(int $agentId, string $from, string $to): array
{
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('s.id, s.scheduled_at, s.status')
->select('l.address, l.city, l.price')
->select('cd.name AS buyer_name, cd.telephone')
->from($db->quoteName('#__mokosuiterealty_showings', 's'))
->join('INNER', $db->quoteName('#__mokosuiterealty_listings', 'l') . ' ON l.id = s.listing_id')
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = s.buyer_contact_id')
->where('s.agent_id = ' . (int) $agentId)
->where('DATE(s.scheduled_at) BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to))
->where($db->quoteName('s.status') . ' != ' . $db->quote('cancelled'))
->order('s.scheduled_at ASC'));
return $db->loadObjectList() ?: [];
}
/**
* Record showing feedback from buyer.
*/
public static function recordFeedback(int $showingId, int $interestLevel, ?string $comments = null): bool
{
if ($interestLevel < 1 || $interestLevel > 5) {
throw new \InvalidArgumentException('Interest level must be 1-5.');
}
$db = Factory::getContainer()->get(DatabaseInterface::class);
$filter = \Joomla\Filter\InputFilter::getInstance();
$db->setQuery($db->getQuery(true)
->update('#__mokosuiterealty_showings')
->set($db->quoteName('status') . ' = ' . $db->quote('completed'))
->set('interest_level = ' . (int) $interestLevel)
->set('feedback = ' . $db->quote($comments ? $filter->clean($comments, 'STRING') : ''))
->set('completed_at = ' . $db->quote(Factory::getDate()->toSql()))
->where('id = ' . (int) $showingId));
$db->execute();
return $db->getAffectedRows() > 0;
}
}