From 3ca55ac09cabe55403137e6acd029bc1f7019ed3 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 09:21:57 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20GpsTrackingHelper=20=E2=80=94=20fleet?= =?UTF-8?q?=20positions,=20drive=20history,=20speed=20alerts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Helper/GpsTrackingHelper.php | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php new file mode 100644 index 0000000..99c8cf8 --- /dev/null +++ b/source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php @@ -0,0 +1,112 @@ + 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() ?: []; + } +}