diff --git a/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php b/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php new file mode 100644 index 0000000..54ad2c5 --- /dev/null +++ b/source/packages/plg_system_mokosuiterealty/src/Helper/OfferHelper.php @@ -0,0 +1,108 @@ +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']; + } + } +} diff --git a/source/packages/plg_system_mokosuiterealty/src/Helper/ShowingHelper.php b/source/packages/plg_system_mokosuiterealty/src/Helper/ShowingHelper.php new file mode 100644 index 0000000..facf082 --- /dev/null +++ b/source/packages/plg_system_mokosuiterealty/src/Helper/ShowingHelper.php @@ -0,0 +1,102 @@ +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; + } +}