generated from MokoConsulting/Template-Joomla
feat: initial scaffold — package manifest with dlid/updateservers + first helper
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
namespace Moko\Plugin\System\MokoSuiteRealty\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Property listing management — CRUD, status workflow, price history, days on market.
|
||||
*/
|
||||
class ListingHelper
|
||||
{
|
||||
/**
|
||||
* Get listings with filters.
|
||||
*/
|
||||
public static function getListings(array $filters = [], int $limit = 20, int $offset = 0): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('l.*, cd_agent.name AS agent_name')
|
||||
->select('(SELECT COUNT(*) FROM #__mokosuiterealty_listing_photos p WHERE p.listing_id = l.id) AS photo_count')
|
||||
->select('DATEDIFF(NOW(), l.listed_at) AS days_on_market')
|
||||
->from($db->quoteName('#__mokosuiterealty_listings', 'l'))
|
||||
->join('LEFT', $db->quoteName('#__contact_details', 'cd_agent') . ' ON cd_agent.id = l.agent_id')
|
||||
->order('l.listed_at DESC');
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$query->where($db->quoteName('l.status') . ' = ' . $db->quote($filters['status']));
|
||||
}
|
||||
if (!empty($filters['type'])) {
|
||||
$query->where($db->quoteName('l.property_type') . ' = ' . $db->quote($filters['type']));
|
||||
}
|
||||
if (!empty($filters['min_price'])) {
|
||||
$query->where('l.price >= ' . (float) $filters['min_price']);
|
||||
}
|
||||
if (!empty($filters['max_price'])) {
|
||||
$query->where('l.price <= ' . (float) $filters['max_price']);
|
||||
}
|
||||
if (!empty($filters['bedrooms'])) {
|
||||
$query->where('l.bedrooms >= ' . (int) $filters['bedrooms']);
|
||||
}
|
||||
if (!empty($filters['city'])) {
|
||||
$query->where($db->quoteName('l.city') . ' = ' . $db->quote($filters['city']));
|
||||
}
|
||||
if (!empty($filters['agent_id'])) {
|
||||
$query->where('l.agent_id = ' . (int) $filters['agent_id']);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, min(max(1, $limit), 100));
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get listing dashboard stats.
|
||||
*/
|
||||
public static function getDashboard(): object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('COUNT(*) AS total_listings')
|
||||
->select('SUM(CASE WHEN status = ' . $db->quote('active') . ' THEN 1 ELSE 0 END) AS active')
|
||||
->select('SUM(CASE WHEN status = ' . $db->quote('pending') . ' THEN 1 ELSE 0 END) AS pending')
|
||||
->select('SUM(CASE WHEN status = ' . $db->quote('sold') . ' THEN 1 ELSE 0 END) AS sold')
|
||||
->select('AVG(CASE WHEN status = ' . $db->quote('active') . ' THEN price ELSE NULL END) AS avg_active_price')
|
||||
->select('AVG(CASE WHEN status = ' . $db->quote('sold') . ' THEN DATEDIFF(sold_at, listed_at) ELSE NULL END) AS avg_days_to_sell')
|
||||
->from('#__mokosuiterealty_listings'));
|
||||
|
||||
$stats = $db->loadObject();
|
||||
$stats->avg_active_price = round((float) ($stats->avg_active_price ?? 0), 0);
|
||||
$stats->avg_days_to_sell = round((float) ($stats->avg_days_to_sell ?? 0), 0);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a price change and log the history.
|
||||
*/
|
||||
public static function changePrice(int $listingId, float $newPrice, ?string $reason = null): bool
|
||||
{
|
||||
if ($newPrice <= 0) {
|
||||
throw new \InvalidArgumentException('Price must be positive.');
|
||||
}
|
||||
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
// Get current price
|
||||
$db->setQuery($db->getQuery(true)->select('price')->from('#__mokosuiterealty_listings')->where('id = ' . (int) $listingId));
|
||||
$oldPrice = (float) $db->loadResult();
|
||||
|
||||
if ($oldPrice === $newPrice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$db->transactionStart();
|
||||
|
||||
try {
|
||||
// Update price
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->update('#__mokosuiterealty_listings')
|
||||
->set('price = ' . (float) $newPrice)
|
||||
->where('id = ' . (int) $listingId));
|
||||
$db->execute();
|
||||
|
||||
// Log history
|
||||
$db->insertObject('#__mokosuiterealty_price_history', (object) [
|
||||
'listing_id' => $listingId,
|
||||
'old_price' => $oldPrice,
|
||||
'new_price' => $newPrice,
|
||||
'reason' => $reason,
|
||||
'changed_at' => Factory::getDate()->toSql(),
|
||||
'changed_by' => (int) Factory::getApplication()->getIdentity()->id,
|
||||
]);
|
||||
|
||||
$db->transactionCommit();
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$db->transactionRollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoSuite Realty</name>
|
||||
<packagename>mokosuiterealty</packagename>
|
||||
<version>01.00.00</version>
|
||||
<creationDate>2026-06-21</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description>MokoSuite Realty - real estate management: listings, showings, offers, commissions. Layer 2 add-on (requires CRM).</description>
|
||||
<php_minimum>8.3</php_minimum>
|
||||
<dlid prefix="dlid=" suffix=""/>
|
||||
<blockChildUninstall>true</blockChildUninstall>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
<files folder="packages">
|
||||
<file type="plugin" id="plg_system_mokosuiterealty" group="system">plg_system_mokosuiterealty.zip</file>
|
||||
<file type="component" id="com_mokosuiterealty">com_mokosuiterealty.zip</file>
|
||||
</files>
|
||||
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="Package - MokoSuite Realty">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteRealty/updates.xml</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
Reference in New Issue
Block a user