|
|
|
@@ -0,0 +1,112 @@
|
|
|
|
|
<?php
|
|
|
|
|
namespace Moko\Plugin\System\MokoSuiteField\Helper;
|
|
|
|
|
|
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
|
|
|
|
|
|
use Joomla\CMS\Factory;
|
|
|
|
|
use Joomla\Database\DatabaseInterface;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GPS tracking — vehicle location history, geofence alerts, drive time analysis.
|
|
|
|
|
*/
|
|
|
|
|
class GpsTrackingHelper
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Record a GPS ping for a vehicle.
|
|
|
|
|
*/
|
|
|
|
|
public static function recordPing(int $vehicleId, float $latitude, float $longitude, float $speed = 0): bool
|
|
|
|
|
{
|
|
|
|
|
if ($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180) {
|
|
|
|
|
throw new \InvalidArgumentException('Invalid GPS coordinates.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
|
|
|
|
|
|
|
|
$ping = (object) [
|
|
|
|
|
'vehicle_id' => $vehicleId,
|
|
|
|
|
'latitude' => round($latitude, 6),
|
|
|
|
|
'longitude' => round($longitude, 6),
|
|
|
|
|
'speed_mph' => max(0, round($speed, 1)),
|
|
|
|
|
'recorded_at'=> Factory::getDate()->toSql(),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$db->insertObject('#__mokosuitefield_gps_pings', $ping);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get latest position for all active vehicles.
|
|
|
|
|
*/
|
|
|
|
|
public static function getFleetPositions(): array
|
|
|
|
|
{
|
|
|
|
|
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
|
|
|
|
|
|
|
|
$db->setQuery($db->getQuery(true)
|
|
|
|
|
->select('v.id AS vehicle_id, v.name AS vehicle_name, v.license_plate')
|
|
|
|
|
->select('gp.latitude, gp.longitude, gp.speed_mph, gp.recorded_at')
|
|
|
|
|
->select('cd.name AS assigned_tech')
|
|
|
|
|
->from($db->quoteName('#__mokosuitefield_vehicles', 'v'))
|
|
|
|
|
->join('LEFT', '(SELECT g1.* FROM #__mokosuitefield_gps_pings g1'
|
|
|
|
|
. ' INNER JOIN (SELECT vehicle_id, MAX(recorded_at) AS max_at'
|
|
|
|
|
. ' FROM #__mokosuitefield_gps_pings GROUP BY vehicle_id) g2'
|
|
|
|
|
. ' ON g1.vehicle_id = g2.vehicle_id AND g1.recorded_at = g2.max_at) AS gp'
|
|
|
|
|
. ' ON gp.vehicle_id = v.id')
|
|
|
|
|
->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.vehicle_id = v.id')
|
|
|
|
|
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = t.contact_id')
|
|
|
|
|
->where($db->quoteName('v.status') . ' = ' . $db->quote('active'))
|
|
|
|
|
->order('v.name ASC'));
|
|
|
|
|
|
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get drive history for a vehicle on a specific date.
|
|
|
|
|
*/
|
|
|
|
|
public static function getDriveHistory(int $vehicleId, string $date = ''): array
|
|
|
|
|
{
|
|
|
|
|
$date = $date ?: date('Y-m-d');
|
|
|
|
|
|
|
|
|
|
if (!\DateTime::createFromFormat('Y-m-d', $date)) {
|
|
|
|
|
throw new \InvalidArgumentException('Date must be Y-m-d format.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
|
|
|
|
|
|
|
|
$db->setQuery($db->getQuery(true)
|
|
|
|
|
->select('gp.latitude, gp.longitude, gp.speed_mph, gp.recorded_at')
|
|
|
|
|
->from($db->quoteName('#__mokosuitefield_gps_pings', 'gp'))
|
|
|
|
|
->where('gp.vehicle_id = ' . (int) $vehicleId)
|
|
|
|
|
->where('DATE(gp.recorded_at) = ' . $db->quote($date))
|
|
|
|
|
->order('gp.recorded_at ASC'));
|
|
|
|
|
|
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get vehicles currently exceeding speed threshold.
|
|
|
|
|
*/
|
|
|
|
|
public static function getSpeeding(float $thresholdMph = 70): array
|
|
|
|
|
{
|
|
|
|
|
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
|
|
|
|
|
|
|
|
$db->setQuery($db->getQuery(true)
|
|
|
|
|
->select('v.id AS vehicle_id, v.name AS vehicle_name, v.license_plate')
|
|
|
|
|
->select('gp.latitude, gp.longitude, gp.speed_mph, gp.recorded_at')
|
|
|
|
|
->select('cd.name AS assigned_tech')
|
|
|
|
|
->from($db->quoteName('#__mokosuitefield_vehicles', 'v'))
|
|
|
|
|
->join('INNER', '(SELECT g1.* FROM #__mokosuitefield_gps_pings g1'
|
|
|
|
|
. ' INNER JOIN (SELECT vehicle_id, MAX(recorded_at) AS max_at'
|
|
|
|
|
. ' FROM #__mokosuitefield_gps_pings GROUP BY vehicle_id) g2'
|
|
|
|
|
. ' ON g1.vehicle_id = g2.vehicle_id AND g1.recorded_at = g2.max_at) AS gp'
|
|
|
|
|
. ' ON gp.vehicle_id = v.id')
|
|
|
|
|
->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.vehicle_id = v.id')
|
|
|
|
|
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = t.contact_id')
|
|
|
|
|
->where($db->quoteName('v.status') . ' = ' . $db->quote('active'))
|
|
|
|
|
->where('gp.speed_mph > ' . (float) $thresholdMph)
|
|
|
|
|
->where('gp.recorded_at > DATE_SUB(NOW(), INTERVAL 10 MINUTE)')
|
|
|
|
|
->order('gp.speed_mph DESC'));
|
|
|
|
|
|
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
|
|
|
}
|
|
|
|
|
}
|